From d4bb5f4e3c2fab209e477390f1d2864117c6f0e9 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 4 Oct 2013 15:25:38 +0200 Subject: [PATCH] [Security\Csrf] Split CsrfTokenGenerator into CsrfTokenManager and TokenGenerator --- UPGRADE-3.0.md | 2 +- src/Symfony/Bridge/Twig/Form/TwigRenderer.php | 15 +- .../Resources/config/form_csrf.xml | 4 +- .../Resources/config/security_csrf.xml | 11 +- .../Helper/FormHelperDivLayoutTest.php | 2 +- .../Helper/FormHelperTableLayoutTest.php | 2 +- .../Templating/Helper/LogoutUrlHelper.php | 28 +-- .../Functional/app/CsrfFormLogin/config.yml | 4 +- .../Form/Extension/Csrf/CsrfExtension.php | 27 ++- .../Csrf/CsrfProvider/CsrfProviderAdapter.php | 75 ++++++++ .../CsrfProvider/CsrfProviderInterface.php | 39 ++++- .../CsrfTokenGeneratorAdapter.php | 26 --- .../CsrfProvider/CsrfTokenManagerAdapter.php | 57 ++++++ .../Csrf/CsrfProvider/DefaultCsrfProvider.php | 2 +- .../Csrf/CsrfProvider/SessionCsrfProvider.php | 2 +- .../EventListener/CsrfValidationListener.php | 22 ++- .../Csrf/Type/FormTypeCsrfExtension.php | 44 +++-- .../Templating/TemplatingExtension.php | 15 +- src/Symfony/Component/Form/FormRenderer.php | 22 ++- .../Form/Tests/AbstractDivLayoutTest.php | 7 +- .../Form/Tests/AbstractLayoutTest.php | 8 +- .../Form/Tests/AbstractTableLayoutTest.php | 7 +- .../CsrfValidationListenerTest.php | 8 +- .../Csrf/Type/FormTypeCsrfExtensionTest.php | 65 +++---- .../Core/Exception/ExceptionInterface.php | 21 +++ .../Exception/InvalidArgumentException.php | 21 +++ .../Core/Exception/RuntimeException.php | 21 +++ .../Component/Security/Csrf/CsrfToken.php | 66 +++++++ .../Security/Csrf/CsrfTokenGenerator.php | 105 ----------- .../Security/Csrf/CsrfTokenManager.php | 106 +++++++++++ .../Csrf/CsrfTokenManagerInterface.php | 67 +++++++ .../Csrf/Exception/TokenNotFoundException.php | 21 +++ src/Symfony/Component/Security/Csrf/README.md | 2 +- .../Csrf/Tests/CsrfTokenGeneratorTest.php | 148 ---------------- .../Csrf/Tests/CsrfTokenManagerTest.php | 164 ++++++++++++++++++ .../UriSafeTokenGeneratorTest.php | 71 ++++++++ .../NativeSessionTokenStorageTest.php | 25 ++- .../TokenStorage/SessionTokenStorageTest.php | 130 +++++++++++++- .../TokenGeneratorInterface.php} | 20 +-- .../TokenGenerator/UriSafeTokenGenerator.php | 73 ++++++++ .../NativeSessionTokenStorage.php | 30 +++- .../Csrf/TokenStorage/SessionTokenStorage.php | 25 ++- .../TokenStorage/TokenStorageInterface.php | 22 ++- .../Security/Http/Firewall/LogoutListener.php | 32 ++-- .../SimpleFormAuthenticationListener.php | 24 ++- ...namePasswordFormAuthenticationListener.php | 22 ++- 46 files changed, 1251 insertions(+), 459 deletions(-) create mode 100644 src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderAdapter.php delete mode 100644 src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenGeneratorAdapter.php create mode 100644 src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenManagerAdapter.php create mode 100644 src/Symfony/Component/Security/Core/Exception/ExceptionInterface.php create mode 100644 src/Symfony/Component/Security/Core/Exception/InvalidArgumentException.php create mode 100644 src/Symfony/Component/Security/Core/Exception/RuntimeException.php create mode 100644 src/Symfony/Component/Security/Csrf/CsrfToken.php delete mode 100644 src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php create mode 100644 src/Symfony/Component/Security/Csrf/CsrfTokenManager.php create mode 100644 src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php create mode 100644 src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php delete mode 100644 src/Symfony/Component/Security/Csrf/Tests/CsrfTokenGeneratorTest.php create mode 100644 src/Symfony/Component/Security/Csrf/Tests/CsrfTokenManagerTest.php create mode 100644 src/Symfony/Component/Security/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php rename src/Symfony/Component/Security/Csrf/{CsrfTokenGeneratorInterface.php => TokenGenerator/TokenGeneratorInterface.php} (64%) create mode 100644 src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index be37fabc2c..dffbf8ca95 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -170,7 +170,7 @@ UPGRADE FROM 2.x to 3.0 * The interface `Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface` and all of its implementations were removed. Use the new interface - `Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface` instead. + `Symfony\Component\Security\Csrf\CsrfTokenManagerInterface` instead. * The options "csrf_provider" and "intention" were renamed to "csrf_token_generator" and "csrf_token_id". diff --git a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php index 5727f626cc..3b47c26ea6 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php @@ -11,8 +11,11 @@ namespace Symfony\Bridge\Twig\Form; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; /** * @author Bernhard Schussek @@ -24,9 +27,15 @@ class TwigRenderer extends FormRenderer implements TwigRendererInterface */ private $engine; - public function __construct(TwigRendererEngineInterface $engine, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) + public function __construct(TwigRendererEngineInterface $engine, $csrfTokenManager = null) { - parent::__construct($engine, $csrfTokenGenerator); + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new UnexpectedTypeException($csrfTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); + } + + parent::__construct($engine, $csrfTokenManager); $this->engine = $engine; } diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 6d9ff2e046..4903004f32 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -5,7 +5,9 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml index b83bf2402a..143c8a68ef 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml @@ -5,18 +5,23 @@ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - Symfony\Component\Security\Csrf\CsrfTokenGenerator + Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage + Symfony\Component\Security\Csrf\CsrfTokenManager + + + + - + + - diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index fa526d0aea..4e244e2cf6 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php @@ -46,7 +46,7 @@ class FormHelperDivLayoutTest extends AbstractDivLayoutTest )); return array_merge(parent::getExtensions(), array( - new TemplatingExtension($this->engine, $this->csrfTokenGenerator, array( + new TemplatingExtension($this->engine, $this->csrfTokenManager, array( 'FrameworkBundle:Form', )), )); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php index 5a917f66a6..e7e6e60bfe 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperTableLayoutTest.php @@ -46,7 +46,7 @@ class FormHelperTableLayoutTest extends AbstractTableLayoutTest )); return array_merge(parent::getExtensions(), array( - new TemplatingExtension($this->engine, $this->csrfTokenGenerator, array( + new TemplatingExtension($this->engine, $this->csrfTokenManager, array( 'FrameworkBundle:Form', 'FrameworkBundle:FormTable', )), diff --git a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php index 7900c15f11..6400ed20e0 100644 --- a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php +++ b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php @@ -12,8 +12,10 @@ namespace Symfony\Bundle\SecurityBundle\Templating\Helper; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Templating\Helper\Helper; /** @@ -43,15 +45,21 @@ class LogoutUrlHelper extends Helper /** * Registers a firewall's LogoutListener, allowing its URL to be generated. * - * @param string $key The firewall key - * @param string $logoutPath The path that starts the logout process - * @param string $csrfTokenId The ID of the CSRF token - * @param string $csrfParameter The CSRF token parameter name - * @param CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface instance + * @param string $key The firewall key + * @param string $logoutPath The path that starts the logout process + * @param string $csrfTokenId The ID of the CSRF token + * @param string $csrfParameter The CSRF token parameter name + * @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance */ - public function registerListener($key, $logoutPath, $csrfTokenId, $csrfParameter, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) + public function registerListener($key, $logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager = null) { - $this->listeners[$key] = array($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenGenerator); + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new \InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); + } + + $this->listeners[$key] = array($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager); } /** @@ -94,9 +102,9 @@ class LogoutUrlHelper extends Helper throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key)); } - list($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenGenerator) = $this->listeners[$key]; + list($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenManager) = $this->listeners[$key]; - $parameters = null !== $csrfTokenGenerator ? array($csrfParameter => $csrfTokenGenerator->generateCsrfToken($csrfTokenId)) : array(); + $parameters = null !== $csrfTokenManager ? array($csrfParameter => (string) $csrfTokenManager->getToken($csrfTokenId)) : array(); if ('/' === $logoutPath[0]) { $request = $this->container->get('request'); diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml index ee41001d7c..e1e2b0e883 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml +++ b/src/Symfony/Bundle/SecurityBundle/Tests/Functional/app/CsrfFormLogin/config.yml @@ -37,12 +37,12 @@ security: username_parameter: "user_login[username]" password_parameter: "user_login[password]" csrf_parameter: "user_login[_token]" - csrf_provider: security.csrf.token_generator + csrf_provider: security.csrf.token_manager anonymous: ~ logout: path: /logout_path target: / - csrf_provider: security.csrf.token_generator + csrf_provider: security.csrf.token_manager access_control: - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index bdaa94fe36..32345a1a1a 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -11,9 +11,12 @@ namespace Symfony\Component\Form\Extension\Csrf; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractExtension; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; /** @@ -24,9 +27,9 @@ use Symfony\Component\Translation\TranslatorInterface; class CsrfExtension extends AbstractExtension { /** - * @var CsrfTokenGeneratorInterface + * @var CsrfTokenManagerInterface */ - private $tokenGenerator; + private $tokenManager; /** * @var TranslatorInterface @@ -41,13 +44,19 @@ class CsrfExtension extends AbstractExtension /** * Constructor. * - * @param CsrfTokenGeneratorInterface $tokenGenerator The CSRF token generator - * @param TranslatorInterface $translator The translator for translating error messages - * @param null|string $translationDomain The translation domain for translating + * @param CsrfTokenManagerInterface $tokenManager The CSRF token manager + * @param TranslatorInterface $translator The translator for translating error messages + * @param null|string $translationDomain The translation domain for translating */ - public function __construct(CsrfTokenGeneratorInterface $tokenGenerator, TranslatorInterface $translator = null, $translationDomain = null) + public function __construct($tokenManager, TranslatorInterface $translator = null, $translationDomain = null) { - $this->tokenGenerator = $tokenGenerator; + if ($tokenManager instanceof CsrfProviderInterface) { + $tokenManager = new CsrfProviderAdapter($tokenManager); + } elseif (!$tokenManager instanceof CsrfTokenManagerInterface) { + throw new UnexpectedTypeException($tokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); + } + + $this->tokenManager = $tokenManager; $this->translator = $translator; $this->translationDomain = $translationDomain; } @@ -58,7 +67,7 @@ class CsrfExtension extends AbstractExtension protected function loadTypeExtensions() { return array( - new Type\FormTypeCsrfExtension($this->tokenGenerator, true, '_token', $this->translator, $this->translationDomain), + new Type\FormTypeCsrfExtension($this->tokenManager, true, '_token', $this->translator, $this->translationDomain), ); } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderAdapter.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderAdapter.php new file mode 100644 index 0000000000..01753135eb --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderAdapter.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; + +use Symfony\Component\Form\Exception\BadMethodCallException; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + +/** + * Adapter for using old CSRF providers where the new {@link CsrfTokenManagerInterface} + * is expected. + * + * @since 2.4 + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. + */ +class CsrfProviderAdapter implements CsrfTokenManagerInterface +{ + /** + * @var CsrfProviderInterface + */ + private $csrfProvider; + + public function __construct(CsrfProviderInterface $csrfProvider) + { + $this->csrfProvider = $csrfProvider; + } + + public function getCsrfProvider() + { + return $this->csrfProvider; + } + + /** + * {@inheritdoc} + */ + public function getToken($tokenId) + { + return $this->csrfProvider->generateCsrfToken($tokenId); + } + + /** + * {@inheritdoc} + */ + public function refreshToken($tokenId) + { + throw new BadMethodCallException('Not supported'); + } + + /** + * {@inheritdoc} + */ + public function removeToken($tokenId) + { + throw new BadMethodCallException('Not supported'); + } + + /** + * {@inheritdoc} + */ + public function isTokenValid(CsrfToken $token) + { + return $this->csrfProvider->isCsrfTokenValid($token->getId(), $token->getValue()); + } +} diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php index 1f10f56b68..abd681626c 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php @@ -11,16 +11,45 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; - /** - * Alias interface of {@link CsrfTokenGeneratorInterface}. + * Marks classes able to provide CSRF protection + * + * You can generate a CSRF token by using the method generateCsrfToken(). To + * this method you should pass a value that is unique to the page that should + * be secured against CSRF attacks. This value doesn't necessarily have to be + * secret. Implementations of this interface are responsible for adding more + * secret information. + * + * If you want to secure a form submission against CSRF attacks, you could + * supply an "intention" string. This way you make sure that the form can only + * be submitted to pages that are designed to handle the form, that is, that use + * the same intention string to validate the CSRF token with isCsrfTokenValid(). * * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use - * {@link CsrfTokenGeneratorInterface} instead. + * {@link \Symfony\Component\Security\Csrf\CsrfTokenManagerInterface} + * instead. */ -interface CsrfProviderInterface extends CsrfTokenGeneratorInterface +interface CsrfProviderInterface { + /** + * Generates a CSRF token for a page of your application. + * + * @param string $intention Some value that identifies the action intention + * (i.e. "authenticate"). Doesn't have to be a secret value. + * + * @return string The generated token + */ + public function generateCsrfToken($intention); + + /** + * Validates a CSRF token. + * + * @param string $intention The intention used when generating the CSRF token + * @param string $token The token supplied by the browser + * + * @return Boolean Whether the token supplied by the browser is correct + */ + public function isCsrfTokenValid($intention, $token); } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenGeneratorAdapter.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenGeneratorAdapter.php deleted file mode 100644 index 07af17d237..0000000000 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenGeneratorAdapter.php +++ /dev/null @@ -1,26 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; - -use Symfony\Component\Security\Csrf\CsrfTokenGenerator; - -/** - * Adapter for using the new token generator with the old interface. - * - * @since 2.4 - * @author Bernhard Schussek - * - * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. - */ -class CsrfTokenGeneratorAdapter extends CsrfTokenGenerator implements CsrfProviderInterface -{ -} diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenManagerAdapter.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenManagerAdapter.php new file mode 100644 index 0000000000..5dbffc2cbc --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenManagerAdapter.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; + +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; + +/** + * Adapter for using the new token generator with the old interface. + * + * @since 2.4 + * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. + */ +class CsrfTokenManagerAdapter implements CsrfProviderInterface +{ + /** + * @var CsrfTokenManagerInterface + */ + private $tokenManager; + + public function __construct(CsrfTokenManagerInterface $tokenManager) + { + $this->tokenManager = $tokenManager; + } + + public function getTokenManager() + { + return $this->tokenManager; + } + + /** + * {@inheritdoc} + */ + public function generateCsrfToken($intention) + { + return $this->tokenManager->getToken($intention)->getValue(); + } + + /** + * {@inheritdoc} + */ + public function isCsrfTokenValid($intention, $token) + { + return $this->tokenManager->isTokenValid(new CsrfToken($intention, $token)); + } +} diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php index 9fb971f966..db6cde97fc 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php @@ -20,7 +20,7 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use - * {@link \Symfony\Component\Security\Csrf\CsrfTokenGenerator} in + * {@link \Symfony\Component\Security\Csrf\CsrfTokenManager} in * combination with {@link \Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage} * instead. */ diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php index 84381adfc8..741db1ec23 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php @@ -22,7 +22,7 @@ use Symfony\Component\HttpFoundation\Session\Session; * @author Bernhard Schussek * * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use - * {@link \Symfony\Component\Security\Csrf\CsrfTokenGenerator} in + * {@link \Symfony\Component\Security\Csrf\CsrfTokenManager} in * combination with {@link \Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage} * instead. */ diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index 70665f0fb7..4e7bc553a9 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -12,10 +12,14 @@ namespace Symfony\Component\Form\Extension\Csrf\EventListener; use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; /** @@ -31,9 +35,9 @@ class CsrfValidationListener implements EventSubscriberInterface /** * The generator for CSRF tokens - * @var CsrfTokenGeneratorInterface + * @var CsrfTokenManagerInterface */ - private $tokenGenerator; + private $tokenManager; /** * A text mentioning the tokenId of the CSRF token @@ -68,10 +72,16 @@ class CsrfValidationListener implements EventSubscriberInterface ); } - public function __construct($fieldName, CsrfTokenGeneratorInterface $tokenGenerator, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) + public function __construct($fieldName, $tokenManager, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) { + if ($tokenManager instanceof CsrfProviderInterface) { + $tokenManager = new CsrfProviderAdapter($tokenManager); + } elseif (!$tokenManager instanceof CsrfTokenManagerInterface) { + throw new UnexpectedTypeException($tokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); + } + $this->fieldName = $fieldName; - $this->tokenGenerator = $tokenGenerator; + $this->tokenManager = $tokenManager; $this->tokenId = $tokenId; $this->errorMessage = $errorMessage; $this->translator = $translator; @@ -84,7 +94,7 @@ class CsrfValidationListener implements EventSubscriberInterface $data = $event->getData(); if ($form->isRoot() && $form->getConfig()->getOption('compound')) { - if (!isset($data[$this->fieldName]) || !$this->tokenGenerator->isCsrfTokenValid($this->tokenId, $data[$this->fieldName])) { + if (!isset($data[$this->fieldName]) || !$this->tokenManager->isTokenValid(new CsrfToken($this->tokenId, $data[$this->fieldName]))) { $errorMessage = $this->errorMessage; if (null !== $this->translator) { diff --git a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php index 1958c0f266..2f90a924fa 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -12,13 +12,17 @@ namespace Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractTypeExtension; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfTokenManagerAdapter; use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormInterface; use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolverInterface; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Translation\TranslatorInterface; /** @@ -27,9 +31,9 @@ use Symfony\Component\Translation\TranslatorInterface; class FormTypeCsrfExtension extends AbstractTypeExtension { /** - * @var CsrfTokenGeneratorInterface + * @var CsrfTokenManagerInterface */ - private $defaultTokenGenerator; + private $defaultTokenManager; /** * @var Boolean @@ -51,9 +55,15 @@ class FormTypeCsrfExtension extends AbstractTypeExtension */ private $translationDomain; - public function __construct(CsrfTokenGeneratorInterface $defaultTokenGenerator, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) + public function __construct($defaultTokenManager, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) { - $this->defaultTokenGenerator = $defaultTokenGenerator; + if ($defaultTokenManager instanceof CsrfProviderInterface) { + $defaultTokenManager = new CsrfProviderAdapter($defaultTokenManager); + } elseif (!$defaultTokenManager instanceof CsrfTokenManagerInterface) { + throw new UnexpectedTypeException($defaultTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); + } + + $this->defaultTokenManager = $defaultTokenManager; $this->defaultEnabled = $defaultEnabled; $this->defaultFieldName = $defaultFieldName; $this->translator = $translator; @@ -75,7 +85,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension $builder ->addEventSubscriber(new CsrfValidationListener( $options['csrf_field_name'], - $options['csrf_token_generator'], + $options['csrf_token_manager'], $options['csrf_token_id'], $options['csrf_message'], $this->translator, @@ -95,7 +105,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension { if ($options['csrf_protection'] && !$view->parent && $options['compound']) { $factory = $form->getConfig()->getFormFactory(); - $data = $options['csrf_token_generator']->generateCsrfToken($options['csrf_token_id']); + $data = (string) $options['csrf_token_manager']->getToken($options['csrf_token_id']); $csrfForm = $factory->createNamed($options['csrf_field_name'], 'hidden', $data, array( 'mapped' => false, @@ -116,18 +126,20 @@ class FormTypeCsrfExtension extends AbstractTypeExtension }; // BC clause for the "csrf_provider" option - $csrfTokenGenerator = function (Options $options) { - return $options['csrf_provider']; + $csrfTokenManager = function (Options $options) { + return $options['csrf_provider'] instanceof CsrfTokenManagerAdapter + ? $options['csrf_provider']->getTokenManager() + : new CsrfProviderAdapter($options['csrf_provider']); }; $resolver->setDefaults(array( - 'csrf_protection' => $this->defaultEnabled, - 'csrf_field_name' => $this->defaultFieldName, - 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', - 'csrf_token_generator' => $csrfTokenGenerator, - 'csrf_token_id' => $csrfTokenId, - 'csrf_provider' => $this->defaultTokenGenerator, - 'intention' => 'unknown', + 'csrf_protection' => $this->defaultEnabled, + 'csrf_field_name' => $this->defaultFieldName, + 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', + 'csrf_token_manager' => $csrfTokenManager, + 'csrf_token_id' => $csrfTokenId, + 'csrf_provider' => new CsrfTokenManagerAdapter($this->defaultTokenManager), + 'intention' => 'unknown', )); } diff --git a/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php b/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php index e3b239873e..09185a3e4d 100644 --- a/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php +++ b/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php @@ -12,8 +12,11 @@ namespace Symfony\Component\Form\Extension\Templating; use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Form\Exception\UnexpectedTypeException; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Templating\PhpEngine; use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper; @@ -24,10 +27,16 @@ use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper; */ class TemplatingExtension extends AbstractExtension { - public function __construct(PhpEngine $engine, CsrfTokenGeneratorInterface $csrfTokenGenerator = null, array $defaultThemes = array()) + public function __construct(PhpEngine $engine, $csrfTokenManager = null, array $defaultThemes = array()) { + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new UnexpectedTypeException($csrfTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); + } + $engine->addHelpers(array( - new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenGenerator)) + new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenManager)) )); } } diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php index 59ccc0c31a..207c1ed6b2 100644 --- a/src/Symfony/Component/Form/FormRenderer.php +++ b/src/Symfony/Component/Form/FormRenderer.php @@ -13,7 +13,7 @@ namespace Symfony\Component\Form; use Symfony\Component\Form\Exception\LogicException; use Symfony\Component\Form\Exception\BadMethodCallException; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; /** * Renders a form into HTML using a rendering engine. @@ -30,9 +30,9 @@ class FormRenderer implements FormRendererInterface private $engine; /** - * @var CsrfTokenGeneratorInterface + * @var CsrfTokenManagerInterface */ - private $csrfTokenGenerator; + private $csrfTokenManager; /** * @var array @@ -49,10 +49,16 @@ class FormRenderer implements FormRendererInterface */ private $variableStack = array(); - public function __construct(FormRendererEngineInterface $engine, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) + public function __construct(FormRendererEngineInterface $engine, $csrfTokenManager = null) { + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new UnexpectedTypeException($csrfTokenManager, 'CsrfProviderInterface or CsrfTokenManagerInterface'); + } + $this->engine = $engine; - $this->csrfTokenGenerator = $csrfTokenGenerator; + $this->csrfTokenManager = $csrfTokenManager; } /** @@ -76,11 +82,11 @@ class FormRenderer implements FormRendererInterface */ public function renderCsrfToken($tokenId) { - if (null === $this->csrfTokenGenerator) { - throw new BadMethodCallException('CSRF tokens can only be generated if a CsrfTokenGeneratorInterface is injected in FormRenderer::__construct().'); + if (null === $this->csrfTokenManager) { + throw new BadMethodCallException('CSRF tokens can only be generated if a CsrfTokenManagerInterface is injected in FormRenderer::__construct().'); } - return $this->csrfTokenGenerator->generateCsrfToken($tokenId); + return $this->csrfTokenManager->getToken($tokenId)->getValue(); } /** diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 7395c7e097..2d32aa38a3 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Tests; use Symfony\Component\Form\FormError; use Symfony\Component\Form\Tests\Fixtures\AlternatingRowType; +use Symfony\Component\Security\Csrf\CsrfToken; abstract class AbstractDivLayoutTest extends AbstractLayoutTest { @@ -471,9 +472,9 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest public function testCsrf() { - $this->csrfTokenGenerator->expects($this->any()) - ->method('generateCsrfToken') - ->will($this->returnValue('foo&bar')); + $this->csrfTokenManager->expects($this->any()) + ->method('getToken') + ->will($this->returnValue(new CsrfToken('token_id', 'foo&bar'))); $form = $this->factory->createNamedBuilder('name', 'form') ->add($this->factory diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index c35235b862..edecb30a89 100644 --- a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php @@ -17,7 +17,7 @@ use Symfony\Component\Form\Extension\Csrf\CsrfExtension; abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormIntegrationTestCase { - protected $csrfTokenGenerator; + protected $csrfTokenManager; protected function setUp() { @@ -27,7 +27,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg \Locale::setDefault('en'); - $this->csrfTokenGenerator = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface'); + $this->csrfTokenManager = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface'); parent::setUp(); } @@ -35,13 +35,13 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg protected function getExtensions() { return array( - new CsrfExtension($this->csrfTokenGenerator), + new CsrfExtension($this->csrfTokenManager), ); } protected function tearDown() { - $this->csrfTokenGenerator = null; + $this->csrfTokenManager = null; parent::tearDown(); } diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php index f43188a914..5c8a9be2d5 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Form\Tests; use Symfony\Component\Form\FormError; +use Symfony\Component\Security\Csrf\CsrfToken; abstract class AbstractTableLayoutTest extends AbstractLayoutTest { @@ -336,9 +337,9 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest public function testCsrf() { - $this->csrfTokenGenerator->expects($this->any()) - ->method('generateCsrfToken') - ->will($this->returnValue('foo&bar')); + $this->csrfTokenManager->expects($this->any()) + ->method('getToken') + ->will($this->returnValue(new CsrfToken('token_id', 'foo&bar'))); $form = $this->factory->createNamedBuilder('name', 'form') ->add($this->factory 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 b3e45ab984..ef013ca82b 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php @@ -19,14 +19,14 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase { protected $dispatcher; protected $factory; - protected $tokenGenerator; + protected $tokenManager; protected $form; protected function setUp() { $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface'); - $this->tokenGenerator = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface'); + $this->tokenManager = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface'); $this->form = $this->getBuilder('post') ->setDataMapper($this->getDataMapper()) ->getForm(); @@ -36,7 +36,7 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase { $this->dispatcher = null; $this->factory = null; - $this->tokenGenerator = null; + $this->tokenManager = null; $this->form = null; } @@ -66,7 +66,7 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase $data = "XP4HUzmHPi"; $event = new FormEvent($this->form, $data); - $validation = new CsrfValidationListener('csrf', $this->tokenGenerator, 'unknown', 'Invalid.'); + $validation = new CsrfValidationListener('csrf', $this->tokenManager, 'unknown', 'Invalid.'); $validation->preSubmit($event); // Validate accordingly 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 ccd7dccbca..962cbdf134 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -16,6 +16,7 @@ use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormError; use Symfony\Component\Form\Test\TypeTestCase; use Symfony\Component\Form\Extension\Csrf\CsrfExtension; +use Symfony\Component\Security\Csrf\CsrfToken; class FormTypeCsrfExtensionTest_ChildType extends AbstractType { @@ -37,7 +38,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $tokenGenerator; + protected $tokenManager; /** * @var \PHPUnit_Framework_MockObject_MockObject @@ -46,7 +47,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase protected function setUp() { - $this->tokenGenerator = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface'); + $this->tokenManager = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface'); $this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); parent::setUp(); @@ -54,7 +55,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase protected function tearDown() { - $this->tokenGenerator = null; + $this->tokenManager = null; $this->translator = null; parent::tearDown(); @@ -63,7 +64,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase protected function getExtensions() { return array_merge(parent::getExtensions(), array( - new CsrfExtension($this->tokenGenerator, $this->translator), + new CsrfExtension($this->tokenManager, $this->translator), )); } @@ -123,16 +124,16 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testGenerateCsrfToken() { - $this->tokenGenerator->expects($this->once()) - ->method('generateCsrfToken') - ->with('%INTENTION%') - ->will($this->returnValue('token')); + $this->tokenManager->expects($this->once()) + ->method('getToken') + ->with('TOKEN_ID') + ->will($this->returnValue(new CsrfToken('TOKEN_ID', 'token'))); $view = $this->factory ->create('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->tokenGenerator, - 'intention' => '%INTENTION%', + 'csrf_token_manager' => $this->tokenManager, + 'csrf_token_id' => 'TOKEN_ID', 'compound' => true, )) ->createView(); @@ -153,16 +154,16 @@ class FormTypeCsrfExtensionTest extends TypeTestCase */ public function testValidateTokenOnSubmitIfRootAndCompound($valid) { - $this->tokenGenerator->expects($this->once()) - ->method('isCsrfTokenValid') - ->with('%INTENTION%', 'token') + $this->tokenManager->expects($this->once()) + ->method('isTokenValid') + ->with(new CsrfToken('TOKEN_ID', 'token')) ->will($this->returnValue($valid)); $form = $this->factory ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->tokenGenerator, - 'intention' => '%INTENTION%', + 'csrf_token_manager' => $this->tokenManager, + 'csrf_token_id' => 'TOKEN_ID', 'compound' => true, )) ->add('child', 'text') @@ -182,14 +183,14 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testFailIfRootAndCompoundAndTokenMissing() { - $this->tokenGenerator->expects($this->never()) - ->method('isCsrfTokenValid'); + $this->tokenManager->expects($this->never()) + ->method('isTokenValid'); $form = $this->factory ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->tokenGenerator, - 'intention' => '%INTENTION%', + 'csrf_token_manager' => $this->tokenManager, + 'csrf_token_id' => 'TOKEN_ID', 'compound' => true, )) ->add('child', 'text') @@ -209,16 +210,16 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testDontValidateTokenIfCompoundButNoRoot() { - $this->tokenGenerator->expects($this->never()) - ->method('isCsrfTokenValid'); + $this->tokenManager->expects($this->never()) + ->method('isTokenValid'); $form = $this->factory ->createNamedBuilder('root', 'form') ->add($this->factory ->createNamedBuilder('form', 'form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->tokenGenerator, - 'intention' => '%INTENTION%', + 'csrf_token_manager' => $this->tokenManager, + 'csrf_token_id' => 'TOKEN_ID', 'compound' => true, )) ) @@ -233,14 +234,14 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testDontValidateTokenIfRootButNotCompound() { - $this->tokenGenerator->expects($this->never()) - ->method('isCsrfTokenValid'); + $this->tokenManager->expects($this->never()) + ->method('isTokenValid'); $form = $this->factory ->create('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->tokenGenerator, - 'intention' => '%INTENTION%', + 'csrf_token_manager' => $this->tokenManager, + 'csrf_token_id' => 'TOKEN_ID', 'compound' => false, )); @@ -269,9 +270,9 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testsTranslateCustomErrorMessage() { - $this->tokenGenerator->expects($this->once()) - ->method('isCsrfTokenValid') - ->with('%INTENTION%', 'token') + $this->tokenManager->expects($this->once()) + ->method('isTokenValid') + ->with(new CsrfToken('TOKEN_ID', 'token')) ->will($this->returnValue(false)); $this->translator->expects($this->once()) @@ -282,9 +283,9 @@ class FormTypeCsrfExtensionTest extends TypeTestCase $form = $this->factory ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->tokenGenerator, + 'csrf_token_manager' => $this->tokenManager, 'csrf_message' => 'Foobar', - 'intention' => '%INTENTION%', + 'csrf_token_id' => 'TOKEN_ID', 'compound' => true, )) ->getForm(); diff --git a/src/Symfony/Component/Security/Core/Exception/ExceptionInterface.php b/src/Symfony/Component/Security/Core/Exception/ExceptionInterface.php new file mode 100644 index 0000000000..5000d02780 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Exception/ExceptionInterface.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * Base ExceptionInterface for the Security component. + * + * @author Bernhard Schussek + */ +interface ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Security/Core/Exception/InvalidArgumentException.php b/src/Symfony/Component/Security/Core/Exception/InvalidArgumentException.php new file mode 100644 index 0000000000..6f85e9500f --- /dev/null +++ b/src/Symfony/Component/Security/Core/Exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * Base InvalidArgumentException for the Security component. + * + * @author Bernhard Schussek + */ +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Security/Core/Exception/RuntimeException.php b/src/Symfony/Component/Security/Core/Exception/RuntimeException.php new file mode 100644 index 0000000000..95edec8ee9 --- /dev/null +++ b/src/Symfony/Component/Security/Core/Exception/RuntimeException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Exception; + +/** + * Base RuntimeException for the Security component. + * + * @author Bernhard Schussek + */ +class RuntimeException extends \RuntimeException implements ExceptionInterface +{ +} diff --git a/src/Symfony/Component/Security/Csrf/CsrfToken.php b/src/Symfony/Component/Security/Csrf/CsrfToken.php new file mode 100644 index 0000000000..aa3da4574a --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/CsrfToken.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf; + +/** + * A CSRF token. + * + * @author Bernhard Schussek + */ +class CsrfToken +{ + /** + * @var string + */ + private $id; + + /** + * @var string + */ + private $value; + + public function __construct($id, $value) + { + $this->id = (string) $id; + $this->value = (string) $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; + } +} diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php b/src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php deleted file mode 100644 index 8ff346284b..0000000000 --- a/src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php +++ /dev/null @@ -1,105 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Security\Csrf; - -use Symfony\Component\Security\Core\Util\SecureRandomInterface; -use Symfony\Component\Security\Core\Util\SecureRandom; -use Symfony\Component\Security\Core\Util\StringUtils; -use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; -use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; - -/** - * Generates and validates CSRF tokens. - * - * @since 2.4 - * @author Bernhard Schussek - */ -class CsrfTokenGenerator implements CsrfTokenGeneratorInterface -{ - /** - * The entropy of the token in bits. - * @var integer - */ - const TOKEN_ENTROPY = 256; - - /** - * @var TokenStorageInterface - */ - private $storage; - - /** - * The generator for random values. - * @var SecureRandomInterface - */ - private $random; - - /** - * Creates a new CSRF provider using PHP's native session storage. - * - * @param TokenStorageInterface $storage The storage for storing generated - * CSRF tokens - * @param SecureRandomInterface $random The used random value generator - * @param integer $entropy The amount of entropy collected for - * newly generated tokens (in bits) - * - */ - public function __construct(TokenStorageInterface $storage = null, SecureRandomInterface $random = null, $entropy = self::TOKEN_ENTROPY) - { - if (null === $storage) { - $storage = new NativeSessionTokenStorage(); - } - - if (null === $random) { - $random = new SecureRandom(); - } - - $this->storage = $storage; - $this->random = $random; - $this->entropy = $entropy; - } - - /** - * {@inheritDoc} - */ - public function generateCsrfToken($tokenId) - { - $currentToken = $this->storage->getToken($tokenId, false); - - // Token exists and is still valid - if (false !== $currentToken) { - return $currentToken; - } - - // Token needs to be (re)generated - // Generate an URI safe base64 encoded string that does not contain "+", - // "/" or "=" which need to be URL encoded and make URLs unnecessarily - // longer. - $bytes = $this->random->nextBytes($this->entropy / 8); - $token = rtrim(strtr(base64_encode($bytes), '+/', '-_'), '='); - - $this->storage->setToken($tokenId, $token); - - return $token; - } - - /** - * {@inheritDoc} - */ - public function isCsrfTokenValid($tokenId, $token) - { - if (!$this->storage->hasToken($tokenId)) { - return false; - } - - return StringUtils::equals((string) $this->storage->getToken($tokenId), $token); - } -} diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php new file mode 100644 index 0000000000..fa6e19e2f1 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/CsrfTokenManager.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf; + +use Symfony\Component\Security\Core\Util\StringUtils; +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; +use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; + +/** + * Default implementation of {@link CsrfTokenManagerInterface}. + * + * @author Bernhard Schussek + */ +class CsrfTokenManager implements CsrfTokenManagerInterface +{ + /** + * @var TokenGeneratorInterface + */ + private $generator; + + /** + * @var TokenStorageInterface + */ + private $storage; + + /** + * Creates a new CSRF provider using PHP's native session storage. + * + * @param TokenGeneratorInterface $generator The token generator + * @param TokenStorageInterface $storage The storage for storing + * generated CSRF tokens + * + */ + public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null) + { + if (null === $generator) { + $generator = new UriSafeTokenGenerator(); + } + + if (null === $storage) { + $storage = new NativeSessionTokenStorage(); + } + + $this->generator = $generator; + $this->storage = $storage; + } + + /** + * {@inheritdoc} + */ + public function getToken($tokenId) + { + if ($this->storage->hasToken($tokenId)) { + $value = $this->storage->getToken($tokenId); + } else { + $value = $this->generator->generateToken(); + + $this->storage->setToken($tokenId, $value); + } + + return new CsrfToken($tokenId, $value); + } + + /** + * {@inheritdoc} + */ + public function refreshToken($tokenId) + { + $value = $this->generator->generateToken(); + + $this->storage->setToken($tokenId, $value); + + return new CsrfToken($tokenId, $value); + } + + /** + * {@inheritdoc} + */ + public function removeToken($tokenId) + { + return $this->storage->removeToken($tokenId); + } + + /** + * {@inheritdoc} + */ + public function isTokenValid(CsrfToken $token) + { + if (!$this->storage->hasToken($token->getId())) { + return false; + } + + return StringUtils::equals((string) $this->storage->getToken($token->getId()), $token->getValue()); + } +} diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php b/src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php new file mode 100644 index 0000000000..878237bc91 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf; + +/** + * Manages CSRF tokens. + * + * @since 2.4 + * @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. + * + * @param string $tokenId The token ID. You may choose an arbitrary value + * for the ID + * + * @return CsrfToken The CSRF token + */ + public function getToken($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($tokenId); + + /** + * Invalidates the CSRF token with the given ID, if one exists. + * + * @param string $tokenId The token ID + * + * @return Boolean Returns true if a token existed for this ID, false + * otherwise + */ + public function removeToken($tokenId); + + /** + * Returns whether the given CSRF token is valid. + * + * @param CsrfToken $token A CSRF token + * + * @return Boolean Returns true if the token is valid, false otherwise + */ + public function isTokenValid(CsrfToken $token); +} diff --git a/src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php b/src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php new file mode 100644 index 0000000000..936afdeb11 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/Exception/TokenNotFoundException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\Exception; + +use Symfony\Component\Security\Core\Exception\RuntimeException; + +/** + * @author Bernhard Schussek + */ +class TokenNotFoundException extends RuntimeException +{ +} diff --git a/src/Symfony/Component/Security/Csrf/README.md b/src/Symfony/Component/Security/Csrf/README.md index 394a29ee8f..2b5136289d 100644 --- a/src/Symfony/Component/Security/Csrf/README.md +++ b/src/Symfony/Component/Security/Csrf/README.md @@ -2,7 +2,7 @@ Security Component - CSRF ========================= The Security CSRF (cross-site request forgery) component provides a class -`CsrfTokenGenerator` for generating and validating CSRF tokens. +`CsrfTokenManager` for generating and validating CSRF tokens. Resources --------- diff --git a/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenGeneratorTest.php b/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenGeneratorTest.php deleted file mode 100644 index f5f95078ab..0000000000 --- a/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenGeneratorTest.php +++ /dev/null @@ -1,148 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider; - -use Symfony\Component\Security\Csrf\CsrfTokenGenerator; - -/** - * @author Bernhard Schussek - */ -class CsrfTokenGeneratorTest extends \PHPUnit_Framework_TestCase -{ - /** - * A non alpha-numeric byte string - * @var string - */ - private static $bytes; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $random; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $storage; - - /** - * @var CsrfTokenGenerator - */ - private $generator; - - public static function setUpBeforeClass() - { - self::$bytes = base64_decode('aMf+Tct/RLn2WQ=='); - } - - protected function setUp() - { - $this->random = $this->getMock('Symfony\Component\Security\Core\Util\SecureRandomInterface'); - $this->storage = $this->getMock('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface'); - $this->generator = new CsrfTokenGenerator($this->storage, $this->random); - } - - protected function tearDown() - { - $this->random = null; - $this->storage = null; - $this->generator = null; - } - - public function testGenerateNewToken() - { - $this->storage->expects($this->once()) - ->method('getToken') - ->with('token_id', false) - ->will($this->returnValue(false)); - - $this->storage->expects($this->once()) - ->method('setToken') - ->with('token_id', $this->anything()) - ->will($this->returnCallback(function ($tokenId, $token) use (&$storedToken) { - $storedToken = $token; - })); - - $this->random->expects($this->once()) - ->method('nextBytes') - ->will($this->returnValue(self::$bytes)); - - $token = $this->generator->generateCsrfToken('token_id'); - - $this->assertSame($token, $storedToken); - $this->assertTrue(ctype_print($token), 'is printable'); - $this->assertStringNotMatchesFormat('%S+%S', $token, 'is URI safe'); - $this->assertStringNotMatchesFormat('%S/%S', $token, 'is URI safe'); - $this->assertStringNotMatchesFormat('%S=%S', $token, 'is URI safe'); - } - - public function testUseExistingTokenIfAvailable() - { - $this->storage->expects($this->once()) - ->method('getToken') - ->with('token_id', false) - ->will($this->returnValue('TOKEN')); - - $this->storage->expects($this->never()) - ->method('setToken'); - - $this->random->expects($this->never()) - ->method('nextBytes'); - - $token = $this->generator->generateCsrfToken('token_id'); - - $this->assertEquals('TOKEN', $token); - } - - public function testMatchingTokenIsValid() - { - $this->storage->expects($this->once()) - ->method('hasToken') - ->with('token_id') - ->will($this->returnValue(true)); - - $this->storage->expects($this->once()) - ->method('getToken') - ->with('token_id') - ->will($this->returnValue('TOKEN')); - - $this->assertTrue($this->generator->isCsrfTokenValid('token_id', 'TOKEN')); - } - - public function testNonMatchingTokenIsNotValid() - { - $this->storage->expects($this->once()) - ->method('hasToken') - ->with('token_id') - ->will($this->returnValue(true)); - - $this->storage->expects($this->once()) - ->method('getToken') - ->with('token_id') - ->will($this->returnValue('TOKEN')); - - $this->assertFalse($this->generator->isCsrfTokenValid('token_id', 'FOOBAR')); - } - - public function testNonExistingTokenIsNotValid() - { - $this->storage->expects($this->once()) - ->method('hasToken') - ->with('token_id') - ->will($this->returnValue(false)); - - $this->storage->expects($this->never()) - ->method('getToken'); - - $this->assertFalse($this->generator->isCsrfTokenValid('token_id', 'FOOBAR')); - } -} diff --git a/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenManagerTest.php b/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenManagerTest.php new file mode 100644 index 0000000000..67c66fb719 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenManagerTest.php @@ -0,0 +1,164 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider; + +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManager; + +/** + * @author Bernhard Schussek + */ +class CsrfTokenManagerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $generator; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $storage; + + /** + * @var CsrfTokenManager + */ + private $manager; + + protected function setUp() + { + $this->generator = $this->getMock('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface'); + $this->storage = $this->getMock('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface'); + $this->manager = new CsrfTokenManager($this->generator, $this->storage); + } + + protected function tearDown() + { + $this->generator = null; + $this->storage = null; + $this->manager = null; + } + + public function testGetNonExistingToken() + { + $this->storage->expects($this->once()) + ->method('hasToken') + ->with('token_id') + ->will($this->returnValue(false)); + + $this->generator->expects($this->once()) + ->method('generateToken') + ->will($this->returnValue('TOKEN')); + + $this->storage->expects($this->once()) + ->method('setToken') + ->with('token_id', 'TOKEN'); + + $token = $this->manager->getToken('token_id'); + + $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token); + $this->assertSame('token_id', $token->getId()); + $this->assertSame('TOKEN', $token->getValue()); + } + + public function testUseExistingTokenIfAvailable() + { + $this->storage->expects($this->once()) + ->method('hasToken') + ->with('token_id') + ->will($this->returnValue(true)); + + $this->storage->expects($this->once()) + ->method('getToken') + ->with('token_id') + ->will($this->returnValue('TOKEN')); + + $token = $this->manager->getToken('token_id'); + + $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token); + $this->assertSame('token_id', $token->getId()); + $this->assertSame('TOKEN', $token->getValue()); + } + + public function testRefreshTokenAlwaysReturnsNewToken() + { + $this->storage->expects($this->never()) + ->method('hasToken'); + + $this->generator->expects($this->once()) + ->method('generateToken') + ->will($this->returnValue('TOKEN')); + + $this->storage->expects($this->once()) + ->method('setToken') + ->with('token_id', 'TOKEN'); + + $token = $this->manager->refreshToken('token_id'); + + $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token); + $this->assertSame('token_id', $token->getId()); + $this->assertSame('TOKEN', $token->getValue()); + } + + public function testMatchingTokenIsValid() + { + $this->storage->expects($this->once()) + ->method('hasToken') + ->with('token_id') + ->will($this->returnValue(true)); + + $this->storage->expects($this->once()) + ->method('getToken') + ->with('token_id') + ->will($this->returnValue('TOKEN')); + + $this->assertTrue($this->manager->isTokenValid(new CsrfToken('token_id', 'TOKEN'))); + } + + public function testNonMatchingTokenIsNotValid() + { + $this->storage->expects($this->once()) + ->method('hasToken') + ->with('token_id') + ->will($this->returnValue(true)); + + $this->storage->expects($this->once()) + ->method('getToken') + ->with('token_id') + ->will($this->returnValue('TOKEN')); + + $this->assertFalse($this->manager->isTokenValid(new CsrfToken('token_id', 'FOOBAR'))); + } + + public function testNonExistingTokenIsNotValid() + { + $this->storage->expects($this->once()) + ->method('hasToken') + ->with('token_id') + ->will($this->returnValue(false)); + + $this->storage->expects($this->never()) + ->method('getToken'); + + $this->assertFalse($this->manager->isTokenValid(new CsrfToken('token_id', 'FOOBAR'))); + } + + public function testRemoveToken() + { + $this->storage->expects($this->once()) + ->method('removeToken') + ->with('token_id') + ->will($this->returnValue('REMOVED_TOKEN')); + + $this->assertSame('REMOVED_TOKEN', $this->manager->removeToken('token_id')); + } +} diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php new file mode 100644 index 0000000000..a55056f538 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider\TokenGenerator; + +use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator; + +/** + * @author Bernhard Schussek + */ +class UriSafeTokenGeneratorTest extends \PHPUnit_Framework_TestCase +{ + const ENTROPY = 1000; + + /** + * A non alpha-numeric byte string + * @var string + */ + private static $bytes; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $random; + + + /** + * @var UriSafeTokenGenerator + */ + private $generator; + + public static function setUpBeforeClass() + { + self::$bytes = base64_decode('aMf+Tct/RLn2WQ=='); + } + + protected function setUp() + { + $this->random = $this->getMock('Symfony\Component\Security\Core\Util\SecureRandomInterface'); + $this->generator = new UriSafeTokenGenerator($this->random, self::ENTROPY); + } + + protected function tearDown() + { + $this->random = null; + $this->generator = null; + } + + public function testGenerateToken() + { + $this->random->expects($this->once()) + ->method('nextBytes') + ->with(self::ENTROPY/8) + ->will($this->returnValue(self::$bytes)); + + $token = $this->generator->generateToken(); + + $this->assertTrue(ctype_print($token), 'is printable'); + $this->assertStringNotMatchesFormat('%S+%S', $token, 'is URI safe'); + $this->assertStringNotMatchesFormat('%S/%S', $token, 'is URI safe'); + $this->assertStringNotMatchesFormat('%S=%S', $token, 'is URI safe'); + } +} diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php index b62eeef53a..ada04c8fc4 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php @@ -96,8 +96,31 @@ class NativeSessionTokenStorageTest extends \PHPUnit_Framework_TestCase $this->assertSame('TOKEN', $this->storage->getToken('token_id')); } + /** + * @expectedException \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException + */ public function testGetNonExistingToken() { - $this->assertSame('DEFAULT', $this->storage->getToken('token_id', 'DEFAULT')); + $this->storage->getToken('token_id'); + } + + /** + * @depends testCheckToken + */ + public function testRemoveNonExistingToken() + { + $this->assertNull($this->storage->removeToken('token_id')); + $this->assertFalse($this->storage->hasToken('token_id')); + } + + /** + * @depends testCheckToken + */ + public function testRemoveExistingToken() + { + $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); + $this->assertFalse($this->storage->hasToken('token_id')); } } diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php index 9ba7edbf92..799b16d825 100644 --- a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php @@ -108,7 +108,7 @@ class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase $this->assertSame('RESULT', $this->storage->hasToken('token_id')); } - public function testGetTokenFromClosedSession() + public function testGetExistingTokenFromClosedSession() { $this->session->expects($this->any()) ->method('isStarted') @@ -117,15 +117,20 @@ class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase $this->session->expects($this->once()) ->method('start'); + $this->session->expects($this->once()) + ->method('has') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue(true)); + $this->session->expects($this->once()) ->method('get') - ->with(self::SESSION_NAMESPACE.'/token_id', 'DEFAULT') + ->with(self::SESSION_NAMESPACE.'/token_id') ->will($this->returnValue('RESULT')); - $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT')); + $this->assertSame('RESULT', $this->storage->getToken('token_id')); } - public function testGetTokenFromActiveSession() + public function testGetExistingTokenFromActiveSession() { $this->session->expects($this->any()) ->method('isStarted') @@ -134,11 +139,124 @@ class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase $this->session->expects($this->never()) ->method('start'); + $this->session->expects($this->once()) + ->method('has') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue(true)); + $this->session->expects($this->once()) ->method('get') - ->with(self::SESSION_NAMESPACE.'/token_id', 'DEFAULT') + ->with(self::SESSION_NAMESPACE.'/token_id') ->will($this->returnValue('RESULT')); - $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT')); + $this->assertSame('RESULT', $this->storage->getToken('token_id')); + } + + /** + * @expectedException \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException + */ + public function testGetNonExistingTokenFromClosedSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $this->session->expects($this->once()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('has') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue(false)); + + $this->storage->getToken('token_id'); + } + + /** + * @expectedException \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException + */ + public function testGetNonExistingTokenFromActiveSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(true)); + + $this->session->expects($this->never()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('has') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue(false)); + + $this->storage->getToken('token_id'); + } + + public function testRemoveNonExistingTokenFromClosedSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $this->session->expects($this->once()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('remove') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue(null)); + + $this->assertNull($this->storage->removeToken('token_id')); + } + + public function testRemoveNonExistingTokenFromActiveSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(true)); + + $this->session->expects($this->never()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('remove') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue(null)); + + $this->assertNull($this->storage->removeToken('token_id')); + } + + public function testRemoveExistingTokenFromClosedSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $this->session->expects($this->once()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('remove') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue('TOKEN')); + + $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); + } + + public function testRemoveExistingTokenFromActiveSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(true)); + + $this->session->expects($this->never()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('remove') + ->with(self::SESSION_NAMESPACE.'/token_id') + ->will($this->returnValue('TOKEN')); + + $this->assertSame('TOKEN', $this->storage->removeToken('token_id')); } } diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenGeneratorInterface.php b/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php similarity index 64% rename from src/Symfony/Component/Security/Csrf/CsrfTokenGeneratorInterface.php rename to src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php index c34549f11d..4d81da9c40 100644 --- a/src/Symfony/Component/Security/Csrf/CsrfTokenGeneratorInterface.php +++ b/src/Symfony/Component/Security/Csrf/TokenGenerator/TokenGeneratorInterface.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Security\Csrf; +namespace Symfony\Component\Security\Csrf\TokenGenerator; /** * Generates and validates CSRF tokens. @@ -29,24 +29,12 @@ namespace Symfony\Component\Security\Csrf; * @since 2.4 * @author Bernhard Schussek */ -interface CsrfTokenGeneratorInterface +interface TokenGeneratorInterface { /** - * Generates a CSRF token with the given token ID. - * - * @param string $tokenId An ID that identifies the token + * Generates a CSRF token. * * @return string The generated CSRF token */ - public function generateCsrfToken($tokenId); - - /** - * Validates a CSRF token. - * - * @param string $tokenId The token ID used when generating the token - * @param string $token The token supplied by the client - * - * @return Boolean Whether the token supplied by the client is correct - */ - public function isCsrfTokenValid($tokenId, $token); + public function generateToken(); } diff --git a/src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php b/src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php new file mode 100644 index 0000000000..9d24f1c029 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/TokenGenerator/UriSafeTokenGenerator.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenGenerator; + +use Symfony\Component\Security\Core\Util\SecureRandomInterface; +use Symfony\Component\Security\Core\Util\SecureRandom; +use Symfony\Component\Security\Core\Util\StringUtils; +use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage; +use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface; + +/** + * Generates CSRF tokens. + * + * @since 2.4 + * @author Bernhard Schussek + */ +class UriSafeTokenGenerator implements TokenGeneratorInterface +{ + /** + * The generator for random values. + * + * @var SecureRandomInterface + */ + private $random; + + /** + * The amount of entropy collected for each token (in bits). + * + * @var integer + */ + private $entropy; + + /** + * Generates URI-safe CSRF tokens. + * + * @param SecureRandomInterface $random The random value generator used for + * generating entropy + * @param integer $entropy The amount of entropy collected for + * each token (in bits) + * + */ + public function __construct(SecureRandomInterface $random = null, $entropy = 256) + { + if (null === $random) { + $random = new SecureRandom(); + } + + $this->random = $random; + $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 = $this->random->nextBytes($this->entropy / 8); + + return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '='); + } +} diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php index 895674391a..c01967cdd1 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Csrf\TokenStorage; +use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; + /** * Token storage that uses PHP's native session handling. * @@ -49,17 +51,17 @@ class NativeSessionTokenStorage implements TokenStorageInterface /** * {@inheritdoc} */ - public function getToken($tokenId, $default = null) + public function getToken($tokenId) { if (!$this->sessionStarted) { $this->startSession(); } - if (isset($_SESSION[$this->namespace][$tokenId])) { - return $_SESSION[$this->namespace][$tokenId]; + if (!isset($_SESSION[$this->namespace][$tokenId])) { + throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.'); } - return $default; + return (string) $_SESSION[$this->namespace][$tokenId]; } /** @@ -71,7 +73,7 @@ class NativeSessionTokenStorage implements TokenStorageInterface $this->startSession(); } - $_SESSION[$this->namespace][$tokenId] = $token; + $_SESSION[$this->namespace][$tokenId] = (string) $token; } /** @@ -86,6 +88,24 @@ class NativeSessionTokenStorage implements TokenStorageInterface return isset($_SESSION[$this->namespace][$tokenId]); } + /** + * {@inheritdoc} + */ + public function removeToken($tokenId) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + $token = isset($_SESSION[$this->namespace][$tokenId]) + ? $_SESSION[$this->namespace][$tokenId] + : null; + + unset($_SESSION[$this->namespace][$tokenId]); + + return $token; + } + private function startSession() { if (version_compare(PHP_VERSION, '5.4', '>=')) { diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php index 3878e4cbfa..f08eb96217 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Security\Csrf\TokenStorage; use Symfony\Component\HttpFoundation\Session\SessionInterface; +use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException; /** * Token storage that uses a Symfony2 Session object. @@ -54,13 +55,17 @@ class SessionTokenStorage implements TokenStorageInterface /** * {@inheritdoc} */ - public function getToken($tokenId, $default = null) + public function getToken($tokenId) { if (!$this->session->isStarted()) { $this->session->start(); } - return $this->session->get($this->namespace . '/' . $tokenId, $default); + 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); } /** @@ -72,7 +77,7 @@ class SessionTokenStorage implements TokenStorageInterface $this->session->start(); } - $this->session->set($this->namespace . '/' . $tokenId, $token); + $this->session->set($this->namespace.'/'.$tokenId, (string) $token); } /** @@ -84,6 +89,18 @@ class SessionTokenStorage implements TokenStorageInterface $this->session->start(); } - return $this->session->has($this->namespace . '/' . $tokenId); + return $this->session->has($this->namespace.'/'.$tokenId); + } + + /** + * {@inheritdoc} + */ + public function removeToken($tokenId) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + return $this->session->remove($this->namespace.'/'.$tokenId); } } diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php index 7dba9e559b..3fb3191249 100644 --- a/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php @@ -23,27 +23,37 @@ interface TokenStorageInterface * Reads a stored CSRF token. * * @param string $tokenId The token ID - * @param mixed $default The value to be returned if no token is set * - * @return mixed The stored token or the default value, if no token is set + * @return string The stored token + * + * @throws \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException If the token ID does not exist */ - public function getToken($tokenId, $default = null); + public function getToken($tokenId); /** * Stores a CSRF token. * * @param string $tokenId The token ID - * @param mixed $token The CSRF token + * @param string $token The CSRF token */ public function setToken($tokenId, $token); + /** + * Removes a CSRF token. + * + * @param string $tokenId The token ID + * + * @return string|null Returns the removed token if one existed, NULL + * otherwise + */ + public function removeToken($tokenId); + /** * Checks whether a token with the given token ID exists. * * @param string $tokenId The token ID * - * @return Boolean Returns true if a token is stored for the given token ID, - * false otherwise. + * @return Boolean Whether a token exists with the given ID */ public function hasToken($tokenId); } diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index 073a48ca81..416f977347 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -11,12 +11,16 @@ namespace Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Event\GetResponseEvent; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\Exception\LogoutException; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface; @@ -34,19 +38,25 @@ class LogoutListener implements ListenerInterface private $handlers; private $successHandler; private $httpUtils; - private $csrfTokenGenerator; + private $csrfTokenManager; /** * Constructor. * * @param SecurityContextInterface $securityContext - * @param HttpUtils $httpUtils An HttpUtilsInterface instance - * @param LogoutSuccessHandlerInterface $successHandler A LogoutSuccessHandlerInterface instance - * @param array $options An array of options to process a logout attempt - * @param CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface instance + * @param HttpUtils $httpUtils An HttpUtilsInterface instance + * @param LogoutSuccessHandlerInterface $successHandler A LogoutSuccessHandlerInterface instance + * @param array $options An array of options to process a logout attempt + * @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance */ - public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), CsrfTokenGeneratorInterface $csrfTokenGenerator = null) + public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), $csrfTokenManager = null) { + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); + } + $this->securityContext = $securityContext; $this->httpUtils = $httpUtils; $this->options = array_merge(array( @@ -55,7 +65,7 @@ class LogoutListener implements ListenerInterface 'logout_path' => '/logout', ), $options); $this->successHandler = $successHandler; - $this->csrfTokenGenerator = $csrfTokenGenerator; + $this->csrfTokenManager = $csrfTokenManager; $this->handlers = array(); } @@ -72,7 +82,7 @@ class LogoutListener implements ListenerInterface /** * Performs the logout if requested * - * If a CsrfTokenGeneratorInterface instance is available, it will be used to + * If a CsrfTokenManagerInterface instance is available, it will be used to * validate the request. * * @param GetResponseEvent $event A GetResponseEvent instance @@ -88,10 +98,10 @@ class LogoutListener implements ListenerInterface return; } - if (null !== $this->csrfTokenGenerator) { + if (null !== $this->csrfTokenManager) { $csrfToken = $request->get($this->options['csrf_parameter'], null, true); - if (false === $this->csrfTokenGenerator->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) { throw new LogoutException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php index c09cbdbc74..f79ce5c412 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -12,9 +12,13 @@ namespace Symfony\Component\Security\Http\Firewall; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; @@ -30,7 +34,7 @@ use Psr\Log\LoggerInterface; class SimpleFormAuthenticationListener extends AbstractAuthenticationListener { private $simpleAuthenticator; - private $csrfTokenGenerator; + private $csrfTokenManager; /** * Constructor. @@ -47,16 +51,22 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener * @param LoggerInterface $logger A LoggerInterface instance * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance * @param SimpleFormAuthenticatorInterface $simpleAuthenticator A SimpleFormAuthenticatorInterface instance - * @param CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface instance + * @param CsrfTokenManagerInterface $csrfTokenManager A CsrfTokenManagerInterface instance */ - public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenGeneratorInterface $csrfTokenGenerator = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null) + public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenManagerInterface $csrfTokenManager = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null) { if (!$simpleAuthenticator) { throw new \InvalidArgumentException('Missing simple authenticator'); } + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); + } + $this->simpleAuthenticator = $simpleAuthenticator; - $this->csrfTokenGenerator = $csrfTokenGenerator; + $this->csrfTokenManager = $csrfTokenManager; $options = array_merge(array( 'username_parameter' => '_username', @@ -85,10 +95,10 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener */ protected function attemptAuthentication(Request $request) { - if (null !== $this->csrfTokenGenerator) { + if (null !== $this->csrfTokenManager) { $csrfToken = $request->get($this->options['csrf_parameter'], null, true); - if (false === $this->csrfTokenGenerator->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php index 7c42decc45..f24d2163f1 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -11,15 +11,19 @@ namespace Symfony\Component\Security\Http\Firewall; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderAdapter; +use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\HttpFoundation\Request; use Psr\Log\LoggerInterface; -use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfToken; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Exception\InvalidArgumentException; use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -32,13 +36,19 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; */ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationListener { - private $csrfTokenGenerator; + private $csrfTokenManager; /** * {@inheritdoc} */ - public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) + public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, $csrfTokenManager = null) { + if ($csrfTokenManager instanceof CsrfProviderInterface) { + $csrfTokenManager = new CsrfProviderAdapter($csrfTokenManager); + } elseif (null !== $csrfTokenManager && !$csrfTokenManager instanceof CsrfTokenManagerInterface) { + throw new InvalidArgumentException('The CSRF token manager should be an instance of CsrfProviderInterface or CsrfTokenManagerInterface.'); + } + parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array( 'username_parameter' => '_username', 'password_parameter' => '_password', @@ -47,7 +57,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL 'post_only' => true, ), $options), $logger, $dispatcher); - $this->csrfTokenGenerator = $csrfTokenGenerator; + $this->csrfTokenManager = $csrfTokenManager; } /** @@ -67,10 +77,10 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL */ protected function attemptAuthentication(Request $request) { - if (null !== $this->csrfTokenGenerator) { + if (null !== $this->csrfTokenManager) { $csrfToken = $request->get($this->options['csrf_parameter'], null, true); - if (false === $this->csrfTokenGenerator->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + if (false === $this->csrfTokenManager->isTokenValid(new CsrfToken($this->options['intention'], $csrfToken))) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } }