<?php
namespace App\Event;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\Security\Core\Security;
// use Symfony\Bundle\FrameworkBundle\Routing\Router;
// use Symfony\Component\HttpFoundation\RedirectResponse;
use App\PaaBundle\Entity\adressesIP ;
use App\PaaBundle\Entity\adressesIP_Evenements ;
use App\PaaBundle\Component\Logs;
use DateTime;
// Gestionnaire de page not found, pour blacklistage des attaques par force brute
// Erreurs NotFoundHttpException : test par un robot de toutes les URLS classiques
// Erreurs d'authentification (A VENIR)
class ForceBruteListener {
/**
* @var string
*/
private $project_dir;
private $entityManager;
private $security;
public function __construct($project_dir, $doctrine, Security $security) {
$this->project_dir = $project_dir;
$this->entityManager = $doctrine->getManager() ;
$this->security = $security ;
}
// Capture des exceptions "page not found"
public function onKernelException(GetResponseForExceptionEvent $event) {
if (
// $_ENV['APP_ENV'] != 'prod' ||
!$event->isMasterRequest() ||
!$event->getException() instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
) {
// Nous ne sommes pas en prod, ou ce n'est pas la requête ppale, ou ce n'est pas une exception "File not found"
return;
}
// A partir de là, on traite les exceptions "Page non trouvée"
$request = $event->getRequest() ;
if ($request->getRequestUri() == "/favicon.ico") {
// Pas à pendre en compte : arrive toujours en prod
return ;
}
if ($this->security->getToken()) {
// Cette condition ne semble jamais respectée et c'est dommage car on ne peut pas savoir si le user est authentifié
// et que dans ce cas on ne devrait pas bannir cette adresse IP car l'erreur "Route not found" est plus probablement due à un bug de l'application (route bosolète/mal configurée/...)
// Du coup, on est obligés de passer par la vérification que cette IP a été authentifiée au moins une fois dans le passé ci-dessous ($loAdresseIP->estAuthentifiéeAuMoinsUneFois())
// C'est src/PaaBundle/Component/Authentication/Handler/LoginSuccessHandler.php qui pose le flag en question
$user = $this->security->getUser();
// L'objet sécurité est à jour avec le token d'authentification, on peut donc vérifier si l'utilisateur en cours est bien authentifié
// $user = $this->security->getUser();
$lbAuthentifié = $this->security->isGranted('IS_AUTHENTICATED_FULLY') ;
if ($lbAuthentifié) {
// Puisque l'utilisateur est authentifié, on ne devrait pas le bannir s'il s'agit d'un bug de l'application et non d'une tentative de force brute
return ;
}
}
$ip = $request->getClientIp() ;
$loRepo = $this->entityManager->getRepository("PaaBundle:adressesIP");
if (!$loRepo->estUtilisable()) {
// La table AdressesIP n'est pas encore créée
return ;
}
$loAdresseIP = $loRepo->findByIP($ip) ;
if (!$loAdresseIP) {
// Adresse IP pas encore référencée : l'ajouter maintenant
$loAdresseIP = new adressesIP() ;
$loAdresseIP->setCIP($ip) ;
} else if ($loAdresseIP->estAuthentifiéeAuMoinsUneFois()) {
// Cette adresse a déja été authentifiée au moins une fois (un utilisateur s'est authentifié avec succès à partir de cette adresse)
// On ne va pas bannir cette adresse IP car l'erreur "Route not found" est plus probablement due à un bug de l'application (route bosolète/mal configurée/...)
return ;
}
// Ajouter l'événement à l'historique de cette adresse IP
$loEvenement = new adressesIP_Evenements() ;
$loEvenement->setAdresseIP($loAdresseIP) ;
$loEvenement->setTEvenement(new DateTime()) ;
$loEvenement->setITypeEvenement($loRepo::TYPE_ROUTENOTFOUND) ;
$loEvenement->setCDescEvenement($request->getRequestUri()) ;
$this->entityManager->persist($loEvenement) ;
$this->entityManager->flush() ;
// LG 20241023 début
// $lbBanni = $loRepo->updateABannir($loAdresseIP) ;
$lbVientDetreBannie = false ;
$lbBanni = $loRepo->updateABannir($loAdresseIP, $lbVientDetreBannie) ;
// LG 20241023 fin
if ($lbBanni) {
// Cette adresse est bannie
// LG 20241023 début
// // Logger le banissement
// $message = "L'adresse IP $ip vient d'être bannie pour dépassement du nombre max d'événements.";
// $this->log($message) ;
if ($lbVientDetreBannie) {
// Logger le banissement
$message = "L'adresse IP vient d'être bannie pour dépassement du nombre max d'événements : $ip.";
$this->log($message) ;
}
// LG 20241023 fin
// Renvoyer une réponse ici évite de passer dans les gestionnaires d'erreur suivants : c'est donc cette réponse qui sera renvoyée au client
$response = new Response($this->getMsg($ip), Response::HTTP_FORBIDDEN);
$event->setResponse($response);
return ;
}
// Si on arrive jusque là, le traitement normal de l'exception va se poursuivre
return ;
}
// Vérification sur chaque requête que l'adresse n'est pas bannie
public function onKernelController(FilterControllerEvent $event) {
if (!$event->isMasterRequest()) {
// Cette requête n'est pas la requête principale
return ;
}
// if ($event->getController() && isset($event->getController()[1]) && isset($event->getController()[1]) == "toolbarAction") {
// // Rien à faire dans ce contexte
// return ;
// }
if ($this->security->getToken()) {
// L'objet sécurité est à jour avec le token d'authentification, on peut donc vérifier si l'utilisateur en cours est bien authentifié
// $user = $this->security->getUser();
$lbAuthentifié = $this->security->isGranted('IS_AUTHENTICATED_FULLY') ;
if ($lbAuthentifié) {
// Puisque l'utilisateur est authentifié, pas besoin de vérifier s'il est banni
return ;
}
}
$request = $event->getRequest() ;
$ip = $request->getClientIp() ;
$loRepo = $this->entityManager->getRepository("PaaBundle:adressesIP");
if (!$loRepo->estUtilisable()) {
// La table AdressesIP n'est pas encore créée
return ;
}
$loAdresseIP = $loRepo->findByIP($ip) ;
if ($loAdresseIP && $loAdresseIP->estBannie()) {
// Cette adresse IP est bannie
// Logger la tentative
// LG 20241023 début
// $message = "L'adresse IP est bannie mais fait une tentative d'accès : $ip.";
$message = "L'adresse IP est bannie mais fait une tentative d'accès : $ip, URI : {$event->getRequest()->getRequestUri()}.";
// LG 20241023 fin
$this->log($message) ;
// Terminer en erreur
throw new \Exception($this->getMsg($ip)) ;
}
// Si on arrive jusque là, le traitement normal va se poursuivre
return ;
}
// Renvoyer le message d'erreur
private function getMsg($ip) {
return "Désolé, l'adresse IP que vous utilisez ($ip) a été bannie pour cette application en raison d'un trop grand nombre de tentatives infructueuses. Merci de demander à un administrateur de vous aider à rétablir la situation." ;
}
// Logguer un message
private function log($msg) {
Logs::setProjectDir($this->project_dir) ;
Logs::setStackTrace("") ;
Logs::critical($msg, [], true) ;
}
}