8000 [Csrf] Moved CSRF component outside Security namespace by wouterj · Pull Request #36423 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Csrf] Moved CSRF component outside Security namespace #36423

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions UPGRADE-5.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ Security

* Deprecated `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface`, register a listener on the `LogoutEvent` event instead.
* Deprecated `DefaultLogoutSuccessHandler` in favor of `DefaultLogoutListener`.
* 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.
* [BC break] `Symfony\Component\Csrf\Exception\TokenNotFoundException` no longer extends from `Symfony\Component\Security\Core\Exception\RuntimeException`.

Yaml
----
Expand Down
1 change: 1 addition & 0 deletions UPGRADE-6.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,4 @@ Security
* Removed `ROLE_PREVIOUS_ADMIN` role in favor of `IS_IMPERSONATOR` attribute
* Removed `LogoutSuccessHandlerInterface` and `LogoutHandlerInterface`, register a listener on the `LogoutEvent` event instead.
* Removed `DefaultLogoutSuccessHandler` in favor of `DefaultLogoutListener`.
* Removed the `Symfony\Component\Security\Csrf` in favor of the `Symfony\Component\Csrf` component.
2 changes: 1 addition & 1 deletion src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Symfony\Bridge\Twig\Extension;

use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Csrf\CsrfTokenManagerInterface;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ALL similar changes are BC breaks

Copy link
Member Author
@wouterj wouterj Apr 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure about this? There is a class_alias between the old and the new namespace.

I just tested this by linking the new CSRF component, and the old CSRF component + twig bridge into the vendor/ directory (+ autoload config). Then this code works as expected (without error, but with the deprecation):

<?php

require_once 'vendor/autoload.php';

use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Bridge\Twig\Extension\CsrfRuntime;

$manager = new CsrfTokenManager();
$runtime = new CsrfRuntime($manager);

echo $runtime->getCsrfToken('foo');


