src/Web/EventSubscriber/LockedUser.php line 53

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace SpringerNature\CPS\AMEDReviewTracker\Web\EventSubscriber;
  4. use FOS\UserBundle\Model\UserManagerInterface;
  5. use SpringerNature\CPS\AMEDReviewTracker\App\LockedUserNotifierInterface;
  6. use SpringerNature\CPS\AMEDReviewTracker\Web\Entity\WebUser;
  7. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  8. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  9. use Symfony\Component\Security\Core\AuthenticationEvents;
  10. use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
  11. use Symfony\Component\Security\Core\Event\AuthenticationSuccessEvent;
  12. use Symfony\Component\Security\Core\Exception\LockedException;
  13. class LockedUser implements EventSubscriberInterface
  14. {
  15.     /**
  16.      * @var SessionInterface
  17.      */
  18.     private $session;
  19.     /**
  20.      * @var UserManagerInterface
  21.      */
  22.     private $userManager;
  23.     /**
  24.      * @var LockedUserNotifierInterface
  25.      */
  26.     private $notifier;
  27.     private const MAX_FAILED_LOGIN_ATTEMPTS 5;
  28.     private const FAILED_LOGIN_TIME_WINDOW_MINUTES 10;
  29.     private const FAILED_LOGINS_KEY 'failed_logins';
  30.     public function __construct(SessionInterface $sessionUserManagerInterface $userManagerLockedUserNotifierInterface $notifier)
  31.     {
  32.         $this->session $session;
  33.         $this->userManager $userManager;
  34.         $this->notifier $notifier;
  35.     }
  36.     public static function getSubscribedEvents(): array
  37.     {
  38.         return [
  39.             AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess',
  40.             AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
  41.         ];
  42.     }
  43.     public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void
  44.     {
  45.         $username $event->getAuthenticationToken()->getUsername();
  46.         /** @var WebUser $user */
  47.         $user $this->userManager->findUserByUsernameOrEmail($username);
  48.         if (null === $user) {
  49.             return;
  50.         }
  51.         if ($user->isAccountLocked()) {
  52.             throw new LockedException('Account is locked');
  53.         }
  54.     }
  55.     public function onAuthenticationFailure(AuthenticationFailureEvent $event): void
  56.     {
  57.         $username $event->getAuthenticationToken()->getUsername();
  58.         /** @var WebUser $user */
  59.         $user $this->userManager->findUserByUsernameOrEmail($username);
  60.         if (null === $user) {
  61.             return;
  62.         }
  63.         $this->incrementFailedLoginCounter($username);
  64.         $numberOfFailedLogins $this->getNumberOfFailedLogins($username);
  65.         if ($numberOfFailedLogins >= self::MAX_FAILED_LOGIN_ATTEMPTS && ! $user->isAccountLocked()) {
  66.             $user->lockAccount();
  67.             $this->userManager->updateUser($user);
  68.             $this->notifier->__invoke($username);
  69.         }
  70.         if ($user->isAccountLocked()) {
  71.             throw new LockedException('Account is locked');
  72.         }
  73.     }
  74.     private function getNumberOfFailedLogins(string $username): int
  75.     {
  76.         $failedLogins $this->session->get(self::FAILED_LOGINS_KEY, []);
  77.         $timestamp time();
  78.         if (isset($failedLogins[$username]) && $failedLogins[$username]['expiry'] > $timestamp) {
  79.             return $failedLogins[$username]['count'];
  80.         }
  81.         return 0;
  82.     }
  83.     private function incrementFailedLoginCounter(string $username): void
  84.     {
  85.         $failedLogins $this->session->get(self::FAILED_LOGINS_KEY, []);
  86.         $timestamp time();
  87.         if (isset($failedLogins[$username]) && $failedLogins[$username]['expiry'] > $timestamp) {
  88.             $failedLogins[$username] = [
  89.                 'count' => $failedLogins[$username]['count'] + 1,
  90.                 'expiry' => $failedLogins[$username]['expiry'],
  91.             ];
  92.         } else {
  93.             $failedLogins[$username] = [
  94.                 'count' => 1,
  95.                 'expiry' => $timestamp + (self::FAILED_LOGIN_TIME_WINDOW_MINUTES 60),
  96.             ];
  97.         }
  98.         $this->session->set(self::FAILED_LOGINS_KEY$failedLogins);
  99.     }
  100. }