Skip to content

Commit be5eee5

Browse files
committed
Added CookieTokenStorage
1 parent 5d4a3a1 commit be5eee5

18 files changed

+484
-13
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

+11-1
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,19 @@ private function addCsrfSection(ArrayNodeDefinition $rootNode)
126126
->treatTrueLike(['enabled' => true])
127127
->treatNullLike(['enabled' => true])
128128
->addDefaultsIfNotSet()
129+
->beforeNormalization()
130+
->ifArray()
131+
->then(function ($v) {
132+
$v['enabled'] = isset($v['enabled']) ? $v['enabled'] : true;
133+
134+
return $v;
135+
})
136+
->end()
129137
->children()
130-
// defaults to framework.session.enabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class)
138+
// defaults to !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class)
131139
->booleanNode('enabled')->defaultNull()->end()
140+
// defaults to session if framework.session.enabled, cookie otherwise
141+
->scalarNode('storage')->defaultNull()->end()
132142
->end()
133143
->end()
134144
->end()

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

+30-5
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,9 @@
107107
use Symfony\Component\Routing\Matcher\Dumper\PhpMatcherDumper;
108108
use Symfony\Component\Security\Core\Security;
109109
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
110+
use Symfony\Component\Security\Csrf\EventListener\CookieTokenStorageListener;
111+
use Symfony\Component\Security\Csrf\TokenStorage\CookieTokenStorage;
112+
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
110113
use Symfony\Component\Serializer\Encoder\DecoderInterface;
111114
use Symfony\Component\Serializer\Encoder\EncoderInterface;
112115
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorFromClassMetadata;
@@ -257,8 +260,11 @@ public function load(array $configs, ContainerBuilder $container)
257260
$this->registerRequestConfiguration($config['request'], $container, $loader);
258261
}
259262

263+
if (null === $config['csrf_protection']['storage']) {
264+
$config['csrf_protection']['storage'] = $this->sessionConfigEnabled || !class_exists(CookieTokenStorage::class) ? 'session' : 'cookie';
265+
}
260266
if (null === $config['csrf_protection']['enabled']) {
261-
$config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class);
267+
$config['csrf_protection']['enabled'] = ($this->sessionConfigEnabled || 'session' !== $config['csrf_protection']['storage']) && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class);
262268
}
263269
$this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);
264270

@@ -1450,12 +1456,31 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild
14501456
throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".');
14511457
}
14521458

1453-
if (!$this->sessionConfigEnabled) {
1454-
throw new \LogicException('CSRF protection needs sessions to be enabled.');
1455-
}
1456-
14571459
// Enable services for CSRF protection (even without forms)
14581460
$loader->load('security_csrf.xml');
1461+
switch ($config['storage']) {
1462+
case 'session':
1463+
if (!$this->sessionConfigEnabled) {
1464+
throw new \LogicException('CSRF protection needs sessions to be enabled.');
1465+
}
1466+
1467+
$container->setAlias('security.csrf.token_storage', SessionTokenStorage::class);
1468+
break;
1469+
case 'cookie':
1470+
if (!class_exists(CookieTokenStorage::class)) {
1471+
throw new LogicException('CSRF support with Cookie Storage is not installed. Try running "composer require symfony/security-csrf:^4.4".');
1472+
}
1473+
1474+
$container->setAlias('security.csrf.token_storage', CookieTokenStorage::class);
1475+
break;
1476+
default:
1477+
$container->setAlias('security.csrf.token_storage', $config['storage']);
1478+
break;
1479+
}
1480+
1481+
if ('cookie' !== $config['storage']) {
1482+
$container->removeDefinition(CookieTokenStorageListener::class);
1483+
}
14591484

