feature#6554 [Security] Added Security\Csrf sub-component with better token generation (bschussek)
This PR was merged into the master branch. Discussion ---------- [Security] Added Security\Csrf sub-component with better token generation | Q | A | ------------- | --- | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | TODO **Update September 27, 2013** This PR simplifies the CSRF mechanism to generate completely random tokens. A random token is generated once per ~~intention~~ token ID and then stored in the session. Tokens are valid until the session expires. Since the CSRF token generator depends on `StringUtils` and `SecureRandom` from Security\Core, and since Security\Http currently depends on the Form component for token generation, I decided to add a new Security\Csrf sub-component that contains the improved CSRF token generator. Consequences: * Security\Http now depends on Security\Csrf instead of Form * Form now optionally depends on Security\Csrf * The configuration for the "security.secure_random" service and the "security.csrf.*" services was moved to FrameworkBundle to guarantee BC In the new Security\Csrf sub-component, I tried to improve the naming where I could do so without breaking BC: * CSRF "providers" are now called "token generators" * CSRF "intentions" are now called "token IDs", because that's really what they are ##### TODO - [ ] The documentation needs to be checked for references to the configuration of the application secret. Remarks that the secret is used for CSRF protection need to be removed. - [ ] Add aliases "csrf_token_generator" and "csrf_token_id" for "csrf_provider" and "intention" in the SecurityBundle configuration - [x] Make sure `SecureRandom` never blocks for `CsrfTokenGenerator` Commits -------7f02304
[Security] Added missing PHPDoc tag2e04e32
Updated Composer dependencies to require the Security\Csrf component where necessarybf85e83
[FrameworkBundle][SecurityBundle] Added service configuration for the new Security CSRF sub-component2048cf6
[Form] Deprecated the CSRF implementation and added an optional dependency to the Security CSRF sub-component instead85d4959
[Security] Changed Security HTTP sub-component to depend on CSRF sub-component instead of Form1bf1640
[Security] Added CSRF sub-component
This commit is contained in:
commit
0f80916313
@ -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
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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 <bschussek@gmail.com>
|
||||
@ -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;
|
||||
}
|
||||
|
@ -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": {
|
||||
|
@ -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
|
||||
-----
|
||||
|
@ -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);
|
||||
|
@ -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">
|
||||
|
||||
<parameters>
|
||||
<parameter key="form.csrf_provider.class">Symfony\Component\Form\Extension\Csrf\CsrfProvider\SessionCsrfProvider</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="form.csrf_provider" class="%form.csrf_provider.class%">
|
||||
<argument type="service" id="session" />
|
||||
<argument>%kernel.secret%</argument>
|
||||
</service>
|
||||
<service id="form.csrf_provider" class="Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfTokenGeneratorAdapter" parent="security.csrf.token_generator" />
|
||||
|
||||
<service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension">
|
||||
<tag name="form.type_extension" alias="form" />
|
||||
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
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">
|
||||
|
||||
<parameters>
|
||||
<parameter key="security.secure_random.class">Symfony\Component\Security\Core\Util\SecureRandom</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<!-- Pseudo-Random Number Generator -->
|
||||
<service id="security.secure_random" class="%security.secure_random.class%">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument>%kernel.cache_dir%/secure_random.seed</argument>
|
||||
<argument type="service" id="logger" on-invalid="ignore" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" ?>
|
||||
|
||||
<container xmlns="http://symfony.com/schema/dic/services"
|
||||
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">
|
||||
|
||||
<parameters>
|
||||
<parameter key="security.csrf.token_generator.class">Symfony\Component\Security\Csrf\CsrfTokenGenerator</parameter>
|
||||
<parameter key="security.csrf.token_storage.class">Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
<service id="security.csrf.token_storage" class="%security.csrf.token_storage.class%" public="false">
|
||||
<argument type="service" id="session" />
|
||||
</service>
|
||||
|
||||
<service id="security.csrf.token_generator" class="%security.csrf.token_generator.class%">
|
||||
<argument type="service" id="security.csrf.token_storage" />
|
||||
<argument type="service" id="security.secure_random" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
@ -247,7 +247,7 @@ class FormHelper extends Helper
|
||||
* Check the token in your action using the same intention.
|
||||
*
|
||||
* <code>
|
||||
* $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.');
|
||||
* }
|
||||
|
@ -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()
|
||||
|
@ -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',
|
||||
)),
|
||||
));
|
||||
|
@ -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',
|
||||
)),
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
@ -151,12 +151,5 @@
|
||||
<argument type="service" id="security.context" />
|
||||
<argument type="service" id="security.encoder_factory" />
|
||||
</service>
|
||||
|
||||
<!-- Pseudorandom Number Generator -->
|
||||
<service id="security.secure_random" class="Symfony\Component\Security\Core\Util\SecureRandom">
|
||||
<tag name="monolog.logger" channel="security" />
|
||||
<argument>%kernel.cache_dir%/secure_random.seed</argument>
|
||||
<argument type="service" id="logger" on-invalid="ignore" />
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -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');
|
||||
|
@ -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 }
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 <bschussek@gmail.com>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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 <bschussek@gmail.com>
|
||||
*
|
||||
* @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0.
|
||||
*/
|
||||
class CsrfTokenGeneratorAdapter extends CsrfTokenGenerator implements CsrfProviderInterface
|
||||
{
|
||||
}
|
@ -18,6 +18,11 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider;
|
||||
* user-defined secret value to secure the CSRF token.
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @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
|
||||
{
|
||||
|
@ -20,6 +20,11 @@ use Symfony\Component\HttpFoundation\Session\Session;
|
||||
* @see DefaultCsrfProvider
|
||||
*
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @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
|
||||
{
|
||||
|
@ -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) {
|
||||
|
@ -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',
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -73,20 +73,20 @@ interface FormRendererInterface
|
||||
* <input type="hidden" name="token" value="<?php $renderer->renderCsrfToken('rm_user_'.$user->getId()) ?>">
|
||||
* </code>
|
||||
*
|
||||
* Check the token in your action using the same intention.
|
||||
* Check the token in your action using the same token ID.
|
||||
*
|
||||
* <code>
|
||||
* $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.');
|
||||
* }
|
||||
* </code>
|
||||
*
|
||||
* @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.
|
||||
|
@ -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'));
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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'));
|
||||
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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\\": "" }
|
||||
|
@ -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
|
||||
-----
|
||||
|
3
src/Symfony/Component/Security/Csrf/.gitignore
vendored
Normal file
3
src/Symfony/Component/Security/Csrf/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
105
src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php
Normal file
105
src/Symfony/Component/Security/Csrf/CsrfTokenGenerator.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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 <bernhard.schussek@symfony.com>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\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 <bschussek@gmail.com>
|
||||
*/
|
||||
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);
|
||||
}
|
19
src/Symfony/Component/Security/Csrf/LICENSE
Normal file
19
src/Symfony/Component/Security/Csrf/LICENSE
Normal file
@ -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.
|
21
src/Symfony/Component/Security/Csrf/README.md
Normal file
21
src/Symfony/Component/Security/Csrf/README.md
Normal file
@ -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
|
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider;
|
||||
|
||||
use Symfony\Component\Security\Csrf\CsrfTokenGenerator;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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'));
|
||||
}
|
||||
}
|
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider;
|
||||
|
||||
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*
|
||||
* @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'));
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider;
|
||||
|
||||
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
|
||||
|
||||
/**
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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'));
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Csrf\TokenStorage;
|
||||
|
||||
/**
|
||||
* Token storage that uses PHP's native session handling.
|
||||
*
|
||||
* @since 2.4
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Csrf\TokenStorage;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
|
||||
/**
|
||||
* Token storage that uses a Symfony2 Session object.
|
||||
*
|
||||
* @since 2.4
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Security\Csrf\TokenStorage;
|
||||
|
||||
/**
|
||||
* Stores CSRF tokens.
|
||||
*
|
||||
* @since 2.4
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
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);
|
||||
}
|
38
src/Symfony/Component/Security/Csrf/composer.json
Normal file
38
src/Symfony/Component/Security/Csrf/composer.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
29
src/Symfony/Component/Security/Csrf/phpunit.xml.dist
Normal file
29
src/Symfony/Component/Security/Csrf/phpunit.xml.dist
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertNoticesToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
processIsolation="false"
|
||||
stopOnFailure="false"
|
||||
syntaxCheck="false"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Symfony Security Component CSRF Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./vendor</directory>
|
||||
<directory>./Tests</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
Reference in New Issue
Block a user