[Form] Fixed CSRF error messages to be translated and added "csrf_message" option
This commit is contained in:
parent
36a8194c95
commit
549a308a37
@ -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>
|
||||||
|
@ -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
|
||||||
-----
|
-----
|
||||||
|
@ -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),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)) {
|
||||||
|
@ -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',
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user