Skip to content

Commit

Permalink
[Form] Fixed CSRF error messages to be translated and added "csrf_mes…
Browse files Browse the repository at this point in the history
…sage" option
  • Loading branch information
webmozart committed May 3, 2013
1 parent 36a8194 commit 549a308
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 8 deletions.
Expand Up @@ -19,6 +19,8 @@
<argument type="service" id="form.csrf_provider" />
<argument>%form.type_extension.csrf.enabled%</argument>
<argument>%form.type_extension.csrf.field_name%</argument>
<argument type="service" id="translator.default" />
<argument>%validator.translation_domain%</argument>
</service>
</services>
</container>
2 changes: 2 additions & 0 deletions src/Symfony/Component/Form/CHANGELOG.md
Expand Up @@ -31,6 +31,8 @@ CHANGELOG
* [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,
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
-----
Expand Down
26 changes: 23 additions & 3 deletions src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php
Expand Up @@ -14,22 +14,42 @@
use Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Form\AbstractExtension;
use Symfony\Component\Translation\TranslatorInterface;

/**
* This extension protects forms by using a CSRF token.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class CsrfExtension extends AbstractExtension
{
/**
* @var CsrfProviderInterface
*/
private $csrfProvider;

/**
* @var TranslatorInterface
*/
private $translator;

/**
* @var null|string
*/
private $translationDomain;

/**
* 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->translator = $translator;
$this->translationDomain = $translationDomain;
}

/**
Expand All @@ -38,7 +58,7 @@ public function __construct(CsrfProviderInterface $csrfProvider)
protected function loadTypeExtensions()
{
return array(
new Type\FormTypeCsrfExtension($this->csrfProvider),
new Type\FormTypeCsrfExtension($this->csrfProvider, true, '_token', $this->translator, $this->translationDomain),
);
}
}
Expand Up @@ -16,6 +16,7 @@
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Translation\TranslatorInterface;

/**
* @author Bernhard Schussek <bschussek@gmail.com>
Expand Down Expand Up @@ -44,18 +45,37 @@ class CsrfValidationListener implements EventSubscriberInterface
*/
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()
{
return array(
FormEvents::PRE_SUBMIT => 'preSubmit',
);
}

public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention)
public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention, $errorMessage, TranslatorInterface $translator = null, $translationDomain = null)
{
$this->fieldName = $fieldName;
$this->csrfProvider = $csrfProvider;
$this->intention = $intention;
$this->errorMessage = $errorMessage;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
}

public function preSubmit(FormEvent $event)
Expand All @@ -65,7 +85,13 @@ public function preSubmit(FormEvent $event)

if ($form->isRoot() && $form->getConfig()->getOption('compound')) {
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)) {
Expand Down
Expand Up @@ -18,21 +18,45 @@
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Translation\TranslatorInterface;

/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class FormTypeCsrfExtension extends AbstractTypeExtension
{
/**
* @var CsrfProviderInterface
*/
private $defaultCsrfProvider;

/**
* @var Boolean
*/
private $defaultEnabled;

/**
* @var string
*/
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->defaultEnabled = $defaultEnabled;
$this->defaultFieldName = $defaultFieldName;
$this->translator = $translator;
$this->translationDomain = $translationDomain;
}

/**
Expand All @@ -49,7 +73,14 @@ public function buildForm(FormBuilderInterface $builder, array $options)

$builder
->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
))
;
}

Expand Down Expand Up @@ -83,6 +114,7 @@ public function setDefaultOptions(OptionsResolverInterface $resolver)
'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',
));
}
Expand Down
Expand Up @@ -13,6 +13,7 @@

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Form\Extension\Csrf\CsrfExtension;

Expand All @@ -33,26 +34,36 @@ public function getName()

class FormTypeCsrfExtensionTest extends TypeTestCase
{
/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $csrfProvider;

/**
* @var \PHPUnit_Framework_MockObject_MockObject
*/
protected $translator;

protected function setUp()
{
$this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
$this->translator = $this->getMock('Symfony\Component\Translation\TranslatorInterface');

parent::setUp();
}

protected function tearDown()
{
$this->csrfProvider = null;
$this->translator = null;

parent::tearDown();
}

protected function getExtensions()
{
return array_merge(parent::getExtensions(), array(
new CsrfExtension($this->csrfProvider),
new CsrfExtension($this->csrfProvider, $this->translator),
));
}

Expand Down Expand Up @@ -255,4 +266,36 @@ public function testNoCsrfProtectionOnPrototype()
$this->assertFalse(isset($prototypeView['csrf']));
$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]);
}
}

0 comments on commit 549a308

Please sign in to comment.