Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 251957a

Browse filesBrowse files
[Security] add "anonymous: lazy" mode to firewalls
1 parent 20df3a1 commit 251957a
Copy full SHA for 251957a

File tree

9 files changed

+223
-3
lines changed
Filter options

9 files changed

+223
-3
lines changed

‎src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\HttpFoundation\Response;
1818
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
1919
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
20+
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
2021
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
2122
use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
2223
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
@@ -127,7 +128,7 @@ public function collect(Request $request, Response $response, \Exception $except
127128

128129
$logoutUrl = null;
129130
try {
130-
if (null !== $this->logoutUrlGenerator) {
131+
if (null !== $this->logoutUrlGenerator && $token && !$token instanceof AnonymousToken) {
131132
$logoutUrl = $this->logoutUrlGenerator->getLogoutPath();
132133
}
133134
} catch (\Exception $e) {

‎src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AnonymousFactory.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AnonymousFactory.php
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ public function getKey()
5555
public function addConfiguration(NodeDefinition $builder)
5656
{
5757
$builder
58+
->beforeNormalization()
59+
->ifTrue(function ($v) { return 'lazy' === $v; })
60+
->then(function ($v) { return ['lazy' => true]; })
61+
->end()
5862
->children()
63+
->booleanNode('lazy')->end()
5964
->scalarNode('secret')->defaultNull()->end()
6065
->end()
6166
;

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+5-2Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@ private function createFirewalls(array $config, ContainerBuilder $container)
243243
list($matcher, $listeners, $exceptionListener, $logoutListener) = $this->createFirewall($container, $name, $firewall, $authenticationProviders, $providerIds, $configId);
244244

245245
$contextId = 'security.firewall.map.context.'.$name;
246-
$context = $container->setDefinition($contextId, new ChildDefinition('security.firewall.context'));
246+
$context = new ChildDefinition($firewall['stateless'] || empty($firewall['anonymous']['lazy']) ? 'security.firewall.context' : 'security.firewall.lazy_context');
247+
$context = $container->setDefinition($contextId, $context);
247248
$context
248249
->replaceArgument(0, new IteratorArgument($listeners))
249250
->replaceArgument(1, $exceptionListener)
@@ -409,7 +410,9 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
409410
}
410411

411412
// Access listener
412-
$listeners[] = new Reference('security.access_listener');
413+
if ($firewall['stateless'] || empty($firewall['anonymous']['lazy'])) {
414+
$listeners[] = new Reference('security.access_listener');
415+
}
413416

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

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
+10Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,16 @@
151151
<argument /> <!-- FirewallConfig -->
152152
</service>
153153

154+
<service id="security.firewall.lazy_context" class="Symfony\Bundle\SecurityBundle\Security\LazyFirewallContext" abstract="true">
155+
<argument type="collection" />
156+
<argument type="service" id="security.exception_listener" />
157+
<argument /> <!-- LogoutListener -->
158+
<argument /> <!-- FirewallConfig -->
159+
<argument type="service" id="security.access_listener" />
160+
<argument type="service" id="security.untracked_token_storage" />
161+
<argument type="service" id="security.access_map" />
162+
</service>
163+
154164
<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">
155165
<argument /> <!-- name -->
156166
<argument /> <!-- user_checker -->
+72Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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\RequestEvent;
15+
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
16+
use Symfony\Component\Security\Core\Exception\LazyResponseException;
17+
use Symfony\Component\Security\Http\AccessMapInterface;
18+
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
19+
use Symfony\Component\Security\Http\Firewall\AccessListener;
20+
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
21+
use Symfony\Component\Security\Http\Firewall\LogoutListener;
22+
23+
/**
24+
* Lazily calls authentication listeners when actually required by the access listener.
25+
*
26+
* @author Nicolas Grekas <p@tchwork.com>
27+
*/
28+
class LazyFirewallContext extends FirewallContext
29+
{
30+
private $accessListener;
31+
private $tokenStorage;
32+
private $map;
33+
34+
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, AccessListener $accessListener, TokenStorage $tokenStorage, AccessMapInterface $map)
35+
{
36+
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
37+
38+
$this->accessListener = $accessListener;
39+
$this->tokenStorage = $tokenStorage;
40+
$this->map = $map;
41+
}
42+
43+
public function getListeners(): iterable
44+
{
45+
return [$this];
46+
}
47+
48+
public function __invoke(RequestEvent $event)
49+
{
50+
$this->tokenStorage->setInitializer(function () use ($event) {
51+
$event = new LazyResponseEvent($event);
52+
foreach (parent::getListeners() as $listener) {
53+
if (\is_callable($listener)) {
54+
$listener($event);
55+
} else {
56+
@trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($this)), E_USER_DEPRECATED);
57+
$listener->handle($event);
58+
}
59+
}
60+
});
61+
62+
try {
63+
[$attributes] = $this->map->getPatterns($event->getRequest());
64+
65+
if ($attributes) {
66+
($this->accessListener)($event);
67+
}
68+
} catch (LazyResponseException $e) {
69+
$event->setResponse($e->getResponse());
70+
}
71+
}
72+
}

