<?php
namespace App\Listener\Security;
use App\Entity\Notification;
use App\Entity\Notification\StaffNotification;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
class LockoutPolicyListener implements EventSubscriberInterface
{
/**
* @var EntityManagerInterface
*/
private $em;
/**
* @var int
*/
private $lockoutPolicy;
/**
* @var RequestStack
*/
private $requestStack;
public static function getSubscribedEvents()
{
return [
AuthenticationEvents::AUTHENTICATION_FAILURE => 'onFailure',
AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onSuccess',
];
}
public function __construct(EntityManagerInterface $em, RequestStack $requestStack, int $lockoutPolicy)
{
$this->em = $em;
$this->lockoutPolicy = $lockoutPolicy;
$this->requestStack = $requestStack;
}
public function onFailure(AuthenticationFailureEvent $event)
{
$email = $event->getAuthenticationToken()->getCredentials()['_username'];
$user = $this->em->getRepository(User::class)->findUser($email);
if (!$user instanceof User) {
return;
}
if ($user->getGroups()->isEmpty()) {
return;
}
if ($user->isRemoved()) {
return;
}
if ($user->getFailures() >= $this->lockoutPolicy) {
throw new CustomUserMessageAuthenticationException('account.locked');
}
$user->incrementFailures();
if ($user->getFailures() >= $this->lockoutPolicy) {
// Create staff notification
$notification = (new StaffNotification())->setup(
$user,
sprintf('User %s has been blocked due to too many consecutive authentication failures.', $user->getEmail()),
sprintf('user.lockout.%d', $user->getId()),
'staff.users.index',
false
);
$this->em->getRepository(Notification::class)->notifyStaff($notification, false);
}
$this->em->flush();
}
public function onSuccess(AuthenticationEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if (!$user instanceof User) {
return;
}
$user->resetFailures();
$this->em->flush();
}
}