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

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. }