8000 [Security/Http] call auth listeners/guards eagerly when they "support… · symfony/symfony@81bcfa3 · GitHub
[go: up one dir, main page]

Skip to content

Commit 81bcfa3

Browse files
[Security/Http] call auth listeners/guards eagerly when they "support" the request
1 parent 59b6cfe commit 81bcfa3

17 files changed

+259
-137
lines changed

src/Symfony/Bundle/SecurityBundle/Debug/WrappedListener.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\Debug;
1313

14+
use Symfony\Component\HttpFoundation\Request;
1415
use Symfony\Component\HttpKernel\Event\RequestEvent;
1516
use Symfony\Component\Security\Http\Firewall\LegacyListenerTrait;
1617
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
@@ -41,9 +42,6 @@ public function __construct($listener)
4142
$this->listener = $listener;
4243
}
4344

44-
/**
45-
* {@inheritdoc}
46-
*/
4745
public function __invoke(RequestEvent $event)
4846
{
4947
$startTime = microtime(true);
@@ -57,6 +55,23 @@ public function __invoke(RequestEvent $event)
5755
$this->response = $event->getResponse();
5856
}
5957

58+
public function supports(Request $request): ?bool
59+
{
60+
$startTime = microtime(true);
61+
$supports = $this->listener->supports($request);
62+
$this->time = microtime(true) - $startTime;
63+
64+
return $supports;
65+
}
66+
67+
public function authenticate(RequestEvent $event)
68+
{
69+
$startTime = microtime(true);
70+
$this->listener->authenticate($event);
71+
$this->time += microtime(true) - $startTime;
72+
$this->response = $event->getResponse();
73+
}
74+
6075
/**
6176
* Proxies all method calls to the original listener.
6277
*/

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,9 +409,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
409409
}
410410

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

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

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,7 @@
156156
<argument type="service" id="security.exception_listener" />
157157
<argument /> <!-- LogoutListener -->
158158
<argument /> <!-- FirewallConfig -->
159-
<argument type="service" id="security.access_listener" />
160159
<argument type="service" id="security.untracked_token_storage" />
161-
<argument type="service" id="security.access_map" />
162160
</service>
163161

164162
<service id="security.firewall.config" class="Symfony\Bundle\SecurityBundle\Security\FirewallConfig" abstract="true">

src/Symfony/Bundle/SecurityBundle/Security/LazyFirewallContext.php

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,11 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\Security;
1313

14+
use Symfony\Bundle\SecurityBundle\Debug\WrappedListener;
1415
use Symfony\Component\HttpKernel\Event\RequestEvent;
1516
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
16-
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
17-
use Symfony\Component\Security\Core\Exception\LazyResponseException;
18-
use Symfony\Component\Security\Http\AccessMapInterface;
1917
use Symfony\Component\Security\Http\Event\LazyResponseEvent;
20-
use Symfony\Component\Security\Http\Firewall\AccessListener;
18+
use Symfony\Component\Security\Http\Firewall\AbstractListener;
2119
use Symfony\Component\Security\Http\Firewall\ExceptionListener;
2220
use Symfony\Component\Security\Http\Firewall\LogoutListener;
2321

@@ -32,13 +30,11 @@ class LazyFirewallContext extends FirewallContext
3230
private $tokenStorage;
3331
private $map;
3432

35-
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, AccessListener $accessListener, TokenStorage $tokenStorage, AccessMapInterface $map)
33+
public function __construct(iterable $listeners, ?ExceptionListener $exceptionListener, ?LogoutListener $logoutListener, ?FirewallConfig $config, TokenStorage $tokenStorage)
3634
{
3735
parent::__construct($listeners, $exceptionListener, $logoutListener, $config);
3836

39-
$this->accessListener = $accessListener;
4037
$this->tokenStorage = $tokenStorage;
41-
$this->map = $map;
4238
}
4339

4440
public function getListeners(): iterable
@@ -48,26 +44,41 @@ public function getListeners(): iterable
4844

