I've just upgraded an app from Symfony 4.3.9 to 4.4.2. After that, I had the debug bar not working and showing "An error occurred while loading the web debug toolbar. "
After a long investigation, I found that it's because of an EventListener on security.authentication.failure event that was the cause.
Commenting the onAuthenticationFailure method content did nothing and after some investigation it works when removing the TokenStorageInterface from the tags and constructor... But I need it.
Any ideas?
Here's the code :
services.yaml
App\EventListener\LoginListener:
arguments: ["@doctrine", "@security.token_storage", "@router", "@event_dispatcher"]
tags:
- { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
LoginListener.php
<?php
namespace App\EventListener;
use App\Entity\AdminUser;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
/**
* Class LoginListener
* Listens to user log in events (failure, interactive log in) to provide additionnal security measures
*
* @package App\EventListener
*/
final class LoginListener
{
protected $doctrine;
protected $request;
protected $tokenStorage;
protected $router;
protected $dispatcher;
/**
* Login constructor.
*
* @param Registry $doctrine
* @param TokenStorageInterface $tokenStorage
* @param RouterInterface $router
* @param EventDispatcherInterface $dispatcher
*/
public function __construct(
Registry $doctrine,
TokenStorageInterface $tokenStorage,
RouterInterface $router,
EventDispatcherInterface $dispatcher
) {
$this->doctrine = $doctrine;
$this->tokenStorage = $tokenStorage;
$this->router = $router;
$this->dispatcher = $dispatcher;
}
/**
* @param AuthenticationFailureEvent $event
* @throws ORMException
* @throws OptimisticLockException
*/
public function onAuthenticationFailure(AuthenticationFailureEvent $event)
{
/** @var EntityManager $em */
$em = $this->doctrine->getManager();
$username = $event->getAuthenticationToken()->getUsername();
/** @var AdminUser $user */
$user = $em->getRepository(AdminUser::class)->findOneBy(['username' => $username]);
if ($user instanceof AdminUser) {
$user->addFailedLogin();
if ($user->getFailedLogin() == 5) {
$user->setLocked(1);
}
$em->persist($user);
$em->flush();
}
}
}
Thanks :)
---EDIT---
In fact that listener was an edit from another one. It doesn't need the TokenStorage but I'll have the problem in that one in a near future then :
<?php
namespace App\XXXBundle\EventListener;
use App\XXXBundle\Entity\AdminUser;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\ORM\EntityManager;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
/**
* Class Login
* Listens to user log in events (failure, interactive log in) to provide additionnal security measures
*
* @package App\XXXBundle\EventListener
*/
class Login
{
protected $doctrine;
protected $request;
protected $tokenStorage;
protected $router;
protected $dispatcher;
/**
* Login constructor.
*
* @param Registry $doctrine
* @param TokenStorage $tokenStorage
* @param RouterInterface $router
* @param EventDispatcherInterface $dispatcher
*/
public function __construct(
Registry $doctrine,
TokenStorage $tokenStorage,
RouterInterface $router,
EventDispatcherInterface $dispatcher
) {
$this->doctrine = $doctrine;
$this->tokenStorage = $tokenStorage;
$this->router = $router;
$this->dispatcher = $dispatcher;
}
/**
* @param AuthenticationFailureEvent $event
* @throws \Doctrine\ORM\ORMException
* @throws \Doctrine\ORM\OptimisticLockException
*/
public function onAuthenticationFailure(AuthenticationFailureEvent $event)
{
/** @var EntityManager $em */
$em = $this->doctrine->getManager();
$userName = $event->getAuthenticationToken()->getUsername();
/** @var AdminUser $user */
$user = $em->getRepository(AdminUser::class)->findOneByUsername($userName);
if ($user instanceof AdvancedUserInterface) {
$user->addFailedLogin();
if ($user->getFailedLogin() == 5) {
$user->setLocked(1);
}
$em->persist($user);
$em->flush();
}
}
/**
* @param InteractiveLoginEvent $event
* @throws \Exception
*/
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$user = $event->getAuthenticationToken()->getUser();
if ($user instanceof AdvancedUserInterface) {
$em = $this->doctrine->getManager();
if ($user->getLocked()) {
$this->tokenStorage->setToken(null);
throw new CustomUserMessageAuthenticationException('Compte verrouillé.');
}
if ($user->getExpiresAt() && $user->getExpiresAt() <= new \DateTime()) {
$user->setIsActive(0);
$em->persist($user);
$em->flush();
$this->tokenStorage->setToken(null);
throw new CustomUserMessageAuthenticationException('Compte expiré.');
}
if ($user->getCredentialsExpireAt() && $user->getCredentialsExpireAt() <= new \DateTime()) {
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'redirectToCredentialsChange']);
}
$user->setLastLogin(new \DateTime());
$user->setFailedLogin(0);
$em->persist($user);
$em->flush();
}
}
public function redirectToCredentialsChange(ResponseEvent $event)
{
$event->getResponse()->headers->set('Location', $this->router->generate('admin_security_changecredentials'));
}
}