<?php
namespace App\EventSubscriber;
use App\Entity\BannedIp;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Twig\Environment;
//Cache
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Contracts\Cache\ItemInterface;
//Entity
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Connection;
use App\Repository\BannedIpRepository;
use App\Repository\ConnectionRepository;
// Look like Hooks
class StartingRequestSubscriber implements EventSubscriberInterface
{
private $twig;
private $manager;
private $connectionRepository;
private $bannedIpRepository;
private $authorizedIps;
private $skipLogPrefixes = [
'/api',
'/build',
'/assets',
'/images',
'/img',
'/officials-imgs',
'/uploads',
'/fonts',
'/bundles',
'/i18n',
'/_profiler',
'/_wdt',
'/favicon.ico',
'/robots.txt',
'/sitemap',
'/user/heart-beat',
'/hub',
];
private $rateLimitError = 30; // 10 erreurs
private $rateLimitErrorDuration = 1 * 60 * 10; // ... en 10 minutes
private $bannedDuration = 1 * 60 * 60 * 24; // ... donc banni 24h
public function __construct(
Environment $twig,
EntityManagerInterface $manager,
ConnectionRepository $connectionRepository,
BannedIpRepository $bannedIpRepository
) {
$this->twig = $twig;
$this->manager = $manager;
$this->connectionRepository = $connectionRepository;
$this->bannedIpRepository = $bannedIpRepository;
$this->authorizedIps = call_user_func(function () {
return [
'92.144.142.121',
'172.18.0.1', // local docker
'172.20.0.1',
];
});
}
public static function getSubscribedEvents()
{
// return the subscribed events, their methods and priorities
return [
KernelEvents::REQUEST => 'checkBannedIp',
// KernelEvents::TERMINATE : Appelé une seul fois après le traitement de la rêquete, evite les doublons dans la dbb
KernelEvents::TERMINATE => 'saveClientInfo',
];
}
public function checkBannedIp(\Symfony\Component\HttpKernel\Event\RequestEvent $event)
{
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
$clientIp = $request->getClientIp() ?? ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1');
$bannedMessage = 'Banned'
. '</br></br>Your IP address has been signaled and marked as potentially unreliable due to suspicious or malicious behavior.'
. '</br>Please refer to this <a href="https://www.abuseipdb.com/">link</a> for more details and reclamations.';
$limitDate = (new \DateTime('now'))->sub(new \DateInterval('PT' . $this->bannedDuration . 'S'));
$isBanned = count($this->bannedIpRepository->findAllWithLimitDateAndWithIp($limitDate, $clientIp)) > 0;
if ($isBanned) {
$connec = new Connection();
$connec->setCreatedAt(new \DateTime());
$connec->setClientIp($clientIp);
$connec->setRequestUri($request->getRequestUri() ?? '');
$connec->setResponseStatus('BANNED'); // ADDED
// $this->addToHtaccesFile($_SERVER['REMOTE_ADDR']); // add to .htaccess for fast rejection
if (array_key_exists('HTTP_REFERER', $_SERVER)) {
$connec->setHttpReferer($_SERVER['HTTP_REFERER']);
} else {
$connec->setHttpReferer("No referer");
}
$this->manager->persist($connec);
$this->manager->flush();
die($bannedMessage);
}
}
public function addToHtaccesFile($ip)
{
// \sleep(1);
$filePath = '/home/joeddqhp/public_html/.htaccess';
\file_put_contents(
$filePath,
\preg_replace(
'/Require not ip(\s((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4})+/',
'$0 ' . $ip,
\file_get_contents($filePath)
)
);
}
public function saveClientInfo($event)
{
if (!$event->isMainRequest()) {
return;
}
$connec = new Connection();
$date = new \DateTime();
// Tout ceux avant la connection "id: 3312, date: 2021-02-21 06:53:30, ip: 46.193.4.149" sont soutrait de 5h de part la ligne qui suit
// $date->sub(new \DateInterval('PT5H'));
$connec->setCreatedAt($date);
$responseStatus = (string) $event->getResponse()->getStatusCode();
// dd($event->getRequest()->getContent());
$clientIp = $event->getRequest()->getClientIp() ?? ($_SERVER['REMOTE_ADDR'] ?? '127.0.0.1');
$connec->setClientIp($clientIp);
$request = $event->getRequest();
$requestUri = $request->getRequestUri() ?? ($_SERVER['REQUEST_URI'] ?? '');
if ($this->shouldSkipLogging($requestUri)) {
return;
}
$connec->setRequestUri($requestUri);
$connec->setResponseStatus($responseStatus); // ADDED
if (array_key_exists('HTTP_REFERER', $_SERVER)) {
$connec->setHttpReferer($_SERVER['HTTP_REFERER']);
} else {
$connec->setHttpReferer("No referer");
}
$connec->setRequestData($this->safeContent($request));
$connec->setResponseData($this->safeResponseContent($event->getResponse()->getContent()));
// netoyer les logs associés a mon ip public
// DELETE FROM `connection` WHERE client_ip=
if (in_array($_SERVER['REMOTE_ADDR'], $this->authorizedIps)) {
$connec->setClientIp("moi (" . mb_substr($_SERVER['REMOTE_ADDR'], 0, 1) . '*.***.***.*' . mb_substr($_SERVER['REMOTE_ADDR'], -2) . ')');
}
$this->manager->persist($connec);
$this->manager->flush();
// Get count of 404 in lastes request
if ($responseStatus === '404' || $responseStatus === '500') {
$limitDate = (new \DateTime('now'))->sub(new \DateInterval('PT' . $this->rateLimitErrorDuration . 'S'));
$count404 = count($this->connectionRepository->findAllWithLimitDateAndWithIpAndWithStatus($limitDate, $clientIp, '404'));
$count500 = count($this->connectionRepository->findAllWithLimitDateAndWithIpAndWithStatus($limitDate, $clientIp, '500'));
if ($count404 + $count500 >= $this->rateLimitError) {
// Add banned ip
$bannedIp = new BannedIp();
$bannedIp->setCreatedAt(new \DateTime());
$bannedIp->setClientIp($clientIp);
$this->manager->persist($bannedIp);
$this->manager->flush();
}
}
// die();
}
private function shouldSkipLogging(string $requestUri): bool
{
foreach ($this->skipLogPrefixes as $prefix) {
if ($prefix !== '/' && str_starts_with($requestUri, $prefix)) {
return true;
}
}
return false;
}
private function safeContent(\Symfony\Component\HttpFoundation\Request $request): string
{
$contentType = (string) $request->headers->get('Content-Type', '');
if (str_contains($contentType, 'multipart/form-data')) {
return '';
}
$content = (string) $request->getContent();
if ($content === '') {
return '';
}
if (strlen($content) > 4096) {
$content = substr($content, 0, 4096) . '...';
}
return \json_encode($content);
}
private function safeResponseContent(string $content): string
{
if ($content === '') {
return '';
}
if (strlen($content) > 4096) {
$content = substr($content, 0, 4096) . '...';
}
return \json_encode($content);
}
}