diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index 85cb84a8cc..be37fabc2c 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -168,6 +168,13 @@ UPGRADE FROM 2.x to 3.0 `ChoiceListInterface::getChoicesForValues()` and `ChoiceListInterface::getValuesForChoices()` should be sufficient. + * 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. + + * The options "csrf_provider" and "intention" were renamed to "csrf_token_generator" + and "csrf_token_id". + ### FrameworkBundle diff --git a/composer.json b/composer.json index 9b7b512c9a..0ce3d9622a 100644 --- a/composer.json +++ b/composer.json @@ -52,6 +52,7 @@ "symfony/security": "self.version", "symfony/security-acl": "self.version", "symfony/security-core": "self.version", + "symfony/security-csrf": "self.version", "symfony/security-http": "self.version", "symfony/security-bundle": "self.version", "symfony/serializer": "self.version", diff --git a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php index 72798d103f..5727f626cc 100644 --- a/src/Symfony/Bridge/Twig/Form/TwigRenderer.php +++ b/src/Symfony/Bridge/Twig/Form/TwigRenderer.php @@ -12,7 +12,7 @@ namespace Symfony\Bridge\Twig\Form; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; /** * @author Bernhard Schussek @@ -24,9 +24,9 @@ class TwigRenderer extends FormRenderer implements TwigRendererInterface */ private $engine; - public function __construct(TwigRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null) + public function __construct(TwigRendererEngineInterface $engine, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) { - parent::__construct($engine, $csrfProvider); + parent::__construct($engine, $csrfTokenGenerator); $this->engine = $engine; } diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 5797586731..766ada5e1b 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=5.3.3", + "symfony/security-csrf": "~2.4", "twig/twig": "~1.11" }, "require-dev": { @@ -26,7 +27,7 @@ "symfony/templating": "~2.1", "symfony/translation": "~2.2", "symfony/yaml": "~2.0", - "symfony/security": "~2.0", + "symfony/security": "~2.4", "symfony/stopwatch": "~2.2" }, "suggest": { diff --git a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md index dbeaf3ab8f..338139128b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md @@ -6,6 +6,8 @@ CHANGELOG * allowed multiple IP addresses in profiler matcher settings * added stopwatch helper to time templates with the WebProfilerBundle + * added service definition for "security.secure_random" service + * added service definitions for the new Security CSRF sub-component 2.3.0 ----- diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 74fe18e91d..915269919c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -56,6 +56,10 @@ class FrameworkExtension extends Extension $loader->load('debug_prod.xml'); + // Enable services for CSRF protection (even without forms) + $loader->load('security.xml'); + $loader->load('security_csrf.xml'); + if ($container->getParameter('kernel.debug')) { $loader->load('debug.xml'); @@ -158,9 +162,7 @@ class FrameworkExtension extends Extension if (!isset($config['session'])) { throw new \LogicException('CSRF protection needs that sessions are enabled.'); } - if (!isset($config['secret'])) { - throw new \LogicException('CSRF protection needs a secret to be set.'); - } + $loader->load('form_csrf.xml'); $container->setParameter('form.type_extension.csrf.enabled', true); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml index 57cad204aa..6d9ff2e046 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml @@ -4,15 +4,8 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd"> - - Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider - - - - - %kernel.secret% - + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml new file mode 100644 index 0000000000..2b6307a9ef --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security.xml @@ -0,0 +1,19 @@ + + + + + + Symfony\Component\Security\Core\Util\SecureRandom + + + + + + + %kernel.cache_dir%/secure_random.seed + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml new file mode 100644 index 0000000000..b83bf2402a --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/security_csrf.xml @@ -0,0 +1,22 @@ + + + + + + Symfony\Component\Security\Csrf\CsrfTokenGenerator + Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php index 060a6ce6bf..7bfa1e72bf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/FormHelper.php @@ -247,7 +247,7 @@ class FormHelper extends Helper * Check the token in your action using the same intention. * * - * $csrfProvider = $this->get('form.csrf_provider'); + * $csrfProvider = $this->get('security.csrf.token_generator'); * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { * throw new \RuntimeException('CSRF attack detected.'); * } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php index 3aef0dc6a0..285679a7ae 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php @@ -30,7 +30,6 @@ abstract class FrameworkExtensionTest extends TestCase $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(1)); $this->assertEquals('_csrf', $container->getParameter('form.type_extension.csrf.field_name')); $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(2)); - $this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1))); } public function testProxies() diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/FormHelperDivLayoutTest.php index 75f12ed789..fa526d0aea 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->csrfProvider, array( + new TemplatingExtension($this->engine, $this->csrfTokenGenerator, 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 e052c6f4e4..5a917f66a6 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->csrfProvider, array( + new TemplatingExtension($this->engine, $this->csrfTokenGenerator, array( 'FrameworkBundle:Form', 'FrameworkBundle:FormTable', )), diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 5af039dddb..ede2bdf3b0 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -23,6 +23,8 @@ "symfony/http-kernel": "~2.3", "symfony/filesystem": "~2.3", "symfony/routing": "~2.2", + "symfony/security-core": "~2.4", + "symfony/security-csrf": "~2.4", "symfony/stopwatch": "~2.3", "symfony/templating": "~2.1", "symfony/translation": "~2.3", @@ -30,7 +32,7 @@ }, "require-dev": { "symfony/finder": "~2.0", - "symfony/security": "~2.3", + "symfony/security": "~2.4", "symfony/form": "~2.3", "symfony/class-loader": "~2.1", "symfony/validator": "~2.1" diff --git a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md index 06d56b3361..5ff5a77d4e 100644 --- a/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md +++ b/src/Symfony/Bundle/SecurityBundle/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG ----- * Added 'host' option to firewall configuration + * Moved 'security.secure_random' service configuration to FrameworkBundle 2.3.0 ----- @@ -79,9 +80,9 @@ CHANGELOG logout: path: /logout_path target: / - csrf_parameter: _csrf_token # Optional (defaults to "_csrf_token") - csrf_provider: form.csrf_provider # Required to enable protection - intention: logout # Optional (defaults to "logout") + csrf_parameter: _csrf_token # Optional (defaults to "_csrf_token") + csrf_provider: security.csrf.token_generator # Required to enable protection + intention: logout # Optional (defaults to "logout") ``` If the LogoutListener has CSRF protection enabled but cannot validate a token, diff --git a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml index 82c98d815a..d90f3206db 100644 --- a/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml +++ b/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml @@ -151,12 +151,5 @@ - - - - - %kernel.cache_dir%/secure_random.seed - - diff --git a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php index c7135f54e1..7900c15f11 100644 --- a/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php +++ b/src/Symfony/Bundle/SecurityBundle/Templating/Helper/LogoutUrlHelper.php @@ -12,8 +12,8 @@ namespace Symfony\Bundle\SecurityBundle\Templating\Helper; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; use Symfony\Component\Templating\Helper\Helper; /** @@ -43,15 +43,15 @@ 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 $intention The intention for CSRF token generation - * @param string $csrfParameter The CSRF token parameter name - * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface 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 CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface instance */ - public function registerListener($key, $logoutPath, $intention, $csrfParameter, CsrfProviderInterface $csrfProvider = null) + public function registerListener($key, $logoutPath, $csrfTokenId, $csrfParameter, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) { - $this->listeners[$key] = array($logoutPath, $intention, $csrfParameter, $csrfProvider); + $this->listeners[$key] = array($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenGenerator); } /** @@ -94,9 +94,9 @@ class LogoutUrlHelper extends Helper throw new \InvalidArgumentException(sprintf('No LogoutListener found for firewall key "%s".', $key)); } - list($logoutPath, $intention, $csrfParameter, $csrfProvider) = $this->listeners[$key]; + list($logoutPath, $csrfTokenId, $csrfParameter, $csrfTokenGenerator) = $this->listeners[$key]; - $parameters = null !== $csrfProvider ? array($csrfParameter => $csrfProvider->generateCsrfToken($intention)) : array(); + $parameters = null !== $csrfTokenGenerator ? array($csrfParameter => $csrfTokenGenerator->generateCsrfToken($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 e0347e1dc4..ee41001d7c 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: form.csrf_provider + csrf_provider: security.csrf.token_generator anonymous: ~ logout: path: /logout_path target: / - csrf_provider: form.csrf_provider + csrf_provider: security.csrf.token_generator access_control: - { path: .*, roles: IS_AUTHENTICATED_FULLY } diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index dbeeb18daa..0d8aab43c0 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -17,13 +17,12 @@ ], "require": { "php": ">=5.3.3", - "symfony/security": "~2.2", + "symfony/security": "~2.4", "symfony/http-kernel": "~2.2" }, "require-dev": { "symfony/framework-bundle": "~2.2", "symfony/twig-bundle": "~2.2", - "symfony/form": "~2.1", "symfony/validator": "~2.2", "symfony/yaml": "~2.0", "symfony/expression-language": "~2.4" diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index a696c7be9a..0e15c157ae 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -1,9 +1,15 @@ CHANGELOG ========= +2.4.0 +----- + + * moved CSRF implementation to the new Security CSRF sub-component + * deprecated CsrfProviderInterface and its implementations + * deprecated options "csrf_provider" and "intention" in favor of the new options "csrf_token_generator" and "csrf_token_id" 2.3.0 ------- +----- * deprecated FormPerformanceTestCase and FormIntegrationTestCase in the Symfony\Component\Form\Tests namespace and moved them to the Symfony\Component\Form\Test namespace * deprecated TypeTestCase in the Symfony\Component\Form\Tests\Extension\Core\Type namespace and moved it to the Symfony\Component\Form\Test namespace diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php index f9d9e40aa8..bdaa94fe36 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php @@ -12,8 +12,8 @@ namespace Symfony\Component\Form\Extension\Csrf; use Symfony\Component\Form\Extension\Csrf\Type; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\AbstractExtension; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; use Symfony\Component\Translation\TranslatorInterface; /** @@ -24,9 +24,9 @@ use Symfony\Component\Translation\TranslatorInterface; class CsrfExtension extends AbstractExtension { /** - * @var CsrfProviderInterface + * @var CsrfTokenGeneratorInterface */ - private $csrfProvider; + private $tokenGenerator; /** * @var TranslatorInterface @@ -41,13 +41,13 @@ class CsrfExtension extends AbstractExtension /** * Constructor. * - * @param CsrfProviderInterface $csrfProvider The CSRF provider - * @param TranslatorInterface $translator The translator for translating error messages. - * @param null|string $translationDomain The translation domain for translating. + * @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 */ - public function __construct(CsrfProviderInterface $csrfProvider, TranslatorInterface $translator = null, $translationDomain = null) + public function __construct(CsrfTokenGeneratorInterface $tokenGenerator, TranslatorInterface $translator = null, $translationDomain = null) { - $this->csrfProvider = $csrfProvider; + $this->tokenGenerator = $tokenGenerator; $this->translator = $translator; $this->translationDomain = $translationDomain; } @@ -58,7 +58,7 @@ class CsrfExtension extends AbstractExtension protected function loadTypeExtensions() { return array( - new Type\FormTypeCsrfExtension($this->csrfProvider, true, '_token', $this->translator, $this->translationDomain), + new Type\FormTypeCsrfExtension($this->tokenGenerator, true, '_token', $this->translator, $this->translationDomain), ); } } diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php index 7143b130c8..1f10f56b68 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.php @@ -11,39 +11,16 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; +use Symfony\Component\Security\Csrf\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(). + * Alias interface of {@link CsrfTokenGeneratorInterface}. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use + * {@link CsrfTokenGeneratorInterface} instead. */ -interface CsrfProviderInterface +interface CsrfProviderInterface extends CsrfTokenGeneratorInterface { - /** - * 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. - */ - 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 new file mode 100644 index 0000000000..07af17d237 --- /dev/null +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfTokenGeneratorAdapter.php @@ -0,0 +1,26 @@ + + * + * 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/DefaultCsrfProvider.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php index 5354886cba..9fb971f966 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php @@ -18,6 +18,11 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider; * user-defined secret value to secure the CSRF token. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use + * {@link \Symfony\Component\Security\Csrf\CsrfTokenGenerator} in + * combination with {@link \Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage} + * instead. */ class DefaultCsrfProvider implements CsrfProviderInterface { diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php index ea1fa58547..84381adfc8 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php +++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/SessionCsrfProvider.php @@ -20,6 +20,11 @@ use Symfony\Component\HttpFoundation\Session\Session; * @see DefaultCsrfProvider * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use + * {@link \Symfony\Component\Security\Csrf\CsrfTokenGenerator} in + * combination with {@link \Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage} + * instead. */ class SessionCsrfProvider extends DefaultCsrfProvider { diff --git a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php index 547e9d756c..70665f0fb7 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php +++ b/src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php @@ -15,7 +15,7 @@ use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormEvent; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; use Symfony\Component\Translation\TranslatorInterface; /** @@ -30,20 +30,20 @@ class CsrfValidationListener implements EventSubscriberInterface private $fieldName; /** - * The provider for generating and validating CSRF tokens - * @var CsrfProviderInterface + * The generator for CSRF tokens + * @var CsrfTokenGeneratorInterface */ - private $csrfProvider; + private $tokenGenerator; /** - * A text mentioning the intention of the CSRF token + * A text mentioning the tokenId of the CSRF token * * Validation of the token will only succeed if it was generated in the - * same session and with the same intention. + * same session and with the same tokenId. * * @var string */ - private $intention; + private $tokenId; /** * The message displayed in case of an error. @@ -68,11 +68,11 @@ class CsrfValidationListener implements EventSubscriberInterface ); } - public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) + public function __construct($fieldName, CsrfTokenGeneratorInterface $tokenGenerator, $tokenId, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null) { $this->fieldName = $fieldName; - $this->csrfProvider = $csrfProvider; - $this->intention = $intention; + $this->tokenGenerator = $tokenGenerator; + $this->tokenId = $tokenId; $this->errorMessage = $errorMessage; $this->translator = $translator; $this->translationDomain = $translationDomain; @@ -84,7 +84,7 @@ class CsrfValidationListener implements EventSubscriberInterface $data = $event->getData(); if ($form->isRoot() && $form->getConfig()->getOption('compound')) { - if (!isset($data[$this->fieldName]) || !$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) { + if (!isset($data[$this->fieldName]) || !$this->tokenGenerator->isCsrfTokenValid($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 5fb2444262..1958c0f266 100644 --- a/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php +++ b/src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php @@ -12,12 +12,13 @@ namespace Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\AbstractTypeExtension; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; 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\Translation\TranslatorInterface; /** @@ -26,9 +27,9 @@ use Symfony\Component\Translation\TranslatorInterface; class FormTypeCsrfExtension extends AbstractTypeExtension { /** - * @var CsrfProviderInterface + * @var CsrfTokenGeneratorInterface */ - private $defaultCsrfProvider; + private $defaultTokenGenerator; /** * @var Boolean @@ -50,9 +51,9 @@ class FormTypeCsrfExtension extends AbstractTypeExtension */ private $translationDomain; - public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) + public function __construct(CsrfTokenGeneratorInterface $defaultTokenGenerator, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null) { - $this->defaultCsrfProvider = $defaultCsrfProvider; + $this->defaultTokenGenerator = $defaultTokenGenerator; $this->defaultEnabled = $defaultEnabled; $this->defaultFieldName = $defaultFieldName; $this->translator = $translator; @@ -74,8 +75,8 @@ class FormTypeCsrfExtension extends AbstractTypeExtension $builder ->addEventSubscriber(new CsrfValidationListener( $options['csrf_field_name'], - $options['csrf_provider'], - $options['intention'], + $options['csrf_token_generator'], + $options['csrf_token_id'], $options['csrf_message'], $this->translator, $this->translationDomain @@ -94,7 +95,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension { if ($options['csrf_protection'] && !$view->parent && $options['compound']) { $factory = $form->getConfig()->getFormFactory(); - $data = $options['csrf_provider']->generateCsrfToken($options['intention']); + $data = $options['csrf_token_generator']->generateCsrfToken($options['csrf_token_id']); $csrfForm = $factory->createNamed($options['csrf_field_name'], 'hidden', $data, array( 'mapped' => false, @@ -109,12 +110,24 @@ class FormTypeCsrfExtension extends AbstractTypeExtension */ public function setDefaultOptions(OptionsResolverInterface $resolver) { + // BC clause for the "intention" option + $csrfTokenId = function (Options $options) { + return $options['intention']; + }; + + // BC clause for the "csrf_provider" option + $csrfTokenGenerator = function (Options $options) { + return $options['csrf_provider']; + }; + $resolver->setDefaults(array( - 'csrf_protection' => $this->defaultEnabled, - 'csrf_field_name' => $this->defaultFieldName, - 'csrf_provider' => $this->defaultCsrfProvider, - 'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.', - '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_generator' => $csrfTokenGenerator, + 'csrf_token_id' => $csrfTokenId, + 'csrf_provider' => $this->defaultTokenGenerator, + 'intention' => 'unknown', )); } diff --git a/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php b/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php index 573cb518d4..e3b239873e 100644 --- a/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php +++ b/src/Symfony/Component/Form/Extension/Templating/TemplatingExtension.php @@ -13,7 +13,7 @@ namespace Symfony\Component\Form\Extension\Templating; use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Form\FormRenderer; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; use Symfony\Component\Templating\PhpEngine; use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper; @@ -24,10 +24,10 @@ use Symfony\Bundle\FrameworkBundle\Templating\Helper\FormHelper; */ class TemplatingExtension extends AbstractExtension { - public function __construct(PhpEngine $engine, CsrfProviderInterface $csrfProvider = null, array $defaultThemes = array()) + public function __construct(PhpEngine $engine, CsrfTokenGeneratorInterface $csrfTokenGenerator = null, array $defaultThemes = array()) { $engine->addHelpers(array( - new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfProvider)) + new FormHelper(new FormRenderer(new TemplatingRendererEngine($engine, $defaultThemes), $csrfTokenGenerator)) )); } } diff --git a/src/Symfony/Component/Form/FormRenderer.php b/src/Symfony/Component/Form/FormRenderer.php index 09b010563f..59ccc0c31a 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\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; /** * Renders a form into HTML using a rendering engine. @@ -30,9 +30,9 @@ class FormRenderer implements FormRendererInterface private $engine; /** - * @var CsrfProviderInterface + * @var CsrfTokenGeneratorInterface */ - private $csrfProvider; + private $csrfTokenGenerator; /** * @var array @@ -49,10 +49,10 @@ class FormRenderer implements FormRendererInterface */ private $variableStack = array(); - public function __construct(FormRendererEngineInterface $engine, CsrfProviderInterface $csrfProvider = null) + public function __construct(FormRendererEngineInterface $engine, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) { $this->engine = $engine; - $this->csrfProvider = $csrfProvider; + $this->csrfTokenGenerator = $csrfTokenGenerator; } /** @@ -74,13 +74,13 @@ class FormRenderer implements FormRendererInterface /** * {@inheritdoc} */ - public function renderCsrfToken($intention) + public function renderCsrfToken($tokenId) { - if (null === $this->csrfProvider) { - throw new BadMethodCallException('CSRF token can only be generated if a CsrfProviderInterface is injected in the constructor.'); + if (null === $this->csrfTokenGenerator) { + throw new BadMethodCallException('CSRF tokens can only be generated if a CsrfTokenGeneratorInterface is injected in FormRenderer::__construct().'); } - return $this->csrfProvider->generateCsrfToken($intention); + return $this->csrfTokenGenerator->generateCsrfToken($tokenId); } /** diff --git a/src/Symfony/Component/Form/FormRendererInterface.php b/src/Symfony/Component/Form/FormRendererInterface.php index 848e7125ca..7cf24c0b00 100644 --- a/src/Symfony/Component/Form/FormRendererInterface.php +++ b/src/Symfony/Component/Form/FormRendererInterface.php @@ -73,20 +73,20 @@ interface FormRendererInterface * * * - * Check the token in your action using the same intention. + * Check the token in your action using the same token ID. * * - * $csrfProvider = $this->get('form.csrf_provider'); + * $csrfProvider = $this->get('security.csrf.token_generator'); * if (!$csrfProvider->isCsrfTokenValid('rm_user_'.$user->getId(), $token)) { * throw new \RuntimeException('CSRF attack detected.'); * } * * - * @param string $intention The intention of the protected action + * @param string $tokenId The ID of the CSRF token * * @return string A CSRF token */ - public function renderCsrfToken($intention); + public function renderCsrfToken($tokenId); /** * Makes a technical name human readable. diff --git a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php index 9e1fb8663a..7395c7e097 100644 --- a/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php @@ -471,7 +471,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest public function testCsrf() { - $this->csrfProvider->expects($this->any()) + $this->csrfTokenGenerator->expects($this->any()) ->method('generateCsrfToken') ->will($this->returnValue('foo&bar')); diff --git a/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractLayoutTest.php index fc8317e9e3..c35235b862 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 $csrfProvider; + protected $csrfTokenGenerator; protected function setUp() { @@ -27,7 +27,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg \Locale::setDefault('en'); - $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); + $this->csrfTokenGenerator = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface'); parent::setUp(); } @@ -35,13 +35,13 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg protected function getExtensions() { return array( - new CsrfExtension($this->csrfProvider), + new CsrfExtension($this->csrfTokenGenerator), ); } protected function tearDown() { - $this->csrfProvider = null; + $this->csrfTokenGenerator = null; parent::tearDown(); } diff --git a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php index 70e1c7ccbc..f43188a914 100644 --- a/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php +++ b/src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php @@ -336,7 +336,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest public function testCsrf() { - $this->csrfProvider->expects($this->any()) + $this->csrfTokenGenerator->expects($this->any()) ->method('generateCsrfToken') ->will($this->returnValue('foo&bar')); 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 7f2220af72..b3e45ab984 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/CsrfValidationListenerTest.php @@ -19,13 +19,14 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase { protected $dispatcher; protected $factory; - protected $csrfProvider; + protected $tokenGenerator; + protected $form; protected function setUp() { $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface'); - $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); + $this->tokenGenerator = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface'); $this->form = $this->getBuilder('post') ->setDataMapper($this->getDataMapper()) ->getForm(); @@ -35,7 +36,7 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase { $this->dispatcher = null; $this->factory = null; - $this->csrfProvider = null; + $this->tokenGenerator = null; $this->form = null; } @@ -65,7 +66,7 @@ class CsrfValidationListenerTest extends \PHPUnit_Framework_TestCase $data = "XP4HUzmHPi"; $event = new FormEvent($this->form, $data); - $validation = new CsrfValidationListener('csrf', $this->csrfProvider, 'unknown', 'Invalid.'); + $validation = new CsrfValidationListener('csrf', $this->tokenGenerator, '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 0a1f0dc481..ccd7dccbca 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php @@ -37,7 +37,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $csrfProvider; + protected $tokenGenerator; /** * @var \PHPUnit_Framework_MockObject_MockObject @@ -46,7 +46,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase protected function setUp() { - $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); + $this->tokenGenerator = $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface'); $this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface'); parent::setUp(); @@ -54,7 +54,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase protected function tearDown() { - $this->csrfProvider = null; + $this->tokenGenerator = null; $this->translator = null; parent::tearDown(); @@ -63,7 +63,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase protected function getExtensions() { return array_merge(parent::getExtensions(), array( - new CsrfExtension($this->csrfProvider, $this->translator), + new CsrfExtension($this->tokenGenerator, $this->translator), )); } @@ -123,7 +123,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testGenerateCsrfToken() { - $this->csrfProvider->expects($this->once()) + $this->tokenGenerator->expects($this->once()) ->method('generateCsrfToken') ->with('%INTENTION%') ->will($this->returnValue('token')); @@ -131,7 +131,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase $view = $this->factory ->create('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->csrfProvider, + 'csrf_provider' => $this->tokenGenerator, 'intention' => '%INTENTION%', 'compound' => true, )) @@ -153,7 +153,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase */ public function testValidateTokenOnSubmitIfRootAndCompound($valid) { - $this->csrfProvider->expects($this->once()) + $this->tokenGenerator->expects($this->once()) ->method('isCsrfTokenValid') ->with('%INTENTION%', 'token') ->will($this->returnValue($valid)); @@ -161,7 +161,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase $form = $this->factory ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->csrfProvider, + 'csrf_provider' => $this->tokenGenerator, 'intention' => '%INTENTION%', 'compound' => true, )) @@ -182,13 +182,13 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testFailIfRootAndCompoundAndTokenMissing() { - $this->csrfProvider->expects($this->never()) + $this->tokenGenerator->expects($this->never()) ->method('isCsrfTokenValid'); $form = $this->factory ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->csrfProvider, + 'csrf_provider' => $this->tokenGenerator, 'intention' => '%INTENTION%', 'compound' => true, )) @@ -209,7 +209,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testDontValidateTokenIfCompoundButNoRoot() { - $this->csrfProvider->expects($this->never()) + $this->tokenGenerator->expects($this->never()) ->method('isCsrfTokenValid'); $form = $this->factory @@ -217,7 +217,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase ->add($this->factory ->createNamedBuilder('form', 'form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->csrfProvider, + 'csrf_provider' => $this->tokenGenerator, 'intention' => '%INTENTION%', 'compound' => true, )) @@ -233,13 +233,13 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testDontValidateTokenIfRootButNotCompound() { - $this->csrfProvider->expects($this->never()) + $this->tokenGenerator->expects($this->never()) ->method('isCsrfTokenValid'); $form = $this->factory ->create('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->csrfProvider, + 'csrf_provider' => $this->tokenGenerator, 'intention' => '%INTENTION%', 'compound' => false, )); @@ -269,7 +269,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase public function testsTranslateCustomErrorMessage() { - $this->csrfProvider->expects($this->once()) + $this->tokenGenerator->expects($this->once()) ->method('isCsrfTokenValid') ->with('%INTENTION%', 'token') ->will($this->returnValue(false)); @@ -282,7 +282,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase $form = $this->factory ->createBuilder('form', null, array( 'csrf_field_name' => 'csrf', - 'csrf_provider' => $this->csrfProvider, + 'csrf_provider' => $this->tokenGenerator, 'csrf_message' => 'Foobar', 'intention' => '%INTENTION%', 'compound' => true, diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index dda5959c9a..8e6f419b68 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -24,11 +24,14 @@ }, "require-dev": { "symfony/validator": "~2.2", - "symfony/http-foundation": "~2.2" + "symfony/http-foundation": "~2.2", + "symfony/security-csrf": "~2.4" }, "suggest": { - "symfony/validator": "", - "symfony/http-foundation": "" + "symfony/validator": "For form validation.", + "symfony/security-csrf": "For protecting forms against CSRF attacks.", + "symfony/twig-bridge": "For templating with Twig.", + "symfony/framework-bundle": "For templating with PHP." }, "autoload": { "psr-0": { "Symfony\\Component\\Form\\": "" } diff --git a/src/Symfony/Component/Security/CHANGELOG.md b/src/Symfony/Component/Security/CHANGELOG.md index 680205542a..9ab61d0bb4 100644 --- a/src/Symfony/Component/Security/CHANGELOG.md +++ b/src/Symfony/Component/Security/CHANGELOG.md @@ -7,6 +7,9 @@ CHANGELOG * The switch user listener now preserves the query string when switching a user * The remember-me cookie hashes now use HMAC, which means that current cookies will be invalidated * added simpler customization options + * structured component into three sub-components Acl, Core and Http + * added Csrf sub-component + * changed Http sub-component to depend on Csrf sub-component instead of the Form component 2.3.0 ----- diff --git a/src/Symfony/Component/Security/Csrf/.gitignore b/src/Symfony/Component/Security/Csrf/.gitignore new file mode 100644 index 0000000000..c49a5d8df5 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php b/src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php new file mode 100644 index 0000000000..8ff346284b --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php @@ -0,0 +1,105 @@ + + * + * 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/CsrfTokenGeneratorInterface.php b/src/Symfony/Component/Security/Csrf/CsrfTokenGeneratorInterface.php new file mode 100644 index 0000000000..c34549f11d --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/CsrfTokenGeneratorInterface.php @@ -0,0 +1,52 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf; + +/** + * Generates and validates CSRF tokens. + * + * You can generate a CSRF token by using the method {@link generateCsrfToken()}. + * This method expects a unique token ID as argument. The token ID can later be + * used to validate a token provided by the user. + * + * Token IDs do not necessarily have to be secret, but they should NEVER be + * created from data provided by the client. A good practice is to hard-code the + * token IDs for the various CSRF tokens used by your application. + * + * You should use the method {@link isCsrfTokenValid()} to check a CSRF token + * submitted by the client. This method will return true if the CSRF token is + * valid. + * + * @since 2.4 + * @author Bernhard Schussek + */ +interface CsrfTokenGeneratorInterface +{ + /** + * Generates a CSRF token with the given token ID. + * + * @param string $tokenId An ID that identifies the 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); +} diff --git a/src/Symfony/Component/Security/Csrf/LICENSE b/src/Symfony/Component/Security/Csrf/LICENSE new file mode 100644 index 0000000000..88a57f8d8d --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2013 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Security/Csrf/README.md b/src/Symfony/Component/Security/Csrf/README.md new file mode 100644 index 0000000000..394a29ee8f --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/README.md @@ -0,0 +1,21 @@ +Security Component - CSRF +========================= + +The Security CSRF (cross-site request forgery) component provides a class +`CsrfTokenGenerator` for generating and validating CSRF tokens. + +Resources +--------- + +Documentation: + +http://symfony.com/doc/2.4/book/security.html + +Tests +----- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Security/Csrf/ + $ composer.phar install --dev + $ phpunit diff --git a/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenGeneratorTest.php b/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenGeneratorTest.php new file mode 100644 index 0000000000..f5f95078ab --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/Tests/CsrfTokenGeneratorTest.php @@ -0,0 +1,148 @@ + + * + * 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/TokenStorage/NativeSessionTokenStorageTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php new file mode 100644 index 0000000000..69df0612fd --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php @@ -0,0 +1,99 @@ + + * + * 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\TokenStorage\NativeSessionTokenStorage; + +/** + * @author Bernhard Schussek + * + * @runTestsInSeparateProcesses + */ +class NativeSessionTokenStorageTest extends \PHPUnit_Framework_TestCase +{ + const SESSION_NAMESPACE = 'foobar'; + + /** + * @var NativeSessionTokenStorage + */ + private $storage; + + public static function setUpBeforeClass() + { + ini_set('session.save_handler', 'files'); + ini_set('session.save_path', sys_get_temp_dir()); + + parent::setUpBeforeClass(); + } + + protected function setUp() + { + $_SESSION = array(); + + $this->storage = new NativeSessionTokenStorage(self::SESSION_NAMESPACE); + } + + public function testStoreTokenInClosedSession() + { + $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertSame(array(self::SESSION_NAMESPACE => array('token_id' => 'TOKEN')), $_SESSION); + } + + public function testStoreTokenInClosedSessionWithExistingSessionId() + { + session_id('foobar'); + + $this->assertSame(PHP_SESSION_NONE, session_status()); + + $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertSame(PHP_SESSION_ACTIVE, session_status()); + $this->assertSame(array(self::SESSION_NAMESPACE => array('token_id' => 'TOKEN')), $_SESSION); + } + + public function testStoreTokenInActiveSession() + { + session_start(); + + $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertSame(array(self::SESSION_NAMESPACE => array('token_id' => 'TOKEN')), $_SESSION); + } + + /** + * @depends testStoreTokenInClosedSession + */ + public function testCheckToken() + { + $this->assertFalse($this->storage->hasToken('token_id')); + + $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertTrue($this->storage->hasToken('token_id')); + } + + /** + * @depends testStoreTokenInClosedSession + */ + public function testGetExistingToken() + { + $this->storage->setToken('token_id', 'TOKEN'); + + $this->assertSame('TOKEN', $this->storage->getToken('token_id')); + } + + public function testGetNonExistingToken() + { + $this->assertSame('DEFAULT', $this->storage->getToken('token_id', 'DEFAULT')); + } +} diff --git a/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php new file mode 100644 index 0000000000..5c8c173d0a --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php @@ -0,0 +1,144 @@ + + * + * 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\TokenStorage\SessionTokenStorage; + +/** + * @author Bernhard Schussek + */ +class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase +{ + const SESSION_NAMESPACE = 'foobar'; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $session; + + /** + * @var SessionTokenStorage + */ + private $storage; + + protected function setUp() + { + if (!class_exists('Symfony\Component\HttpFoundation\Session\SessionInterface')) { + $this->markTestSkipped('The "HttpFoundation" component is not available'); + } + + $this->session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface') + ->disableOriginalConstructor() + ->getMock(); + $this->storage = new SessionTokenStorage($this->session, self::SESSION_NAMESPACE); + } + + public function testStoreTokenInClosedSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $this->session->expects($this->once()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('set') + ->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN'); + + $this->storage->setToken('token_id', 'TOKEN'); + } + + public function testStoreTokenInActiveSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(true)); + + $this->session->expects($this->never()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('set') + ->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN'); + + $this->storage->setToken('token_id', 'TOKEN'); + } + + public function testCheckTokenInClosedSession() + { + $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('RESULT')); + + $this->assertSame('RESULT', $this->storage->hasToken('token_id')); + } + + public function testCheckTokenInActiveSession() + { + $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('RESULT')); + + $this->assertSame('RESULT', $this->storage->hasToken('token_id')); + } + + public function testGetTokenFromClosedSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(false)); + + $this->session->expects($this->once()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('get') + ->with(self::SESSION_NAMESPACE.'/token_id', 'DEFAULT') + ->will($this->returnValue('RESULT')); + + $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT')); + } + + public function testGetTokenFromActiveSession() + { + $this->session->expects($this->any()) + ->method('isStarted') + ->will($this->returnValue(true)); + + $this->session->expects($this->never()) + ->method('start'); + + $this->session->expects($this->once()) + ->method('get') + ->with(self::SESSION_NAMESPACE.'/token_id', 'DEFAULT') + ->will($this->returnValue('RESULT')); + + $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT')); + } +} diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php new file mode 100644 index 0000000000..895674391a --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/NativeSessionTokenStorage.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +/** + * Token storage that uses PHP's native session handling. + * + * @since 2.4 + * @author Bernhard Schussek + */ +class NativeSessionTokenStorage implements TokenStorageInterface +{ + /** + * The namespace used to store values in the session. + * @var string + */ + const SESSION_NAMESPACE = '_csrf'; + + /** + * @var Boolean + */ + private $sessionStarted = false; + + /** + * @var string + */ + private $namespace; + + /** + * Initializes the storage with a session namespace. + * + * @param string $namespace The namespace under which the token is stored + * in the session + */ + public function __construct($namespace = self::SESSION_NAMESPACE) + { + $this->namespace = $namespace; + } + + /** + * {@inheritdoc} + */ + public function getToken($tokenId, $default = null) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + if (isset($_SESSION[$this->namespace][$tokenId])) { + return $_SESSION[$this->namespace][$tokenId]; + } + + return $default; + } + + /** + * {@inheritdoc} + */ + public function setToken($tokenId, $token) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + $_SESSION[$this->namespace][$tokenId] = $token; + } + + /** + * {@inheritdoc} + */ + public function hasToken($tokenId) + { + if (!$this->sessionStarted) { + $this->startSession(); + } + + return isset($_SESSION[$this->namespace][$tokenId]); + } + + private function startSession() + { + if (version_compare(PHP_VERSION, '5.4', '>=')) { + if (PHP_SESSION_NONE === session_status()) { + session_start(); + } + } elseif (!session_id()) { + session_start(); + } + + $this->sessionStarted = true; + } +} diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php new file mode 100644 index 0000000000..3878e4cbfa --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/SessionTokenStorage.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Token storage that uses a Symfony2 Session object. + * + * @since 2.4 + * @author Bernhard Schussek + */ +class SessionTokenStorage implements TokenStorageInterface +{ + /** + * The namespace used to store values in the session. + * @var string + */ + const SESSION_NAMESPACE = '_csrf'; + + /** + * The user session from which the session ID is returned + * @var SessionInterface + */ + private $session; + + /** + * @var string + */ + private $namespace; + + /** + * Initializes the storage with a Session object and a session namespace. + * + * @param SessionInterface $session The user session + * @param string $namespace The namespace under which the token + * is stored in the session + */ + public function __construct(SessionInterface $session, $namespace = self::SESSION_NAMESPACE) + { + $this->session = $session; + $this->namespace = $namespace; + } + + /** + * {@inheritdoc} + */ + public function getToken($tokenId, $default = null) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + return $this->session->get($this->namespace . '/' . $tokenId, $default); + } + + /** + * {@inheritdoc} + */ + public function setToken($tokenId, $token) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + $this->session->set($this->namespace . '/' . $tokenId, $token); + } + + /** + * {@inheritdoc} + */ + public function hasToken($tokenId) + { + if (!$this->session->isStarted()) { + $this->session->start(); + } + + return $this->session->has($this->namespace . '/' . $tokenId); + } +} diff --git a/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php new file mode 100644 index 0000000000..7dba9e559b --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/TokenStorage/TokenStorageInterface.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Csrf\TokenStorage; + +/** + * Stores CSRF tokens. + * + * @since 2.4 + * @author Bernhard Schussek + */ +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 + */ + public function getToken($tokenId, $default = null); + + /** + * Stores a CSRF token. + * + * @param string $tokenId The token ID + * @param mixed $token The CSRF token + */ + public function setToken($tokenId, $token); + + /** + * 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. + */ + public function hasToken($tokenId); +} diff --git a/src/Symfony/Component/Security/Csrf/composer.json b/src/Symfony/Component/Security/Csrf/composer.json new file mode 100644 index 0000000000..3cfc2b4672 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/security-csrf", + "type": "library", + "description": "Symfony Security Component - CSRF Library", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/security-core": "~2.4" + }, + "require-dev": { + "symfony/http-foundation": "~2.1" + }, + "suggest": { + "symfony/http-foundation": "For using the class SessionTokenStorage." + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Security\\Csrf\\": "" } + }, + "target-dir": "Symfony/Component/Security/Csrf", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + } + } +} diff --git a/src/Symfony/Component/Security/Csrf/phpunit.xml.dist b/src/Symfony/Component/Security/Csrf/phpunit.xml.dist new file mode 100644 index 0000000000..0718c768c5 --- /dev/null +++ b/src/Symfony/Component/Security/Csrf/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + diff --git a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php index 983eab0a31..073a48ca81 100644 --- a/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/LogoutListener.php @@ -11,12 +11,12 @@ namespace Symfony\Component\Security\Http\Firewall; -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\SecurityContextInterface; use Symfony\Component\Security\Core\Exception\LogoutException; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; use Symfony\Component\Security\Http\HttpUtils; use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface; use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface; @@ -34,18 +34,18 @@ class LogoutListener implements ListenerInterface private $handlers; private $successHandler; private $httpUtils; - private $csrfProvider; + private $csrfTokenGenerator; /** * 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 CsrfProviderInterface $csrfProvider A CsrfProviderInterface 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 CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface instance */ - public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), CsrfProviderInterface $csrfProvider = null) + public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), CsrfTokenGeneratorInterface $csrfTokenGenerator = null) { $this->securityContext = $securityContext; $this->httpUtils = $httpUtils; @@ -55,7 +55,7 @@ class LogoutListener implements ListenerInterface 'logout_path' => '/logout', ), $options); $this->successHandler = $successHandler; - $this->csrfProvider = $csrfProvider; + $this->csrfTokenGenerator = $csrfTokenGenerator; $this->handlers = array(); } @@ -72,7 +72,7 @@ class LogoutListener implements ListenerInterface /** * Performs the logout if requested * - * If a CsrfProviderInterface instance is available, it will be used to + * If a CsrfTokenGeneratorInterface instance is available, it will be used to * validate the request. * * @param GetResponseEvent $event A GetResponseEvent instance @@ -88,10 +88,10 @@ class LogoutListener implements ListenerInterface return; } - if (null !== $this->csrfProvider) { + if (null !== $this->csrfTokenGenerator) { $csrfToken = $request->get($this->options['csrf_parameter'], null, true); - if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + if (false === $this->csrfTokenGenerator->isCsrfTokenValid($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 054616bcec..c09cbdbc74 100644 --- a/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/SimpleFormAuthenticationListener.php @@ -13,10 +13,11 @@ namespace Symfony\Component\Security\Http\Firewall; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException; +use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; -use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Http\HttpUtils; @@ -29,7 +30,7 @@ use Psr\Log\LoggerInterface; class SimpleFormAuthenticationListener extends AbstractAuthenticationListener { private $simpleAuthenticator; - private $csrfProvider; + private $csrfTokenGenerator; /** * Constructor. @@ -46,16 +47,16 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener * @param LoggerInterface $logger A LoggerInterface instance * @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance * @param SimpleFormAuthenticatorInterface $simpleAuthenticator A SimpleFormAuthenticatorInterface instance - * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance + * @param CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface 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, CsrfProviderInterface $csrfProvider = 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, CsrfTokenGeneratorInterface $csrfTokenGenerator = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null) { if (!$simpleAuthenticator) { throw new \InvalidArgumentException('Missing simple authenticator'); } $this->simpleAuthenticator = $simpleAuthenticator; - $this->csrfProvider = $csrfProvider; + $this->csrfTokenGenerator = $csrfTokenGenerator; $options = array_merge(array( 'username_parameter' => '_username', @@ -84,10 +85,10 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener */ protected function attemptAuthentication(Request $request) { - if (null !== $this->csrfProvider) { + if (null !== $this->csrfTokenGenerator) { $csrfToken = $request->get($this->options['csrf_parameter'], null, true); - if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + if (false === $this->csrfTokenGenerator->isCsrfTokenValid($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 81c2b374d0..7c42decc45 100644 --- a/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php +++ b/src/Symfony/Component/Security/Http/Firewall/UsernamePasswordFormAuthenticationListener.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Security\Http\Firewall; -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\Http\Authentication\AuthenticationFailureHandlerInterface; use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface; use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface; @@ -32,12 +32,12 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; */ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationListener { - private $csrfProvider; + private $csrfTokenGenerator; /** * {@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, CsrfProviderInterface $csrfProvider = 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, CsrfTokenGeneratorInterface $csrfTokenGenerator = null) { parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array( 'username_parameter' => '_username', @@ -47,7 +47,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL 'post_only' => true, ), $options), $logger, $dispatcher); - $this->csrfProvider = $csrfProvider; + $this->csrfTokenGenerator = $csrfTokenGenerator; } /** @@ -67,10 +67,10 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL */ protected function attemptAuthentication(Request $request) { - if (null !== $this->csrfProvider) { + if (null !== $this->csrfTokenGenerator) { $csrfToken = $request->get($this->options['csrf_parameter'], null, true); - if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) { + if (false === $this->csrfTokenGenerator->isCsrfTokenValid($this->options['intention'], $csrfToken)) { throw new InvalidCsrfTokenException('Invalid CSRF token.'); } } diff --git a/src/Symfony/Component/Security/Http/composer.json b/src/Symfony/Component/Security/Http/composer.json index 6b610a09e4..4dfd98555d 100644 --- a/src/Symfony/Component/Security/Http/composer.json +++ b/src/Symfony/Component/Security/Http/composer.json @@ -25,10 +25,11 @@ "require-dev": { "symfony/form": "~2.0", "symfony/routing": "~2.2", + "symfony/security-csrf": "~2.4", "psr/log": "~1.0" }, "suggest": { - "symfony/form": "", + "symfony/security-csrf": "", "symfony/routing": "" }, "autoload": { diff --git a/src/Symfony/Component/Security/composer.json b/src/Symfony/Component/Security/composer.json index 164deb36ea..6be388692f 100644 --- a/src/Symfony/Component/Security/composer.json +++ b/src/Symfony/Component/Security/composer.json @@ -24,6 +24,7 @@ "replace": { "symfony/security-acl": "self.version", "symfony/security-core": "self.version", + "symfony/security-csrf": "self.version", "symfony/security-http": "self.version" }, "require-dev": {