vendor/scheb/2fa-bundle/Security/Authentication/Provider/AuthenticationProviderDecorator.php line 68

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Scheb\TwoFactorBundle\Security\Authentication\Provider;
  4. use Scheb\TwoFactorBundle\DependencyInjection\Factory\Security\TwoFactorFactory;
  5. use Scheb\TwoFactorBundle\Security\Authentication\Token\TwoFactorTokenInterface;
  6. use Scheb\TwoFactorBundle\Security\TwoFactor\AuthenticationContextFactoryInterface;
  7. use Scheb\TwoFactorBundle\Security\TwoFactor\Handler\AuthenticationHandlerInterface;
  8. use Symfony\Bundle\SecurityBundle\Security\FirewallConfig;
  9. use Symfony\Bundle\SecurityBundle\Security\FirewallMap;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\HttpFoundation\RequestStack;
  12. use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
  13. use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
  14. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  15. /**
  16. * @final
  17. */
  18. class AuthenticationProviderDecorator implements AuthenticationProviderInterface
  19. {
  20. /**
  21. * @var AuthenticationProviderInterface
  22. */
  23. private $decoratedAuthenticationProvider;
  24. /**
  25. * @var AuthenticationHandlerInterface
  26. */
  27. private $twoFactorAuthenticationHandler;
  28. /**
  29. * @var AuthenticationContextFactoryInterface
  30. */
  31. private $authenticationContextFactory;
  32. /**
  33. * @var FirewallMap
  34. */
  35. private $firewallMap;
  36. /**
  37. * @var RequestStack
  38. */
  39. private $requestStack;
  40. public function __construct(
  41. AuthenticationProviderInterface $decoratedAuthenticationProvider,
  42. AuthenticationHandlerInterface $twoFactorAuthenticationHandler,
  43. AuthenticationContextFactoryInterface $authenticationContextFactory,
  44. FirewallMap $firewallMap,
  45. RequestStack $requestStack
  46. ) {
  47. $this->decoratedAuthenticationProvider = $decoratedAuthenticationProvider;
  48. $this->twoFactorAuthenticationHandler = $twoFactorAuthenticationHandler;
  49. $this->authenticationContextFactory = $authenticationContextFactory;
  50. $this->firewallMap = $firewallMap;
  51. $this->requestStack = $requestStack;
  52. }
  53. public function supports(TokenInterface $token): bool
  54. {
  55. return $this->decoratedAuthenticationProvider->supports($token);
  56. }
  57. public function authenticate(TokenInterface $token): TokenInterface
  58. {
  59. $wasAlreadyAuthenticated = $token->isAuthenticated();
  60. /** @psalm-suppress InternalMethod */
  61. $token = $this->decoratedAuthenticationProvider->authenticate($token);
  62. // Only trigger two-factor authentication when the provider was called with an unauthenticated token. When we
  63. // get an authenticated token passed, we're not doing a login, but the system refreshes the token. Then we don't
  64. // want to start two-factor authentication or we're ending in an endless loop.
  65. if ($wasAlreadyAuthenticated) {
  66. return $token;
  67. }
  68. // AnonymousToken and TwoFactorTokenInterface can be ignored
  69. if ($token instanceof AnonymousToken || $token instanceof TwoFactorTokenInterface) {
  70. return $token;
  71. }
  72. $request = $this->getRequest();
  73. $firewallConfig = $this->getFirewallConfig($request);
  74. if (!\in_array(TwoFactorFactory::AUTHENTICATION_PROVIDER_KEY, $firewallConfig->getListeners(), true)) {
  75. return $token; // This firewall doesn't support two-factor authentication
  76. }
  77. $context = $this->authenticationContextFactory->create($request, $token, $firewallConfig->getName());
  78. return $this->twoFactorAuthenticationHandler->beginTwoFactorAuthentication($context);
  79. }
  80. /**
  81. * @return mixed
  82. */
  83. public function __call(string $method, array $arguments)
  84. {
  85. return ($this->decoratedAuthenticationProvider)->{$method}(...$arguments);
  86. }
  87. private function getRequest(): Request
  88. {
  89. // Compatibility for Symfony >= 5.3
  90. if (method_exists(RequestStack::class, 'getMainRequest')) {
  91. $request = $this->requestStack->getMainRequest();
  92. } else {
  93. $request = $this->requestStack->getMasterRequest();
  94. }
  95. if (null === $request) {
  96. throw new \RuntimeException('No request available');
  97. }
  98. return $request;
  99. }
  100. private function getFirewallConfig(Request $request): FirewallConfig
  101. {
  102. $firewallConfig = $this->firewallMap->getFirewallConfig($request);
  103. if (null === $firewallConfig) {
  104. throw new \RuntimeException('No firewall configuration available');
  105. }
  106. return $firewallConfig;
  107. }
  108. }