src/EventListener/SecurityListener.php line 188

Open in your IDE?
  1. <?php
  2. /**
  3.  * Copyright (c) 2019, MND Next GmbH - www.mndnext.de
  4.  */
  5. namespace App\EventListener;
  6. use App\Entity\User;
  7. use App\Entity\UserActionLog;
  8. use App\Services\MailService;
  9. use Doctrine\ORM\EntityManagerInterface;
  10. use FOS\UserBundle\Event\FilterUserResponseEvent;
  11. use FOS\UserBundle\Event\GetResponseUserEvent;
  12. use FOS\UserBundle\FOSUserEvents;
  13. use FOS\UserBundle\Model\UserManagerInterface;
  14. use Psr\Log\LoggerInterface;
  15. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  16. use Symfony\Component\HttpFoundation\RedirectResponse;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\HttpFoundation\RequestStack;
  19. use Symfony\Component\HttpKernel\Event\RequestEvent;
  20. use Symfony\Component\HttpKernel\KernelEvents;
  21. use Symfony\Component\Routing\Router;
  22. use Symfony\Component\Routing\RouterInterface;
  23. use Symfony\Component\Security\Core\AuthenticationEvents;
  24. use Symfony\Component\Security\Core\Event\AuthenticationEvent;
  25. use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
  26. use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
  27. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  28. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  29. use Symfony\Component\Security\Http\SecurityEvents;
  30. class SecurityListener implements EventSubscriberInterface
  31. {
  32.     const MAX_LOGIN_ATTEMPTS 10;
  33.     const ROUTE_LOGIN 'fos_user_security_check';
  34.     const ROUTE_RESET_PASSWORD 'fos_user_resetting_send_email';
  35.     const PERIOD_BLOCK_LOGIN 'PT1H'// \DateInterval period for blocking login after to manx login fails
  36.     const PERIOD_RESET_ATTEMPTS 'PT1H'// \DateInterval period after which time the login attempts counter should be reset
  37.     /** @var RouterInterface */
  38.     private $router;
  39.     /** @var Request */
  40.     private $request;
  41.     /** @var EntityManagerInterface */
  42.     private $em;
  43.     /** @var UserManagerInterface */
  44.     private $userManager;
  45.     /** @var MailService */
  46.     private $mailer;
  47.     public static function getSubscribedEvents()
  48.     {
  49.         return [
  50.             KernelEvents::REQUEST => ['checkHoneyPot'10],
  51.             AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
  52.             SecurityEvents::INTERACTIVE_LOGIN => 'onLogin',
  53.             FOSUserEvents::CHANGE_PASSWORD_COMPLETED => 'onChangePassword',
  54.             FOSUserEvents::RESETTING_RESET_COMPLETED => 'onResetPasswordFinish',
  55.             FOSUserEvents::RESETTING_SEND_EMAIL_COMPLETED => 'onResetPasswordStart'
  56.         ];
  57.     }
  58.     public function __construct(RouterInterface $routerRequestStack $requestUserManagerInterface $userManagerEntityManagerInterface $emMailService $mailer)
  59.     {
  60.         $this->router $router;
  61.         $this->request $request->getCurrentRequest();
  62.         $this->userManager $userManager;
  63.         $this->em $em;
  64.         $this->mailer $mailer;
  65.     }
  66.     public function checkHoneyPot(RequestEvent $event)
  67.     {
  68.         // Only post routes, first check is for typehint on $this->router
  69.         $request $event->getRequest();
  70.         if (!$this->router instanceof Router || !$request->isMethod(Request::METHOD_POST)) {
  71.             return;
  72.         }
  73.         // Only for the login check route
  74.         $route $this->router->matchRequest($request)['_route'] ?? '';
  75.         if (self::ROUTE_LOGIN == $route) {
  76.             $honeyPot $this->request->get('_password_repeat');
  77.             if ($honeyPot) {
  78.                 $this->request->request->set('_username''');
  79.                 $this->request->request->set('_password''');
  80.                 $event->setResponse(new RedirectResponse('/'));
  81.             }
  82.         }
  83.         if (self::ROUTE_RESET_PASSWORD == $route) {
  84.             $honeyPot $this->request->get('username_repeat');
  85.             if ($honeyPot) {
  86.                 $this->request->request->set('username''');
  87.                 $event->setResponse(new RedirectResponse('/'));
  88.             }
  89.         }
  90.         /* check not needed as isAccountNonLocked() is automatically checked
  91.         $user = $this->userManager->findUserByEmail($this->request->get('_username'));
  92.         if ($user) {
  93.             if ($user->isAccountNonLocked()) {
  94.                 throw new AuthenticationException('to many login fails, login blocked for 2 hours' );
  95.             }
  96.         }
  97.         */
  98.     }
  99.     public function onAuthenticationFailure(AuthenticationFailureEvent $event)
  100.     {
  101.         $email $this->request->get('_username');
  102.         $log = new UserActionLog();
  103.         $log->setAction(UserActionLog::ACTION_FAILED_LOGIN);
  104.         $log->setEmail($email);
  105.         $log->setIp($this->request->getClientIp());
  106.         $this->em->persist($log);
  107.         $this->em->flush();
  108.         if (!$email) {
  109.             return;
  110.         }
  111.         /** @var User $user */
  112.         $user $this->userManager->findUserByEmail($email);
  113.         if (!$user) {
  114.             return;
  115.         }
  116.         $date $user->getLastFailedLogin();
  117.         if ($date instanceof \DateTime) {
  118.             $date->add(new \DateInterval(self::PERIOD_RESET_ATTEMPTS));
  119.             $now = new \DateTime();
  120.             if ($date <= $now) {
  121.                 $user->resetLoginAttempts();
  122.             }
  123.         }
  124.         $user->addFailedLoginAttempt();
  125.         if ($user->getLoginAttempts() >= self::MAX_LOGIN_ATTEMPTS) {
  126.             $user->bockTemporary(new \DateInterval(self::PERIOD_BLOCK_LOGIN));
  127.         }
  128.         $this->em->persist($user);
  129.         $this->em->flush();
  130.     }
  131.     public function onLogin(InteractiveLoginEvent $event)
  132.     {
  133.         $user $event->getAuthenticationToken()->getUser();
  134.         $user->resetLoginAttempts();
  135.         $this->em->persist($user);
  136.         $this->em->flush();
  137.     }
  138.     public function onChangePassword(FilterUserResponseEvent $event)
  139.     {
  140.         $user $event->getUser();
  141.         $log = new UserActionLog();
  142.         $log->setEmail($user->getEmail());
  143.         $log->setIp($this->request->getClientIp());
  144.         $log->setAction(UserActionLog::ACTION_PWD_CHANGED);
  145.         $this->em->persist($log);
  146.         $this->em->flush();
  147.         $this->mailer->sendPasswordChanged($user);
  148.     }
  149.     public function onResetPasswordStart(GetResponseUserEvent $event)
  150.     {
  151.         $user $event->getUser();
  152.         $log = new UserActionLog();
  153.         $log->setEmail($user->getEmail());
  154.         $log->setIp($this->request->getClientIp());
  155.         $log->setAction(UserActionLog::ACTION_PWD_CHANGED);
  156.         $this->em->persist($log);
  157.         $this->em->flush();
  158.     }
  159.     public function onResetPasswordFinish(FilterUserResponseEvent $event)
  160.     {
  161.         $user $event->getUser();
  162.         if ($user->getRegisterState() == User::REGISTERED_RESET) {
  163.             $user->setRegisterState(User::REGISTERED_CONFIRMED);
  164.             $this->userManager->updateUser($user);
  165.         }
  166.     }
  167. }