Skip to content
This repository was archived by the owner on May 31, 2024. It is now read-only.

Commit c6fc8fd

Browse files
xabbuhfabpot
authored andcommitted
clear CSRF tokens when the user is logged out
1 parent 99ec54e commit c6fc8fd

File tree

8 files changed

+215
-3
lines changed

8 files changed

+215
-3
lines changed

Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,32 @@ public function testRemoveExistingToken()
116116
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
117117
$this->assertFalse($this->storage->hasToken('token_id'));
118118
}
119+
120+
public function testClearRemovesAllTokensFromTheConfiguredNamespace()
121+
{
122+
$this->storage->setToken('foo', 'bar');
123+
$this->storage->clear();
124+
125+
$this->assertFalse($this->storage->hasToken('foo'));
126+
$this->assertArrayNotHasKey(self::SESSION_NAMESPACE, $_SESSION);
127+
}
128+
129+
public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces()
130+
{
131+
$_SESSION['foo']['bar'] = 'baz';
132+
$this->storage->clear();
133+
134+
$this->assertArrayHasKey('foo', $_SESSION);
135+
$this->assertArrayHasKey('bar', $_SESSION['foo']);
136+
$this->assertSame('baz', $_SESSION['foo']['bar']);
137+
}
138+
139+
public function testClearDoesNotRemoveNonNamespacedSessionValues()
140+
{
141+
$_SESSION['foo'] = 'baz';
142+
$this->storage->clear();
143+
144+
$this->assertArrayHasKey('foo', $_SESSION);
145+
$this->assertSame('baz', $_SESSION['foo']);
146+
}
119147
}

Csrf/Tests/TokenStorage/SessionTokenStorageTest.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,31 @@ public function testRemoveExistingTokenFromActiveSession()
129129

130130
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
131131
}
132+
133+
public function testClearRemovesAllTokensFromTheConfiguredNamespace()
134+
{
135+
$this->storage->setToken('foo', 'bar');
136+
$this->storage->clear();
137+
138+
$this->assertFalse($this->storage->hasToken('foo'));
139+
$this->assertFalse($this->session->has(self::SESSION_NAMESPACE.'/foo'));
140+
}
141+
142+
public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces()
143+
{
144+
$this->session->set('foo/bar', 'baz');
145+
$this->storage->clear();
146+
147+
$this->assertTrue($this->session->has('foo/bar'));
148+
$this->assertSame('baz', $this->session->get('foo/bar'));
149+
}
150+
151+
public function testClearDoesNotRemoveNonNamespacedSessionValues()
152+
{
153+
$this->session->set('foo', 'baz');
154+
$this->storage->clear();
155+
156+
$this->assertTrue($this->session->has('foo'));
157+
$this->assertSame('baz', $this->session->get('foo'));
158+
}
132159
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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\Csrf\TokenStorage;
13+
14+
/**
15+
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
16+
*/
17+
interface ClearableTokenStorageInterface extends TokenStorageInterface
18+
{
19+
/**
20+
* Removes all CSRF tokens.
21+
*/
22+
public function clear();
23+
}