4945
public function __invoke(RequestEvent $event)
5046
{
51-
$this->tokenStorage->setInitializer(function () use ($event) {
52-
$event = new LazyResponseEvent($event);
53-
foreach (parent::getListeners() as $listener) {
54-
if (\is_callable($listener)) {
55-
$listener($event);
56-
} else {
57-
@trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($listener)), E_USER_DEPRECATED);
58-
$listener->handle($event);
59-
}
47+
$listeners = [];
48+
$request = $event->getRequest();
49+
$lazy = $request->isMethodCacheabl F438 e();
50+
51+
foreach (parent::getListeners() as $listener) {
52+
$unwrappedListener = $listener instanceof WrappedListener ? $listener->getWrappedListener() : $listener;
53+
54+
if (!\is_callable($listener)) {
55+
@trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, implement "__invoke()" instead.', \get_class($listener)), E_USER_DEPRECATED);
56+
$listeners[] = [$listener, 'handle'];
57+
} elseif (!$lazy || !$unwrappedListener instanceof AbstractListener) {
58+
$listeners[] = $listener;
59+
} elseif (false !== $supports = $listener->supports($request)) {
60+
$listeners[] = [$listener, 'authenticate'];
61+
$lazy = null === $supports;
6062
}
61-
});
63+
}
6264

63-
try {
64-
[$attributes] = $this->map->getPatterns($event->getRequest());
65+
if (!$lazy) {
66+
foreach ($listeners as $listener) {
67+
$listener($event);
6568

66-
if ($attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes) {
67-
($this->accessListener)($event);
69+
if ($event->hasResponse()) {
70+
return;
71+
}
6872
}
69-
} catch (LazyResponseException $e) {
70-
$event->setResponse($e->getResponse());
73+
74+
return;
7175
}
76+
77+
$this->tokenStorage->setInitializer(function () use ($event, $listeners) {
78+
$event = new LazyResponseEvent($event);
79+
foreach ($listeners as $listener) {
80+
$listener($event);
81+
}
82+
});
7283
}
7384
}

