<?php 
 
/* 
 * This file is part of the Symfony package. 
 * 
 * (c) Fabien Potencier <fabien@symfony.com> 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Symfony\Component\Security\Guard\Firewall; 
 
use Psr\Log\LoggerInterface; 
use Symfony\Component\HttpFoundation\Request; 
use Symfony\Component\HttpFoundation\Response; 
use Symfony\Component\HttpKernel\Event\RequestEvent; 
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; 
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; 
use Symfony\Component\Security\Core\Exception\AccountStatusException; 
use Symfony\Component\Security\Core\Exception\AuthenticationException; 
use Symfony\Component\Security\Core\Exception\BadCredentialsException; 
use Symfony\Component\Security\Core\Exception\CustomUserMessageAccountStatusException; 
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; 
use Symfony\Component\Security\Guard\AuthenticatorInterface; 
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; 
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken; 
use Symfony\Component\Security\Http\Firewall\AbstractListener; 
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; 
 
trigger_deprecation('symfony/security-guard', '5.3', 'The "%s" class is deprecated, use the new authenticator system instead.', GuardAuthenticationListener::class); 
 
/** 
 * Authentication listener for the "guard" system. 
 * 
 * @author Ryan Weaver <ryan@knpuniversity.com> 
 * @author Amaury Leroux de Lens <amaury@lerouxdelens.com> 
 * 
 * @final 
 * 
 * @deprecated since Symfony 5.3, use the new authenticator system instead 
 */ 
class GuardAuthenticationListener extends AbstractListener 
{ 
    private $guardHandler; 
    private $authenticationManager; 
    private $providerKey; 
    private $guardAuthenticators; 
    private $logger; 
    private $rememberMeServices; 
    private $hideUserNotFoundExceptions; 
 
    /** 
     * @param string                                      $providerKey         The provider (i.e. firewall) key 
     * @param iterable<array-key, AuthenticatorInterface> $guardAuthenticators The authenticators, with keys that match what's passed to GuardAuthenticationProvider 
     */ 
    public function __construct(GuardAuthenticatorHandler $guardHandler, AuthenticationManagerInterface $authenticationManager, string $providerKey, iterable $guardAuthenticators, LoggerInterface $logger = null, bool $hideUserNotFoundExceptions = true) 
    { 
        if (empty($providerKey)) { 
            throw new \InvalidArgumentException('$providerKey must not be empty.'); 
        } 
 
        $this->guardHandler = $guardHandler; 
        $this->authenticationManager = $authenticationManager; 
        $this->providerKey = $providerKey; 
        $this->guardAuthenticators = $guardAuthenticators; 
        $this->logger = $logger; 
        $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; 
    } 
 
