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

Skip to content

Commit 86a039a

Browse files
xabbuhfabpot
authored andcommitted
clear CSRF tokens when the user is logged out
1 parent f6f2f97 commit 86a039a

File tree

14 files changed

+309
-4
lines changed

14 files changed

+309
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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\DependencyInjection\Compiler;
13+
14+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
15+
use Symfony\Component\DependencyInjection\ContainerBuilder;
16+
use Symfony\Component\DependencyInjection\Reference;
17+
18+
/**
19+
* @author Christian Flothmann <[email protected]>
20+
*/
21+
class RegisterCsrfTokenClearingLogoutHandlerPass implements CompilerPassInterface
22+
{
23+
public function process(ContainerBuilder $container)
24+
{
25+
if (!$container->has('security.logout_listener') || !$container->has('security.csrf.token_storage')) {
26+
return;
27+
}
28+
29+
$csrfTokenStorage = $container->findDefinition('security.csrf.token_storage');
30+
$csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass());
31+
32+
if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) {
33+
return;
34+
}
35+
36+
$container->register('security.logout.handler.csrf_token_clearing', 'Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler')
37+
->addArgument(new Reference('security.csrf.token_storage'))
38+
->setPublic(false);
39+
40+
$container->findDefinition('security.logout_listener')->addMethodCall('addHandler', array(new Reference('security.logout.handler.csrf_token_clearing')));
41+
}
42+
}

src/Symfony/Bundle/SecurityBundle/SecurityBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Bundle\SecurityBundle;
1313

1414
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\JsonLoginFactory;
15+
use Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler\RegisterCsrfTokenClearingLogoutHandlerPass;
1516
use Symfony\Component\HttpKernel\Bundle\Bundle;
1617
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
1718
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -60,5 +61,6 @@ public function build(ContainerBuilder $container)
6061
$extension->addUserProviderFactory(new LdapFactory());
6162
$container->addCompilerPass(new AddSecurityVotersPass());
6263
$container->addCompilerPass(new AddSessionDomainConstraintPass(), PassConfig::TYPE_AFTER_REMOVING);
64+
$container->addCompilerPass(new RegisterCsrfTokenClearingLogoutHandlerPass());
6365
}
6466
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
use Symfony\Bundle\SecurityBundle\SecurityBundle;
13+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
14+
15+
return array(
16+
new FrameworkBundle(),
17+
new SecurityBundle(),
18+
);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
imports:
2+
- { resource: ./../config/framework.yml }
3+
4+
security:
5+
encoders:
6+
Symfony\Component\Security\Core\User\User: plaintext
7+
8+
providers:
9+
in_memory:
10+
memory:
11+
users:
12+
johannes: { password: test, roles: [ROLE_USER] }
13+
14+
firewalls:
15+
default:
16+
form_login:
17+
check_path: login
18+
remember_me: true
19+
require_previous_session: false
20+
remember_me:
21+
always_remember_me: true
22+
key: key
23+
logout:
24+
invalidate_session: false
25+
anonymous: ~
26+
stateless: true
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
login:
2+
path: /login
3+
4+
logout:
5+
path: /logout

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"require": {
1919
"php": "^5.5.9|>=7.0.8",
2020
"ext-xml": "*",
21-
"symfony/security": "~3.3.13|~3.4-beta5",
21+
"symfony/security": "~3.3.17|~3.4.11",
2222
"symfony/dependency-injection": "~3.3",
2323
"symfony/http-kernel": "~3.3",
2424
"symfony/polyfill-php70": "~1.0"

src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php

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

src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php

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

257257
$this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
258258
}
259+
260+
public function testClearRemovesAllTokensFromTheConfiguredNamespace()
261+
{
262+
$this->storage->setToken('foo', 'bar');
263+
$this->storage->clear();
264+
265+
$this->assertFalse($this->storage->hasToken('foo'));
266+
$this->assertFalse($this->session->has(self::SESSION_NAMESPACE.'/foo'));
267+
}
268+
269+
public function testClearDoesNotRemoveSessionValuesFromOtherNamespaces()
270+
{
271+
$this->session->set('foo/bar', 'baz');
272+
$this->storage->clear();
273+
274+
$this->assertTrue($this->session->has('foo/bar'));
275+
$this->assertSame('baz', $this->session->get('foo/bar'));
276+
}
277+
278+
public function testClearDoesNotRemoveNonNamespacedSessionValues()
279+
{
280+
$this->session->set('foo', 'baz');
281+
$this->storage->clear();
282+
283+
$this->assertTrue($this->session->has('foo'));
284+
$this->assertSame('baz', $this->session->get('foo'));
285+
}
259286
}
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 <[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\Csrf\TokenStorage;
13+
14+
/**
15+
* @author Christian Flothmann <[email protected]>
16+
*/
17+
interface ClearableTokenStorageInterface extends TokenStorageInterface
18+
{
19+
/**
20+
* Removes all CSRF tokens.
21+
*/
22+
public function clear();
23+
}

src/Symfony/Component/Security/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 <[email protected]>
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_SESSION_NONE === session_status()) {

src/Symfony/Component/Security/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 <[email protected]>
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 <[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\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 <[email protected]>
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 <[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\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+
}

src/Symfony/Component/Security/Http/composer.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@
2727
},
2828
"require-dev": {
2929
"symfony/routing": "~2.8|~3.0",
30-
"symfony/security-csrf": "~2.8|~3.0",
30+
"symfony/security-csrf": "~2.8.41|~3.3.17",
3131
"psr/log": "~1.0"
3232
},
33+
"conflict": {
34+
"symfony/security-csrf": "<2.7.48 || >=2.8.0,<2.8.41 || >=3.0.0"
35+
},
3336
"suggest": {
3437
"symfony/security-csrf": "For using tokens to protect authentication/logout attempts",
3538
"symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs"

0 commit comments

Comments
 (0)