<?php
declare(strict_types=1);
namespace Scheb\TwoFactorBundle\Security\Http\Firewall;
use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
use Scheb\TwoFactorBundle\Security\Http\Authentication\AuthenticationRequiredHandlerInterface;
use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvent;
use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
/**
* @final
*/
class ExceptionListener implements EventSubscriberInterface
{
// Just before the firewall's Symfony\Component\Security\Http\Firewall\ExceptionListener
private const LISTENER_PRIORITY = 2;
/**
* @var string
*/
private $firewallName;
/**
* @var TokenStorageInterface
*/
private $tokenStorage;
/**
* @var AuthenticationRequiredHandlerInterface
*/
private $authenticationRequiredHandler;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
public function __construct(
string $firewallName,
TokenStorageInterface $tokenStorage,
AuthenticationRequiredHandlerInterface $authenticationRequiredHandler,
EventDispatcherInterface $eventDispatcher
) {
$this->firewallName = $firewallName;
$this->tokenStorage = $tokenStorage;
$this->authenticationRequiredHandler = $authenticationRequiredHandler;
$this->eventDispatcher = $eventDispatcher;
}
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
do {
if ($exception instanceof AccessDeniedException) {
$this->handleAccessDeniedException($event);
return;
}
} while (null !== $exception = $exception->getPrevious());
}
private function handleAccessDeniedException(ExceptionEvent $exceptionEvent): void
{
$token = $this->tokenStorage->getToken();
if (!($token instanceof TwoFactorTokenInterface && $token->getProviderKey(true) === $this->firewallName)) {
return;
}
/** @var TwoFactorTokenInterface $token */
$request = $exceptionEvent->getRequest();
$event = new TwoFactorAuthenticationEvent($request, $token);
$this->eventDispatcher->dispatch($event, TwoFactorAuthenticationEvents::REQUIRE);
$response = $this->authenticationRequiredHandler->onAuthenticationRequired($request, $token);
$exceptionEvent->allowCustomResponseCode();
$exceptionEvent->setResponse($response);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::EXCEPTION => ['onKernelException', self::LISTENER_PRIORITY],
];
}
}