[Form] Fixed CSRF error messages to be translated and added "csrf_message" option

This commit is contained in:
Bernhard Schussek 2013-05-02 11:05:04 +02:00
parent 36a8194c95
commit 549a308a37
6 changed files with 133 additions and 8 deletions

View File

@ -19,6 +19,8 @@
<argument type="service" id="form.csrf_provider" /> <argument type="service" id="form.csrf_provider" />
<argument>%form.type_extension.csrf.enabled%</argument> <argument>%form.type_extension.csrf.enabled%</argument>
<argument>%form.type_extension.csrf.field_name%</argument> <argument>%form.type_extension.csrf.field_name%</argument>
<argument type="service" id="translator.default" />
<argument>%validator.translation_domain%</argument>
</service> </service>
</services> </services>
</container> </container>

View File

@ -31,6 +31,8 @@ CHANGELOG
* [BC BREAK] initialization for Form instances added to a form tree must be manually disabled * [BC BREAK] initialization for Form instances added to a form tree must be manually disabled
* PRE_SET_DATA is now guaranteed to be called after children were added by the form builder, * PRE_SET_DATA is now guaranteed to be called after children were added by the form builder,
unless FormInterface::setData() is called manually unless FormInterface::setData() is called manually
* fixed CSRF error message to be translated
* custom CSRF error messages can now be set through the "csrf_message" option
2.2.0 2.2.0
----- -----

View File

