<?php
namespace App\EntityManager;
use App\Cache\VisitorCache;
use App\Entity\Repository\UserIpBannedRepository;
use App\Entity\User;
use App\Entity\UserIpBanned;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\PaginatorInterface;
use Psr\SimpleCache\CacheInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Security;
class UserIpBannedManager extends AbstractEntityManager
{
private $redis;
private $abkBans;
private $abkVisitsLimits;
public function __construct(
EntityManagerInterface $entityManager,
TokenStorageInterface $tokenStorage,
CacheInterface $cache,
RequestStack $requestStack,
PaginatorInterface $paginator,
Security $security,
\Predis\Client $redis,
array $abkBans,
array $abkVisitsLimits
) {
parent::__construct($entityManager, $tokenStorage, $cache, $requestStack, $paginator, $security);
$this->redis = $redis;
$this->abkBans = $abkBans;
$this->abkVisitsLimits = $abkVisitsLimits;
}
private function getRepository(): UserIpBannedRepository
{
return $this->entityManager->getRepository(UserIpBanned::class);
}
private function createIpBan(string $ip, string $reason)
{
$ban = new UserIpBanned();
$ban->setComment('AUTOBAN : ' . $reason)
->setIp($ip)
->setDomain(gethostbyaddr($ip));
$this->entityManager->persist($ban);
$this->entityManager->flush();
return $ban;
}
public function checkBan(Request $request)
{
$ipBan = $this->getRepository()->getIpBanned($request->getClientIp());
if ($ipBan == null) {
$ipBan = $this->checkRequest($request);
}
if ($ipBan instanceof UserIpBanned) {
$this->addBlockedHit($ipBan);
} else {
$this->checkDailyHits($request);
}
return $ipBan instanceof UserIpBanned;
}
private function checkRequest(Request $request): ?UserIpBanned
{
$getBanReason = function($request) {
foreach ($this->abkBans['request_parameters'] as $key => $value) {
if ($request->get($key) == $value) {
return 'tor';
}
}
$userAgent = $request->headers->get('User-Agent');
foreach ($this->abkBans['user_agents'] as $bannedUserAgent) {
if (strpos($userAgent, $bannedUserAgent) !== false) {
return sprintf('userbrowser=%s', $userAgent);
}
}
$url = $request->getRequestUri();
foreach ($this->abkBans['urls'] as $bannedUrl) {
if (strpos($url, $bannedUrl) !== false) {
return sprintf('url=%s', $bannedUrl);
}
}
return null;
};
if ($message = $getBanReason($request)) {
return $this->createIpBan($request->getClientIp(), $message);
}
return null;
}
private function checkDailyHits(Request $request)
{
$user = $this->getUser();
$allowedRoles = null;
if ($user instanceof User) {
$allowedRoles = array_intersect(User::ADMIN_ASSIGNABLE_ROLES, $user->getRoles());
$hitsKey = VisitorCache::keyDailyHitsUser($user->getId());
$limit = $this->abkVisitsLimits['user'];
} else {
$hitsKey = VisitorCache::keyDailyHitsIp($request->getClientIp());
$limit = $this->abkVisitsLimits['anonymous'];
}
$results = $this->redis
->transaction()
->incr($hitsKey)
->expire($hitsKey, 60 * 60 * 24)
->execute();
if (!empty($allowedRoles)) {
return false;
}
$blocked = $results[0] >= $limit;
if ($blocked && $user instanceof User) {
$message = sprintf('%s hits limit reached, account temporary blocked.', $limit);
$adminComment = $user->getAdminComment();
if (!empty($adminComment)) {
$user->setAdminComment($adminComment . "\r\n" . $message);
} else {
$user->setAdminComment($message);
}
$user->setDateBlockedUntil(new \DateTime('+8 days'));
$this->entityManager->persist($user);
$this->entityManager->flush();
}
else if ($blocked && $user == null) {
$message = sprintf('%s hits limit reached, anonymous user.', $limit);
$this->createIpBan($request->getClientIp(), $message);
}
return $blocked;
}
public function addBlockedHit(UserIpBanned $ipBan)
{
$qb = $this->getRepository()->createQueryBuilder('ip');
$qb->update()
->set('ip.hitsblocked', $qb->expr()->sum('ip.hitsblocked', 1))
->where('ip = :ipban')
->setParameter('ipban', $ipBan);
$qb->getQuery()->execute();
}
}