8000 Add ability to prioritize firewall listeners · symfony/symfony@7e359df · GitHub
[go: up one dir, main page]

Skip to content

Commit 7e359df

Browse files
committed
Add ability to prioritize firewall listeners
1 parent 6f6e4ce commit 7e359df

File tree

8 files changed

+110
-10
lines changed

8 files changed

+110
-10
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
4+
5+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
6+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
7+
use Symfony\Component\DependencyInjection\ContainerBuilder;
8+
use Symfony\Component\DependencyInjection\Definition;
9+
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
10+
use Symfony\Component\DependencyInjection\Reference;
11+
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
12+
13+
class SortFirewallListenersPass implements CompilerPassInterface
14+
{
15+
public function process(ContainerBuilder $container): void
16+
{
17+
$taggedServices = $container->findTaggedServiceIds('security.firewall_map_context');
18+
foreach ($taggedServices as $serviceId => $attributes) {
19+
$firewallContextDefinition = $container->getDefinition($serviceId);
20+
$this->sortFirewallContextListeners($firewallContextDefinition, $container);
21+
}
22+
}
23+
24+
private function sortFirewallContextListeners(Definition $definition, ContainerBuilder $container): void
25+
{
26+
/** @var IteratorArgument $listenerIteratorArgument */
27+
$listenerIteratorArgument = $definition->getArgument(0);
28+
$prioritiesByServiceId = $this->getListenerPriorities($listenerIteratorArgument, $container);
29+
30+
$listeners = $listenerIteratorArgument->getValues();
31+
usort($listeners, function (Reference $a, Reference $b) use ($prioritiesByServiceId) {
32+
return $prioritiesByServiceId[(string) $b] <=> $prioritiesByServiceId[(string) $a];
33+
});
34+
35+
$listenerIteratorArgument->setValues(array_values($listeners));
36+
}
37+
38+
private function getListenerPriorities(IteratorArgument $listeners, ContainerBuilder $container): array
39+
{
40+
$priorities = [];
41+
42+
foreach ($listeners->getValues() as $reference) {
43+
$id = (string) $reference;
44+
$def = $container->getDefinition($id);
45+
46+
// We must assume that the class value has been correctly filled, even if the service is created by a factory
47+
$class = $def->getClass();
48+
49+
if (!$r = $container->getReflectionClass($class)) {
50+
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $id));
51+
}
52+
53+
$priority = 0;
54+
if ($r->isSubclassOf(FirewallListenerInterface::class)) {
55+
$priority = $r->getMethod('getPriority')->invoke(null);
56+
}
57+
58+
$priorities[$id] = $priority;
59+
}
60+
61+
return $priorities;
62+
}
63+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ private function createFirewalls(array $config, ContainerBuilder $container)
284284
->replaceArgument(1, $exceptionListener)
285285
->replaceArgument(2, $logoutListener)
286286
->replaceArgument(3, new Reference($configId))
287+
->addTag('security.firewall_map_context')
287288
;
288289

289290
$contextRefs[$contextId] = new Reference($contextId);

src/Symfony/Bundle/SecurityBundle/SecurityBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfFeaturesPass;
1818
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterLdapLocatorPass;
1919
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterTokenUsageTrackingPass;
20+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\SortFirewallListenersPass;
2021
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\AnonymousFactory;
2122
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\CustomAuthenticatorFactory;
2223
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\FormLoginFactory;
@@ -75,6 +76,7 @@ public function build(ContainerBuilder $container)
7576
$container->addCompilerPass(new RegisterCsrfFeaturesPass());
7677
$container->addCompilerPass(new RegisterTokenUsageTrackingPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 200);
7778
$container->addCompilerPass(new RegisterLdapLocatorPass());
79+
$container->addCompilerPass(new SortFirewallListenersPass(), PassConfig::TYPE_BEFORE_REMOVING);
7880

7981
$container->addCompilerPass(new AddEventAliasesPass([
8082
AuthenticationSuccessEvent::class => AuthenticationEvents::AUTHENTICATION_SUCCESS,

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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
1616
use Symfony\Component\HttpKernel\Event\RequestEvent;
1717
use Symfony\Component\HttpKernel\KernelEvents;
18-
use Symfony\Component\Security\Http\Firewall\AccessListener;
18+
use Symfony\Component\Security\Http\Firewall\FirewallListenerInterface;
1919
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
2020

2121
/**
@@ -59,26 +59,28 @@ public function onKernelRequest(RequestEvent $event)
5959
$exceptionListener->register($this->dispatcher);
6060
}
6161

62+
// Authentication listeners are pre-sorted by SortFirewallListenersPass
6263
$authenticationListeners = function () use ($authenticationListeners, $logoutListener) {
63-
$accessListener = null;
64+
if (null !== $logoutListener) {
65+
$logoutListenerPriority = $this->getListenerPriority($logoutListener);
66+
}
6467

6568
foreach ($authenticationListeners as $listener) {
66-
if ($listener instanceof AccessListener) {
67-
$accessListener = $listener;
69+
$listenerPriority = $this->getListenerPriority($listener);
6870

69-
continue;
71+
// Yielding the LogoutListener at the correct position
72+
if (null !== $logoutListener && $listenerPriority < $logoutListenerPriority) {
73+
yield $logoutListener;
74+
$logoutListener = null;
7075
}
7176

7277
yield $listener;
7378
}
7479

80+
// When LogoutListener has the lowest priority of all listeners
7581
if (null !== $logoutListener) {
7682
yield $logoutListener;
7783
}
78-
79-
if (null !== $accessListener) {
80-
yield $accessListener;
81-
}
8284
};
8385

8486
$this->callListeners($event, $authenticationListeners());
@@ -115,4 +117,9 @@ protected function callListeners(RequestEvent $event, iterable $listeners)
115117
}
116118
}
117119
}
120+
121+
private function getListenerPriority(object $logoutListener): int
122+
{
123+
return $logoutListener instanceof FirewallListenerInterface ? $logoutListener->getPriority() : 0;
124+
}
118125
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* @author Nicolas Grekas <p@tchwork.com>
2121
*/
22-
abstract class AbstractListener
22+
abstract class AbstractListener implements FirewallListenerInterface
2323
{
2424
final public function __invoke(RequestEvent $event)
2525
{
@@ -39,4 +39,9 @@ abstract public function supports(Request $request): ?bool;
3939
* Does whatever is required to authenticate the request, typically calling $event->setResponse() internally.
4040
*/
4141
abstract public function authenticate(RequestEvent $event);
42+
43+
public static function getPriority(): int
44+
{
45+
return 0; // Default
46+
}
4247
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,4 +122,9 @@ private function createAccessDeniedException(Request $request, array $attributes
122122

123123
return $exception;
124124
}
125+
126+
public static function getPriority(): int
127+
{
128+
return -255;
129+
}
125130
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Symfony\Component\Security\Http\Firewall;
4+
5+
interface FirewallListenerInterface
6+
{
7+
/**
8+
* Defines the priority of the listener.
9+
* The higher the number, the earlier a listener is executed.
10+
*/
11+
public static function getPriority(): int;
12+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,9 @@ protected function requiresLogout(Request $request): bool
142142
{
143143
return isset($this->options['logout_path']) && $this->httpUtils->checkRequestPath($request, $this->options['logout_path']);
144144
}
145+
146+
public static function getPriority(): int
147+
{
148+
return -127;
149+
}
145150
}

0 commit comments

Comments
 (0)
0