    /** 
     * {@inheritdoc} 
     */ 
    public function supports(Request $request): ?bool 
    { 
        if (null !== $this->logger) { 
            $context = ['firewall_key' => $this->providerKey]; 
 
            if ($this->guardAuthenticators instanceof \Countable || \is_array($this->guardAuthenticators)) { 
                $context['authenticators'] = \count($this->guardAuthenticators); 
            } 
 
            $this->logger->debug('Checking for guard authentication credentials.', $context); 
        } 
 
        $guardAuthenticators = []; 
 
        foreach ($this->guardAuthenticators as $key => $guardAuthenticator) { 
            if (null !== $this->logger) { 
                $this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); 
            } 
 
            if ($guardAuthenticator->supports($request)) { 
                $guardAuthenticators[$key] = $guardAuthenticator; 
            } elseif (null !== $this->logger) { 
                $this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); 
            } 
        } 
 
        if (!$guardAuthenticators) { 
            return false; 
        } 
 
        $request->attributes->set('_guard_authenticators', $guardAuthenticators); 
 
        return true; 
    } 
 
    /** 
     * Iterates over each authenticator to see if each wants to authenticate the request. 
     */ 
    public function authenticate(RequestEvent $event) 
    { 
        $request = $event->getRequest(); 
        $guardAuthenticators = $request->attributes->get('_guard_authenticators'); 
        $request->attributes->remove('_guard_authenticators'); 
 
        foreach ($guardAuthenticators as $key => $guardAuthenticator) { 
            // get a key that's unique to *this* guard authenticator 
            // this MUST be the same as GuardAuthenticationProvider 
            $uniqueGuardKey = $this->providerKey.'_'.$key; 
 
            $this->executeGuardAuthenticator($uniqueGuardKey, $guardAuthenticator, $event); 
 
            if ($event->hasResponse()) { 
                if (null !== $this->logger) { 
                    $this->logger->debug('The "{authenticator}" authenticator set the response. Any later authenticator will not be called', ['authenticator' => \get_class($guardAuthenticator)]); 
                } 
 
                break; 
            } 
        } 
    } 
 
    private function executeGuardAuthenticator(string $uniqueGuardKey, AuthenticatorInterface $guardAuthenticator, RequestEvent $event) 
    { 
        $request = $event->getRequest(); 
        try { 
            if (null !== $this->logger) { 
                $this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); 
            } 
 
            // allow the authenticator to fetch authentication info from the request 
            $credentials = $guardAuthenticator->getCredentials($request); 
 
            if (null === $credentials) { 
                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))); 
            } 
 
            // create a token with the unique key, so that the provider knows which authenticator to use 
            $token = new PreAuthenticationGuardToken($credentials, $uniqueGuardKey); 
 
            if (null !== $this->logger) { 
                $this->logger->debug('Passing guard token information to the GuardAuthenticationProvider', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]); 
            } 
            // pass the token into the AuthenticationManager system 
            // this indirectly calls GuardAuthenticationProvider::authenticate() 
            $token = $this->authenticationManager->authenticate($token); 
 
            if (null !== $this->logger) { 
                $this->logger->info('Guard authentication successful!', ['token' => $token, 'authenticator' => \get_class($guardAuthenticator)]); 
            } 
 
            // sets the token on the token storage, etc 
            $this->guardHandler->authenticateWithToken($token, $request, $this->providerKey); 
        } catch (AuthenticationException $e) { 
            // oh no! Authentication failed! 
 
            if (null !== $this->logger) { 
                $this->logger->info('Guard authentication failed.', ['exception' => $e, 'authenticator' => \get_class($guardAuthenticator)]); 
            } 
 
            // Avoid leaking error details in case of invalid user (e.g. user not found or invalid account status) 
            // to prevent user enumeration via response content 
            if ($this->hideUserNotFoundExceptions && ($e instanceof UsernameNotFoundException || ($e instanceof AccountStatusException && !$e instanceof CustomUserMessageAccountStatusException))) { 
                $e = new BadCredentialsException('Bad credentials.', 0, $e); 
            } 
 
            $response = $this->guardHandler->handleAuthenticationFailure($e, $request, $guardAuthenticator, $this->providerKey); 
 
            if ($response instanceof Response) { 
                $event->setResponse($response); 
            } 
 
            return; 
        } 
 
        // success! 
        $response = $this->guardHandler->handleAuthenticationSuccess($token, $request, $guardAuthenticator, $this->providerKey); 
        if ($response instanceof Response) { 
            if (null !== $this->logger) { 
                $this->logger->debug('Guard authenticator set success response.', ['response' => $response, 'authenticator' => \get_class($guardAuthenticator)]); 
            } 
 
            $event->setResponse($response); 
        } else { 
            if (null !== $this->logger) { 
                $this->logger->debug('Guard authenticator set no success response: request continues.', ['authenticator' => \get_class($guardAuthenticator)]); 
            } 
        } 
 
        // attempt to trigger the remember me functionality 
        $this->triggerRememberMe($guardAuthenticator, $request, $token, $response); 
    } 
 
    /** 
     * Should be called if this listener will support remember me. 
     */ 
    public function setRememberMeServices(RememberMeServicesInterface $rememberMeServices) 
    { 
        $this->rememberMeServices = $rememberMeServices; 
    } 
 
    /** 
     * Checks to see if remember me is supported in the authenticator and 
     * on the firewall. If it is, the RememberMeServicesInterface is notified. 
     */ 
    private function triggerRememberMe(AuthenticatorInterface $guardAuthenticator, Request $request, TokenInterface $token, Response $response = null) 
    { 
        if (null === $this->rememberMeServices) { 
            if (null !== $this->logger) { 
                $this->logger->debug('Remember me skipped: it is not configured for the firewall.', ['authenticator' => \get_class($guardAuthenticator)]); 
            } 
 
            return; 
        } 
 
        if (!$guardAuthenticator->supportsRememberMe()) { 
            if (null !== $this->logger) { 
                $this->logger->debug('Remember me skipped: your authenticator does not support it.', ['authenticator' => \get_class($guardAuthenticator)]); 
            } 
 
            return; 
        } 
 
        if (!$response instanceof Response) { 
            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))); 
        } 
 
        $this->rememberMeServices->loginSuccess($request, $response, $token); 
    } 
}