Skip to content

Commit c20d516

Browse files
committed
[Security] Add logout configuration for Clear-Site-Data header
1 parent ea7cc20 commit c20d516

File tree

7 files changed

+164
-2
lines changed

7 files changed

+164
-2
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,18 @@ private function addFirewallsSection(ArrayNodeDefinition $rootNode, array $facto
251251
->scalarNode('path')->defaultValue('/logout')->end()
252252
->scalarNode('target')->defaultValue('/')->end()
253253
->booleanNode('invalidate_session')->defaultTrue()->end()
254+
->scalarNode('clear_site_data')->defaultValue('off')->end()
255+
->arrayNode('clear_site_data')
256+
->fixXmlConfig('clear_site_data')
257+
->performNoDeepMerging()
258+
->defaultValue(['off'])
259+
->beforeNormalization()->ifString()->then(function ($v) { return $v ? array_map('trim', explode(',', $v)) : []; })->end()
260+
->enumPrototype()
261+
->values([
262+
'off', '*', 'cache', 'cookies', 'storage', 'executionContexts',
263+
])
264+
->end()
265+
->end()
254266
->end()
255267
->fixXmlConfig('delete_cookie')
256268
->children()

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -451,8 +451,7 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
451451
'logout_path' => $firewall['logout']['path'],
452452
]);
453453

454-
$logoutSuccessListenerId = 'security.logout.listener.default.'.$id;
455-
$container->setDefinition($logoutSuccessListenerId, new ChildDefinition('security.logout.listener.default'))
454+
$container->setDefinition('security.logout.listener.default.'.$id, new ChildDefinition('security.logout.listener.default'))
456455
->replaceArgument(1, $firewall['logout']['target'])
457456
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
458457

@@ -474,6 +473,13 @@ private function createFirewall(ContainerBuilder $container, string $id, array $
474473
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
475474
}
476475

476+
// add clear site data listener
477+
if (!empty($firewall['logout']['clear_site_data']) && !\in_array('off', $firewall['logout']['clear_site_data'])) {
478+
$container->setDefinition('security.logout.listener.clear_site_data.'.$id, new ChildDefinition('security.logout.listener.clear_site_data'))
479+
->addArgument(implode(', ', array_map(function (string $clearSiteDataValue) { return '"'.$clearSiteDataValue.'"'; }, $firewall['logout']['clear_site_data'])))
480+
->addTag('kernel.event_subscriber', ['dispatcher' => $firewallEventDispatcherId]);
481+
}
482+
477483
// register with LogoutUrlGenerator
478484
$container
479485
->getDefinition('security.logout_url_generator')

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\Security\Http\Authentication\CustomAuthenticationSuccessHandler;
1818
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationFailureHandler;
1919
use Symfony\Component\Security\Http\Authentication\DefaultAuthenticationSuccessHandler;
20+
use Symfony\Component\Security\Http\EventListener\ClearSiteDataLogoutListener;
2021
use Symfony\Component\Security\Http\EventListener\CookieClearingLogoutListener;
2122
use Symfony\Component\Security\Http\EventListener\DefaultLogoutListener;
2223
use Symfony\Component\Security\Http\EventListener\SessionLogoutListener;
@@ -64,6 +65,9 @@
6465
->set('security.logout.listener.session', SessionLogoutListener::class)
6566
->abstract()
6667

68+
->set('security.logout.listener.clear_site_data', ClearSiteDataLogoutListener::class)
69+
->abstract()
70+
6771
->set('security.logout.listener.cookie_clearing', CookieClearingLogoutListener::class)
6872
->abstract()
6973

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/CompleteConfigurationTestCase.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ public function testFirewalls()
181181
'invalidate_session' => true,
182182
'delete_cookies' => [],
183183
'enable_csrf' => null,
184+
'clear_site_data' => ['off'],
184185
],
185186
],
186187
[

src/Symfony/Bundle/SecurityBundle/Tests/DependencyInjection/SecurityExtensionTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,48 @@ public function testConfigureCustomFirewallListener()
848848
$this->assertContains('custom_firewall_listener_id', $firewallListeners);
849849
}
850850

