diff --git a/UPGRADE-5.1.md b/UPGRADE-5.1.md index 456b1d0bf1ca3..b8605a3cc908a 100644 --- a/UPGRADE-5.1.md +++ b/UPGRADE-5.1.md @@ -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 ---- diff --git a/UPGRADE-6.0.md b/UPGRADE-6.0.md index b79d74ee25097..8ac7e766f5b29 100644 --- a/UPGRADE-6.0.md +++ b/UPGRADE-6.0.md @@ -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. diff --git a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php index c3d5da6470c25..e07c7bf8e009f 100644 --- a/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php +++ b/src/Symfony/Bridge/Twig/Extension/CsrfRuntime.php @@ -11,7 +11,7 @@ namespace Symfony\Bridge\Twig\Extension; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; /** * @author Christian Flothmann diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 5980fad64896f..a0fbbabda805c 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -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", @@ -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", @@ -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", diff --git a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php index 343c2bbaf5528..ab40a3081aa9d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php +++ b/src/Symfony/Bundle/FrameworkBundle/Controller/AbstractController.php @@ -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; @@ -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; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index cef82f8ce1fc0..5954122aa85ce 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -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; @@ -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; @@ -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')) { @@ -1435,14 +1436,14 @@ 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) { @@ -1450,7 +1451,7 @@ private function registerSecurityCsrfConfiguration(array $config, ContainerBuild } // 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'); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/csrf.xml similarity index 76% rename from src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml rename to src/Symfony/Bundle/FrameworkBundle/Resources/config/csrf.xml index eefe6ad73601f..5374ad13cb9fb 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/csrf.xml @@ -7,20 +7,23 @@ - + + - + + - + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php index 0b30d684d863c..7eaad4514ac17 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Controller/AbstractControllerTest.php @@ -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'); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 9aca3a8432bf3..4671cf88afa2a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -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", @@ -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", diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php index 2d6960e1fe45d..ae10219c8ef4f 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Compiler/RegisterCsrfTokenClearingLogoutHandlerPass.php @@ -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; @@ -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; } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index b06d8b4c3a05f..24291ac7386a0 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -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" }, diff --git a/src/Symfony/Component/Security/Csrf/.gitattributes b/src/Symfony/Component/Csrf/.gitattributes similarity index 100% rename from src/Symfony/Component/Security/Csrf/.gitattributes rename to src/Symfony/Component/Csrf/.gitattributes diff --git a/src/Symfony/Component/Security/Csrf/.gitignore b/src/Symfony/Component/Csrf/.gitignore similarity index 100% rename from src/Symfony/Component/Security/Csrf/.gitignore rename to src/Symfony/Component/Csrf/.gitignore diff --git a/src/Symfony/Component/Csrf/CHANGELOG.md b/src/Symfony/Component/Csrf/CHANGELOG.md new file mode 100644 index 0000000000000..91cfcccebb6d2 --- /dev/null +++ b/src/Symfony/Component/Csrf/CHANGELOG.md @@ -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` diff --git a/src/Symfony/Component/Csrf/CsrfToken.php b/src/Symfony/Component/Csrf/CsrfToken.php new file mode 100644 index 0000000000000..246892773187b --- /dev/null +++ b/src/Symfony/Component/Csrf/CsrfToken.php @@ -0,0 +1,60 @@ + + * + * 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 + */ +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); diff --git a/src/Symfony/Component/Csrf/CsrfTokenManager.php b/src/Symfony/Component/Csrf/CsrfTokenManager.php new file mode 100644 index 0000000000000..fb2014c8e3254 --- /dev/null +++ b/src/Symfony/Component/Csrf/CsrfTokenManager.php @@ -0,0 +1,122 @@ + + * + * 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 + * @author Kévin Dunglas + */ +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); diff --git a/src/Symfony/Component/Csrf/CsrfTokenManagerInterface.php b/src/Symfony/Component/Csrf/CsrfTokenManagerInterface.php new file mode 100644 index 0000000000000..98f6b2c897106 --- /dev/null +++ b/src/Symfony/Component/Csrf/CsrfTokenManagerInterface.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf; + +/** + * Manages CSRF tokens. + * + * @author Bernhard Schussek + */ +interface CsrfTokenManagerInterface +{ + /** + * Returns a CSRF token for the given ID. + * + * If previously no token existed for the given ID, a new token is + * generated. Otherwise the existing token is returned (with the same value, + * not the same instance). + * + * @param string $tokenId The token ID. You may choose an arbitrary value + * for the ID + * + * @return CsrfToken The CSRF token + */ + public function getToken(string $tokenId); + + /** + * Generates a new token value for the given ID. + * + * This method will generate a new token for the given token ID, independent + * of whether a token value previously existed or not. It can be used to + * enforce once-only tokens in environments with high security needs. + * + * @param string $tokenId The token ID. You may choose an arbitrary value + * for the ID + * + * @return CsrfToken The CSRF token + */ + public function refreshToken(string $tokenId); + + /** + * Invalidates the CSRF token with the given ID, if one exists. + * + * @return string|null Returns the removed token value if one existed, NULL + * otherwise + */ + public function removeToken(string $tokenId); + + /** + * Returns whether the given CSRF token is valid. + * + * @return bool Returns true if the token is valid, false otherwise + */ + public function isTokenValid(CsrfToken $token); +} +class_alias(CsrfTokenManagerInterface::class, \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface::class); diff --git a/src/Symfony/Component/Csrf/Exception/InvalidArgumentException.php b/src/Symfony/Component/Csrf/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000000..f0eac70b18d99 --- /dev/null +++ b/src/Symfony/Component/Csrf/Exception/InvalidArgumentException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\Exception; + +/** + * @author Wouter de Jong + */ +class InvalidArgumentException extends \InvalidArgumentException +{ +} diff --git a/src/Symfony/Component/Csrf/Exception/TokenNotFoundException.php b/src/Symfony/Component/Csrf/Exception/TokenNotFoundException.php new file mode 100644 index 0000000000000..dd155e9ee570b --- /dev/null +++ b/src/Symfony/Component/Csrf/Exception/TokenNotFoundException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\Exception; + +/** + * @author Bernhard Schussek + */ +class TokenNotFoundException extends \RuntimeException +{ +} +class_alias(TokenNotFoundException::class, \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException::class); diff --git a/src/Symfony/Component/Security/Csrf/LICENSE b/src/Symfony/Component/Csrf/LICENSE similarity index 100% rename from src/Symfony/Component/Security/Csrf/LICENSE rename to src/Symfony/Component/Csrf/LICENSE diff --git a/src/Symfony/Component/Csrf/README.md b/src/Symfony/Component/Csrf/README.md new file mode 100644 index 0000000000000..6a50d03c698f5 --- /dev/null +++ b/src/Symfony/Component/Csrf/README.md @@ -0,0 +1,32 @@ +CSRF Component +============== + +The CSRF (cross-site request forgery) component provides a class +`CsrfTokenManager` for generating and validating CSRF tokens. + +Getting Started +--------------- + +``` +$ composer require symfony/csrf +``` + +```php +use Symfony\Component\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfTokenManager; + +$csrfTokenManager = new CsrfTokenManager(); +$csrfToken = new CsrfToken('csrftokenid', $_POST['_csrf_param']); +if (!$csrfTokenManager->isTokenValid($csrfToken)) { + // ... invalid CSRF token +} +``` + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/security/csrf.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenManagerTest.php b/src/Symfony/Component/Csrf/Tests/CsrfTokenManagerTest.php similarity index 88% rename from src/Symfony/Component/Security/Csrf/Tests/CsrfTokenManagerTest.php rename to src/Symfony/Component/Csrf/Tests/CsrfTokenManagerTest.php index 34dc1698b9695..96fd62f246aef 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenManagerTest.php +++ b/src/Symfony/Component/Csrf/Tests/CsrfTokenManagerTest.php @@ -9,13 +9,15 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Security\Csrf\Tests; +namespace Symfony\Component\Csrf\Tests; use PHPUnit\Framework\TestCase; +use Symfony\Component\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfTokenManager; +use Symfony\Component\Csrf\TokenGenerator\TokenGeneratorInterface; +use Symfony\Component\Csrf\TokenStorage\TokenStorageInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Security\Csrf\CsrfToken; -use Symfony\Component\Security\Csrf\CsrfTokenManager; /** * @author Bernhard Schussek @@ -42,7 +44,7 @@ public function testGetNonExistingToken($namespace, $manager, $storage, $generat $token = $manager->getToken('token_id'); - $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token); + $this->assertInstanceOf(CsrfToken::class, $token); $this->assertSame('token_id', $token->getId()); $this->assertSame('TOKEN', $token->getValue()); } @@ -64,7 +66,7 @@ public function testUseExistingTokenIfAvailable($namespace, $manager, $storage) $token = $manager->getToken('token_id'); - $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token); + $this->assertInstanceOf(CsrfToken::class, $token); $this->assertSame('token_id', $token->getId()); $this->assertSame('TOKEN', $token->getValue()); } @@ -87,7 +89,7 @@ public function testRefreshTokenAlwaysReturnsNewToken($namespace, $manager, $sto $token = $manager->refreshToken('token_id'); - $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token); + $this->assertInstanceOf(CsrfToken::class, $token); $this->assertSame('token_id', $token->getId()); $this->assertSame('TOKEN', $token->getValue()); } @@ -159,9 +161,9 @@ public function testRemoveToken($namespace, $manager, $storage) public function testNamespaced() { - $generator = $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface')->getMock(); + $generator = $this->getMockBuilder(TokenGeneratorInterface::class)->getMock(); $generator->expects($this->once())->method('generateToken')->willReturn('random'); - $storage = $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface')->getMock(); + $storage = $this->getMockBuilder(TokenStorageInterface::class)->getMock(); $requestStack = new RequestStack(); $requestStack->push(new Request([], [], [], [], [], ['HTTPS' => 'on'])); @@ -207,8 +209,8 @@ public function getManagerGeneratorAndStorage() private function getGeneratorAndStorage(): array { return [ - $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface')->getMock(), - $this->getMockBuilder('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface')->getMock(), + $this->getMockBuilder(TokenGeneratorInterface::class)->getMock(), + $this->getMockBuilder(TokenStorageInterface::class)->getMock(), ]; } diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php b/src/Symfony/Component/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php similarity index 90% rename from src/Symfony/Component/Security/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php rename to src/Symfony/Component/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php index dc97acc239ddc..e81890b2d02dc 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php +++ b/src/Symfony/Component/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php @@ -9,10 +9,10 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Security\Csrf\Tests\TokenGenerator; +namespace Symfony\Component\Csrf\Tests\TokenGenerator; use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Csrf\TokenGenerator\UriSafeTokenGenerator; /** * @author Bernhard Schussek diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php b/src/Symfony/Component/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php similarity index 93% rename from src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php rename to src/Symfony/Component/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php index dd353515fea43..eb9202fc15e15 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php +++ b/src/Symfony/Component/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php @@ -9,10 +9,11 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Security\Csrf\Tests\TokenStorage; +namespace Symfony\Component\Csrf\Tests\TokenStorage; use PHPUnit\Framework\TestCase; -use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; +use Symfony\Component\Csrf\Exception\TokenNotFoundException; +use Symfony\Component\Csrf\TokenStorage\NativeSessionTokenStorage; /** * @author Bernhard Schussek @@ -88,7 +89,7 @@ public function testGetExistingToken() public function testGetNonExistingToken() { - $this->expectException('Symfony\Component\Security\Csrf\Exception\TokenNotFoundException'); + $this->expectException(TokenNotFoundException::class); $this->storage->getToken('token_id'); } diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php b/src/Symfony/Component/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php similarity index 92% rename from src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php rename to src/Symfony/Component/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php index 7ddd965e51d7b..238de1a62ed28 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php +++ b/src/Symfony/Component/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php @@ -9,12 +9,13 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Security\Csrf\Tests\TokenStorage; +namespace Symfony\Component\Csrf\Tests\TokenStorage; use PHPUnit\Framework\TestCase; +use Symfony\Component\Csrf\Exception\TokenNotFoundException; +use Symfony\Component\Csrf\TokenStorage\SessionTokenStorage; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; -use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; /** * @author Bernhard Schussek @@ -88,13 +89,13 @@ public function testGetExistingTokenFromActiveSession() public function testGetNonExistingTokenFromClosedSession() { - $this->expectException('Symfony\Component\Security\Csrf\Exception\TokenNotFoundException'); + $this->expectException(TokenNotFoundException::class); $this->storage->getToken('token_id'); } public function testGetNonExistingTokenFromActiveSession() { - $this->expectException('Symfony\Component\Security\Csrf\Exception\TokenNotFoundException'); + $this->expectException(TokenNotFoundException::class); $this->session->start(); $this->storage->getToken('token_id'); } diff --git a/src/Symfony/Component/Csrf/TokenGenerator/TokenGeneratorInterface.php b/src/Symfony/Component/Csrf/TokenGenerator/TokenGeneratorInterface.php new file mode 100644 index 0000000000000..ef32ad00bb2b6 --- /dev/null +++ b/src/Symfony/Component/Csrf/TokenGenerator/TokenGeneratorInterface.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\TokenGenerator; + +/** + * Generates CSRF tokens. + * + * @author Bernhard Schussek + */ +interface TokenGeneratorInterface +{ + /** + * Generates a CSRF token. + * + * @return string The generated CSRF token + */ + public function generateToken(); +} +class_alias(TokenGeneratorInterface::class, \Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface::class); diff --git a/src/Symfony/Component/Csrf/TokenGenerator/UriSafeTokenGenerator.php b/src/Symfony/Component/Csrf/TokenGenerator/UriSafeTokenGenerator.php new file mode 100644 index 0000000000000..0850fac2f1720 --- /dev/null +++ b/src/Symfony/Component/Csrf/TokenGenerator/UriSafeTokenGenerator.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\TokenGenerator; + +/** + * Generates CSRF tokens. + * + * @author Bernhard Schussek + */ +class UriSafeTokenGenerator implements TokenGeneratorInterface +{ + private $entropy; + + /** + * Generates URI-safe CSRF tokens. + * + * @param int $entropy The amount of entropy collected for each token (in bits) + */ + public function __construct(int $entropy = 256) + { + $this->entropy = $entropy; + } + + /** + * {@inheritdoc} + */ + public function generateToken() + { + // Generate an URI safe base64 encoded string that does not contain "+", + // "/" or "=" which need to be URL encoded and make URLs unnecessarily + // longer. + $bytes = random_bytes($this->entropy / 8); + + return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '='); + } +} +class_alias(UriSafeTokenGenerator::class, \Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator::class); diff --git a/src/Symfony/Component/Csrf/TokenStorage/ClearableTokenStorageInterface.php b/src/Symfony/Component/Csrf/TokenStorage/ClearableTokenStorageInterface.php new file mode 100644 index 0000000000000..da89ab168431d --- /dev/null +++ b/src/Symfony/Component/Csrf/TokenStorage/ClearableTokenStorageInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\TokenStorage; + +/** + * @author Christian Flothmann + */ +interface ClearableTokenStorageInterface extends TokenStorageInterface +{ + /** + * Removes all CSRF tokens. + */ + public function clear(); +} +class_alias(ClearableTokenStorageInterface::class, \Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface::class); diff --git a/src/Symfony/Component/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Csrf/TokenStorage/NativeSessionTokenStorage.php new file mode 100644 index 0000000000000..5d136315cfcbb --- /dev/null +++ b/src/Symfony/Component/Csrf/TokenStorage/NativeSessionTokenStorage.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\TokenStorage; + +use Symfony\Component\Csrf\Exception\TokenNotFoundException; + +/** + * Token storage that uses PHP's native session handling. + * + * @author Bernhard Schussek + */ +class NativeSessionTokenStorage implements ClearableTokenStorageInterface +{ + /** + * The namespace used to store values in the session. + */ + const SESSION_NAMESPACE = '_csrf'; + + private $sessionStarted = false; + private $namespace; + + /** + * Initializes the storage with a session namespace. + * + * @param string $namespace The namespace under which the token is stored in the session + */ + public function __construct(string $namespace = self::SESSION_NAMESPACE) + { + $this->namespace = $namespace; + } + + /** + * {@inheritdoc} + */ + public function getToken(string $tokenId) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + if (!isset($_SESSION[$this->namespace][$tokenId])) { + throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.'); + } + + return (string) $_SESSION[$this->namespace][$tokenId]; + } + + /** + * {@inheritdoc} + */ + public function setToken(string $tokenId, string $token) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + $_SESSION[$this->namespace][$tokenId] = $token; + } + + /** + * {@inheritdoc} + */ + public function hasToken(string $tokenId) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + return isset($_SESSION[$this->namespace][$tokenId]); + } + + /** + * {@inheritdoc} + */ + public function removeToken(string $tokenId) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + if (!isset($_SESSION[$this->namespace][$tokenId])) { + return null; + } + + $token = (string) $_SESSION[$this->namespace][$tokenId]; + + unset($_SESSION[$this->namespace][$tokenId]); + + if (!$_SESSION[$this->namespace]) { + unset($_SESSION[$this->namespace]); + } + + return $token; + } + + /** + * {@inheritdoc} + */ + public function clear() + { + unset($_SESSION[$this->namespace]); + } + + private function startSession() + { + if (PHP_SESSION_NONE === session_status()) { + session_start(); + } + + $this->sessionStarted = true; + } +} +class_alias(NativeSessionTokenStorage::class, \Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage::class); diff --git a/src/Symfony/Component/Csrf/TokenStorage/SessionTokenStorage.php b/src/Symfony/Component/Csrf/TokenStorage/SessionTokenStorage.php new file mode 100644 index 0000000000000..159c08d9df869 --- /dev/null +++ b/src/Symfony/Component/Csrf/TokenStorage/SessionTokenStorage.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\TokenStorage; + +use Symfony\Component\Csrf\Exception\TokenNotFoundException; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Token storage that uses a Symfony Session object. + * + * @author Bernhard Schussek + */ +class SessionTokenStorage implements ClearableTokenStorageInterface +{ + /** + * The namespace used to store values in the session. + */ + const SESSION_NAMESPACE = '_csrf'; + + private $session; + private $namespace; + + /** + * Initializes the storage with a Session object and a session namespace. + * + * @param string $namespace The namespace under which the token is stored in the session + */ + public function __construct(SessionInterface $session, string $namespace = self::SESSION_NAMESPACE) + { + $this->session = $session; + $this->namespace = $namespace; + } + + /** + * {@inheritdoc} + */ + public function getToken(string $tokenId) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + if (!$this->session->has($this->namespace.'/'.$tokenId)) { + throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.'); + } + + return (string) $this->session->get($this->namespace.'/'.$tokenId); + } + + /** + * {@inheritdoc} + */ + public function setToken(string $tokenId, string $token) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + $this->session->set($this->namespace.'/'.$tokenId, $token); + } + + /** + * {@inheritdoc} + */ + public function hasToken(string $tokenId) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + return $this->session->has($this->namespace.'/'.$tokenId); + } + + /** + * {@inheritdoc} + */ + public function removeToken(string $tokenId) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + return $this->session->remove($this->namespace.'/'.$tokenId); + } + + /** + * {@inheritdoc} + */ + public function clear() + { + foreach (array_keys($this->session->all()) as $key) { + if (0 === strpos($key, $this->namespace.'/')) { + $this->session->remove($key); + } + } + } +} +class_alias(SessionTokenStorage::class, \Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage::class); diff --git a/src/Symfony/Component/Csrf/TokenStorage/TokenStorageInterface.php b/src/Symfony/Component/Csrf/TokenStorage/TokenStorageInterface.php new file mode 100644 index 0000000000000..b9d9a3df3d5d0 --- /dev/null +++ b/src/Symfony/Component/Csrf/TokenStorage/TokenStorageInterface.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Csrf\TokenStorage; + +/** + * Stores CSRF tokens. + * + * @author Bernhard Schussek + */ +interface TokenStorageInterface +{ + /** + * Reads a stored CSRF token. + * + * @return string The stored token + * + * @throws \Symfony\Component\Csrf\Exception\TokenNotFoundException If the token ID does not exist + */ + public function getToken(string $tokenId); + + /** + * Stores a CSRF token. + */ + public function setToken(string $tokenId, string $token); + + /** + * Removes a CSRF token. + * + * @return string|null Returns the removed token if one existed, NULL + * otherwise + */ + public function removeToken(string $tokenId); + + /** + * Checks whether a token with the given token ID exists. + * + * @return bool Whether a token exists with the given ID + */ + public function hasToken(string $tokenId); +} +class_alias(TokenStorageInterface::class, \Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface::class); diff --git a/src/Symfony/Component/Csrf/composer.json b/src/Symfony/Component/Csrf/composer.json new file mode 100644 index 0000000000000..b8bf23a0edf46 --- /dev/null +++ b/src/Symfony/Component/Csrf/composer.json @@ -0,0 +1,42 @@ +{ + "name": "symfony/csrf", + "type": "library", + "description": "A library to generate and validate CSRF tokens", + "keywords": ["csrf"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": "^7.2.5" + }, + "require-dev": { + "symfony/http-foundation": "^4.4|^5.0" + }, + "conflict": { + "symfony/http-foundation": "<4.4" + }, + "suggest": { + "symfony/http-foundation": "For using the class SessionTokenStorage." + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Security\\Csrf\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "5.1-dev" + } + } +} diff --git a/src/Symfony/Component/Security/Csrf/phpunit.xml.dist b/src/Symfony/Component/Csrf/phpunit.xml.dist similarity index 91% rename from src/Symfony/Component/Security/Csrf/phpunit.xml.dist rename to src/Symfony/Component/Csrf/phpunit.xml.dist index c37ee148e36b3..f518404c59a9b 100644 --- a/src/Symfony/Component/Security/Csrf/phpunit.xml.dist +++ b/src/Symfony/Component/Csrf/phpunit.xml.dist @@ -13,7 +13,7 @@ - + ./Tests/ diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index 609a371ea05d9..7006785d2f133 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Form\Extension\Csrf; use Symfony\Component\Form\AbstractExtension; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index d30bdef61e8e3..c5deda6e20e25 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -16,8 +16,8 @@ use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\Util\ServerParams; -use Symfony\Component\Security\Csrf\CsrfToken; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index 5d991e616414d..3a305cee53842 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -19,7 +19,7 @@ use Symfony\Component\Form\FormView; use Symfony\Component\Form\Util\ServerParams; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Contracts\Translation\TranslatorInterface; /** diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php index 2ac3b57ee5581..9cbee5d6fb22b 100644 --- a/src/Symfony/Component/Form/FormRenderer.php +++ b/src/Symfony/Component/Form/FormRenderer.php @@ -13,7 +13,7 @@ use Symfony\Component\Form\Exception\BadMethodCallException; use Symfony\Component\Form\Exception\LogicException; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Twig\Environment; /** diff --git a/src/Symfony/Component/Form/FormRendererInterface.php b/src/Symfony/Component/Form/FormRendererInterface.php index 16c29744edd97..9a28efb609ccb 100644 --- a/src/Symfony/Component/Form/FormRendererInterface.php +++ b/src/Symfony/Component/Form/FormRendererInterface.php @@ -73,7 +73,7 @@ public function searchAndRenderBlock(FormView $view, string $blockNameSuffix, ar * * Check the token in your action using the same token ID. * - * // $csrfProvider being an instance of Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface + * // $csrfProvider being an instance of Symfony\Component\Csrf\TokenGenerator\TokenGeneratorInterface * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { * throw new \RuntimeException('CSRF attack detected.'); * } diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 6eef4179e89f0..f9bf10bdb8207 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Form\Tests; use Symfony\Component\Form\FormError; -use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfToken; abstract class AbstractDivLayoutTest extends AbstractLayoutTest { diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index 9c5594bcb8dd6..addb0a7cea498 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Tests; use PHPUnit\Framework\SkippedTestError; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Form\Extension\Core\Type\PercentType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; @@ -34,7 +35,7 @@ protected function setUp(): void \Locale::setDefault('en'); - $this->csrfTokenManager = $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock(); + $this->csrfTokenManager = $this->getMockBuilder(CsrfTokenManagerInterface::class)->getMock(); parent::setUp(); } diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php index 6240ad23168c4..2c004b7623cae 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Form\Tests; use Symfony\Component\Form\FormError; -use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfToken; abstract class AbstractTableLayoutTest extends AbstractLayoutTest { diff --git a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php index e535fe5c03d23..c71083bce0f52 100644 --- a/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php +++ b/src/Symfony/Component/Form/Tests/Console/Descriptor/AbstractDescriptorTest.php @@ -24,7 +24,7 @@ use Symfony\Component\Form\ResolvedFormTypeInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; -use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Csrf\CsrfTokenManager; abstract class AbstractDescriptorTest extends TestCase { diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php index 37d7594bef666..0084d50c20a63 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php @@ -18,7 +18,7 @@ use Symfony\Component\Form\FormBuilder; use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormFactoryBuilder; -use Symfony\Component\Security\Csrf\CsrfTokenManager; +use Symfony\Component\Csrf\CsrfTokenManager; class CsrfValidationListenerTest extends TestCase { diff --git a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php index abde92b1559f9..ba69b1e9cbace 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -17,8 +17,8 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\Test\TypeTestCase; -use Symfony\Component\Security\Csrf\CsrfToken; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Contracts\Translation\TranslatorInterface; class FormTypeCsrfExtensionTest_ChildType extends AbstractType diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 84ab9374e5b1f..de1817f5032a8 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -33,9 +33,9 @@ "symfony/dependency-injection": "^4.4|^5.0", "symfony/config": "^4.4|^5.0", "symfony/console": "^4.4|^5.0", + "symfony/csrf": "^5.1", "symfony/http-foundation": "^4.4|^5.0", "symfony/http-kernel": "^4.4|^5.0", - "symfony/security-csrf": "^4.4|^5.0", "symfony/translation": "^4.4|^5.0", "symfony/var-dumper": "^4.4|^5.0" }, @@ -54,7 +54,7 @@ }, "suggest": { "symfony/validator": "For form validation.", - "symfony/security-csrf": "For protecting forms against CSRF attacks.", + "symfony/csrf": "For protecting forms against CSRF attacks.", "symfony/twig-bridge": "For templating with Twig." }, "autoload": { diff --git a/src/Symfony/Component/Security/Csrf/CsrfToken.php b/src/Symfony/Component/Security/Csrf/CsrfToken.php index c959cc867d2a8..ba3893f54bab8 100644 --- a/src/Symfony/Component/Security/Csrf/CsrfToken.php +++ b/src/Symfony/Component/Security/Csrf/CsrfToken.php @@ -11,49 +11,17 @@ namespace Symfony\Component\Security\Csrf; -/** - * A CSRF token. - * - * @author Bernhard Schussek - */ -class CsrfToken -{ - private $id; - private $value; +use Symfony\Component\Csrf\CsrfToken as ComponentCsrfToken; - public function __construct(string $id, ?string $value) - { - $this->id = $id; - $this->value = $value ?? ''; - } +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', CsrfToken::class, ComponentCsrfToken::class); - /** - * 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; - } +class_exists(ComponentCsrfToken::class); +if (false) { /** - * Returns the value of the CSRF token. - * - * @return string The token value + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function __toString() + class CsrfToken { - return $this->value; } } diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php index da28302b78f31..37ead7031d2f3 100644 --- a/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php +++ b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php @@ -11,111 +11,17 @@ namespace Symfony\Component\Security\Csrf; -use Symfony\Component\HttpFoundation\RequestStack; -use Symfony\Component\Security\Core\Exception\InvalidArgumentException; -use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; -use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; -use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; -use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; +use Symfony\Component\Csrf\CsrfTokenManager as ComponentCsrfTokenManager; -/** - * Default implementation of {@link CsrfTokenManagerInterface}. - * - * @author Bernhard Schussek - * @author Kévin Dunglas - */ -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); - } +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', CsrfTokenManager::class, ComponentCsrfTokenManager::class); - /** - * {@inheritdoc} - */ - public function removeToken(string $tokenId) - { - return $this->storage->removeToken($this->getNamespace().$tokenId); - } +class_exists(ComponentCsrfTokenManager::class); +if (false) { /** - * {@inheritdoc} + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - 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 + class CsrfTokenManager { - return \is_callable($ns = $this->namespace) ? $ns() : $ns; } } diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php b/src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php index a2dfdaf0f2031..60cd4aecd4c23 100644 --- a/src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php +++ b/src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php @@ -11,53 +11,17 @@ namespace Symfony\Component\Security\Csrf; -/** - * Manages CSRF tokens. - * - * @author Bernhard Schussek - */ -interface CsrfTokenManagerInterface -{ - /** - * Returns a CSRF token for the given ID. - * - * If previously no token existed for the given ID, a new token is - * generated. Otherwise the existing token is returned (with the same value, - * not the same instance). - * - * @param string $tokenId The token ID. You may choose an arbitrary value - * for the ID - * - * @return CsrfToken The CSRF token - */ - public function getToken(string $tokenId); +use Symfony\Component\Csrf\CsrfTokenManagerInterface as ComponentCsrfTokenManagerInterface; - /** - * Generates a new token value for the given ID. - * - * This method will generate a new token for the given token ID, independent - * of whether a token value previously existed or not. It can be used to - * enforce once-only tokens in environments with high security needs. - * - * @param string $tokenId The token ID. You may choose an arbitrary value - * for the ID - * - * @return CsrfToken The CSRF token - */ - public function refreshToken(string $tokenId); +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', CsrfTokenManagerInterface::class, ComponentCsrfTokenManagerInterface::class); - /** - * Invalidates the CSRF token with the given ID, if one exists. - * - * @return string|null Returns the removed token value if one existed, NULL - * otherwise - */ - public function removeToken(string $tokenId); +class_exists(ComponentCsrfTokenManagerInterface::class); +if (false) { /** - * Returns whether the given CSRF token is valid. - * - * @return bool Returns true if the token is valid, false otherwise + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function isTokenValid(CsrfToken $token); + interface CsrfTokenManagerInterface + { + } } diff --git a/src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php b/src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php index 936afdeb113e4..92542976a6671 100644 --- a/src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php +++ b/src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php @@ -11,11 +11,17 @@ namespace Symfony\Component\Security\Csrf\Exception; -use Symfony\Component\Security\Core\Exception\RuntimeException; +use Symfony\Component\Csrf\Exception\TokenNotFoundException as ComponentTokenNotFoundException; -/** - * @author Bernhard Schussek - */ -class TokenNotFoundException extends RuntimeException -{ +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', TokenNotFoundException::class, ComponentTokenNotFoundException::class); + +class_exists(ComponentTokenNotFoundException::class); + +if (false) { + /** + * @deprecated since Symfony 5.1, use symfony/csrf instead. + */ + class TokenNotFoundException + { + } } diff --git a/src/Symfony/Component/Security/Csrf/README.md b/src/Symfony/Component/Security/Csrf/README.md deleted file mode 100644 index 15b9ace238fb9..0000000000000 --- a/src/Symfony/Component/Security/Csrf/README.md +++ /dev/null @@ -1,14 +0,0 @@ -Security Component - CSRF -========================= - -The Security CSRF (cross-site request forgery) component provides a class -`CsrfTokenManager` for generating and validating CSRF tokens. - -Resources ---------- - - * [Documentation](https://symfony.com/doc/current/components/security.html) - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php b/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php index 0ec2881774b93..5b367fb90f30b 100644 --- a/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php +++ b/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php @@ -11,17 +11,17 @@ namespace Symfony\Component\Security\Csrf\TokenGenerator; -/** - * Generates CSRF tokens. - * - * @author Bernhard Schussek - */ -interface TokenGeneratorInterface -{ +use Symfony\Component\Csrf\TokenGenerator\TokenGeneratorInterface as ComponentTokenGeneratorInterface; + +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', TokenGeneratorInterface::class, ComponentTokenGeneratorInterface::class); + +class_exists(ComponentTokenGeneratorInterface::class); + +if (false) { /** - * Generates a CSRF token. - * - * @return string The generated CSRF token + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function generateToken(); + interface TokenGeneratorInterface + { + } } diff --git a/src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php b/src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php index 59638518f5c42..a0ede250e6a7e 100644 --- a/src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php +++ b/src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php @@ -11,35 +11,17 @@ namespace Symfony\Component\Security\Csrf\TokenGenerator; -/** - * Generates CSRF tokens. - * - * @author Bernhard Schussek - */ -class UriSafeTokenGenerator implements TokenGeneratorInterface -{ - private $entropy; +use Symfony\Component\Csrf\TokenGenerator\UriSafeTokenGenerator as ComponentUriSafeTokenGenerator; - /** - * Generates URI-safe CSRF tokens. - * - * @param int $entropy The amount of entropy collected for each token (in bits) - */ - public function __construct(int $entropy = 256) - { - $this->entropy = $entropy; - } +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', UriSafeTokenGenerator::class, ComponentUriSafeTokenGenerator::class); +class_exists(ComponentUriSafeTokenGenerator::class); + +if (false) { /** - * {@inheritdoc} + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function generateToken() + class UriSafeTokenGenerator { - // Generate an URI safe base64 encoded string that does not contain "+", - // "/" or "=" which need to be URL encoded and make URLs unnecessarily - // longer. - $bytes = random_bytes($this->entropy / 8); - - return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '='); } } diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php b/src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php index 0d6f16b68d0b6..df73d1fa1d2ad 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/ClearableTokenStorageInterface.php @@ -11,13 +11,17 @@ namespace Symfony\Component\Security\Csrf\TokenStorage; -/** - * @author Christian Flothmann - */ -interface ClearableTokenStorageInterface extends TokenStorageInterface -{ +use Symfony\Component\Csrf\TokenStorage\ClearableTokenStorageInterface as ComponentClearableTokenStorageInterface; + +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', ClearableTokenStorageInterface::class, ComponentClearableTokenStorageInterface::class); + +class_exists(ComponentClearableTokenStorageInterface::class); + +if (false) { /** - * Removes all CSRF tokens. + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function clear(); + interface ClearableTokenStorageInterface + { + } } diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php index 75f03df7bbe06..2df872b011004 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php @@ -11,111 +11,17 @@ namespace Symfony\Component\Security\Csrf\TokenStorage; -use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; +use Symfony\Component\Csrf\TokenStorage\NativeSessionTokenStorage as ComponentNativeSessionTokenStorage; -/** - * Token storage that uses PHP's native session handling. - * - * @author Bernhard Schussek - */ -class NativeSessionTokenStorage implements ClearableTokenStorageInterface -{ - /** - * The namespace used to store values in the session. - */ - const SESSION_NAMESPACE = '_csrf'; - - private $sessionStarted = false; - private $namespace; - - /** - * Initializes the storage with a session namespace. - * - * @param string $namespace The namespace under which the token is stored in the session - */ - public function __construct(string $namespace = self::SESSION_NAMESPACE) - { - $this->namespace = $namespace; - } - - /** - * {@inheritdoc} - */ - public function getToken(string $tokenId) - { - if (!$this->sessionStarted) { - $this->startSession(); - } - - if (!isset($_SESSION[$this->namespace][$tokenId])) { - throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.'); - } - - return (string) $_SESSION[$this->namespace][$tokenId]; - } - - /** - * {@inheritdoc} - */ - public function setToken(string $tokenId, string $token) - { - if (!$this->sessionStarted) { - $this->startSession(); - } +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', NativeSessionTokenStorage::class, ComponentNativeSessionTokenStorage::class); - $_SESSION[$this->namespace][$tokenId] = $token; - } +class_exists(ComponentNativeSessionTokenStorage::class); +if (false) { /** - * {@inheritdoc} + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function hasToken(string $tokenId) + class NativeSessionTokenStorage { - if (!$this->sessionStarted) { - $this->startSession(); - } - - return isset($_SESSION[$this->namespace][$tokenId]); - } - - /** - * {@inheritdoc} - */ - public function removeToken(string $tokenId) - { - if (!$this->sessionStarted) { - $this->startSession(); - } - - if (!isset($_SESSION[$this->namespace][$tokenId])) { - return null; - } - - $token = (string) $_SESSION[$this->namespace][$tokenId]; - - unset($_SESSION[$this->namespace][$tokenId]); - - if (!$_SESSION[$this->namespace]) { - unset($_SESSION[$this->namespace]); - } - - return $token; - } - - /** - * {@inheritdoc} - */ - public function clear() - { - unset($_SESSION[$this->namespace]); - } - - private function startSession() - { - if (PHP_SESSION_NONE === session_status()) { - session_start(); - } - - $this->sessionStarted = true; } } diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php index 70df4f5f9871f..a757d7ff624a8 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php @@ -11,96 +11,17 @@ namespace Symfony\Component\Security\Csrf\TokenStorage; -use Symfony\Component\HttpFoundation\Session\SessionInterface; -use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; +use Symfony\Component\Csrf\TokenStorage\SessionTokenStorage as ComponentSessionTokenStorage; -/** - * Token storage that uses a Symfony Session object. - * - * @author Bernhard Schussek - */ -class SessionTokenStorage implements ClearableTokenStorageInterface -{ - /** - * The namespace used to store values in the session. - */ - const SESSION_NAMESPACE = '_csrf'; - - private $session; - private $namespace; - - /** - * Initializes the storage with a Session object and a session namespace. - * - * @param string $namespace The namespace under which the token is stored in the session - */ - public function __construct(SessionInterface $session, string $namespace = self::SESSION_NAMESPACE) - { - $this->session = $session; - $this->namespace = $namespace; - } - - /** - * {@inheritdoc} - */ - public function getToken(string $tokenId) - { - if (!$this->session->isStarted()) { - $this->session->start(); - } - - if (!$this->session->has($this->namespace.'/'.$tokenId)) { - throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.'); - } - - return (string) $this->session->get($this->namespace.'/'.$tokenId); - } +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', SessionTokenStorage::class, ComponentSessionTokenStorage::class); - /** - * {@inheritdoc} - */ - public function setToken(string $tokenId, string $token) - { - if (!$this->session->isStarted()) { - $this->session->start(); - } - - $this->session->set($this->namespace.'/'.$tokenId, $token); - } - - /** - * {@inheritdoc} - */ - public function hasToken(string $tokenId) - { - if (!$this->session->isStarted()) { - $this->session->start(); - } - - return $this->session->has($this->namespace.'/'.$tokenId); - } - - /** - * {@inheritdoc} - */ - public function removeToken(string $tokenId) - { - if (!$this->session->isStarted()) { - $this->session->start(); - } - - return $this->session->remove($this->namespace.'/'.$tokenId); - } +class_exists(ComponentSessionTokenStorage::class); +if (false) { /** - * {@inheritdoc} + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function clear() + class SessionTokenStorage { - foreach (array_keys($this->session->all()) as $key) { - if (0 === strpos($key, $this->namespace.'/')) { - $this->session->remove($key); - } - } } } diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php index 88ef40379fba6..48d869d9b8a6c 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php @@ -11,39 +11,17 @@ namespace Symfony\Component\Security\Csrf\TokenStorage; -/** - * Stores CSRF tokens. - * - * @author Bernhard Schussek - */ -interface TokenStorageInterface -{ - /** - * Reads a stored CSRF token. - * - * @return string The stored token - * - * @throws \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException If the token ID does not exist - */ - public function getToken(string $tokenId); +use Symfony\Component\Csrf\TokenStorage\TokenStorageInterface as ComponentTokenStorageInterface; - /** - * Stores a CSRF token. - */ - public function setToken(string $tokenId, string $token); +trigger_deprecation('symfony/security-csrf', '5.1', 'The "%s" class is deprecated, use "%s" instead. The CSRF library has moved to the "symfony/csrf" package, replace the requirement on "symfony/security-csrf" with "symfony/csrf".', TokenStorageInterface::class, ComponentTokenStorageInterface::class); - /** - * Removes a CSRF token. - * - * @return string|null Returns the removed token if one existed, NULL - * otherwise - */ - public function removeToken(string $tokenId); +class_exists(ComponentTokenStorageInterface::class); +if (false) { /** - * Checks whether a token with the given token ID exists. - * - * @return bool Whether a token exists with the given ID + * @deprecated since Symfony 5.1, use symfony/csrf instead. */ - public function hasToken(string $tokenId); + interface TokenStorageInterface + { + } } diff --git a/src/Symfony/Component/Security/Csrf/composer.json b/src/Symfony/Component/Security/Csrf/composer.json index 284ddf58dadb7..f93ac2c6de45d 100644 --- a/src/Symfony/Component/Security/Csrf/composer.json +++ b/src/Symfony/Component/Security/Csrf/composer.json @@ -17,7 +17,8 @@ ], "require": { "php": "^7.2.5", - "symfony/security-core": "^4.4|^5.0" + "symfony/csrf": "^5.1", + "symfony/deprecation-contracts": "^2.1" }, "require-dev": { "symfony/http-foundation": "^4.4|^5.0" diff --git a/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php b/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php index 984041ee3c1af..496ed8171b484 100644 --- a/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php +++ b/src/Symfony/Component/Security/Http/EventListener/CsrfTokenClearingLogoutListener.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Security\Http\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; +use Symfony\Component\Csrf\TokenStorage\ClearableTokenStorageInterface; use Symfony\Component\Security\Http\Event\LogoutEvent; /** diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index b8a56e41c1db7..8973558277340 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -18,8 +18,8 @@ use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; use Symfony\Component\Security\Core\Exception\LogicException; use Symfony\Component\Security\Core\Exception\LogoutException; -use Symfony\Component\Security\Csrf\CsrfToken; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Event\LogoutEvent; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php index 7e69f33c8feef..bcf537b0c15e2 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -20,8 +20,8 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\Security; -use Symfony\Component\Security\Csrf\CsrfToken; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfToken; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\HttpUtils; diff --git a/src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php b/src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php index ad6b888aad562..380b4ee12da8e 100644 --- a/src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php +++ b/src/Symfony/Component/Security/Http/Logout/CsrfTokenClearingLogoutHandler.php @@ -14,7 +14,7 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Csrf\TokenStorage\ClearableTokenStorageInterface; +use Symfony\Component\Csrf\TokenStorage\ClearableTokenStorageInterface; /** * @author Christian Flothmann diff --git a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php index c6b721209f363..09fb683747e93 100644 --- a/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php +++ b/src/Symfony/Component/Security/Http/Logout/LogoutUrlGenerator.php @@ -15,7 +15,7 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; -use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; /** * Provides generator functions for the logout URL. diff --git a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php index 76a975d0baeaf..0d0044efc053b 100644 --- a/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Firewall/LogoutListenerTest.php @@ -13,6 +13,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Bridge\PhpUnit\ExpectDeprecationTrait; +use Symfony\Component\Csrf\CsrfTokenManagerInterface; use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -194,7 +195,7 @@ public function testLegacyLogoutHandlers() private function getTokenManager() { - return $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock(); + return $this->getMockBuilder(CsrfTokenManagerInterface::class)->getMock(); } private function getTokenStorage() diff --git a/src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php b/src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php index 06eb56139ea11..74db7d5e2c35f 100644 --- a/src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php +++ b/src/Symfony/Component/Security/Http/Tests/Logout/CsrfTokenClearingLogoutHandlerTest.php @@ -16,7 +16,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\Session; use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; -use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage; +use Symfony\Component\Csrf\TokenStorage\SessionTokenStorage; use Symfony\Component\Security\Http\Logout\CsrfTokenClearingLogoutHandler; class CsrfTokenClearingLogoutHandlerTest extends TestCase diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 376ee410facce..a6035ab849045 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -26,15 +26,14 @@ }, "require-dev": { "symfony/routing": "^4.4|^5.0", - "symfony/security-csrf": "^4.4|^5.0", + "symfony/csrf": "^5.1", "psr/log": "~1.0" }, "conflict": { - "symfony/event-dispatcher": "<4.3", - "symfony/security-csrf": "<4.4" + "symfony/event-dispatcher": "<4.3" }, "suggest": { - "symfony/security-csrf": "For using tokens to protect authentication/logout attempts", + "symfony/csrf": "For using tokens to protect authentication/logout attempts", "symfony/routing": "For using the HttpUtils class to create sub-requests, redirect the user, and match URLs" }, "autoload": {