10000 [Security] add "lazy_authentication" mode to firewalls by nicolas-grekas · Pull Request #27817 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Security] add "lazy_authentication" mode to firewalls #27817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?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\DependencyInjection\Compiler;

use Symfony\Bridge\Monolog\Processor\ProcessorInterface;
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

/**
* Adds a rule to bind "security.actual_token_storage" to ProcessorInterface instances.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class RegisterForAutoconfigurationPass implements CompilerPassInterface
{
/**
* {@inheritdoc}
*/
public function process(ContainerBuilder $container)
{
if ($container->has('security.actual_token_storage')) {
$processorAutoconfiguration = $container->registerForAutoconfiguration(ProcessorInterface::class);
$processorAutoconfiguration->setBindings($processorAutoconfiguration->getBindings() + array(
TokenStorageInterface::class => new BoundArgument(new Reference('security.actual_token_storage'), false),
));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
->scalarNode('entry_point')->end()
->scalarNode( 10000 9;provider')->end()
->booleanNode('stateless')->defaultFalse()->end()
->booleanNode('lazy_authentication')->defaultFalse()->end()
->scalarNode('context')->cannotBeEmpty()->end()
->booleanNode('logout_on_user_change')
->defaultTrue()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ private function createFirewalls($config, ContainerBuilder $container)
list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);

$contextId = 'security.firewall.map.context.'.$name;
$context = $container->setDefinition($contextId, new ChildDefinition('security.firewall.context'));
$context = new ChildDefinition($firewall['stateless'] || !$firewall['lazy_authentication'] ? 'security.firewall.context' : 'security.firewall.lazy_context');
$context = $container->setDefinition($contextId, $context);
$context
->replaceArgument(0, new IteratorArgument($listeners))
->replaceArgument(1, $exceptionListener)
Expand Down Expand Up @@ -374,7 +375,9 @@ private function createFirewall(ContainerBuilder $container, $id, $firewall, &$a
}

// Access listener
$listeners[] = new Reference('security.access_listener');
if ($firewall['stateless'] || !$firewall['lazy_authentication']) {
$listeners[] = new Reference('security.access_listener');
}

// Exception listener
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<service id="data_collector.security" class="Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector">
<tag name="data_collector" template="@Security/Collector/security.html.twig" id="security" priority="270" />
<argument type="service" id="security.token_storage" on-invalid="ignore" />
<argument type="service" id="security.actual_token_storage" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this name feels weird (I don't have a better suggestion though)

<argument type="service" id="security.role_hierarchy" />
<argument type="service" id="security.logout_url_generator" />
<argument type="service" id="security.access.decision_manager" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,14 @@
</service>
<service id="Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface" alias="security.authorization_checker" />

<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" public="true">
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\LazyTokenStorage" public="true">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about implementing a decoration for the actual_token_storage here, instead ? So that you can rename it token_storage and let this service decorate the token_storage ?

<tag name="kernel.reset" method="setToken" />
<argument type="service" id="security.actual_token_storage" />
</service>
<service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" />

<service id="security.actual_token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" />

<service id="security.helper" class="Symfony\Component\Security\Core\Security">
<argument type="service_locator">
<argument key="security.token_storage" type="service" id="security.token_storage" />
Expand Down Expand Up @@ -145,6 +148,14 @@
<argument /> <!-- FirewallConfig -->
</service>

<service id="security.firewall.lazy_context" class="Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext" abstract="true">
<argument type="collection" />
<argument type="service" id="security.exception_listener" />
<argument /> <!-- LogoutListener -->
<argument /> <!-- FirewallConfig -->
<argument type="service" id="security.lazy_access_listener" />
</service>

<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">
<argument /> <!-- name -->
<argument /> <!-- user_checker -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,5 +242,13 @@
<argument type="service" id="security.access_map" />
<argument type="service" id="security.authentication.manager" />
</service>

<service id="security.lazy_access_listener" class="Symfony\Component\Security\Http\Firewall\LazyAccessListener">
<tag name="monolog.logger" channel="security" />
<argument type="service" id="security.token_storage" />
<argument type="service" id="security.access.decision_manager" />
<argument type="service" id="security.access_map" />
<argument type="service" id="security.authentication.manager" />
</service>
</services>
</container>
Original file line number Diff line number Diff line change
@@ -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\Security;

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
use Symfony\Component\Security\Http\Firewall\LazyAccessListener;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Http\Firewall\LogoutListener;

/**
* Lazily calls authentication listeners when actually required by the access listener.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class LazyFirewallContext extends FirewallContext implements ListenerInterface
{
private $accessListener;

public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, LazyAccessListener $accessListener)
{
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);

$this->accessListener = $accessListener;
}

public function getListeners(): iterable
{
return array($this);
}

public function handle(GetResponseEvent $event)
{
$this->accessListener->getTokenStorage()->setInitializer(function () use ($event) {
$event = new LazyResponseEvent($event);
foreach (parent::getListeners() as $listener) {
$listener->handle($event);
}
});

10000 try {
$this->accessListener->handle($event);
} catch (LazyResponseException $e) {
$event->setResponse($e->getResponse());
}
}
}
2 changes: 2 additions & 0 deletions src/Symfony/Bundle/SecurityBundle/SecurityBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterForAutoconfigurationPass;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
Expand Down Expand Up @@ -64,5 +65,6 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new AddSecurityVotersPass());
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING);
$container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass());
$container->addCompilerPass(new RegisterForAutoconfigurationPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?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\Core\Authentication\Token\Storage;

use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;

/**
* Lazily populates a token storage.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*/
class LazyTokenStorage implements TokenStorageInterface
{
private $storage;
private $initializer;

public function __construct(TokenStorageInterface $storage)
{
$this->storage = $storage;
}

public function setInitializer(\Closure $initializer)
{
$this->initializer = $initializer;
}

/**
* {@inheritdoc}
*/
public function getToken()
{
if ($initializer = $this->initializer) {
$this->initializer = null;
$initializer();
}

return $this->storage->getToken();
}

/**
* {@inheritdoc}
*/
public function setToken(TokenInterface $token = null)
{
$this->initializer = null;
$this->storage->setToken($token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?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\Core\Exception;

use Symfony\Component\HttpFoundation\Response;

/**
* Wraps a lazily computed response in a signaling exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class LazyResponseException extends \Exception implements ExceptionInterface
{
private $response;

public function __construct(Response $response)
{
$this->response = $response;
}

public function getResponse(): Response
{
return $this->response;
}
}
76 changes: 76 additions & 0 deletions src/Symfony/Component/Security/Http/Event/LazyResponseEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?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\Http\Event;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\LazyResponseException;

/**
* Wraps a lazily computed response in a signaling exception.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @final
*/
class LazyResponseEvent extends GetResponseEvent
{
private $event;

public function __construct(parent $event)
{
$this->event = $event;
}

/**
* {@inheritdoc}
*/
public function setResponse(Response $response)
{
$this->stopPropagation();
$this->event->stopPropagation();

throw new LazyResponseException($response);
}

/**
* {@inheritdoc}
*/
public function getKernel()
{
return $this->event->getKernel();
}

/**
* {@inheritdoc}
*/
public function getRequest()
{
return $this->event->getRequest();
}

/**
* {@inheritdoc}
*/
public function getRequestType()
{
return $this->event->getRequestType();
}

/**
* {@inheritdoc}
*/
public function isMasterRequest()
{
return $this->event->isMasterRequest();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use Symfony\Component\Security\Core\Exception\AccountStatusException;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
use Symfony\Component\Security\Core\Exception\LazyResponseException;
use Symfony\Component\Security\Core\Exception\LogoutException;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
Expand Down Expand Up @@ -92,6 +93,8 @@ public function onKernelException(GetResponseForExceptionEvent $event)
return $this->handleAuthenticationException($event, $exception);
} elseif ($exception instanceof AccessDeniedException) {
return $this->handleAccessDeniedException($event, $exception);
} elseif ($exception instanceof LazyResponseException) {
return $event->setResponse($exception->getResponse());
} elseif ($exception instanceof LogoutException) {
return $this->handleLogoutException($exception);
}
Expand Down
Loading
0