@ -14,22 +14,42 @@ namespace Symfony\Component\Form\Extension\Csrf;
use Symfony\Component\Form\Extension\Csrf\Type; use Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\AbstractExtension; use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Translation\TranslatorInterface;
/** /**
* This extension protects forms by using a CSRF token. * This extension protects forms by using a CSRF token.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class CsrfExtension extends AbstractExtension class CsrfExtension extends AbstractExtension
{ {
/**
* @var CsrfProviderInterface
*/
private $csrfProvider; private $csrfProvider;
/**
* @var TranslatorInterface
*/
private $translator;
/**
* @var null|string
*/
private $translationDomain;
/** /**
* Constructor. * Constructor.
* *
* @param CsrfProviderInterface $csrfProvider The CSRF provider * @param CsrfProviderInterface $csrfProvider The CSRF provider
* @param TranslatorInterface $translator The translator for translating error messages.
* @param null|string $translationDomain The translation domain for translating.
*/ */
public function __construct(CsrfProviderInterface $csrfProvider) public function __construct(CsrfProviderInterface $csrfProvider, TranslatorInterface $translator = null, $translationDomain = null)
{ {
$this->csrfProvider = $csrfProvider; $this->csrfProvider = $csrfProvider;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
} }
/** /**
@ -38,7 +58,7 @@ class CsrfExtension extends AbstractExtension
protected function loadTypeExtensions() protected function loadTypeExtensions()
{ {
return array( return array(
new Type\FormTypeCsrfExtension($this->csrfProvider), new Type\FormTypeCsrfExtension($this->csrfProvider, true, '_token', $this->translator, $this->translationDomain),
); );
} }
} }

View File

@ -16,6 +16,7 @@ use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface; use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Translation\TranslatorInterface;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
@ -44,6 +45,22 @@ class CsrfValidationListener implements EventSubscriberInterface
*/ */
private $intention; private $intention;
/**
* The message displayed in case of an error.
* @var string
*/
private $errorMessage;
/**
* @var TranslatorInterface
*/
private $translator;
/**
* @var null|string
*/
private $translationDomain;
public static function getSubscribedEvents() public static function getSubscribedEvents()
{ {
return array( return array(
@ -51,11 +68,14 @@ class CsrfValidationListener implements EventSubscriberInterface
); );
} }
public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention) public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null)
{ {
$this->fieldName = $fieldName; $this->fieldName = $fieldName;
$this->csrfProvider = $csrfProvider; $this->csrfProvider = $csrfProvider;
$this->intention = $intention; $this->intention = $intention;
$this->errorMessage = $errorMessage;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
} }
public function preSubmit(FormEvent $event) public function preSubmit(FormEvent $event)
@ -65,7 +85,13 @@ class CsrfValidationListener implements EventSubscriberInterface
if ($form->isRoot() && $form->getConfig()->getOption('compound')) { 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->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form.')); $errorMessage = $this->errorMessage;
if (null !== $this->translator) {
$errorMessage = $this->translator->trans($errorMessage, array(), $this->translationDomain);
}
$form->addError(new FormError($errorMessage));
} }
if (is_array($data)) { if (is_array($data)) {

View File

@ -18,21 +18,45 @@ use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface; use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class FormTypeCsrfExtension extends AbstractTypeExtension class FormTypeCsrfExtension extends AbstractTypeExtension
{ {
/**
* @var CsrfProviderInterface
*/
private $defaultCsrfProvider; private $defaultCsrfProvider;
/**
* @var Boolean
*/
private $defaultEnabled; private $defaultEnabled;
/**
* @var string
*/
private $defaultFieldName; private $defaultFieldName;
public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token') /**
* @var TranslatorInterface
*/
private $translator;
/**
* @var null|string
*/
private $translationDomain;
public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token', TranslatorInterface $translator = null, $translationDomain = null)
{ {
$this->defaultCsrfProvider = $defaultCsrfProvider; $this->defaultCsrfProvider = $defaultCsrfProvider;
$this->defaultEnabled = $defaultEnabled; $this->defaultEnabled = $defaultEnabled;
$this->defaultFieldName = $defaultFieldName; $this->defaultFieldName = $defaultFieldName;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
} }
/** /**
@ -49,7 +73,14 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
$builder $builder
->setAttribute('csrf_factory', $builder->getFormFactory()) ->setAttribute('csrf_factory', $builder->getFormFactory())
->addEventSubscriber(new CsrfValidationListener($options['csrf_field_name'], $options['csrf_provider'], $options['intention'])) ->addEventSubscriber(new CsrfValidationListener(
$options['csrf_field_name'],
$options['csrf_provider'],
$options['intention'],
$options['csrf_message'],
$this->translator,
$this->translationDomain
))
; ;
} }
@ -83,6 +114,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
'csrf_protection' => $this->defaultEnabled, 'csrf_protection' => $this->defaultEnabled,
'csrf_field_name' => $this->defaultFieldName, 'csrf_field_name' => $this->defaultFieldName,
'csrf_provider' => $this->defaultCsrfProvider, 'csrf_provider' => $this->defaultCsrfProvider,
'csrf_message' => 'The CSRF token is invalid. Please try to resubmit the form.',
'intention' => 'unknown', 'intention' => 'unknown',
)); ));
} }

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Tests\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Test\TypeTestCase; use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension; use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
@ -33,11 +34,20 @@ class FormTypeCsrfExtensionTest_ChildType extends AbstractType
class FormTypeCsrfExtensionTest extends TypeTestCase class FormTypeCsrfExtensionTest extends TypeTestCase
{ {
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $csrfProvider; protected $csrfProvider;
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $translator;
protected function setUp() protected function setUp()
{ {
$this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface'); $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
$this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface');
parent::setUp(); parent::setUp();
} }
@ -45,6 +55,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase
protected function tearDown() protected function tearDown()
{ {
$this->csrfProvider = null; $this->csrfProvider = null;
$this->translator = null;
parent::tearDown(); parent::tearDown();
} }
@ -52,7 +63,7 @@ class FormTypeCsrfExtensionTest extends TypeTestCase
protected function getExtensions() protected function getExtensions()
{ {
return array_merge(parent::getExtensions(), array( return array_merge(parent::getExtensions(), array(
new CsrfExtension($this->csrfProvider), new CsrfExtension($this->csrfProvider, $this->translator),
)); ));
} }
@ -255,4 +266,36 @@ class FormTypeCsrfExtensionTest extends TypeTestCase
$this->assertFalse(isset($prototypeView['csrf'])); $this->assertFalse(isset($prototypeView['csrf']));
$this->assertCount(1, $prototypeView); $this->assertCount(1, $prototypeView);
} }
public function testsTranslateCustomErrorMessage()
{
$this->csrfProvider->expects($this->once())
->method('isCsrfTokenValid')
->with('%INTENTION%', 'token')
->will($this->returnValue(false));
$this->translator->expects($this->once())
->method('trans')
->with('Foobar')
->will($this->returnValue('[trans]Foobar[/trans]'));
$form = $this->factory
->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
'csrf_provider' => $this->csrfProvider,
'csrf_message' => 'Foobar',
'intention' => '%INTENTION%',
'compound' => true,
))
->getForm();
$form->submit(array(
'csrf' => 'token',
));
$errors = $form->getErrors();
$this->assertGreaterThan(0, count($errors));
$this->assertEquals(new FormError('[trans]Foobar[/trans]'), $errors[0]);
}
} }