/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Bridge/Twig/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"require-dev": {
"egulias/email-validator": "^2.1.10",
"symfony/asset": "^4.4|^5.0",
"symfony/csrf": "^5.1",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",
"symfony/form": "^5.1",
Expand All @@ -36,7 +37,6 @@
"symfony/yaml": "^4.4|^5.0",
"symfony/security-acl": "^2.8|^3.0",
"symfony/security-core": "^4.4|^5.0",
"symfony/security-csrf": "^4.4|^5.0",
"symfony/security-http": "^4.4|^5.0",
"symfony/stopwatch": "^4.4|^5.0",
"symfony/console": "^4.4|^5.0",
Expand Down Expand Up @@ -64,7 +64,7 @@
"symfony/translation": "For using the TranslationExtension",
"symfony/yaml": "For using the YamlExtension",
"symfony/security-core": "For using the SecurityExtension",
"symfony/security-csrf": "For using the CsrfExtension",
"symfony/csrf": "For using the CsrfExtension",
"symfony/security-http": "For using the LogoutUrlExtension",
"symfony/stopwatch": "For using the StopwatchExtension",
"symfony/var-dumper": "For using the DumpExtension",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Doctrine\Persistence\ManagerRegistry;
use Psr\Container\ContainerInterface;
use Psr\Link\LinkInterface;
use Symfony\Component\Csrf\CsrfToken;
use Symfony\Component\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface;
use Symfony\Component\Form\Extension\Core\Type\FormType;
Expand All @@ -39,8 +41,6 @@
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\WebLink\EventListener\AddLinkHeaderListener;
use Symfony\Component\WebLink\GenericLinkProvider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
use Symfony\Component\Config\ResourceCheckerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Csrf\CsrfToken;
use Symfony\Component\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
Expand Down Expand Up @@ -114,7 +116,6 @@
use Symfony\Component\Routing\Loader\AnnotationDirectoryLoader;
use Symfony\Component\Routing\Loader\AnnotationFileLoader;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
Expand Down Expand Up @@ -283,7 +284,7 @@ public function load(array $configs, ContainerBuilder $container)
if (null === $config['csrf_protection']['enabled']) {
$config['csrf_protection']['enabled'] = $this->sessionConfigEnabled && !class_exists(FullStack::class) && interface_exists(CsrfTokenManagerInterface::class);
}
$this->registerSecurityCsrfConfiguration($config['csrf_protection'], $container, $loader);
$this->registerCsrfConfiguration($config['csrf_protection'], $container, $loader);

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

private function registerSecurityCsrfConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
private function registerCsrfConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
{
if (!$this->isConfigEnabled($container, $config)) {
return;
}

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

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

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

if (!class_exists(CsrfExtension::class)) {
$container->removeDefinition('twig.extension.security_csrf');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,23 @@
<services>
<defaults public="false" />

<service id="security.csrf.token_generator" class="Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator" />
<service id="security.csrf.token_generator" class="Symfony\Component\Csrf\TokenGenerator\UriSafeTokenGenerator" />
<service id="Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface" alias="security.csrf.token_generator" />
<service id="Symfony\Component\Csrf\TokenGenerator\TokenGeneratorInterface" alias="security.csrf.token_generator" />

<service id="security.csrf.token_storage" class="Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage">
<service id="security.csrf.token_storage" class="Symfony\Component\Csrf\TokenStorage\SessionTokenStorage">
<argument type="service" id="session" />
</service>
<service id="Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface" alias="security.csrf.token_storage" />
<service id="Symfony\Component\Csrf\TokenStorage\TokenStorageInterface" alias="security.csrf.token_storage" />

<service id="security.csrf.token_manager" class="Symfony\Component\Security\Csrf\CsrfTokenManager" public="true">
<service id="security.csrf.token_manager" class="Symfony\Component\Csrf\CsrfTokenManager" public="true">
<argument type="service" id="security.csrf.token_generator" />
<argument type="service" id="security.csrf.token_storage" />
<argument type="service" id="request_stack" on-invalid="ignore" />
</service>
<service id="Symfony\Component\Security\Csrf\CsrfTokenManagerInterface" alias="security.csrf.token_manager" />
<service id="Symfony\Component\Csrf\CsrfTokenManagerInterface" alias="security.csrf.token_manager" />

<service id="twig.runtime.security_csrf" class="Symfony\Bridge\Twig\Extension\CsrfRuntime">
<tag name="twig.runtime" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function testSubscribedServices()
'message_bus' => '?Symfony\\Component\\Messenger\\MessageBusInterface',
'messenger.default_bus' => '?Symfony\\Component\\Messenger\\MessageBusInterface',
'security.token_storage' => '?Symfony\\Component\\Security\\Core\\Authentication\\Token\\Storage\\TokenStorageInterface',
'security.csrf.token_manager' => '?Symfony\\Component\\Security\\Csrf\\CsrfTokenManagerInterface',
'security.csrf.token_manager' => '?Symfony\\Component\\Csrf\\CsrfTokenManagerInterface',
];

$this->assertEquals($expectedServices, $subscribed, 'Subscribed core services in AbstractController have changed');
Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"symfony/asset": "^5.1",
"symfony/browser-kit": "^4.4|^5.0",
"symfony/console": "^4.4|^5.0",
"symfony/csrf": "^5.1",
"symfony/css-selector": "^4.4|^5.0",
"symfony/dom-crawler": "^4.4|^5.0",
"symfony/dotenv": "^5.1",
Expand All @@ -49,7 +50,6 @@
"symfony/mime": "^4.4|^5.0",
"symfony/process": "^4.4|^5.0",
"symfony/security-bundle": "^5.1",
"symfony/security-csrf": "^4.4|^5.0",
"symfony/security-http": "^4.4|^5.0",
"symfony/serializer": "^4.4|^5.0",
"symfony/stopwatch": "^4.4|^5.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Bundle\SecurityBundle\DependencyInjection\Compiler;

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

if (!is_subclass_of($csrfTokenStorageClass, 'Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface')) {
if (!is_subclass_of($csrfTokenStorageClass, ClearableTokenStorageInterface::class)) {
return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/SecurityBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
"php": "^7.2.5",
"ext-xml": "*",
"symfony/config": "^4.4|^5.0",
"symfony/csrf": "^5.1",
"symfony/dependency-injection": "^4.4|^5.0",
"symfony/event-dispatcher": "^5.1",
"symfony/http-kernel": "^5.0",
"symfony/polyfill-php80": "^1.15",
"symfony/security-core": "^4.4|^5.0",
"symfony/security-csrf": "^4.4|^5.0",
"symfony/security-guard": "^4.4|^5.0",
"symfony/security-http": "^5.1"
},
Expand Down
8 changes: 8 additions & 0 deletions src/Symfony/Component/Csrf/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CHANGELOG
=========

5.1.0
-----

* Moved the component from `Symfony\Component\Security\Csrf` to `Symfony\Component\Csrf`
* `InvalidArgumentException` and `TokenNotFoundException` no longer implement `Symfony\Component\Security\Core\Exception\ExceptionInterface`
60 changes: 60 additions & 0 deletions src/Symfony/Component/Csrf/CsrfToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Csrf;

/**
* A CSRF token.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CsrfToken
{
private $id;
private $value;

public function __construct(string $id, ?string $value)
{
$this->id = $id;
$this->value = $value ?? '';
}

/**
* Returns the ID of the CSRF token.
*
* @return string The token ID
*/
public function getId()
{
return $this->id;
}

/**
* Returns the value of the CSRF token.
*
* @return string The token value
*/
public function getValue()
{
return $this->value;
}

/**
* Returns the value of the CSRF token.
*
* @return string The token value
*/
public function __toString()
{
return $this->value;
}
}
class_alias(CsrfToken::class, \Symfony\Component\Security\Csrf\CsrfToken::class);
122 changes: 122 additions & 0 deletions src/Symfony/Component/Csrf/CsrfTokenManager.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Csrf;

use Symfony\Component\Csrf\Exception\InvalidArgumentException;
use Symfony\Component\Csrf\TokenGenerator\TokenGeneratorInterface;
use Symfony\Component\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Csrf\TokenStorage\NativeSessionTokenStorage;
use Symfony\Component\Csrf\TokenStorage\TokenStorageInterface;
use Symfony\Component\HttpFoundation\RequestStack;

/**
* Default implementation of {@link CsrfTokenManagerInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class CsrfTokenManager implements CsrfTokenManagerInterface
{
private $generator;
private $storage;
private $namespace;

/**
* @param string|RequestStack|callable|null $namespace
* * null: generates a namespace using $_SERVER['HTTPS']
* * string: uses the given string
* * RequestStack: generates a namespace using the current master request
* * callable: uses the result of this callable (must return a string)
*/
public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null, $namespace = null)
{
$this->generator = $generator ?: new UriSafeTokenGenerator();
$this->storage = $storage ?: new NativeSessionTokenStorage();

$superGlobalNamespaceGenerator = function () {
return !empty($_SERVER['HTTPS']) && 'off' !== strtolower($_SERVER['HTTPS']) ? 'https-' : '';
};

if (null === $namespace) {
$this->namespace = $superGlobalNamespaceGenerator;
} elseif ($namespace instanceof RequestStack) {
$this->namespace = function () use ($namespace, $superGlobalNamespaceGenerator) {
if ($request = $namespace->getMasterRequest()) {
return $request->isSecure() ? 'https-' : '';
}

return $superGlobalNamespaceGenerator();
};
} elseif (\is_callable($namespace) || \is_string($namespace)) {
$this->namespace = $namespace;
} else {
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)));
}
}

