Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 504bece

Browse filesBrowse 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 1673107 commit 504bece
Copy full SHA for 504bece

24 files changed

+565
-98
lines changed

‎UPGRADE-5.1.md

Copy file name to clipboardExpand all lines: UPGRADE-5.1.md
+3Lines 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

Copy file name to clipboardExpand all lines: UPGRADE-6.0.md
+2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php
+3-3Lines 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 <christian.flothmann@sensiolabs.de>
@@ -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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/MainConfiguration.php
+3-2Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/RememberMeFactory.php
+7-7Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/DependencyInjection/SecurityExtension.php
+30-16Lines 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
+44Lines 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 <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\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 <wouter@wouterj.nl>
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml
+4Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/Resources/config/security_listeners.xml
+5-5Lines 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">
+52Lines 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 <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\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 <wouter@wouterj.nl>
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/SecurityBundle/composer.json
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/Security/CHANGELOG.md
+2Lines 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
-----
+53Lines 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 <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\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 <wouter@wouterj.nl>
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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.