Skip to content

Commit

Permalink
Added GuardManagerListener
Browse files Browse the repository at this point in the history
This replaces all individual authentication listeners when guard authentication
manager is enabled.
  • Loading branch information
wouterj committed Apr 20, 2020
1 parent a172bac commit 526f756
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 122 deletions.
Expand Up @@ -264,11 +264,24 @@ private function createFirewalls(array $config, ContainerBuilder $container)
// 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)
Expand Down Expand Up @@ -498,7 +511,7 @@ private function createAuthenticationListeners(ContainerBuilder $container, stri
list($provider, $listenerId, $defaultEntryPoint) = $factory->create($container, $id, $firewall[$key], $userProvider, $defaultEntryPoint);

$listeners[] = new Reference($listenerId);
$authenticationProviders[] = $provider;
$authenticationProviders[$id.'_'.$key] = $provider;
}
$hasListeners = true;
}
Expand Down
@@ -0,0 +1,58 @@
<?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\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 <wouter@wouterj.nl>
*/
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;
}
}
Expand Up @@ -4,6 +4,21 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="security.firewall.guard.locator"
class="Symfony\Component\DependencyInjection\ServiceLocator"
abstract="true" />

<service id="security.firewall.guard"
class="Symfony\Bundle\SecurityBundle\EventListener\LazyGuardManagerListener"
abstract="true">
<tag name="monolog.logger" channel="security" />
<argument type="service" id="security.authentication.manager"/>
<argument type="service" id="security.authentication.guard_handler"/>
<argument/> <!-- guard authenticator locator -->
<argument/> <!-- provider key -->
<argument type="service" id="logger" on-invalid="null" />
</service>

<service id="security.authenticator.http_basic"
class="Symfony\Component\Security\Core\Authentication\Authenticator\HttpBasicAuthenticator"
abstract="true">
Expand Down
Expand Up @@ -34,6 +34,8 @@
*/
class GuardAuthenticationListener extends AbstractListener
{
use GuardAuthenticatorListenerTrait;

private $guardHandler;
private $authenticationManager;
private $providerKey;
Expand Down Expand Up @@ -73,20 +75,7 @@ public function supports(Request $request): ?bool
$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;
}
Expand All @@ -105,86 +94,7 @@ public function authenticate(RequestEvent $event)
$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);
}

/**
Expand All @@ -195,32 +105,10 @@ public function setRememberMeServices(RememberMeServicesInterface $rememberMeSer
$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;
}
}

0 comments on commit 526f756

Please sign in to comment.