vendor/symfony/security-guard/Firewall/GuardAuthenticationListener.php line 32

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Security\Guard\Firewall;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpKernel\Event\RequestEvent;
  15. use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
  16. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  17. use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
  18. use Symfony\Component\Security\Core\Exception\AccountStatusException;
  19. use Symfony\Component\Security\Core\Exception\AuthenticationException;
  20. use Symfony\Component\Security\Core\Exception\BadCredentialsException;
  21. use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException;
  22. use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
  23. use Symfony\Component\Security\Guard\AuthenticatorInterface;
  24. use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
  25. use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
  26. use Symfony\Component\Security\Http\Firewall\AbstractListener;
  27. use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
  28. trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', GuardAuthenticationListener::class);
  29. /**
  30. * Authentication listener for the "guard" system.
  31. *
  32. * @author Ryan Weaver <ryan@knpuniversity.com>
  33. * @author Amaury Leroux de Lens <amaury@lerouxdelens.com>
  34. *
  35. * @final
  36. *
  37. * @deprecated since Symfony 5.3, use the new authenticator system instead
  38. */
  39. class GuardAuthenticationListener extends AbstractListener
  40. {
  41. private $guardHandler;
  42. private $authenticationManager;
  43. private $providerKey;
  44. private $guardAuthenticators;
  45. private $logger;
  46. private $rememberMeServices;
  47. private $hideUserNotFoundExceptions;
  48. private $tokenStorage;
  49. /**
  50. * @param string $providerKey The provider (i.e. firewall) key
  51. * @param iterable<array-key, AuthenticatorInterface> $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider
  52. */
  53. public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, ?LoggerInterface $logger = null, bool $hideUserNotFoundExceptions = true, ?TokenStorageInterface $tokenStorage = null)
  54. {
  55. if (empty($providerKey)) {
  56. throw new \InvalidArgumentException('$providerKey must not be empty.');
  57. }
  58. $this->guardHandler = $guardHandler;
  59. $this->authenticationManager = $authenticationManager;
  60. $this->providerKey = $providerKey;
  61. $this->guardAuthenticators = $guardAuthenticators;
  62. $this->logger = $logger;
  63. $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions;
  64. $this->tokenStorage = $tokenStorage;
  65. }
  66. /**
  67. * {@inheritdoc}
  68. */
  69. public function supports(Request $request): ?bool
  70. {
  71. if (null !== $this->logger) {
  72. $context = ['firewall_key' => $this->providerKey];
  73. if ($this->guardAuthenticators instanceof \Countable || \is_array($this->guardAuthenticators)) {
  74. $context['authenticators'] = \count($this->guardAuthenticators);
  75. }
  76. $this->logger->debug('Checking for guard authentication credentials.', $context);
  77. }
  78. $guardAuthenticators = [];
  79. foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
  80. if (null !== $this->logger) {
  81. $this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
  82. }
  83. if ($guardAuthenticator->supports($request)) {
  84. $guardAuthenticators[$key] = $guardAuthenticator;
  85. } elseif (null !== $this->logger) {
  86. $this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
  87. }
  88. }
  89. if (!$guardAuthenticators) {
  90. return false;
  91. }
  92. $request->attributes->set('_guard_authenticators', $guardAuthenticators);
  93. return true;
  94. }
  95. /**
  96. * Iterates over each authenticator to see if each wants to authenticate the request.
  97. */
  98. public function authenticate(RequestEvent $event)
  99. {
  100. $request = $event->getRequest();
  101. $guardAuthenticators = $request->attributes->get('_guard_authenticators');
  102. $request->attributes->remove('_guard_authenticators');
  103. foreach ($guardAuthenticators as $key => $guardAuthenticator) {
  104. // get a key that's unique to *this* guard authenticator
  105. // this MUST be the same as GuardAuthenticationProvider
  106. $uniqueGuardKey = $this->providerKey.'_'.$key;
  107. $this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event);
  108. if ($event->hasResponse()) {
  109. if (null !== $this->logger) {
  110. $this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($guardAuthenticator)]);
  111. }
  112. break;
  113. }
  114. }
  115. }
  116. private function executeGuardAuthenticator(string $uniqueGuardKey, AuthenticatorInterface $guardAuthenticator, RequestEvent $event)
  117. {
  118. $request = $event->getRequest();
  119. $previousToken = $this->tokenStorage ? $this->tokenStorage->getToken() : null;
  120. try {
  121. if (null !== $this->logger) {
  122. $this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
  123. }
  124. // allow the authenticator to fetch authentication info from the request
  125. $credentials = $guardAuthenticator->getCredentials($request);
  126. if (null === $credentials) {
  127. throw new \UnexpectedValueException(sprintf('The return value of "%1$s::getCredentials()" must not be null. Return false from "%1$s::supports()" instead.', get_debug_type($guardAuthenticator)));
  128. }
  129. // create a token with the unique key, so that the provider knows which authenticator to use
  130. $token = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey);
  131. if (null !== $this->logger) {
  132. $this->logger->debug('Passing guard token information to the GuardAuthenticationProvider', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
  133. }
  134. // pass the token into the AuthenticationManager system
  135. // this indirectly calls GuardAuthenticationProvider::authenticate()
  136. $token = $this->authenticationManager->authenticate($token);
  137. if (null !== $this->logger) {
  138. $this->logger->info('Guard authentication successful!', ['token' => $token, 'authenticator' => \get_class($guardAuthenticator)]);
  139. }
  140. // sets the token on the token storage, etc
  141. if ($this->tokenStorage) {
  142. $this->guardHandler->authenticateWithToken($token, $request, $this->providerKey, $previousToken);
  143. } else {
  144. $this->guardHandler->authenticateWithToken($token, $request, $this->providerKey);
  145. }
  146. } catch (AuthenticationException $e) {
  147. // oh no! Authentication failed!
  148. if (null !== $this->logger) {
  149. $this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]);
  150. }
  151. // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status)
  152. // to prevent user enumeration via response content
  153. if ($this->hideUserNotFoundExceptions && ($e instanceof UsernameNotFoundException || ($e instanceof AccountStatusException && !$e instanceof CustomUserMessageAccountStatusException))) {
  154. $e = new BadCredentialsException('Bad credentials.', 0, $e);
  155. }
  156. $response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey);
  157. if ($response instanceof Response) {
  158. $event->setResponse($response);
  159. }
  160. return;
  161. }
  162. // success!
  163. $response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey);
  164. if ($response instanceof Response) {
  165. if (null !== $this->logger) {
  166. $this->logger->debug('Guard authenticator set success response.', ['response' => $response, 'authenticator' => \get_class($guardAuthenticator)]);
  167. }
  168. $event->setResponse($response);
  169. } else {
  170. if (null !== $this->logger) {
  171. $this->logger->debug('Guard authenticator set no success response: request continues.', ['authenticator' => \get_class($guardAuthenticator)]);
  172. }
  173. }
  174. // attempt to trigger the remember me functionality
  175. $this->triggerRememberMe($guardAuthenticator, $request, $token, $response);
  176. }
  177. /**
  178. * Should be called if this listener will support remember me.
  179. */
  180. public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices)
  181. {
  182. $this->rememberMeServices = $rememberMeServices;
  183. }
  184. /**
  185. * Checks to see if remember me is supported in the authenticator and
  186. * on the firewall. If it is, the RememberMeServicesInterface is notified.
  187. */
  188. private function triggerRememberMe(AuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, ?Response $response = null)
  189. {
  190. if (null === $this->rememberMeServices) {
  191. if (null !== $this->logger) {
  192. $this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($guardAuthenticator)]);
  193. }
  194. return;
  195. }
  196. if (!$guardAuthenticator->supportsRememberMe()) {
  197. if (null !== $this->logger) {
  198. $this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($guardAuthenticator)]);
  199. }
  200. return;
  201. }
  202. if (!$response instanceof Response) {
  203. throw new \LogicException(sprintf('"%s::onAuthenticationSuccess()" *must* return a Response if you want to use the remember me functionality. Return a Response, or set remember_me to false under the guard configuration.', get_debug_type($guardAuthenticator)));
  204. }
  205. $this->rememberMeServices->loginSuccess($request, $response, $token);
  206. }
  207. }