vendor/scheb/2fa-bundle/Security/TwoFactor/Provider/TwoFactorProviderPreparationListener.php line 60

  1. <?php
  2. declare(strict_types=1);
  3. namespace Scheb\TwoFactorBundle\Security\TwoFactor\Provider;
  4. use Psr\Log\LoggerInterface;
  5. use Psr\Log\NullLogger;
  6. use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
  7. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvent;
  8. use Scheb\TwoFactorBundle\Security\TwoFactor\Event\TwoFactorAuthenticationEvents;
  9. use Scheb\TwoFactorBundle\Security\TwoFactor\Provider\Exception\UnexpectedTokenException;
  10. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  11. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  12. use Symfony\Component\HttpKernel\KernelEvents;
  13. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  14. use Symfony\Component\Security\Core\AuthenticationEvents;
  15. use Symfony\Component\Security\Core\Event\AuthenticationEvent;
  16. use function assert;
  17. use function sprintf;
  18. use const PHP_INT_MAX;
  19. /**
  20.  * @final
  21.  */
  22. class TwoFactorProviderPreparationListener implements EventSubscriberInterface
  23. {
  24.     // This must trigger very first, followed by AuthenticationSuccessEventSuppressor
  25.     public const AUTHENTICATION_SUCCESS_LISTENER_PRIORITY PHP_INT_MAX;
  26.     // Execute right before ContextListener, which is serializing the security token into the session
  27.     public const RESPONSE_LISTENER_PRIORITY 1;
  28.     private ?TwoFactorTokenInterface $twoFactorToken null;
  29.     private LoggerInterface $logger;
  30.     public function __construct(
  31.         private TwoFactorProviderRegistry $providerRegistry,
  32.         private PreparationRecorderInterface $preparationRecorder,
  33.         ?LoggerInterface $logger,
  34.         private string $firewallName,
  35.         private bool $prepareOnLogin,
  36.         private bool $prepareOnAccessDenied
  37.     ) {
  38.         $this->logger $logger ?? new NullLogger();
  39.     }
  40.     public function onLogin(AuthenticationEvent $event): void
  41.     {
  42.         $token $event->getAuthenticationToken();
  43.         if (!$this->prepareOnLogin || !$this->supports($token)) {
  44.             return;
  45.         }
  46.         // After login, when the token is a TwoFactorTokenInterface, execute preparation
  47.         assert($token instanceof TwoFactorTokenInterface);
  48.         $this->twoFactorToken $token;
  49.     }
  50.     public function onAccessDenied(TwoFactorAuthenticationEvent $event): void
  51.     {
  52.         $token $event->getToken();
  53.         if (!$this->prepareOnAccessDenied || !$this->supports($token)) {
  54.             return;
  55.         }
  56.         // Whenever two-factor authentication is required, execute preparation
  57.         assert($token instanceof TwoFactorTokenInterface);
  58.         $this->twoFactorToken $token;
  59.     }
  60.     public function onTwoFactorForm(TwoFactorAuthenticationEvent $event): void
  61.     {
  62.         $token $event->getToken();
  63.         if (!$this->supports($token)) {
  64.             return;
  65.         }
  66.         // Whenever two-factor authentication form is shown, execute preparation
  67.         assert($token instanceof TwoFactorTokenInterface);
  68.         $this->twoFactorToken $token;
  69.     }
  70.     public function onKernelResponse(ResponseEvent $event): void
  71.     {
  72.         if (!$event->isMainRequest()) {
  73.             return;
  74.         }
  75.         // Unset the token from context. This is important for environments where this instance of the class is reused
  76.         // for multiple requests, such as PHP PM.
  77.         $twoFactorToken $this->twoFactorToken;
  78.         $this->twoFactorToken null;
  79.         if (!($twoFactorToken instanceof TwoFactorTokenInterface)) {
  80.             return;
  81.         }
  82.         $providerName $twoFactorToken->getCurrentTwoFactorProvider();
  83.         if (null === $providerName) {
  84.             return;
  85.         }
  86.         $firewallName $twoFactorToken->getFirewallName();
  87.         try {
  88.             if ($this->preparationRecorder->isTwoFactorProviderPrepared($firewallName$providerName)) {
  89.                 $this->logger->info(sprintf('Two-factor provider "%s" was already prepared.'$providerName));
  90.                 return;
  91.             }
  92.             $user $twoFactorToken->getUser();
  93.             $this->providerRegistry->getProvider($providerName)->prepareAuthentication($user);
  94.             $this->preparationRecorder->setTwoFactorProviderPrepared($firewallName$providerName);
  95.             $this->logger->info(sprintf('Two-factor provider "%s" prepared.'$providerName));
  96.         } catch (UnexpectedTokenException) {
  97.             $this->logger->info(sprintf('Two-factor provider "%s" was not prepared, security token was change within the request.'$providerName));
  98.         }
  99.     }
  100.     private function supports(TokenInterface $token): bool
  101.     {
  102.         return $token instanceof TwoFactorTokenInterface && $token->getFirewallName() === $this->firewallName;
  103.     }
  104.     /**
  105.      * {@inheritdoc}
  106.      */
  107.     public static function getSubscribedEvents(): array
  108.     {
  109.         return [
  110.             AuthenticationEvents::AUTHENTICATION_SUCCESS => ['onLogin'self::AUTHENTICATION_SUCCESS_LISTENER_PRIORITY],
  111.             TwoFactorAuthenticationEvents::REQUIRE => 'onAccessDenied',
  112.             TwoFactorAuthenticationEvents::FORM => 'onTwoFactorForm',
  113.             KernelEvents::RESPONSE => ['onKernelResponse'self::RESPONSE_LISTENER_PRIORITY],
  114.         ];
  115.     }
  116. }