8000 [Csrf] Moved CSRF component outside Security namespace · symfony/symfony@bca5f15 · GitHub
[go: up one dir, main page]

Skip to content

Commit bca5f15

Browse files
committed
[Csrf] Moved CSRF component outside Security namespace
* CSRF not directly related on the rest of Security (nor has dependencies on it) * CSRF is usable in any form or request * CSRF is configured through the FrameworkBundle * This allows re-introducing the `symfony/security` component (see #33558)
1 parent 7800a2a commit bca5f15

File tree

65 files changed

+906
-527
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

65 files changed

+906
-527
lines changed

UPGRADE-5.1.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ Security
130130

131131
* Deprecated `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface`, register a listener on the `LogoutEvent` event instead.
132132
* Deprecated `DefaultLogoutSuccessHandler` in favor of `DefaultLogoutListener`.
133+
* Deprecated the `Symfony\Component\Security\Csrf` component in favor of the `Symfony\Component\Csrf` component. Remove the `symfony/security-csrf` requirement and install `symfony/csrf` instead.
134+
* [BC break] `Symfony\Component\Csrf\Exception\TokenNotFoundException` no longer extends from `Symfony\Component\Security\Core\Exception\RuntimeException`.
133135

134136
Yaml
135137
----

UPGRADE-6.0.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,4 @@ Security
9898
* Removed `ROLE_PREVIOUS_ADMIN` role in favor of `IS_IMPERSONATOR` attribute
9999
* Removed `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface`, register a listener on the `LogoutEvent` event instead.
100100
* Removed `DefaultLogoutSuccessHandler` in favor of `DefaultLogoutListener`.
101+
* Removed the `Symfony\Component\Security\Csrf` in favor of the `Symfony\Component\Csrf` component.

src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
namespace Symfony\Bridge\Twig\Extension;
1313

14-
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
14+
use Symfony\Component\Csrf\CsrfTokenManagerInterface;
1515

1616
/**
1717
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>

src/Symfony/Bridge/Twig/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"require-dev": {
2525
"egulias/email-validator": "^2.1.10",
2626
"symfony/asset": "^4.4|^5.0",
27+
"symfony/csrf": "^5.1",
2728
"symfony/dependency-injection": "^4.4|^5.0",
2829
"symfony/finder": "^4.4|^5.0",
2930
"symfony/form": "^5.1",
@@ -36,7 +37,6 @@
3637
"symfony/yaml": "^4.4|^5.0",
3738
"symfony/security-acl": "^2.8|^3.0",
3839
"symfony/security-core": "^4.4|^5.0",
39-
"symfony/security-csrf": "^4.4|^5.0",
4040
"symfony/security-http": "^4.4|^5.0",
4141
"symfony/stopwatch": "^4.4|^5.0",
4242
"symfony/console": "^4.4|^5.0",
@@ -64,7 +64,7 @@
6464
"symfony/translation": "For using the TranslationExtension",
6565
"symfony/yaml": "For using the YamlExtension",
6666
"symfony/security-core": "For using the SecurityExtension",
67-
"symfony/security-csrf": "For using the CsrfExtension",
67+
"symfony/csrf": "For using the CsrfExtension",
6868
"symfony/security-http": "For using the LogoutUrlExtension",
6969
"symfony/stopwatch": "For using the StopwatchExtension",
7070
"symfony/var-dumper": "For using the DumpExtension",

src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
use Doctrine\Persistence\ManagerRegistry;
1515
use Psr\Container\ContainerInterface;
1616
use Psr\Link\LinkInterface;
17+
use Symfony\Component\Csrf\CsrfToken;
18+
use Symfony\Component\Csrf\CsrfTokenManagerInterface;
1719
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
1820
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
1921
use Symfony\Component\Form\Extension\Core\Type\FormType;
@@ -39,8 +41,6 @@
3941
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
4042
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
4143
use Symfony\Component\Security\Core\User\UserInterface;
42-
use Symfony\Component\Security\Csrf\CsrfToken;
43-
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
4444
use Symfony\Component\Serializer\SerializerInterface;
4545
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
4646
use Symfony\Component\WebLink\GenericLinkProvider;

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
use Symfony\Component\Config\ResourceCheckerInterface;
4242
use Symfony\Component\Console\Application;
4343
use Symfony\Component\Console\Command\Command;
44+
use Symfony\Component\Csrf\CsrfToken;
45+
use Symfony\Component\Csrf\CsrfTokenManagerInterface;
4446
use Symfony\Component\DependencyInjection\Alias;
4547
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
4648
use Symfony\Component\DependencyInjection\ChildDefinition;
@@ -114,7 +116,6 @@
114116
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
115117
use Symfony\Component\Routing\Loader\AnnotationFileLoader;
116118
use Symfony\Component\Security\Core\Security;
117-
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
118119
use Symfony\Component\Serializer\Encoder\DecoderInterface;
119120
use Symfony\Component\Serializer\Encoder\EncoderInterface;
120121
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
@@ -283,7 +284,7 @@ public function load(array $configs, ContainerBuilder $container)
283284
if (null === $config['csrf_protection']['enabled']) {
284285
$config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class);
285286
}
286-
$this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);
287+
$this->registerCsrfConfiguration($config['csrf_protection'], $container, $loader);
287288

288289
if ($this->isConfigEnabled($container, $config['form'])) {
289290
if (!class_exists('Symfony\Component\Form\Form')) {
@@ -1435,22 +1436,22 @@ private function registerSecretsConfiguration(array $config, ContainerBuilder $c
14351436
}
14361437
}
14371438

1438-
private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
1439+
private function registerCsrfConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
14391440
{
14401441
if (!$this->isConfigEnabled($container, $config)) {
14411442
return;
14421443
}
14431444

1444-
if (!class_exists('Symfony\Component\Security\Csrf\CsrfToken')) {
1445-
throw new LogicException('CSRF support cannot be enabled as the Security CSRF component is not installed. Try running "composer require symfony/security-csrf".');
1445+
if (!class_exists(CsrfToken::class)) {
1446+
throw new LogicException('CSRF support cannot be enabled as the CSRF component is not installed. Try running "composer require symfony/csrf".');
14461447
}
14471448

14481449
if (!$this->sessionConfigEnabled) {
14491450
throw new \LogicException('CSRF protection needs sessions to be enabled.');
14501451
}
14511452

14521453
// Enable services for CSRF protection (even without forms)
1453-
$loader->load('security_csrf.xml');
1454+
$loader->load('csrf.xml');
14541455

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

src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml renamed to src/Symfony/Bundle/FrameworkBundle/Resources/config/csrf.xml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,23 @@
77
<services>
88
<defaults public="false" />
99

10-
<service id="security.csrf.token_generator" class="Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator" />
10+
<service id="security.csrf.token_generator" class="Symfony\Component\Csrf\TokenGenerator\UriSafeTokenGenerator" />
1111
<service id="Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface" alias="security.csrf.token_generator" />
12+
<service id="Symfony\Component\Csrf\TokenGenerator\TokenGeneratorInterface" alias="security.csrf.token_generator" />
1213

13-
<service id="security.csrf.token_storage" class="Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage">
14+
<service id="security.csrf.token_storage" class="Symfony\Component\Csrf\TokenStorage\SessionTokenStorage">
1415
<argument type="service" id="session" />
1516
</service>
1617
<service id="Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface" alias="security.csrf.token_storage" />
18+
<service id="Symfony\Component\Csrf\TokenStorage\TokenStorageInterface" alias="security.csrf.token_storage" />
1719

18-
<service id="security.csrf.token_manager" class="Symfony\Component\Security\Csrf\CsrfTokenManager" public="true">
20+
<service id="security.csrf.token_manager" class="Symfony\Component\Csrf\CsrfTokenManager" public="true">
< 10000 div aria-hidden="true" style="left:-2px" class="position-absolute top-0 d-flex user-select-none DiffLineTableCellParts-module__in-progress-comment-indicator--hx3m3">
1921
<argument type="service" id="security.csrf.token_generator" />
2022
<argument type="service" id="security.csrf.token_storage" />
2123
<argument type="service" id="request_stack" on-invalid="ignore" />
2224
</service>
2325
<service id="Symfony\Component\Security\Csrf\CsrfTokenManagerInterface" alias="security.csrf.token_manager" />
26+
<service id="Symfony\Component\Csrf\CsrfTokenManagerInterface" alias="security.csrf.token_manager" />
2427

2528
<service id="twig.runtime.security_csrf" class="Symfony\Bridge\Twig\Extension\CsrfRuntime">
2629
<tag name="twig.runtime" />

src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public function testSubscribedServices()
5959
'message_bus' => '?Symfony\\Component\\Messenger\\MessageBusInterface',
6060
'messenger.default_bus' => '?Symfony\\Component\\Messenger\\MessageBusInterface',
6161
'security.token_storage' => '?Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface',
62-
'security.csrf.token_manager' => '?Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface',
62+
'security.csrf.token_manager' => '?Symfony\\Component\\Csrf\\CsrfTokenManagerInterface',
6363
];
6464

6565
$this->assertEquals($expectedServices, $subscribed, 'Subscribed core services in AbstractController have changed');

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"symfony/asset": "^5.1",
3737
"symfony/browser-kit": "^4.4|^5.0",
3838
"symfony/console": "^4.4|^5.0",
39+
"symfony/csrf": "^5.1",
3940
"symfony/css-selector": "^4.4|^5.0",
4041
"symfony/dom-crawler": "^4.4|^5.0",
4142
"symfony/dotenv": "^5.1",
@@ -49,7 +50,6 @@
4950
"symfony/mime": "^4.4|^5.0",
5051
"symfony/process": "^4.4|^5.0",
5152
"symfony/security-bundle": "^5.1",
52-
"symfony/security-csrf": "^4.4|^5.0",
5353
"symfony/security-http": "^4.4|^5.0",
5454
"symfony/serializer": "^4.4|^5.0",
5555
"symfony/stopwatch": "^4.4|^5.0",

src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;
1313

14+
use Symfony\Component\Csrf\TokenStorage\ClearableTokenStorageInterface;
1415
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1516
use Symfony\Component\DependencyInjection\ContainerBuilder;
1617
use Symfony\Component\DependencyInjection\Reference;
@@ -30,7 +31,7 @@ public function process(ContainerBuilder $container)
3031
$csrfTokenStorage = $container->findDefinition('security.csrf.token_storage');
3132
$csrfTokenStorageClass = $container->getParameterBag()->resolveValue($csrfTokenStorage->getClass());
3233

33-
if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) {
34+
if (!is_subclass_of($csrfTokenStorageClass, ClearableTokenStorageInterface::class)) {
3435
return;
3536
}
3637

src/Symfony/Bundle/SecurityBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
"php": "^7.2.5",
2020
"ext-xml": "*",
2121
"symfony/config": "^4.4|^5.0",
22+
"symfony/csrf": "^5.1",
2223
"symfony/dependency-injection": "^4.4|^5.0",
2324
"symfony/event-dispatcher": "^5.1",
2425
"symfony/http-kernel": "^5.0",
2526
"symfony/polyfill-php80": "^1.15",
2627
"symfony/security-core": "^4.4|^5.0",
27-
"symfony/security-csrf": "^4.4|^5.0",
2828
"symfony/security-guard": "^4.4|^5.0",
2929
"symfony/security-http": "^5.1"
3030
},
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CHANGELOG
2+
=========
3+
4+
5.1.0
5+
-----
6+
7+
* Moved the component from `Symfony\Component\Security\Csrf` to `Symfony\Component\Csrf`
8+
* `InvalidArgumentException` and `TokenNotFoundException` no longer implement `Symfony\Component\Security\Core\Exception\ExceptionInterface`
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
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\Csrf;
13+
14+
/**
15+
* A CSRF token.
16+
*
17+
* @author Bernhard Schussek <bschussek@gmail.com>
18+
*/
19+
class CsrfToken
20+
{
21+
private $id;
22+
private $value;
23+
24+
public function __construct(string $id, ?string $value)
25+
{
26+
$this->id = $id;
27+
$this->value = $value ?? '';
28+
}
29+
30+
/**
31+
* Returns the ID of the CSRF token.
32+
*
33+
* @return string The token ID
34+
*/
35+
public function getId()
36+
{
37+
return $this->id;
38+
}
39+
40+
/**
41+
* Returns the value of the CSRF token.
42+
*
43+
* @return string The token value
44+
*/
45+
public function getValue()
46+
{
47+
return $this->value;
48+
}
49+
50+
/**
51+
* Returns the value of the CSRF token.
52+
*
53+
* @return string The token value
54+
*/
55+
public function __toString()
56+
{
57+
return $this->value;
58+
}
59+
}
60+
class_alias(CsrfToken::class, \Symfony\Component\Security\Csrf\CsrfToken::class);
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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\Csrf;
13+
14+
use Symfony\Component\Csrf\Exception\InvalidArgumentException;
15+
use Symfony\Component\Csrf\TokenGenerator\TokenGeneratorInterface;
16+
use Symfony\Component\Csrf\TokenGenerator\UriSafeTokenGenerator;
17+
use Symfony\Component\Csrf\TokenStorage\NativeSessionTokenStorage;
18+
use Symfony\Component\Csrf\TokenStorage\TokenStorageInterface;
19+
use Symfony\Component\HttpFoundation\RequestStack;
20+
21+
/**
22+
* Default implementation of {@link CsrfTokenManagerInterface}.
23+
*
24+
* @author Bernhard Schussek <bschussek@gmail.com>
25+
* @author Kévin Dunglas <dunglas@gmail.com>
26+
*/
27+
class CsrfTokenManager implements CsrfTokenManagerInterface
28+
{
29+
private $generator;
30+
private $storage;
31+
private $namespace;
32+
33+
/**
34+
* @param string|RequestStack|callable|null $namespace
35+
* * null: generates a namespace using $_SERVER['HTTPS']
36+
* * string: uses the given string
37+
* * RequestStack: generates a namespace using the current master request
38+
* * callable: uses the result of this callable (must return a string)
39+
*/
40+
public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null, $namespace = null)
41+
{
42+
$this->generator = $generator ?: new UriSafeTokenGenerator();
43+
$this->storage = $storage ?: new NativeSessionTokenStorage();
44+
45+
$superGlobalNamespaceGenerator = function () {
46+
return !empty($_SERVER['HTTPS']) && 'off' !== strtolower($_SERVER['HTTPS']) ? 'https-' : '';
47+
};
48+
49+
if (null === $namespace) {
50+
$this->namespace = $superGlobalNamespaceGenerator;
51+
} elseif ($namespace instanceof RequestStack) {
52+
$this->namespace = function () use ($namespace, $superGlobalNamespaceGenerator) {
53+
if ($request = $namespace->getMasterRequest()) {
54+
return $request->isSecure() ? 'https-' : '';
55+
}
56+
57+
return $superGlobalNamespaceGenerator();
58+
};
59+
} elseif (\is_callable($namespace) || \is_string($namespace)) {
60+
$this->namespace = $namespace;
61+
} else {
62+
throw new InvalidArgumentException(sprintf('$namespace must be a string, a callable returning a string, null or an instance of "RequestStack". "%s" given.', get_debug_type($namespace)));
63+
}
64+
}
65+
66+
/**
67+
* {@inheritdoc}
68+
*/
69+
public function getToken(string $tokenId)
70+
{
71+
$namespacedId = $this->getNamespace().$tokenId;
72+
if ($this->storage->hasToken($namespacedId)) {
73+
$value = $this->storage->getToken($namespacedId);
74+
} else {
75+
$value = $this->generator->generateToken();
76+
77+
$this->storage->setToken($namespacedId, $value);
78+
}
79+
80+
return new CsrfToken($tokenId, $value);
81+
}
82+
83+
/**
84+
* {@inheritdoc}
85+
*/
86+
public function refreshToken(string $tokenId)
87+
{
88+
$namespacedId = $this->getNamespace().$tokenId;
89+
$value = $this->generator->generateToken();
90+
91+
$this->storage->setToken($namespacedId, $value);
92+
93+
return new CsrfToken($tokenId, $value);
94+
}
95+
96+
/**
97+
* {@inheritdoc}
98+
*/
99+
public function removeToken(string $tokenId)
100+
{
101+
return $this->storage->removeToken($this->getNamespace().$tokenId);
102+
}
103+
104+
/**
105+
* {@inheritdoc}
106+
*/
107+
public function isTokenValid(CsrfToken $token)
108+
{
109+
$namespacedId = $this->getNamespace().$token->getId();
110+
if (!$this->storage->hasToken($namespacedId)) {
111+
return false;
112+
}
113+
114+
return hash_equals($this->storage->getToken($namespacedId), $token->getValue());
115+
}
116+
117+
private function getNamespace(): string
118+
{
119+
return \is_callable($ns = $this->namespace) ? $ns() : $ns;
120+
}
121+
}
122+
class_alias(CsrfTokenManager::class, \Symfony\Component\Security\Csrf\CsrfTokenManager::class);

0 commit comments

Comments
 (0)
0