14601485
if (!class_exists(CsrfExtension::class)) {
14611486
$container->removeDefinition('twig.extension.security_csrf');

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757

5858
<xsd:complexType name="csrf_protection">
5959
<xsd:attribute name="enabled" type="xsd:boolean" />
60+
<xsd:attribute name="storage" type="xsd:string" />
6061
</xsd:complexType>
6162

6263
<xsd:complexType name="esi">

src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml

+9-1
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,17 @@
1010
<service id="security.csrf.token_generator" class="Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator" />
1111
<service id="Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface" alias="security.csrf.token_generator" />
1212

13-
<service id="security.csrf.token_storage" class="Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage">
13+
<service id="Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage">
1414
<argument type="service" id="session" />
1515
</service>
16+
<service id="Symfony\Component\Security\Csrf\TokenStorage\CookieTokenStorage">
17+
<argument type="service" id="request_stack" />
18+
<argument>%kernel.secret%</argument>
19+
</service>
20+
<service id="Symfony\Component\Security\Csrf\EventListener\CookieTokenStorageListener">
21+
<argument type="service" id="security.csrf.token_storage"/>
22+
<tag name="kernel.event_subscriber" />
23+
</service>
1624
<service id="Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface" alias="security.csrf.token_storage" />
1725

1826
<service id="security.csrf.token_manager" class="Symfony\Component\Security\Csrf\CsrfTokenManager" public="true">

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,8 @@ protected static function getBundleDefaultConfig()
215215
'ide' => null,
216216
'default_locale' => 'en',
217217
'csrf_protection' => [
218-
'enabled' => false,
218+
'enabled' => null,
219+
'storage' => null,
219220
],
220221
'form' => [
221222
'enabled' => !class_exists(FullStack::class),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
$container->loadFromExtension('framework', [
4+
'session' => false,
5+
'csrf_protection' => [
6+
'enabled' => true,
7+
],
8+
]);

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/csrf_needs_session.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22

33
$container->loadFromExtension('framework', [
44
'csrf_protection' => [
5-
'enabled' => true,
5+
'storage' => 'session',
66
],
77
]);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xmlns:framework="http://symfony.com/schema/dic/symfony"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
7+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
8+
9+
<framework:config>
10+
<framework:csrf-protection />
11+
</framework:config>
12+
</container>

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/csrf_needs_session.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
88

99
<framework:config>
10-
<framework:csrf-protection />
10+
<framework:csrf-protection storage="session"/>
1111
</framework:config>
1212
</container>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
framework:
2+
csrf_protection: ~
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
framework:
2-
csrf_protection: ~
2+
csrf_protection:
3+
storage: session

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

+11
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
4242
use Symfony\Component\Messenger\Transport\TransportFactory;
4343
use Symfony\Component\PropertyAccess\PropertyAccessor;
44+
use Symfony\Component\Security\Csrf\TokenStorage\CookieTokenStorage;
4445
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
4546
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
4647
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
@@ -131,6 +132,16 @@ public function testCsrfProtectionNeedsSessionToBeEnabled()
131132
$this->createContainerFromFile('csrf_needs_session');
132133
}
133134

135+
public function testCsrfProtectionFallbackToCookie()
136+
{
137+
if (!class_exists(CookieTokenStorage::class)) {
138+
$this->markTestSkipped('Cookie storage requires symfony/security 4.4+');
139+
}
140+
$container = $this->createContainerFromFile('csrf_fallback_to_cookie');
141+
142+
$this->assertSame(CookieTokenStorage::class, (string) $container->getAlias('security.csrf.token_storage'));
143+
}
144+
134145
public function testCsrfProtectionForFormsEnablesCsrfProtectionAutomatically()
135146
{
136147
$container = $this->createContainerFromFile('csrf');

src/Symfony/Component/Security/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ CHANGELOG
1212
for "guard" authenticators that deal with user passwords
1313
* Marked all dispatched event classes as `@final`
1414
* Deprecated returning a non-boolean value when implementing `Guard\AuthenticatorInterface::checkCredentials()`.
15+
* Added `CookieTokenStorage`
1516

1617
4.3.0
1718
-----
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\Csrf\EventListener;
13+
14+
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
15+
use Symfony\Component\HttpKernel\Event\ResponseEvent;
16+
use Symfony\Component\HttpKernel\KernelEvents;
17+
use Symfony\Component\Security\Csrf\TokenStorage\CookieTokenStorage;
18+
19+
/**
20+
* Inject transient cookies in the response.
21+
*
22+
* @author Oliver Hoff <oliver@hofff.com>
23+
* @author Jérémy Derussé <jeremy@derusse.com>
24+
*/
25+
class CookieTokenStorageListener implements EventSubscriberInterface
26+
{
27+
private $cookieTokenStorage;
28+
29+
public function __construct(CookieTokenStorage $cookieTokenStorage)
30+
{
31+
$this->cookieTokenStorage = $cookieTokenStorage;
32+
}
33+
34+
public function onKernelResponse(ResponseEvent $event)
35+
{
36+
$this->cookieTokenStorage->sendCookies($event->getResponse());
37+
}
38+
39+
/**
40+
* {@inheritdoc}
41+
*/
42+
public static function getSubscribedEvents()
43+
{
44+
return [
45+
KernelEvents::RESPONSE => 'onKernelResponse',
46+
];
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
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\Exception;
13+
14+
use Symfony\Component\Security\Core\Exception\RuntimeException as CoreRuntimeException;
15+
16+
/**
17+
* @author Jérémy Derussé <jeremy@derusse.com>
18+
*/
19+
class RuntimeException extends CoreRuntimeException
20+
{
21+
}

src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php

-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
namespace Symfony\Component\Security\Csrf\Exception;
1313

14-
use Symfony\Component\Security\Core\Exception\RuntimeException;
15-
1614
/**
1715
* @author Bernhard Schussek <bschussek@gmail.com>
1816
*/

0 commit comments

Comments
 (0)