851+
public function testClearSiteDataLogoutListenerEnabled()
852+
{
853+
$container = $this->getRawContainer();
854+
855+
$firewallId = 'logout_firewall';
856+
$container->loadFromExtension('security', [
857+
'firewalls' => [
858+
$firewallId => [
859+
'logout' => [
860+
'clear_site_data' => ['*'],
861+
],
862+
],
863+
],
864+
]);
865+
866+
$container->compile();
867+
868+
$this->assertTrue($container->has('security.logout.listener.clear_site_data.'.$firewallId));
869+
$listenerArgument = $container->getDefinition('security.logout.listener.clear_site_data.'.$firewallId)->getArgument(0);
870+
$this->assertEquals('"*"', $listenerArgument);
871+
}
872+
873+
public function testClearSiteDataLogoutListenerDisabled()
874+
{
875+
$container = $this->getRawContainer();
876+
877+
$firewallId = 'logout_firewall';
878+
$container->loadFromExtension('security', [
879+
'firewalls' => [
880+
$firewallId => [
881+
'logout' => [
882+
'clear_site_data' => ['off'],
883+
],
884+
],
885+
],
886+
]);
887+
888+
$container->compile();
889+
890+
$this->assertFalse($container->has('security.logout.listener.clear_site_data.'.$firewallId));
891+
}
892+
851893
/**
852894
* @group legacy
853895
*/
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\Security\Http\Event\LogoutEvent;
16+
17+
/**
18+
* Handler for Clear-Site-Data header during logout.
19+
*
20+
* @author Max Beckers <beckers.maximilian@gmail.com>
21+
*
22+
* @final
23+
*/
24+
class ClearSiteDataLogoutListener implements EventSubscriberInterface
25+
{
26+
private const HEADER_NAME = 'Clear-Site-Data';
27+
28+
/**
29+
* @param string $cookieValue The value for the Clear-Site-Data header.
30+
* Can be '*' or a subset of 'cache', 'cookies', 'storage', 'executionContexts'.
31+
*/
32+
public function __construct(private readonly string $cookieValue)
33+
{
34+
}
35+
36+
public function onLogout(LogoutEvent $event): void
37+
{
38+
if (!$event->getResponse()?->headers->has(static::HEADER_NAME)) {
39+
$event->getResponse()->headers->set(static::HEADER_NAME, $this->cookieValue);
40+
}
41+
}
42+
43+
public static function getSubscribedEvents(): array
44+
{
45+
return [
46+
LogoutEvent::class => 'onLogout',
47+
];
48+
}
49+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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\Tests\EventListener;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\Security\Http\Event\LogoutEvent;
18+
use Symfony\Component\Security\Http\EventListener\ClearSiteDataLogoutListener;
19+
20+
class ClearSiteDataLogoutListenerTest extends TestCase
21+
{
22+
/**
23+
* @dataProvider provideClearSiteDataConfig
24+
*/
25+
public function testLogout(string $clearSiteDataConfig)
26+
{
27+
$response = new Response();
28+
$event = new LogoutEvent(new Request(), null);
29+
$event->setResponse($response);
30+
31+
$listener = new ClearSiteDataLogoutListener($clearSiteDataConfig);
32+
33+
$headerCountBefore = $response->headers->count();
34+
35+
$listener->onLogout($event);
36+
37+
$this->assertEquals(++$headerCountBefore, $response->headers->count());
38+
39+
$this->assertNotNull($response->headers->get('Clear-Site-Data'));
40+
$this->assertEquals($clearSiteDataConfig, $response->headers->get('Clear-Site-Data'));
41+
}
42+
43+
public static function provideClearSiteDataConfig(): iterable
44+
{
45+
yield ['"*"'];
46+
yield ['"cache", "cookies", "storage", "executionContexts"'];
47+
}
48+
}

0 commit comments

Comments
 (0)