Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fdf8bd4

Browse files
committed
Use event dispatcher in the Logout firewall listener
This provides several advantages: * An official system to hook into the logout listener (instead of the current handlers) * A unified hook (instead of success handler + handlers) * A small clean-up of the RememberMeServices
1 parent e5c025a commit fdf8bd4

23 files changed

+562
-98
lines changed

UPGRADE-5.1.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ Security
9191
{% endif %}
9292
```
9393

94+
* Deprecated `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface`, register a listener on the `LogoutEvent` event instead.
95+
* Deprecated `DefaultLogoutSuccessHandler` in favor of `DefaultLogoutListener`.
96+
9497
Yaml
9598
----
9699

UPGRADE-6.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ Security
6060
--------
6161

6262
* Removed `ROLE_PREVIOUS_ADMIN` role in favor of `IS_IMPERSONATOR` attribute
63+
* Removed `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface`, register a listener on the `LogoutEvent` event instead.
64+
* Removed `DefaultLogoutSuccessHandler` in favor of `DefaultLogoutListener`.

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1515
use Symfony\Component\DependencyInjection\ContainerBuilder;
1616
use Symfony\Component\DependencyInjection\Reference;
17+
use Symfony\Component\Security\Http\EventListener\CsrfTokenClearingLogoutListener;
1718

1819
/**
1920
* @author Christian Flothmann <[email protected]>
@@ -33,10 +34,9 @@ public function process(ContainerBuilder $container)
3334
return;
3435
}
3536

36-
$container->register('security.logout.handler.csrf_token_clearing', 'Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler')
37+
$container->register('security.logout.listener.csrf_token_clearing', CsrfTokenClearingLogoutListener::class)
3738
->addArgument(new Reference('security.csrf.token_storage'))
39+
->addTag('kernel.event_subscriber')
3840
->setPublic(false);
39-
40-
$container->findDefinition('security.logout_listener')->addMethodCall('addHandler', [new Reference('security.logout.handler.csrf_token_clearing')]);
4141
}
4242
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
1717
use Symfony\Component\Config\Definition\ConfigurationInterface;
1818
use Symfony\Component\Security\Core\Authorization\AccessDecisionManager;
19+
use Symfony\Component\Security\Http\Event\LogoutEvent;
1920
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy;
2021

2122
/**
@@ -205,7 +206,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
205206
->scalarNode('csrf_token_id')->defaultValue('logout')->end()
206207
->scalarNode('path')->defaultValue('/logout')->end()
207208
->scalarNode('target')->defaultValue('/')->end()
208-
->scalarNode('success_handler')->end()
209+
->scalarNode('success_handler')->setDeprecated(sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.', LogoutEvent::class))->end()
209210
->booleanNode('invalidate_session')->defaultTrue()->end()
210211
->end()
211212
->fixXmlConfig('delete_cookie')
@@ -228,7 +229,7 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
228229
->fixXmlConfig('handler')
229230
->children()
230231
->arrayNode('handlers')
231-
->prototype('scalar')->end()
232+
->prototype('scalar')->setDeprecated(sprintf('The "%%node%%" at path "%%path%%" is deprecated, register a listener on the "%s" event instead.', LogoutEvent::class))->end()
232233
->end()
233234
->end()
234235
->end()

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
1616
use Symfony\Component\DependencyInjection\ChildDefinition;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\Definition;
1819
use Symfony\Component\DependencyInjection\Reference;
1920
use Symfony\Component\HttpFoundation\Cookie;
21+
use Symfony\Component\Security\Http\EventListener\RememberMeLogoutListener;
2022

2123
class RememberMeFactory implements SecurityFactoryInterface
2224
{
@@ -55,13 +57,6 @@ public function create(ContainerBuilder $container, string $id, array $config, ?
5557
$rememberMeServicesId = $templateId.'.'.$id;
5658
}
5759

58-
if ($container->hasDefinition('security.logout_listener.'.$id)) {
59-
$container
60-
->getDefinition('security.logout_listener.'.$id)
61-
->addMethodCall('addHandler', [new Reference($rememberMeServicesId)])
62-
;
63-
}
64-
6560
$rememberMeServices = $container->setDefinition($rememberMeServicesId, new ChildDefinition($templateId));
6661
$rememberMeServices->replaceArgument(1, $config['secret']);
6762
$rememberMeServices->replaceArgument(2, $id);
@@ -116,6 +111,11 @@ public function create(ContainerBuilder $container, string $id, array $config, ?
116111
$listener->replaceArgument(1, new Reference($rememberMeServicesId));
117112
$listener->replaceArgument(5, $config['catch_exceptions']);
118113

114+
// remember-me logout listener
115+
$container->setDefinition('security.logout.listener.remember_me.'.$id, new Definition(RememberMeLogoutListener::class))
116+
->addArgument(new Reference($rememberMeServicesId))
117+
->addTag('kernel.event_subscriber', ['dispatcher' => 'security.event_dispatcher.'.$id]);
118+
119119
return [$authProviderId, $listenerId, $defaultEntryPoint];
120120
}
121121

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

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\RememberMeFactory;
1515
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
1616
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\UserProvider\UserProviderFactoryInterface;
17+
use Symfony\Bundle\SecurityBundle\Security\LegacyLogoutHandlerListener;
1718
use Symfony\Bundle\SecurityBundle\SecurityUserValueResolver;
1819
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
1920
use Symfony\Component\Config\FileLocator;
@@ -26,6 +27,7 @@
2627
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
2728
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
2829
use Symfony\Component\DependencyInjection\Reference;
30+
use Symfony\Component\EventDispatcher\EventDispatcher;
2931
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
3032
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
3133
use Symfony\Component\Security\Core\Encoder\NativePasswordEncoder;
@@ -307,6 +309,12 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
307309

308310
$config->replaceArgument(5, $defaultProvider);
309311

312+
// Register Firewall-specific event dispatcher
313+
$firewallEventDispatcherId = 'security.event_dispatcher.'.$id;
314+
$container->register($firewallEventDispatcherId, EventDispatcher::class);
315+
$container->setDefinition($firewallEventDispatcherId.'.event_bubbling_listener', new ChildDefinition('security.event_dispatcher.event_bubbling_listener'))
316+
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
317+
310318
// Register listeners
311319
$listeners = [];
312320
$listenerKeys = [];
@@ -334,44 +342,50 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
334342
if (isset($firewall['logout'])) {
335343
$logoutListenerId = 'security.logout_listener.'.$id;
336344
$logoutListener = $container->setDefinition($logoutListenerId, new ChildDefinition('security.logout_listener'));
345+
$logoutListener->replaceArgument(2, new Reference($firewallEventDispatcherId));
337346
$logoutListener->replaceArgument(3, [
338347
'csrf_parameter' => $firewall['logout']['csrf_parameter'],
339348
'csrf_token_id' => $firewall['logout']['csrf_token_id'],
340349
'logout_path' => $firewall['logout']['path'],
341350
]);
342351

343-
// add logout success handler
352+
// add default logout listener
344353
if (isset($firewall['logout']['success_handler'])) {
354+
// deprecated, to be removed in Symfony 6.0
345355
$logoutSuccessHandlerId = $firewall['logout']['success_handler'];
356+
$container->register('security.logout.listener.legacy_success_listener.'.$id, LegacyLogoutHandlerListener::class)
357+
->setArguments([new Reference($logoutSuccessHandlerId)])
358+
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
346359
} else {
347-
$logoutSuccessHandlerId = 'security.logout.success_handler.'.$id;
348-
$logoutSuccessHandler = $container->setDefinition($logoutSuccessHandlerId, new ChildDefinition('security.logout.success_handler'));
349-
$logoutSuccessHandler->replaceArgument(1, $firewall['logout']['target']);
360+
$logoutSuccessListenerId = 'security.logout.listener.default.'.$id;
361+
$container->setDefinition($logoutSuccessListenerId, new ChildDefinition('security.logout.listener.default'))
362+
->replaceArgument(1, $firewall['logout']['target'])
363+
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
350364
}
351-
$logoutListener->replaceArgument(2, new Reference($logoutSuccessHandlerId));
352365

353366
// add CSRF provider
354367
if (isset($firewall['logout']['csrf_token_generator'])) {
355368
$logoutListener->addArgument(new Reference($firewall['logout']['csrf_token_generator']));
356369
}
357370

358-
// add session logout handler
371+
// add session logout listener
359372
if (true === $firewall['logout']['invalidate_session'] && false === $firewall['stateless']) {
360-
$logoutListener->addMethodCall('addHandler', [new Reference('security.logout.handler.session')]);
373+
$container->setDefinition('security.logout.listener.session.'.$id, new ChildDefinition('security.logout.listener.session'))
374+
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
361375
}
362376

363-
// add cookie logout handler
377+
// add cookie logout listener
364378
if (\count($firewall['logout']['delete_cookies']) > 0) {
365-
$cookieHandlerId = 'security.logout.handler.cookie_clearing.'.$id;
366-
$cookieHandler = $container->setDefinition($cookieHandlerId, new ChildDefinition('security.logout.handler.cookie_clearing'));
367-
$cookieHandler->addArgument($firewall['logout']['delete_cookies']);
368-
369-
$logoutListener->addMethodCall('addHandler', [new Reference($cookieHandlerId)]);
379+
$container->setDefinition('security.logout.listener.cookie_clearing.'.$id, new ChildDefinition('security.logout.listener.cookie_clearing'))
380+
->addArgument($firewall['logout']['delete_cookies'])
381+
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
370382
}
371383

372-
// add custom handlers
373-
foreach ($firewall['logout']['handlers'] as $handlerId) {
374-
$logoutListener->addMethodCall('addHandler', [new Reference($handlerId)]);
384+
// add custom listeners (deprecated)
385+
foreach ($firewall['logout']['handlers'] as $i => $handlerId) {
386+
$container->register('security.logout.listener.legacy_handler.'.$i, LegacyLogoutHandlerListener::class)
387+
->addArgument(new Reference($handlerId))
388+
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
375389
}
376390

377391
// register with LogoutUrlGenerator
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Security\Http\Event\LogoutEvent;
16+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
17+
18+
/**
19+
* A listener that dispatches all security events from the firewall-specific
20+
* dispatcher on the global event dispatcher.
21+
*
22+
* @author Wouter de Jong <[email protected]>
23+
*/
24+
class FirewallEventBubblingListener implements EventSubscriberInterface
25+
{
26+
private $eventDispatcher;
27+
28+
public function __construct(EventDispatcherInterface $eventDispatcher)
29+
{
30+
$this->eventDispatcher = $eventDispatcher;
31+
}
32+
33+
public static function getSubscribedEvents(): array
34+
{
35+
return [
36+
LogoutEvent::class => 'bubbleEvent',
37+
];
38+
}
39+
40+
public function bubbleEvent($event): void
41+
{
42+
$this->eventDispatcher->dispatch($event);
43+
}
44+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@
9090
</service>
9191
<service id="Symfony\Component\Security\Http\Authentication\AuthenticationUtils" alias="security.authentication_utils" />
9292

93+
<service id="security.event_dispatcher.event_bubbling_listener" class="Symfony\Bundle\SecurityBundle\EventListener\FirewallEventBubblingListener" abstract="true">
94+
<argument type="service" id="event_dispatcher" />
95+
</service>
96+
9397
<!-- Authorization related services -->
9498
<service id="security.access.decision_manager" class="Symfony\Component\Security\Core\Authorization\AccessDecisionManager">
9599
<argument type="collection" />

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,17 @@
4848
<service id="security.logout_listener" class="Symfony\Component\Security\Http\Firewall\LogoutListener" abstract="true">
4949
<argument type="service" id="security.token_storage" />
5050
<argument type="service" id="security.http_utils" />
51-
<argument type="service" id="security.logout.success_handler" />
51+
<argument /> <!-- event dispatcher -->
5252
<argument /> <!-- Options -->
5353
</service>
5454

55-
<service id="security.logout.handler.session" class="Symfony\Component\Security\Http\Logout\SessionLogoutHandler" />
55+
<service id="security.logout.listener.session" class="Symfony\Component\Security\Http\EventListener\SessionLogoutListener" abstract="true" />
5656

57-
<service id="security.logout.handler.cookie_clearing" class="Symfony\Component\Security\Http\Logout\CookieClearingLogoutHandler" abstract="true" />
57+
<service id="security.logout.listener.cookie_clearing" class="Symfony\Component\Security\Http\Logout\CookieClearingLogoutHandler" abstract="true" />
5858

59-
<service id="security.logout.success_handler" class="Symfony\Component\Security\Http\Logout\DefaultLogoutSuccessHandler" abstract="true">
59+
<service id="security.logout.listener.default" class="Symfony\Component\Security\Http\EventListener\DefaultLogoutListener" abstract="true">
6060
<argument type="service" id="security.http_utils" />
61-
<argument>/</argument>
61+
<argument>/</argument> <!-- target url -->
6262
</service>
6363

6464
<service id="security.authentication.form_entry_point" class="Symfony\Component\Security\Http\EntryPoint\FormAuthenticationEntryPoint" abstract="true">
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Security\Http\Event\LogoutEvent;
16+
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
17+
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
18+
19+
/**
20+
* @author Wouter de Jong <[email protected]>
21+
*
22+
* @internal
23+
*/
24+
class LegacyLogoutHandlerListener implements EventSubscriberInterface
25+
{
26+
private $logoutHandler;
27+
28+
public function __construct(object $logoutHandler)
29+
{
30+
if (!$logoutHandler instanceof LogoutSuccessHandlerInterface && !$logoutHandler instanceof LogoutHandlerInterface) {
31+
throw new \InvalidArgumentException(sprintf('An instance of "%s" or "%s" must be passed to "%s", "%s" given.', LogoutHandlerInterface::class, LogoutSuccessHandlerInterface::class, __METHOD__, get_debug_type($logoutHandler)));
32+
}
33+
34+
$this->logoutHandler = $logoutHandler;
35+
}
36+
37+
public function onLogout(LogoutEvent $event): void
38+
{
39+
if ($this->logoutHandler instanceof LogoutSuccessHandlerInterface) {
40+
$event->setResponse($this->logoutHandler->onLogoutSuccess($event->getRequest()));
41+
} elseif ($this->logoutHandler instanceof LogoutHandlerInterface) {
42+
$this->logoutHandler->logout($event->getRequest(), $event->getResponse(), $event->getToken());
43+
}
44+
}
45+
46+
public static function getSubscribedEvents(): array
47+
{
48+
return [
49+
LogoutEvent::class => 'onLogout',
50+
];
51+
}
52+
}

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"ext-xml": "*",
2121
"symfony/config": "^4.4|^5.0",
2222
"symfony/dependency-injection": "^4.4|^5.0",
23+
"symfony/event-dispatcher": "^5.1",
2324
"symfony/http-kernel": "^5.0",
2425
"symfony/polyfill-php80": "^1.15",
2526
"symfony/security-core": "^4.4|^5.0",

src/Symfony/Component/Security/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ CHANGELOG
77
* Added access decision strategy to override access decisions by voter service priority
88
* Added `IS_ANONYMOUS`, `IS_REMEMBERED`, `IS_IMPERSONATOR`
99
* Hash the persistent RememberMe token value in database.
10+
* Added `LogoutEvent` to allow custom logout listeners.
11+
* Deprecated `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface` in favor of listening on the `LogoutEvent`.
1012

1113
5.0.0
1214
-----
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Security\Core\Authentication\Token\TokenInterface;
17+
use Symfony\Contracts\EventDispatcher\Event;
18+
19+
/**
20+
* @author Wouter de Jong <[email protected]>
21+
*/
22+
class LogoutEvent extends Event
23+
{
24+
private $request;
25+
private $response;
26+
private $token;
27+
28+
public function __construct(Request $request, ?TokenInterface $token)
29+
{
30+
$this->request = $request;
31+
$this->token = $token;
32+
}
33+
34+
public function getRequest(): Request
35+
{
36+
return $this->request;
37+
}
38+
39+
public function getToken(): ?TokenInterface
40+
{
41+
return $this->token;
42+
}
43+
44+
public function setResponse(Response $response): void
45+
{
46+
$this->response = $response;
47+
}
48+
49+
public function getResponse(): ?Response
50+
{
51+
return $this->response;
52+
}
53+
}

0 commit comments

Comments
 (0)