Skip to content
This repository was archived by the owner on May 31, 2024. It is now read-only.

Commit 77d0b93

Browse files
[Security/Http] call auth listeners/guards eagerly when they "support" the request
1 parent 297ac03 commit 77d0b93

20 files changed

+299
-210
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ CHANGELOG
1414
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`.
1515
* Deprecated passing more than one attribute to `AccessDecisionManager::decide()` and `AuthorizationChecker::isGranted()`
1616
* Added new `argon2id` encoder, undeprecated the `bcrypt` and `argon2i` ones (using `auto` is still recommended by default.)
17+
* Added `AbstractListener` which replaces the deprecated `ListenerInterface`
1718

1819
4.3.0
1920
-----

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
}

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"

Http/Firewall.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ protected function handleRequest(GetResponseEvent $event, $listeners)
138138
if (\is_callable($listener)) {
139139
$listener($event);
140140
} else {
141-
@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);
141+
@trigger_error(sprintf('Calling the "%s::handle()" method from the firewall is deprecated since Symfony 4.3, extend "%s" instead.', \get_class($listener), AbstractListener::class), E_USER_DEPRECATED);
142142
$listener->handle($event);
143143
}
144144

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
}

Http/Firewall/AbstractListener.php

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+
final 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 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+
}

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
}

Http/Firewall/AccessListener.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
namespace Symfony\Component\Security\Http\Firewall;
1313

14+
use Symfony\Component\HttpFoundation\Request;
1415
use Symfony\Component\HttpKernel\Event\RequestEvent;
1516
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
1617
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
1718
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
19+
use Symfony\Component\Security\Core\Authorization\Voter\AuthenticatedVoter;
1820
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
1921
use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
2022
use Symfony\Component\Security\Http\AccessMapInterface;
@@ -27,7 +29,7 @@
2729
*
2830
* @final since Symfony 4.3
2931
*/
30-
class AccessListener implements ListenerInterface
32+
class AccessListener extends AbstractListener implements ListenerInterface
3133
{
3234
use LegacyListenerTrait;
3335

@@ -44,23 +46,35 @@ public function __construct(TokenStorageInterface $tokenStorage, AccessDecisionM
4446
$this->authManager = $authManager;
4547
}
4648

49+
/**
50+
* {@inheritdoc}
51+
*/
52+
public function supports(Request $request): ?bool
53+
{
54+
[$attributes] = $this->map->getPatterns($request);
55+
$request->attributes->set('_access_control_attributes', $attributes);
56+
57+
return $attributes && [AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] !== $attributes ? true : null;
58+
}
59+
4760
/**
4861
* Handles access authorization.
4962
*
5063
* @throws AccessDeniedException
5164
* @throws AuthenticationCredentialsNotFoundException
5265
*/
53-
public function __invoke(RequestEvent $event)
66+
public function authenticate(RequestEvent $event)
5467
{
5568
if (!$event instanceof LazyResponseEvent && null === $token = $this->tokenStorage->getToken()) {
5669
throw new AuthenticationCredentialsNotFoundException('A Token was not found in the TokenStorage.');
5770
}
5871

5972
$request = $event->getRequest();
6073

61-
list($attributes) = $this->map->getPatterns($request);
74+
$attributes = $request->attributes->get('_access_control_attributes');
75+
$request->attributes->remove('_access_control_attributes');
6276

63-
if (!$attributes) {
77+
if (!$attributes || ([AuthenticatedVoter::IS_AUTHENTICATED_ANONYMOUSLY] === $attributes && $event instanceof LazyResponseEvent)) {
6478
return;
6579
}
6680

Http/Firewall/AnonymousAuthenticationListener.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Security\Http\Firewall;
1313

1414
use Psr\Log\LoggerInterface;
15+
use Symfony\Component\HttpFoundation\Request;
1516
use Symfony\Component\HttpKernel\Event\RequestEvent;
1617
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
1718
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
@@ -26,7 +27,7 @@
2627
*
2728
* @final since Symfony 4.3
2829
*/
29-
class AnonymousAuthenticationListener implements ListenerInterface
30+
class AnonymousAuthenticationListener extends AbstractListener implements ListenerInterface
3031
{
3132
use LegacyListenerTrait;
3233

@@ -43,10 +44,18 @@ public function __construct(TokenStorageInterface $tokenStorage, string $secret,
4344
$this->logger = $logger;
4445
}
4546

47+
/**
48+
* {@inheritdoc}
49+
*/
50+
public function supports(Request $request): ?bool
51+
{
52+
return null; // always run authenticate() lazily with lazy firewalls
53+
}
54+
4655
/**
4756
* Handles anonymous authentication.
4857
*/
49-
public function __invoke(RequestEvent $event)
58+
public function authenticate(RequestEvent $event)
5059
{
5160
if (null !== $this->tokenStorage->getToken()) {
5261
return;

Http/Firewall/BasicAuthenticationListener.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
*
3030
* @final since Symfony 4.3
3131
*/
32-
class BasicAuthenticationListener implements ListenerInterface
32+
class BasicAuthenticationListener extends AbstractListener implements ListenerInterface
3333
{
3434
use LegacyListenerTrait;
3535

@@ -55,10 +55,18 @@ public function __construct(TokenStorageInterface $tokenStorage, AuthenticationM
5555
$this->ignoreFailure = false;
5656
}
5757

58+
/**
59+
* {@inheritdoc}
60+
*/
61+
public function supports(Request $request): ?bool
62+
{
63+
return null !== $request->headers->get('PHP_AUTH_USER');
64+
}
65+
5866
/**
5967
* Handles basic authentication.
6068
*/
61-
public function __invoke(RequestEvent $event)
69+
public function authenticate(RequestEvent $event)
6270
{
6371
$request = $event->getRequest();
6472

0 commit comments

Comments
 (0)