Commit c2a9015e authored by Fabien Potencier's avatar Fabien Potencier

feature #1222 added a CSRF service provider (fabpot)

This PR was merged into the 2.0.x-dev branch.

Discussion
----------

added a CSRF service provider

Not yet finished.

Commits
-------

3639c56a added a CSRF service provider
parents c6f938f6 3639c56a
...@@ -4,6 +4,7 @@ Changelog ...@@ -4,6 +4,7 @@ Changelog
2.0.0 (2015-XX-XX) 2.0.0 (2015-XX-XX)
------------------ ------------------
* [BC BREAK] CSRF has been moved to a standalone provider (``form.secret`` is not available anymore)
* added support for the Symfony HttpFoundation Twig bridge extension * added support for the Symfony HttpFoundation Twig bridge extension
* added support for the Symfony Asset Component * added support for the Symfony Asset Component
* bumped minimum version of Symfony to 2.7 * bumped minimum version of Symfony to 2.7
......
...@@ -2,10 +2,11 @@ Disabling CSRF Protection on a Form using the FormExtension ...@@ -2,10 +2,11 @@ Disabling CSRF Protection on a Form using the FormExtension
=========================================================== ===========================================================
The *FormExtension* provides a service for building form in your application The *FormExtension* provides a service for building form in your application
with the Symfony Form component. By default, the *FormExtension* uses the with the Symfony Form component. When the `CSRF Service Provider
CSRF Protection avoiding Cross-site request forgery, a method by which a </providers/csrf.rst>` is registered, the *FormExtension* uses the CSRF
malicious user attempts to make your legitimate users unknowingly submit data Protection avoiding Cross-site request forgery, a method by which a malicious
that they don't intend to submit. user attempts to make your legitimate users unknowingly submit data that they
don't intend to submit.
You can find more details about CSRF Protection and CSRF token in the You can find more details about CSRF Protection and CSRF token in the
`Symfony Book `Symfony Book
......
CsrfServiceProvider
===================
The *CsrfServiceProvider* provides a service for building forms in your
application with the Symfony2 Form component.
Parameters
----------
* none
Services
--------
* **form.csrf_provider**: An instance of an implementation of the
`CsrfProviderInterface
<http://api.symfony.com/master/Symfony/Component/Form/Extension/Csrf/CsrfProvider/CsrfProviderInterface.html>`_,
defaults to a `DefaultCsrfProvider
<http://api.symfony.com/master/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.html>`_.
Registering
-----------
.. code-block:: php
use Silex\Provider\CsrfServiceProvider;
$app->register(new CsrfServiceProvider());
.. note::
The *SerializerServiceProvider* relies on Symfony's `Serializer Component
<http://symfony.com/doc/current/components/serializer.html>`_, which comes
with the "fat" Silex archive but not with the regular one. If you are using
Composer, add it as a dependency:
.. code-block:: bash
composer require symfony/security-csrf
Usage
-----
When the CSRF Service Provider is registered, all forms created via the Form
Service Provider are protected against CSRF by default.
You can also use the CSRF protection even without using the Symfony Form
component. If, for example, you're doing a DELETE action, you can check the
CSRF token::
.. code-block:: php
use Symfony\Component\Security\Csrf\CsrfToken;
$app['csrf.token_manager']->isTokenValid(new CsrfToken('token_id', 'TOKEN'));
...@@ -7,10 +7,7 @@ your application with the Symfony Form component. ...@@ -7,10 +7,7 @@ your application with the Symfony Form component.
Parameters Parameters
---------- ----------
* **form.secret**: This secret value is used for generating and validating the * none
CSRF token for a specific page. It is very important for you to set this
value to a static randomly generated value, to prevent hijacking of your
forms. Defaults to ``md5(__DIR__)``.
Services Services
-------- --------
...@@ -19,9 +16,6 @@ Services ...@@ -19,9 +16,6 @@ Services
<http://api.symfony.com/master/Symfony/Component/Form/FormFactory.html>`_, <http://api.symfony.com/master/Symfony/Component/Form/FormFactory.html>`_,
that is used to build a form. that is used to build a form.
* **form.csrf_provider**: An instance of an implementation of
`CsrfTokenManagerInterface <http://api.symfony.com/2.7/Symfony/Component/Security/Csrf/CsrfTokenManagerInterface.html>`_.
Registering Registering
----------- -----------
...@@ -63,13 +57,6 @@ Registering ...@@ -63,13 +57,6 @@ Registering
.. code-block:: bash .. code-block:: bash
composer require symfony/validator symfony/config symfony/translation composer require symfony/validator symfony/config symfony/translation
The Symfony Security CSRF component is used to protect forms against CSRF
attacks:
.. code-block:: bash
composer require symfony/security-csrf
If you want to use forms in your Twig templates, you can also install the If you want to use forms in your Twig templates, you can also install the
Symfony Twig Bridge. Make sure to install, if you didn't do that already, Symfony Twig Bridge. Make sure to install, if you didn't do that already,
...@@ -183,6 +170,11 @@ You can register form type guessers by extending ``form.type.guessers``:: ...@@ -183,6 +170,11 @@ You can register form type guessers by extending ``form.type.guessers``::
return $guessers; return $guessers;
}); });
.. warning::
CSRF protection is only available and automatically enabled when the `CSRF
Service Provider </providers/csrf.rst>` is registered.
Traits Traits
------ ------
......
...@@ -34,7 +34,7 @@ Registering ...@@ -34,7 +34,7 @@ Registering
.. note:: .. note::
The *SerializerServiceProvider* relies on Symfony's `Serializer Component The *SerializerServiceProvider* relies on Symfony's `Serializer Component
<http://symfony.com/doc/current/components/serializer.html>`_, <http://symfony.com/doc/current/components/serializer.html>`_,
which comes with the "fat" Silex archive but not with the regular which comes with the "fat" Silex archive but not with the regular
one. If you are using Composer, add it as a dependency: one. If you are using Composer, add it as a dependency:
......
<?php
/*
* This file is part of the Silex framework.
*
* (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 Silex\Provider;
use Pimple\Container;
use Pimple\ServiceProviderInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
/**
* Symfony CSRF Security component Provider.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class CsrfServiceProvider implements ServiceProviderInterface
{
public function register(Container $app)
{
$app['csrf.token_manager'] = function ($app) {
return new CsrfTokenManager($app['csrf.token_generator'], $app['csrf.token_storage']);
};
$app['csrf.token_storage'] = function ($app) {
if (isset($app['session'])) {
return new SessionTokenStorage($app['session'], $app['csrf.session_namespace']);
}
return new NativeSessionTokenStorage($app['csrf.session_namespace']);
};
$app['csrf.token_generator'] = function ($app) {
return new UriSafeTokenGenerator();
};
$app['csrf.session_namespace'] = '_csrf';
}
}
...@@ -18,9 +18,6 @@ use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension; ...@@ -18,9 +18,6 @@ use Symfony\Component\Form\Extension\HttpFoundation\HttpFoundationExtension;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension; use Symfony\Component\Form\Extension\Validator\ValidatorExtension as FormValidatorExtension;
use Symfony\Component\Form\Forms; use Symfony\Component\Form\Forms;
use Symfony\Component\Form\ResolvedFormTypeFactory; use Symfony\Component\Form\ResolvedFormTypeFactory;
use Symfony\Component\Security\Csrf\CsrfTokenManager;
use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
/** /**
* Symfony Form component Provider. * Symfony Form component Provider.
...@@ -46,8 +43,6 @@ class FormServiceProvider implements ServiceProviderInterface ...@@ -46,8 +43,6 @@ class FormServiceProvider implements ServiceProviderInterface
require_once $path.'/NumberFormatter.php'; require_once $path.'/NumberFormatter.php';
} }
$app['form.secret'] = md5(__DIR__);
$app['form.types'] = function ($app) { $app['form.types'] = function ($app) {
return array(); return array();
}; };
...@@ -62,18 +57,21 @@ class FormServiceProvider implements ServiceProviderInterface ...@@ -62,18 +57,21 @@ class FormServiceProvider implements ServiceProviderInterface
$app['form.extension.csrf'] = function ($app) { $app['form.extension.csrf'] = function ($app) {
if (isset($app['translator'])) { if (isset($app['translator'])) {
return new CsrfExtension($app['form.csrf_provider'], $app['translator']); return new CsrfExtension($app['csrf.token_manager'], $app['translator']);
} }
return new CsrfExtension($app['form.csrf_provider']); return new CsrfExtension($app['csrf.token_manager']);
}; };
$app['form.extensions'] = function ($app) { $app['form.extensions'] = function ($app) {
$extensions = array( $extensions = array(
$app['form.extension.csrf'],
new HttpFoundationExtension(), new HttpFoundationExtension(),
); );
if (isset($app['csrf.token_manager'])) {
$extensions[] = $app['form.extension.csrf'];
}
if (isset($app['validator'])) { if (isset($app['validator'])) {
$extensions[] = new FormValidatorExtension($app['validator']); $extensions[] = new FormValidatorExtension($app['validator']);
...@@ -103,11 +101,5 @@ class FormServiceProvider implements ServiceProviderInterface ...@@ -103,11 +101,5 @@ class FormServiceProvider implements ServiceProviderInterface
$app['form.resolved_type_factory'] = function ($app) { $app['form.resolved_type_factory'] = function ($app) {
return new ResolvedFormTypeFactory(); return new ResolvedFormTypeFactory();
}; };
$app['form.csrf_provider'] = function ($app) {
$storage = isset($app['session']) ? new SessionTokenStorage($app['session']) : new NativeSessionTokenStorage();
return new CsrfTokenManager(null, $storage);
};
} }
} }
...@@ -13,6 +13,8 @@ namespace Silex\Tests\Provider; ...@@ -13,6 +13,8 @@ namespace Silex\Tests\Provider;
use Silex\Application; use Silex\Application;
use Silex\Provider\FormServiceProvider; use Silex\Provider\FormServiceProvider;
use Silex\Provider\CsrfServiceProvider;
use Silex\Provider\SessionServiceProvider;
use Silex\Provider\TranslationServiceProvider; use Silex\Provider\TranslationServiceProvider;
use Silex\Provider\ValidatorServiceProvider; use Silex\Provider\ValidatorServiceProvider;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
...@@ -22,8 +24,6 @@ use Symfony\Component\HttpFoundation\Request; ...@@ -22,8 +24,6 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\OptionsResolver\OptionsResolver; use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\Exception\NotFoundResourceException; use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
class FormServiceProviderTest extends \PHPUnit_Framework_TestCase class FormServiceProviderTest extends \PHPUnit_Framework_TestCase
{ {
...@@ -102,8 +102,8 @@ class FormServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -102,8 +102,8 @@ class FormServiceProviderTest extends \PHPUnit_Framework_TestCase
); );
$app['locale'] = 'de'; $app['locale'] = 'de';
$app['form.csrf_provider'] = function () { $app['csrf.token_manager'] = function () {
return new FakeCsrfProvider(); return $this->getMock('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface');
}; };
$form = $app['form.factory']->createBuilder('form', array()) $form = $app['form.factory']->createBuilder('form', array())
...@@ -144,6 +144,19 @@ class FormServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -144,6 +144,19 @@ class FormServiceProviderTest extends \PHPUnit_Framework_TestCase
$this->fail('Form factory should not add a translation resource that does not exist'); $this->fail('Form factory should not add a translation resource that does not exist');
} }
} }
public function testFormCsrf()
{
$app = new Application();
$app->register(new FormServiceProvider());
$app->register(new SessionServiceProvider());
$app->register(new CsrfServiceProvider());
$app['session.test'] = true;
$form = $app['form.factory']->createBuilder('form', array())->getForm();
$this->assertTrue(isset($form->createView()['_token']));
}
} }
class DummyFormType extends AbstractType class DummyFormType extends AbstractType
...@@ -188,25 +201,3 @@ if (method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) { ...@@ -188,25 +201,3 @@ if (method_exists('Symfony\Component\Form\AbstractType', 'configureOptions')) {
} }
} }
} }
class FakeCsrfProvider implements CsrfTokenManagerInterface
{
public function getToken($tokenId)
{
return new CsrfToken($tokenId, '123');
}
public function refreshToken($tokenId)
{
return new CsrfToken($tokenId, '123');
}
public function removeToken($tokenId)
{
}
public function isTokenValid(CsrfToken $token)
{
return '123' === $token->getValue();
}
}
...@@ -101,7 +101,6 @@ class ValidatorServiceProviderTest extends \PHPUnit_Framework_TestCase ...@@ -101,7 +101,6 @@ class ValidatorServiceProviderTest extends \PHPUnit_Framework_TestCase
$builder = $app['form.factory']->createBuilder('form', array(), array( $builder = $app['form.factory']->createBuilder('form', array(), array(
'constraints' => $constraints, 'constraints' => $constraints,
'csrf_protection' => false,
)); ));
$form = $builder $form = $builder
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment