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

Open in your IDE?
  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\KernelEvent;
  12. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  13. use Symfony\Component\HttpKernel\KernelEvents;
  14. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  15. use Symfony\Component\Security\Core\AuthenticationEvents;
  16. use Symfony\Component\Security\Core\Event\AuthenticationEvent;
  17. /**
  18.  * @final
  19.  */
  20. class TwoFactorProviderPreparationListener implements EventSubscriberInterface
  21. {
  22.     // This must trigger very first, followed by AuthenticationSuccessEventSuppressor
  23.     public const AUTHENTICATION_SUCCESS_LISTENER_PRIORITY PHP_INT_MAX;
  24.     // Execute right before ContextListener, which is serializing the security token into the session
  25.     public const RESPONSE_LISTENER_PRIORITY 1;
  26.     /** @deprecated */
  27.     public const LISTENER_PRIORITY self::AUTHENTICATION_SUCCESS_LISTENER_PRIORITY;
  28.     /**
  29.      * @var TwoFactorProviderRegistry
  30.      */
  31.     private $providerRegistry;
  32.     /**
  33.      * @var PreparationRecorderInterface
  34.      */
  35.     private $preparationRecorder;
  36.     /**
  37.      * @var TwoFactorTokenInterface|null
  38.      */
  39.     private $twoFactorToken;
  40.     /**
  41.      * @var LoggerInterface
  42.      */
  43.     private $logger;
  44.     /**
  45.      * @var string
  46.      */
  47.     private $firewallName;
  48.     /**
  49.      * @var bool
  50.      */
  51.     private $prepareOnLogin;
  52.     /**
  53.      * @var bool
  54.      */
  55.     private $prepareOnAccessDenied;
  56.     public function __construct(
  57.         TwoFactorProviderRegistry $providerRegistry,
  58.         PreparationRecorderInterface $preparationRecorder,
  59.         ?LoggerInterface $logger,
  60.         string $firewallName,
  61.         bool $prepareOnLogin,
  62.         bool $prepareOnAccessDenied
  63.     ) {
  64.         $this->providerRegistry $providerRegistry;
  65.         $this->preparationRecorder $preparationRecorder;
  66.         $this->logger $logger ?? new NullLogger();
  67.         $this->firewallName $firewallName;
  68.         $this->prepareOnLogin $prepareOnLogin;
  69.         $this->prepareOnAccessDenied $prepareOnAccessDenied;
  70.     }
  71.     public function onLogin(AuthenticationEvent $event): void
  72.     {
  73.         $token $event->getAuthenticationToken();
  74.         if ($this->prepareOnLogin && $this->supports($token)) {
  75.             /** @var TwoFactorTokenInterface $token */
  76.             // After login, when the token is a TwoFactorTokenInterface, execute preparation
  77.             $this->twoFactorToken $token;
  78.         }
  79.     }
  80.     public function onAccessDenied(TwoFactorAuthenticationEvent $event): void
  81.     {
  82.         $token $event->getToken();
  83.         if ($this->prepareOnAccessDenied && $this->supports($token)) {
  84.             /** @var TwoFactorTokenInterface $token */
  85.             // Whenever two-factor authentication is required, execute preparation
  86.             $this->twoFactorToken $token;
  87.         }
  88.     }
  89.     public function onTwoFactorForm(TwoFactorAuthenticationEvent $event): void
  90.     {
  91.         $token $event->getToken();
  92.         if ($this->supports($token)) {
  93.             /** @var TwoFactorTokenInterface $token */
  94.             // Whenever two-factor authentication form is shown, execute preparation
  95.             $this->twoFactorToken $token;
  96.         }
  97.     }
  98.     public function onKernelResponse(ResponseEvent $event): void
  99.     {
  100.         // Compatibility for Symfony >= 5.3
  101.         if (method_exists(KernelEvent::class, 'isMainRequest')) {
  102.             if (!$event->isMainRequest()) {
  103.                 return;
  104.             }
  105.         } else {
  106.             if (!$event->isMasterRequest()) {
  107.                 return;
  108.             }
  109.         }
  110.         // Unset the token from context. This is important for environments where this instance of the class is reused
  111.         // for multiple requests, such as PHP PM.
  112.         $twoFactorToken $this->twoFactorToken;
  113.         $this->twoFactorToken null;
  114.         if (!($twoFactorToken instanceof TwoFactorTokenInterface)) {
  115.             return;
  116.         }
  117.         $providerName $twoFactorToken->getCurrentTwoFactorProvider();
  118.         if (null === $providerName) {
  119.             return;
  120.         }
  121.         $firewallName $twoFactorToken->getProviderKey(true);
  122.         try {
  123.             if ($this->preparationRecorder->isTwoFactorProviderPrepared($firewallName$providerName)) {
  124.                 $this->logger->info(sprintf('Two-factor provider "%s" was already prepared.'$providerName));
  125.                 return;
  126.             }
  127.             $user $twoFactorToken->getUser();
  128.             $this->providerRegistry->getProvider($providerName)->prepareAuthentication($user);
  129.             $this->preparationRecorder->setTwoFactorProviderPrepared($firewallName$providerName);
  130.             $this->logger->info(sprintf('Two-factor provider "%s" prepared.'$providerName));
  131.         } catch (UnexpectedTokenException $e) {
  132.             $this->logger->info(sprintf('Two-factor provider "%s" was not prepared, security token was change within the request.'$providerName));
  133.         }
  134.     }
  135.     private function supports(TokenInterface $token): bool
  136.     {
  137.         return $token instanceof TwoFactorTokenInterface && $token->getProviderKey(true) === $this->firewallName;
  138.     }
  139.     public static function getSubscribedEvents()
  140.     {
  141.         return [
  142.             AuthenticationEvents::AUTHENTICATION_SUCCESS => ['onLogin'self::AUTHENTICATION_SUCCESS_LISTENER_PRIORITY],
  143.             TwoFactorAuthenticationEvents::REQUIRE => 'onAccessDenied',
  144.             TwoFactorAuthenticationEvents::FORM => 'onTwoFactorForm',
  145.             KernelEvents::RESPONSE => ['onKernelResponse'self::RESPONSE_LISTENER_PRIORITY],
  146.         ];
  147.     }
  148. }