Skip to content

Commit 31af8f7

Browse files
[Security/Http] call auth listeners/guards eagerly when they "support" the request
1 parent e49361b commit 31af8f7

17 files changed

+261
-193
lines changed

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

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
}

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

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
}

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

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;

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

Firewall/ChannelListener.php

Lines changed: 16 additions & 12 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\Http\AccessMapInterface;
1718
use Symfony\Component\Security\Http\EntryPoint\AuthenticationEntryPointInterface;
@@ -24,7 +25,7 @@
2425
*
2526
* @final since Symfony 4.3
2627
*/
27-
class ChannelListener implements ListenerInterface
28+
class ChannelListener extends AbstractListener implements ListenerInterface
2829
{
2930
use LegacyListenerTrait;
3031

@@ -42,10 +43,8 @@ public function __construct(AccessMapInterface $map, AuthenticationEntryPointInt
4243
/**
4344
* Handles channel management.
4445
*/
45-
public function __invoke(RequestEvent $event)
46+
public function supports(Request $request): ?bool
4647
{
47-
$request = $event->getRequest();
48-
4948
list(, $channel) = $this->map->getPatterns($request);
5049

5150
if ('https' === $channel && !$request->isSecure()) {
@@ -59,21 +58,26 @@ public function __invoke(RequestEvent $event)
5958
}
6059
}
6160

62-
$response = $this->authenticationEntryPoint->start($request);
63-
64-
$event->setResponse($response);
65-
66-
return;
61+
return true;
6762
}
6863

6964
if ('http' === $channel && $request->isSecure()) {
7065
if (null !== $this->logger) {
7166
$this->logger->info('Redirecting to HTTP.');
7267
}
7368

74-
$response = $this->authenticationEntryPoint->start($request);
75-
76-
$event->setResponse($response);
69+
return true;
7770
}
71+
72+
return false;
73+
}
74+
75+
public function authenticate(RequestEvent $event)
76+
{
77+
$request = $event->getRequest();
78+
79+
$response = $this->authenticationEntryPoint->start($request);
80+
81+
$event->setResponse($response);
7882
}
7983
}

Firewall/ContextListener.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
*
4242
* @final since Symfony 4.3
4343
*/
44-
class ContextListener implements ListenerInterface
44+
class ContextListener extends AbstractListener implements ListenerInterface
4545
{
4646
use LegacyListenerTrait;
4747

@@ -84,10 +84,18 @@ public function setLogoutOnUserChange($logoutOnUserChange)
8484
@trigger_error(sprintf('The "%s()" method is deprecated since Symfony 4.1.', __METHOD__), E_USER_DEPRECATED);
8585
}
8686

87+
/**
88+
* {@inheritdoc}
89+
*/
90+
public function supports(Request $request): ?bool
91+
{
92+
return null; // always run authenticate() lazily with lazy firewalls
93+
}
94+
8795
/**
8896
* Reads the Security Token from the session.
8997
*/
90-
public function __invoke(RequestEvent $event)
98+
public function authenticate(RequestEvent $event)
9199
{
92100
if (!$this->registered && null !== $this->dispatcher && $event->isMasterRequest()) {
93101
$this->dispatcher->addListener(KernelEvents::RESPONSE, [$this, 'onKernelResponse']);

Firewall/LogoutListener.php

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
*
3131
* @final since Symfony 4.3
3232
*/
33-
class LogoutListener implements ListenerInterface
33+
class LogoutListener extends AbstractListener implements ListenerInterface
3434
{
3535
use LegacyListenerTrait;
3636

@@ -63,6 +63,14 @@ public function addHandler(LogoutHandlerInterface $handler)
6363
$this->handlers[] = $handler;
6464
}
6565

66+
/**
67+
* {@inheritdoc}
68+
*/
69+
public function supports(Request $request): ?bool
70+
{
71+
return $this->requiresLogout($request);
72+
}
73+
6674
/**
6775
* Performs the logout if requested.
6876
*
@@ -72,14 +80,10 @@ public function addHandler(LogoutHandlerInterface $handler)
7280
* @throws LogoutException if the CSRF token is invalid
7381
* @throws \RuntimeException if the LogoutSuccessHandlerInterface instance does not return a response
7482
*/
75-
public function __invoke(RequestEvent $event)
83+
public function authenticate(RequestEvent $event)
7684
{
7785
$request = $event->getRequest();
7886

79-
if (!$this->requiresLogout($request)) {
80-
return;
81-
}
82-
8387
if (null !== $this->csrfTokenManager) {
8488
$csrfToken = ParameterBagUtils::getRequestParameterValue($request, $this->options['csrf_parameter']);
8589

0 commit comments

Comments
 (0)