src/Symfony/Component/Security/Guard/Firewall/GuardAuthenticationListener.php

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Symfony\Component\Security\Guard\AuthenticatorInterface;
2222
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
2323
use Symfony\Component\Security\Guard\Token\PreAuthenticationGuardToken;
24+
use Symfony\Component\Security\Http\Firewall\AbstractListener;
2425
use Symfony\Component\Security\Http\Firewall\LegacyListenerTrait;
2526
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
2627
use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface;
@@ -33,7 +34,7 @@
3334
*
3435
* @final since Symfony 4.3
3536
*/
36-
class GuardAuthenticationListener implements ListenerInterface
37+
class GuardAuthenticationListener extends AbstractListener implements ListenerInterface
3738
{
3839
use LegacyListenerTrait;
3940

@@ -62,9 +63,9 @@ public function __construct(GuardAuthenticatorHandler $guardHandler, Authenticat
6263
}
6364

6465
/**
65-
* Iterates over each authenticator to see if each wants to authenticate the request.
66+
* {@inheritdoc}
6667
*/
67-
public function __invoke(RequestEvent $event)
68+
public function supports(Request $request): ?bool
6869
{
6970
if (null !== $this->logger) {
7071
$context = ['firewall_key' => $this->providerKey];
@@ -76,7 +77,39 @@ public function __invoke(RequestEvent $event)
7677
$this->logger->debug('Checking for guard authentication credentials.', $context);
7778
}
7879

80+
$guardAuthenticators = [];
81+
7982
foreach ($this->guardAuthenticators as $key => $guardAuthenticator) {
83+
if (null !== $this->logger) {
84+
$this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
85+
}
86+
87+
if ($guardAuthenticator->supports($request)) {
88+
$guardAuthenticators[$key] = $guardAuthenticator;
89+
} elseif (null !== $this->logger) {
90+
$this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
91+
}
92+
}
93+
94+
if (!$guardAuthenticators) {
95+
return false;
96+
}
97+
98+
$request->attributes->set('_guard_authenticators', $guardAuthenticators);
99+
100+
return true;
101+
}
102+
103+
/**
104+
* Iterates over each authenticator to see if each wants to authenticate the request.
105+
*/
106+
public function authenticate(RequestEvent $event)
107+
{
108+
$request = $event->getRequest();
109+
$guardAuthenticators = $request->attributes->get('_guard_authenticators');
110+
$request->attributes->remove('_guard_authenticators');
111+
112+
foreach ($guardAuthenticators as $key => $guardAuthenticator) {
80113
// get a key that's unique to *this* guard authenticator
81114
// this MUST be the same as GuardAuthenticationProvider
82115
$uniqueGuardKey = $this->providerKey.'_'.$key;
@@ -97,19 +130,6 @@ private function executeGuardAuthenticator(string $uniqueGuardKey, Authenticator
97130
{
98131
$request = $event->getRequest();
99132
try {
100-
if (null !== $this->logger) {
101-
$this->logger->debug('Checking support on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
102-
}
103-
104-
// abort the execution of the authenticator if it doesn't support the request
105-
if (!$guardAuthenticator->supports($request)) {
106-
if (null !== $this->logger) {
107-
$this->logger->debug('Guard authenticator does not support the request.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
108-
}
109-
110-
return;
111-
}
112-
113133
if (null !== $this->logger) {
114134
$this->logger->debug('Calling getCredentials() on guard authenticator.', ['firewall_key' => $this->providerKey, 'authenticator' => \get_class($guardAuthenticator)]);
115135
}

src/Symfony/Component/Security/Guard/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": "^7.1.3",
2020
"symfony/security-core": "^3.4.22|^4.2.3|^5.0",
21-
"symfony/security-http": "^4.3"
21+
"symfony/security-http": "^4.4.1"
2222
},
2323
"require-dev": {
2424
"psr/log": "~1.0"

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

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
* @author Fabien Potencier <fabien@symfony.com>
5050
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
5151
*/
52-
abstract class AbstractAuthenticationListener implements ListenerInterface
52+
abstract class AbstractAuthenticationListener extends AbstractListener implements ListenerInterface
5353
{
5454
use LegacyListenerTrait;
5555

@@ -105,20 +105,24 @@ public function setRememberMeServices(RememberMeServicesInterface $rememberMeSer
105105
$this->rememberMeServices = $rememberMeServices;
106106
}
107107

108+
/**
109+
* {@inheritdoc}
110+
*/
111+
public function supports(Request $request): ?bool
112+
{
113+
return $this->requiresAuthentication($request);
114+
}
115+
108116
/**
109117
* Handles form based authentication.
110118
*
111119
* @throws \RuntimeException
112120
* @throws SessionUnavailableException
113121
*/
114-
public function __invoke(RequestEvent $event)
122+
public function authenticate(RequestEvent $event)
115123
{
116124
$request = $event->getRequest();
117125

118-
if (!$this->requiresAuthentication($request)) {
119-
return;
120-
}
121-
122126
if (!$request->hasSession()) {
123127
throw new \RuntimeException('This authentication method requires a session.');
124128
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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\Firewall;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Event\RequestEvent;
16+
17+
/**
18+
* A base class for listeners that can tell whether they should authenticate incoming requests.
19+
*
20+
* @author Nicolas Grekas <p@tchwork.com>
21+
*/
22+
abstract class AbstractListener
23+
{
24+
public function __invoke(RequestEvent $event)
25+
{
26+
if (false !== $this->supports($event->getRequest())) {
27+
$this->authenticate($event);
28+
}
29+
}
30+
31+
/**
32+
* Tells whether the authenticate() method should be called or not depending on the incoming request.
33+
*
34+
* Returning null means the authenticate() can be called lazily when accessing the token storage.
35+
*/
36+
abstract public function supports(Request $request): ?bool;
37+
38+
/**
39+
* Does whatever is required to authenticate the request, typically calling $event->setResponse() internally.
40+
*/
41+
abstract public function authenticate(RequestEvent $event);
42+
}

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
*
3636
* @internal since Symfony 4.3
3737
*/
38-
abstract class AbstractPreAuthenticatedListener implements ListenerInterface
38+
abstract class AbstractPreAuthenticatedListener extends AbstractListener implements ListenerInterface
3939
{
4040
use LegacyListenerTrait;
4141

@@ -56,20 +56,31 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM
5656
}
5757

5858
/**
59-
* Handles pre-authentication.
59+
* {@inheritdoc}
6060
*/
61-
public function __invoke(RequestEvent $event)
61+
public function supports(Request $request): ?bool
6262
{
63-
$request = $event->getRequest();
64-
6563
try {
66-
list($user, $credentials) = $this->getPreAuthenticatedData($request);
64+
$request->attributes->set('_pre_authenticated_data', $this->getPreAuthenticatedData($request));
6765
} catch (BadCredentialsException $e) {
6866
$this->clearToken($e);
6967

70-
return;
68+
return false;
7169
}
7270

71+
return true;
72+
}
73+
74+
/**
75+
* Handles pre-authentication.
76+
*/
77+
public function authenticate(RequestEvent $event)
78+
{
79+
$request = $event->getRequest();
80+
81+
[$user, $credentials] = $request->attributes->get('_pre_authenticated_data');
82+
$request->attributes->remove('_pre_authenticated_data');
83+
7384
if (null !== $this->logger) {
7485
$this->logger->debug('Checking current security token.', ['token' => (string) $this->tokenStorage->getToken()]);
7586
}

0 commit comments

Comments
 (0)
0