Csrf/TokenStorage/NativeSessionTokenStorage.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*
1919
* @author Bernhard Schussek <bschussek@gmail.com>
2020
*/
21-
class NativeSessionTokenStorage implements TokenStorageInterface
21+
class NativeSessionTokenStorage implements ClearableTokenStorageInterface
2222
{
2323
/**
2424
* The namespace used to store values in the session.
@@ -96,6 +96,14 @@ public function removeToken($tokenId)
9696
return $token;
9797
}
9898

99+
/**
100+
* {@inheritdoc}
101+
*/
102+
public function clear()
103+
{
104+
unset($_SESSION[$this->namespace]);
105+
}
106+
99107
private function startSession()
100108
{
101109
if (\PHP_VERSION_ID >= 50400) {

Csrf/TokenStorage/SessionTokenStorage.php

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* @author Bernhard Schussek <bschussek@gmail.com>
2121
*/
22-
class SessionTokenStorage implements TokenStorageInterface
22+
class SessionTokenStorage implements ClearableTokenStorageInterface
2323
{
2424
/**
2525
* The namespace used to store values in the session.
@@ -92,4 +92,16 @@ public function removeToken($tokenId)
9292

9393
return $this->session->remove($this->namespace.'/'.$tokenId);
9494
}
95+
96+
/**
97+
* {@inheritdoc}
98+
*/
99+
public function clear()
100+
{
101+
foreach (array_keys($this->session->all()) as $key) {
102+
if (0 === strpos($key, $this->namespace.'/')) {
103+
$this->session->remove($key);
104+
}
105+
}
106+
}
95107
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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\Logout;
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\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface;
18+
19+
/**
20+
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
21+
*/
22+
class CsrfTokenClearingLogoutHandler implements LogoutHandlerInterface
23+
{
24+
private $csrfTokenStorage;
25+
26+
public function __construct(ClearableTokenStorageInterface $csrfTokenStorage)
27+
{
28+
$this->csrfTokenStorage = $csrfTokenStorage;
29+
}
30+
31+
public function logout(Request $request, Response $response, TokenInterface $token)
32+
{
33+
$this->csrfTokenStorage->clear();
34+
}
35+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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\Logout;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\HttpFoundation\Session\Session;
18+
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
19+
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
20+
use Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler;
21+
22+
class CsrfTokenClearingLogoutHandlerTest extends TestCase
23+
{
24+
private $session;
25+
private $csrfTokenStorage;
26+
private $csrfTokenClearingLogoutHandler;
27+
28+
protected function setUp()
29+
{
30+
$this->session = new Session(new MockArraySessionStorage());
31+
$this->csrfTokenStorage = new SessionTokenStorage($this->session, 'foo');
32+
$this->csrfTokenStorage->setToken('foo', 'bar');
33+
$this->csrfTokenStorage->setToken('foobar', 'baz');
34+
$this->csrfTokenClearingLogoutHandler = new CsrfTokenClearingLogoutHandler($this->csrfTokenStorage);
35+
}
36+
37+
public function testCsrfTokenCookieWithSameNamespaceIsRemoved()
38+
{
39+
$this->assertSame('bar', $this->session->get('foo/foo'));
40+
$this->assertSame('baz', $this->session->get('foo/foobar'));
41+
42+
$this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock());
43+
44+
$this->assertFalse($this->csrfTokenStorage->hasToken('foo'));
45+
$this->assertFalse($this->csrfTokenStorage->hasToken('foobar'));
46+
47+
$this->assertFalse($this->session->has('foo/foo'));
48+
$this->assertFalse($this->session->has('foo/foobar'));
49+
}
50+
51+
public function testCsrfTokenCookieWithDifferentNamespaceIsNotRemoved()
52+
{
53+
$barNamespaceCsrfSessionStorage = new SessionTokenStorage($this->session, 'bar');
54+
$barNamespaceCsrfSessionStorage->setToken('foo', 'bar');
55+
$barNamespaceCsrfSessionStorage->setToken('foobar', 'baz');
56+
57+
$this->assertSame('bar', $this->session->get('foo/foo'));
58+
$this->assertSame('baz', $this->session->get('foo/foobar'));
59+
$this->assertSame('bar', $this->session->get('bar/foo'));
60+
$this->assertSame('baz', $this->session->get('bar/foobar'));
61+
62+
$this->csrfTokenClearingLogoutHandler->logout(new Request(), new Response(), $this->getMockBuilder('Symfony\Component\Security\Core\Authentication\Token\TokenInterface')->getMock());
63+
64+
$this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foo'));
65+
$this->assertTrue($barNamespaceCsrfSessionStorage->hasToken('foobar'));
66+
$this->assertSame('bar', $barNamespaceCsrfSessionStorage->getToken('foo'));
67+
$this->assertSame('baz', $barNamespaceCsrfSessionStorage->getToken('foobar'));
68+
$this->assertFalse($this->csrfTokenStorage->hasToken('foo'));
69+
$this->assertFalse($this->csrfTokenStorage->hasToken('foobar'));
70+
71+
$this->assertFalse($this->session->has('foo/foo'));
72+
$this->assertFalse($this->session->has('foo/foobar'));
73+
$this->assertSame('bar', $this->session->get('bar/foo'));
74+
$this->assertSame('baz', $this->session->get('bar/foobar'));
75+
}
76+
}

Http/composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@
2424
},
2525
"require-dev": {
2626
"symfony/routing": "~2.2",
27-
"symfony/security-csrf": "~2.4",
27+
"symfony/security-csrf": "~2.7.48 || ~2.8.41",
2828
"psr/log": "~1.0"
2929
},
30+
"conflict": {
31+
"symfony/security-csrf": "<2.7.48 || >=2.8.0,<2.8.41 || >=3.0.0"
32+
},
3033
"suggest": {
3134
"symfony/security-csrf": "For using tokens to protect authentication/logout attempts",
3235
"symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs"

0 commit comments

Comments
 (0)