8000 [Security] add "lazy_authentication" mode to firewalls · symfony/symfony@cad381f · GitHub
[go: up one dir, main page]

Skip to content

Commit cad381f

Browse files
[Security] add "lazy_authentication" mode to firewalls
1 parent 510977d commit cad381f

File tree

13 files changed

+352
-4
lines changed

13 files changed

+352
-4
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
13+
14+
use Symfony\Bridge\Monolog\Processor\ProcessorInterface;
15+
use Symfony\Component\DependencyInjection\Argument\BoundArgument;
16+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
17+
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Reference;
19+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
20+
21+
/**
22+
* Adds a rule to bind "security.actual_token_storage" to ProcessorInterface instances.
23+
*
24+
* @author Nicolas Grekas <p@tchwork.com>
25+
*/
26+
class RegisterForAutoconfigurationPass implements CompilerPassInterface
27+
{
28+
/**
29+
* {@inheritdoc}
30+
*/
31+
public function process(ContainerBuilder $container)
32+
{
33+
if ($container->has('security.actual_token_storage')) {
34+
$processorAutoconfiguration = $container->registerForAutoconfiguration(ProcessorInterface::class);
35+
$processorAutoconfiguration->setBindings($processorAutoconfiguration->getBindings() + array(
36+
TokenStorageInterface::class => new BoundArgument(new Reference('security.actual_token_storage'), false),
37+
));
38+
}
39+
}
40+
}

src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
196196
->scalarNode('entry_point')->end()
197197
->scalarNode('provider')->end()
198198
->booleanNode('stateless')->defaultFalse()->end()
199+
->booleanNode('lazy_authentication')->defaultFalse()->end()
199200
->scalarNode('context')->cannotBeEmpty()->end()
200201
->booleanNode('logout_on_user_change')
201202
->defaultTrue()

src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@ private function createFirewalls($config, ContainerBuilder $container)
204204
list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
205205

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

376377
// Access listener
377-
$listeners[] = new Reference('security.access_listener');
378+
if ($firewall['stateless'] || !$firewall['lazy_authentication']) {
379+
$listeners[] = new Reference('security.access_listener');
380+
}
378381

379382
// Exception listener
380383
$exceptionListener = new Reference($this->createExceptionListener($container, $firewall, $id, $configuredEntryPoint ?: $defaultEntryPoint, $firewall['stateless']));

src/Symfony/Bundle/SecurityBundle/Resources/config/collectors.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
<service id="data_collector.security" class="Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector">
1111
<tag name="data_collector" template="@Security/Collector/security.html.twig" id="security" priority="270" />
12-
<argument type="service" id="security.token_storage" on-invalid="ignore" />
12+
<argument type="service" id="security.actual_token_storage" />
1313
<argument type="service" id="security.role_hierarchy" />
1414
<argument type="service" id="security.logout_url_generator" />
1515
<argument type="service" id="security.access.decision_manager" />

src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@
2121
</service>
2222
<service id="Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface" alias="security.authorization_checker" />
2323

24-
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" public="true">
24+
<service id="security.token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\LazyTokenStorage" public="true">
2525
<tag name="kernel.reset" method="setToken" />
26+
<argument type="service" id="security.actual_token_storage" />
2627
</service>
2728
<service id="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface" alias="security.token_storage" />
2829

30+
<service id="security.actual_token_storage" class="Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage" />
31+
2932
<service id="security.helper" class="Symfony\Component\Security\Core\Security">
3033
<argument type="service_locator">
3134
<argument key="security.token_storage" type="service" id="security.token_storage" />
@@ -145,6 +148,14 @@
145148
<argument /> <!-- FirewallConfig -->
146149
</service>
147150

151+
<service id="security.firewall.lazy_context" class="Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext" abstract="true">
152+
<argument type="collection" />
153+
<argument type="service" id="security.exception_listener" />
154+
<argument /> <!-- LogoutListener -->
155+
<argument /> <!-- FirewallConfig -->
156+
<argument type="service" id="security.lazy_access_listener" />
157+
</service>
158+
148159
<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">
149160
<argument /> <!-- name -->
150161
<argument /> <!-- user_checker -->

src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,5 +242,13 @@
242242
<argument type="service" id="security.access_map" />
243243
<argument type="service" id="security.authentication.manager" />
244244
</service>
245+
246+
<service id="security.lazy_access_listener" class="Symfony\Component\Security\Http\Firewall\LazyAccessListener">
247+
<tag name="monolog.logger" channel="security" />
248+
<argument type="service" id="security.token_storage" />
249+
<argument type="service" id="security.access.decision_manager" />
250+
<argument type="service" id="security.access_map" />
251+
<argument type="service" id="security.authentication.manager" />
252+
</service>
245253
</services>
246254
</container>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bundle\SecurityBundle\Security;
13+
14+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
15+
use Symfony\Component\Security\Core\Exception\LazyResponseException;
16+
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
17+
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
18+
use Symfony\Component\Security\Http\Firewall\LazyAccessListener;
19+
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
20+
use Symfony\Component\Security\Http\Firewall\LogoutListener;
21+
22+
/**
23+
* Lazily calls authentication listeners when actually required by the access listener.
24+
*
25+
* @author Nicolas Grekas <p@tchwork.com>
26+
*/
27+
class LazyFirewallContext extends FirewallContext implements ListenerInterface
28+
{
29+
private $accessListener;
30+
31+
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, LazyAccessListener $accessListener)
32+
{
33+
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
34+
35+
$this->accessListener = $accessListener;
36+
}
37+
38+
public function getListeners(): iterable
39+
{
40+
return array($this);
41+
}
42+
43+
public function handle(GetResponseEvent $event)
44+
{
45+
$this->accessListener->getTokenStorage()->setInitializer(function () use ($event) {
46+
$event = new LazyResponseEvent($event);
47+
foreach (parent::getListeners() as $listener) {
48+
$listener->handle($event);
49+
}
50+
});
51+
52+
try {
53+
$this->accessListener->handle($event);
54+
} catch (LazyResponseException $e) {
55+
$event->setResponse($e->getResponse());
56+
}
57+
}
58+
}