/**
* {@inheritdoc}
*/
public function getToken(string $tokenId)
{
$namespacedId = $this->getNamespace().$tokenId;
if ($this->storage->hasToken($namespacedId)) {
$value = $this->storage->getToken($namespacedId);
} else {
$value = $this->generator->generateToken();

$this->storage->setToken($namespacedId, $value);
}

return new CsrfToken($tokenId, $value);
}

/**
* {@inheritdoc}
*/
public function refreshToken(string $tokenId)
{
$namespacedId = $this->getNamespace().$tokenId;
$value = $this->generator->generateToken();

$this->storage->setToken($namespacedId, $value);

return new CsrfToken($tokenId, $value);
}

/**
* {@inheritdoc}
*/
public function removeToken(string $tokenId)
{
return $this->storage->removeToken($this->getNamespace().$tokenId);
}

/**
* {@inheritdoc}
*/
public function isTokenValid(CsrfToken $token)
{
$namespacedId = $this->getNamespace().$token->getId();
if (!$this->storage->hasToken($namespacedId)) {
return false;
}

return hash_equals($this->storage->getToken($namespacedId), $token->getValue());
}

private function getNamespace(): string
{
return \is_callable($ns = $this->namespace) ? $ns() : $ns;
}
}
class_alias(CsrfTokenManager::class, \Symfony\Component\Security\Csrf\CsrfTokenManager::class);
Loading
0