‎src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Core/Authentication/Token/Storage/TokenStorage.php
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@
2525
class TokenStorage implements TokenStorageInterface, ResetInterface
2626
{
2727
private $token;
28+
private $initializer;
2829

2930
/**
3031
* {@inheritdoc}
3132
*/
3233
public function getToken()
3334
{
35+
if ($initializer = $this->initializer) {
36+
$this->initializer = null;
37+
$initializer();
38+
}
39+
3440
return $this->token;
3541
}
3642

@@ -43,9 +49,15 @@ public function setToken(TokenInterface $token = null)
4349
@trigger_error(sprintf('Not implementing the "%s::getRoleNames()" method in "%s" is deprecated since Symfony 4.3.', TokenInterface::class, \get_class($token)), E_USER_DEPRECATED);
4450
}
4551

52+
$this->initializer = null;
4653
$this->token = $token;
4754
}
4855

56+
public function setInitializer(\Closure $initializer): void
57+
{
58+
$this->initializer = $initializer;
59+
}
60+
4961
public function reset()
5062
{
5163
$this->setToken(null);
+34Lines 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+
}
+76Lines 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\Request;
15+
use Symfony\Component\HttpFoundation\Response;
16+
use Symfony\Component\HttpKernel\Event\RequestEvent;
17+
use Symfony\Component\HttpKernel\HttpKernelInterface;
18+
use Symfony\Component\Security\Core\Exception\LazyResponseException;
19+
20+
/**
21+
* Wraps a lazily computed response in a signaling exception.
22+
*
23+
* @author Nicolas Grekas <p@tchwork.com>
24+
*/
25+
final class LazyResponseEvent extends RequestEvent
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(): HttpKernelInterface
49+
{
50+
return $this->event->getKernel();
51+
}
52+
53+
/**
54+
* {@inheritdoc}
55+
*/
56+
public function getRequest(): Request
57+
{
58+
return $this->event->getRequest();
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function getRequestType(): int
65+
{
66+
return $this->event->getRequestType();
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function isMasterRequest(): bool
73+
{
74+
return $this->event->isMasterRequest();
75+
}
76+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/Http/Firewall/ExceptionListener.php
+7Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
use Symfony\Component\Security\Core\Exception\AccountStatusException;
2727
use Symfony\Component\Security\Core\Exception\AuthenticationException;
2828
use Symfony\Component\Security\Core\Exception\InsufficientAuthenticationException;
29+
use Symfony\Component\Security\Core\Exception\LazyResponseException;
2930
use Symfony\Component\Security\Core\Exception\LogoutException;
3031
use Symfony\Component\Security\Core\Security;
3132
use Symfony\Component\Security\Http\Authorization\AccessDeniedHandlerInterface;
@@ -103,6 +104,12 @@ public function onKernelException(GetResponseForExceptionEvent $event)
103104
return;
104105
}
105106

107+
if ($exception instanceof LazyResponseException) {
108+
$event->setResponse($exception->getResponse());
109+
110+
return;
111+
}
112+
106113
if ($exception instanceof LogoutException) {
107114
$this->handleLogoutException($exception);
108115

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.