diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php index 5a707a9f26..55ebd0d62f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php @@ -264,11 +264,24 @@ class SecurityExtension extends Extension implements PrependExtensionInterface // add authentication providers to authentication manager $authenticationProviders = array_map(function ($id) { return new Reference($id); - }, array_values(array_unique($authenticationProviders))); + }, array_unique($authenticationProviders)); $authenticationManagerId = 'security.authentication.manager.provider'; if ($this->guardAuthenticationManagerEnabled) { $authenticationManagerId = 'security.authentication.manager.guard'; $container->setAlias('security.authentication.manager', new Alias($authenticationManagerId)); + + // guard authentication manager listener + $container + ->setDefinition('security.firewall.guard.'.$name.'locator', new ChildDefinition('security.firewall.guard.locator')) + ->setArguments([$authenticationProviders]) + ->addTag('container.service_locator') + ; + $container + ->setDefinition('security.firewall.guard.'.$name, new ChildDefinition('security.firewall.guard')) + ->replaceArgument(2, new Reference('security.firewall.guard.'.$name.'locator')) + ->replaceArgument(3, $name) + ->addTag('kernel.event_listener', ['event' => KernelEvents::REQUEST]) + ; } $container ->getDefinition($authenticationManagerId) @@ -498,7 +511,7 @@ class SecurityExtension extends Extension implements PrependExtensionInterface list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint); $listeners[] = new Reference($listenerId); - $authenticationProviders[] = $provider; + $authenticationProviders[$id.'_'.$key] = $provider; } $hasListeners = true; } diff --git a/src/Symfony/Bundle/SecurityBundle/EventListener/LazyGuardManagerListener.php b/src/Symfony/Bundle/SecurityBundle/EventListener/LazyGuardManagerListener.php new file mode 100644 index 0000000000..63b201cb66 --- /dev/null +++ b/src/Symfony/Bundle/SecurityBundle/EventListener/LazyGuardManagerListener.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\SecurityBundle\EventListener; + +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ServiceLocator; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; +use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; +use Symfony\Component\Security\Http\Firewall\GuardManagerListener; + +/** + * @author Wouter de Jong + */ +class LazyGuardManagerListener extends GuardManagerListener +{ + private $guardLocator; + + public function __construct( + AuthenticationManagerInterface $authenticationManager, + GuardAuthenticatorHandler $guardHandler, + ServiceLocator $guardLocator, + string $providerKey, + ?LoggerInterface $logger = null + ) { + parent::__construct($authenticationManager, $guardHandler, [], $providerKey, $logger); + + $this->guardLocator = $guardLocator; + } + + protected function getSupportingGuardAuthenticators(Request $request): array + { + $guardAuthenticators = []; + foreach ($this->guardLocator->getProvidedServices() as $key => $type) { + $guardAuthenticator = $this->guardLocator->get($key); + 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)]); + } + } + + return $guardAuthenticators; + } +} diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml index 588f4d1567..f9268c380e 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/authenticators.xml @@ -4,6 +4,21 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd"> + + + + + + + + + + + diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php index 022538731d..35c4bda103 100644 --- a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php @@ -34,6 +34,8 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; */ class GuardAuthenticationListener extends AbstractListener { + use GuardAuthenticatorListenerTrait; + private $guardHandler; private $authenticationManager; private $providerKey; @@ -73,20 +75,7 @@ class GuardAuthenticationListener extends AbstractListener $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)]); - } - } - + $guardAuthenticators = $this->getSupportingGuardAuthenticators($request); if (!$guardAuthenticators) { return false; } @@ -105,86 +94,7 @@ class GuardAuthenticationListener extends AbstractListener $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)]); - } - - $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); + $this->executeGuardAuthenticators($guardAuthenticators, $event); } /** @@ -195,32 +105,10 @@ class GuardAuthenticationListener extends AbstractListener $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) + protected function getGuardKey(string $key): string { - 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); + // get a key that's unique to *this* guard authenticator + // this MUST be the same as GuardAuthenticationProvider + return $this->providerKey.'_'.$key; } } diff --git a/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticatorListenerTrait.php b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticatorListenerTrait.php new file mode 100644 index 0000000000..935f8fa064 --- /dev/null +++ b/src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticatorListenerTrait.php @@ -0,0 +1,154 @@ + + * @author Amaury Leroux de Lens + * + * @internal + */ +trait GuardAuthenticatorListenerTrait +{ + protected function getSupportingGuardAuthenticators(Request $request): array + { + $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)]); + } + } + + return $guardAuthenticators; + } + + /** + * @param AuthenticatorInterface[] $guardAuthenticators + */ + protected function executeGuardAuthenticators(array $guardAuthenticators, RequestEvent $event): void + { + foreach ($guardAuthenticators as $key => $guardAuthenticator) { + $uniqueGuardKey = $this->getGuardKey($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)]); + } + + $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); + } + + /** + * 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); + } + + abstract protected function getGuardKey(string $key): string; +} diff --git a/src/Symfony/Component/Security/Http/Firewall/GuardManagerListener.php b/src/Symfony/Component/Security/Http/Firewall/GuardManagerListener.php new file mode 100644 index 0000000000..2cfa86d420 --- /dev/null +++ b/src/Symfony/Component/Security/Http/Firewall/GuardManagerListener.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Http\Firewall; + +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Event\RequestEvent; +use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; +use Symfony\Component\Security\Guard\AuthenticatorInterface; +use Symfony\Component\Security\Guard\Firewall\GuardAuthenticatorListenerTrait; +use Symfony\Component\Security\Guard\GuardAuthenticatorHandler; + +/** + * @author Wouter de Jong + */ +class GuardManagerListener +{ + use GuardAuthenticatorListenerTrait; + + private $authenticationManager; + private $guardHandler; + private $guardAuthenticators; + protected $providerKey; + protected $logger; + + /** + * @param AuthenticatorInterface[] $guardAuthenticators + */ + public function __construct(AuthenticationManagerInterface $authenticationManager, GuardAuthenticatorHandler $guardHandler, iterable $guardAuthenticators, string $providerKey, ?LoggerInterface $logger = null) + { + $this->authenticationManager = $authenticationManager; + $this->guardHandler = $guardHandler; + $this->guardAuthenticators = $guardAuthenticators; + $this->providerKey = $providerKey; + $this->logger = $logger; + } + + public function __invoke(RequestEvent $requestEvent) + { + $request = $requestEvent->getRequest(); + $guardAuthenticators = $this->getSupportingGuardAuthenticators($request); + if (!$guardAuthenticators) { + return; + } + + $this->executeGuardAuthenticators($guardAuthenticators, $requestEvent); + } + + protected function getGuardKey(string $key): string + { + // Guard authenticators in the GuardAuthenticationManager are already indexed + // by an unique key + return $key; + } +}