src/Symfony/Bundle/SecurityBundle/SecurityBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSecurityVotersPass;
1616
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\AddSessionDomainConstraintPass;
1717
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass;
18+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterForAutoconfigurationPass;
1819
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
1920
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginLdapFactory;
2021
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\GuardAuthenticationFactory;
@@ -64,5 +65,6 @@ public function build(ContainerBuilder $container)
6465
$container->addCompilerPass(new AddSecurityVotersPass());
6566
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_BEFORE_REMOVING);
6667
$container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass());
68+
$container->addCompilerPass(new RegisterForAutoconfigurationPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
6769
}
6870
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Core\Authentication\Token\Storage;
13+
14+
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15+
16+
/**
17+
* Lazily populates a token storage.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*
21+
* @final
22+
*/
23+
class LazyTokenStorage implements TokenStorageInterface
24+
{
25+
private $storage;
26+
private $initializer;
27+
28+
public function __construct(TokenStorageInterface $storage)
29+
{
30+
$this->storage = $storage;
31+
}
32+
33+
public function setInitializer(\Closure $initializer)
34+
{
35+
$this->initializer = $initializer;
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function getToken()
42+
{
43+
if ($initializer = $this->initializer) {
44+
$this->initializer = null;
45+
$initializer();
46+
}
47+
48+
return $this->storage->getToken();
49+
}
50+
51+
/**
52+
* {@inheritdoc}
53+
*/
54+
public function setToken(TokenInterface $token = null)
55+
{
56+
$this->initializer = null;
57+
$this->storage->setToken($token);
58+
}
59+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Core\Exception;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
16+
/**
17+
* Wraps a lazily computed response in a signaling exception.
18+
*
19+
* @author Nicolas Grekas <p@tchwork.com>
20+
*/
21+
class LazyResponseException extends \Exception implements ExceptionInterface
22+
{
23+
private $response;
24+
25+
public function __construct(Response $response)
26+
{
27+
$this->response = $response;
28+
}
29+
30+
public function getResponse(): Response
31+
{
32+
return $this->response;
33+
}
34+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Security\Http\Event;
13+
14+
use Symfony\Component\HttpFoundation\Response;
15+
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
16+
use Symfony\Component\Security\Core\Exception\LazyResponseException;
17+
18+
/**
19+
* Wraps a lazily computed response in a signaling exception.
20+
*
21+
* @author Nicolas Grekas <p@tchwork.com>
22+
*
23+
* @final
24+
*/
25+
class LazyResponseEvent extends GetResponseEvent
26+
{
27+
private $event;
28+
29+
public function __construct(parent $event)
30+
{
31+
$this->event = $event;
32+
}
33+
34+
/**
35+
* {@inheritdoc}
36+
*/
37+
public function setResponse(Response $response)
38+
{
39+
$this->stopPropagation();
40+
$this->event->stopPropagation();
41+
42+
throw new LazyResponseException($response);
43+
}
44+
45+
/**
46+
* {@inheritdoc}
47+
*/
48+
public function getKernel()
49+
{
50+
return $this->event->getKernel();
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function getRequest()
57+
{
58+
return $this->event->getRequest();
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function getRequestType()
65+
{
66+
return $this->event->getRequestType();
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function isMasterRequest()
73+
{
74+
return $this->event->isMasterRequest();
75+
}
76+
}

src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
use Symfony\Component\Security\Core\Exception\AccountStatusException;
2626
use Symfony\Component\Security\Core\Exception\AuthenticationException;
2727
use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
28+
use Symfony\Component\Security\Core\Exception\LazyResponseException;
2829
use Symfony\Component\Security\Core\Exception\LogoutException;
2930
use Symfony\Component\Security\Core\Security;
3031
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
@@ -92,6 +93,8 @@ public function onKernelException(GetResponseForExceptionEvent $event)
9293
return $this->handleAuthenticationException($event, $exception);
9394
} elseif ($exception instanceof AccessDeniedException) {
9495
return $this->handleAccessDeniedException($event, $exception);
96+
} elseif ($exception instanceof LazyResponseException) {
97+
return $event->setResponse($exception->getResponse());
9598
} elseif ($exception instanceof LogoutException) {
9699
return $this->handleLogoutException($exception);
97100
}

0 commit comments

Comments
 (0)
0