<?php
namespace App\Services\Security;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\RequestStack;
use App\Entity\Security\IpAttempt;
use App\Entity\Security\IpBan;
use App\Services\LogTools;
class IpTools
{
const MAX_LOGIN_FAILURE_ATTEMPTS = 25;
const ATTEMPTS_INTERVAL_SECONDS = 60 * 10;
const BAN_TIME_SECONDS = 60 * 10;
public function __construct(ManagerRegistry $doctrine, RequestStack $requestStack, LogTools $logTools)
{
$this->em = $doctrine->getManager();
$this->requestStack = $requestStack;
$this->logTools = $logTools;
}
public function fail2ban($args)
{
$request = $this->requestStack->getCurrentRequest();
if ($request === null)
{
return false;
}
if (empty($request->getClientIp()))
{
return false;
}
// Various initialisations
$username = null;
if (array_key_exists('username', $args))
{
$username = $args['username'];
}
$errors = array();
// FormErrors
if (array_key_exists('form_errors', $args))
{
foreach ($args['form_errors'] as $key => $error)
{
if ($error->getCause() !== null)
{
$errors[] = $error->getCause()->getMessageTemplate();
}
else
{
$errors[] = $error->getMessage();
}
}
}
// Errors, in general
if (array_key_exists('error', $args))
{
$errors[] = $args['error'];
}
// Make only one error field
$errorDisplay = null;
if (count($errors) == 1)
{
$errorDisplay = $errors[0];
}
else
{
if (count($errors) > 1)
{
$errorDisplay = "";
foreach ($errors as $error)
{
$errorDisplay .= $error;
$errorDisplay .= ", ";
}
$errorDisplay = substr($errorDisplay,0,-1);
$errorDisplay = substr($errorDisplay,0,-1);
}
}
// Get the client IP from the Request Stack
$ip = $request->getClientIp();
// Get the lastest (according to MAX_LOGIN_FAILURE_ATTEMPTS) attempts
$ipAttempts = $this->em
->getRepository(IpAttempt::class)
->getLatest($ip, self::MAX_LOGIN_FAILURE_ATTEMPTS);
$ipAttempts = array_reverse($ipAttempts);
// Count them
$attempts = count($ipAttempts);
// If we have less than the maximum allowed, do nothing
if ($attempts >= self::MAX_LOGIN_FAILURE_ATTEMPTS)
{
// Get oldest entry
$oldest = $ipAttempts[0]->getCreationDate();
if ($oldest !== null)
{
// Compare it to the present
$now = new \DateTime();
// Get diff btw old/now in seconds
$diff = $now->getTimestamp() - $oldest->getTimestamp();
// If too recent, ban the ip
if ($diff < self::ATTEMPTS_INTERVAL_SECONDS)
{
$ipBan = new IpBan();
$ipBan->setIp($ip);
$ipBan->setEndDate($now->add(new \DateInterval('PT'.self::BAN_TIME_SECONDS.'S')));
$this->em->persist($ipBan);
}
}
}
// In all cases log new attempt
$attempt = new IpAttempt();
$attempt->setIp($ip);
$attempt->setUsername($username);
$attempt->setError($errorDisplay);
$attempt->setUri($request->getUri());
$this->em->persist($attempt);
try
{
$this->em->flush();
}
catch (\Exception $e)
{
// Something went bad
$this->logTools->errorLog($e->getMessage());
return false;
}
return true;
}
public function isBanned($ip)
{
$ipBan = $this->em
->getRepository(IpBan::class)
->findOneBy(
array('ip' => $ip),
array('endDate' => 'DESC',)
);
if ($ipBan === null)
{
return false;
}
// Check timestamp
$now = new \DateTime();
if ($now->format("YmdHis") <= $ipBan->getEndDate()->format("YmdHis"))
{
return true;
}
return false;
}
}