Skip to content
Browse files

[Form] Simplified CSRF mechanism and removed "csrf" type

CSRF fields are now only added when the view is built. For this reason we already know if
the form is the root form and avoid to create unnecessary CSRF fields for nested fields.
  • Loading branch information...
1 parent e7470ff commit 2a49449862ab74fbd80126384daa1957abaf2e0c @webmozart webmozart committed Apr 13, 2012
Showing with 369 additions and 619 deletions.
  1. +1 −0 CHANGELOG-2.1.md
  2. +1 −4 src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml
  3. +2 −2 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
  4. +1 −15 src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php
  5. +17 −9 src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php
  6. +0 −66 src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php
  7. +0 −27 src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php
  8. +0 −83 src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php
  9. +0 −27 src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php
  10. +30 −24 src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php
  11. +0 −27 src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php
  12. +0 −27 src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php
  13. +3 −7 src/Symfony/Component/Form/Form.php
  14. +50 −5 src/Symfony/Component/Form/FormView.php
  15. +31 −4 src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
  16. +12 −24 src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
  17. +57 −9 src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
  18. +0 −87 src/Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php
  19. +0 −112 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php
  20. +163 −18 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
  21. +0 −41 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php
  22. +1 −1 src/Symfony/Component/Form/Tests/FormTest.php
View
1 CHANGELOG-2.1.md
@@ -269,6 +269,7 @@ To get the diff between two versions, go to https://github.com/symfony/symfony/c
don't receive an options array anymore.
* Deprecated FormValidatorInterface and substituted its implementations
by event subscribers
+ * simplified CSRF protection and removed the csrf type
### HttpFoundation
View
5 src/Symfony/Bundle/FrameworkBundle/Resources/config/form_csrf.xml
@@ -14,12 +14,9 @@
<argument>%kernel.secret%</argument>
</service>
- <service id="form.type.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\CsrfType">
- <tag name="form.type" alias="csrf" />
- <argument type="service" id="form.csrf_provider" />
- </service>
<service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension">
<tag name="form.type_extension" alias="form" />
+ <argument type="service" id="form.csrf_provider" />
<argument>%form.type_extension.csrf.enabled%</argument>
<argument>%form.type_extension.csrf.field_name%</argument>
</service>
View
4 src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
@@ -27,9 +27,9 @@ public function testCsrfProtection()
$def = $container->getDefinition('form.type_extension.csrf');
$this->assertTrue($container->getParameter('form.type_extension.csrf.enabled'));
- $this->assertEquals('%form.type_extension.csrf.enabled%', $def->getArgument(0));
+ $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(1));
+ $this->assertEquals('%form.type_extension.csrf.field_name%', $def->getArgument(2));
$this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1)));
}
View
16 src/Symfony/Component/Form/Extension/Csrf/CsrfExtension.php
@@ -35,24 +35,10 @@ public function __construct(CsrfProviderInterface $csrfProvider)
/**
* {@inheritDoc}
*/
- protected function loadTypes()
- {
- return array(
- new Type\CsrfType($this->csrfProvider),
- );
- }
-
- /**
- * {@inheritDoc}
- */
protected function loadTypeExtensions()
{
return array(
- new Type\ChoiceTypeCsrfExtension(),
- new Type\DateTypeCsrfExtension(),
- new Type\FormTypeCsrfExtension(),
- new Type\RepeatedTypeCsrfExtension(),
- new Type\TimeTypeCsrfExtension(),
+ new Type\FormTypeCsrfExtension($this->csrfProvider),
);
}
}
View
26 src/Symfony/Component/Form/Extension/Csrf/EventListener/CsrfValidationListener.php
@@ -14,7 +14,7 @@
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError;
-use Symfony\Component\Form\Event\DataEvent;
+use Symfony\Component\Form\Event\FilterDataEvent;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
/**
@@ -23,6 +23,12 @@
class CsrfValidationListener implements EventSubscriberInterface
{
/**
+ * The name of the CSRF field
+ * @var string
+ */
+ private $fieldName;
+
+ /**
* The provider for generating and validating CSRF tokens
* @var CsrfProviderInterface
*/
@@ -45,24 +51,26 @@ static public function getSubscribedEvents()
);
}
- public function __construct(CsrfProviderInterface $csrfProvider, $intention)
+ public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention)
{
+ $this->fieldName = $fieldName;
$this->csrfProvider = $csrfProvider;
$this->intention = $intention;
}
- public function onBindClientData(DataEvent $event)
+ public function onBindClientData(FilterDataEvent $event)
{
$form = $event->getForm();
$data = $event->getData();
- if ((!$form->hasParent() || $form->getParent()->isRoot())
- && !$this->csrfProvider->isCsrfTokenValid($this->intention, $data)) {
- $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
+ if ($form->isRoot() && $form->hasChildren() && isset($data[$this->fieldName])) {
+ if (!$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) {
+ $form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
+ }
- // If the session timed out, the token is invalid now.
- // Regenerate the token so that a resubmission is possible.
- $event->setData($this->csrfProvider->generateCsrfToken($this->intention));
+ unset($data[$this->fieldName]);
}
+
+ $event->setData($data);
}
}
View
66 src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php
@@ -1,66 +0,0 @@
-<?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\EventListener;
-
-use Symfony\Component\Form\Event\DataEvent;
-use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
-use Symfony\Component\Form\FormFactoryInterface;
-
-/**
- * Ensures the CSRF field.
- *
- * @author Bulat Shakirzyanov <mallluhuct@gmail.com>
- * @author Kris Wallsmith <kris@symfony.com>
- */
-class EnsureCsrfFieldListener
-{
- private $factory;
- private $name;
- private $intention;
- private $provider;
-
- /**
- * Constructor.
- *
- * @param FormFactoryInterface $factory The form factory
- * @param string $name A name for the CSRF field
- * @param string $intention The intention string
- * @param CsrfProviderInterface $provider The CSRF provider
- */
- public function __construct(FormFactoryInterface $factory, $name, $intention = null, CsrfProviderInterface $provider = null)
- {
- $this->factory = $factory;
- $this->name = $name;
- $this->intention = $intention;
- $this->provider = $provider;
- }
-
- /**
- * Ensures a root form has a CSRF field.
- *
- * This method should be connected to both form.pre_set_data and form.pre_bind.
- */
- public function ensureCsrfField(DataEvent $event)
- {
- $form = $event->getForm();
-
- $options = array();
- if ($this->intention) {
- $options['intention'] = $this->intention;
- }
- if ($this->provider) {
- $options['csrf_provider'] = $this->provider;
- }
-
- $form->add($this->factory->createNamed('csrf', $this->name, null, $options));
- }
-}
View
27 src/Symfony/Component/Form/Extension/Csrf/Type/ChoiceTypeCsrfExtension.php
@@ -1,27 +0,0 @@
-<?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\Type;
-
-use Symfony\Component\Form\AbstractTypeExtension;
-
-class ChoiceTypeCsrfExtension extends AbstractTypeExtension
-{
- public function getDefaultOptions()
- {
- return array('csrf_protection' => false);
- }
-
- public function getExtendedType()
- {
- return 'choice';
- }
-}
View
83 src/Symfony/Component/Form/Extension/Csrf/Type/CsrfType.php
@@ -1,83 +0,0 @@
-<?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\Type;
-
-
-use Symfony\Component\Form\AbstractType;
-use Symfony\Component\Form\FormBuilder;
-use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
-use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
-
-class CsrfType extends AbstractType
-{
- private $csrfProvider;
-
- /**
- * Constructor.
- *
- * @param CsrfProviderInterface $csrfProvider The provider to use to generate the token
- */
- public function __construct(CsrfProviderInterface $csrfProvider)
- {
- $this->csrfProvider = $csrfProvider;
- }
-
- /**
- * Builds the CSRF field.
- *
- * A validator is added to check the token value when the CSRF field is added to
- * a root form
- *
- * @param FormBuilder $builder The form builder
- * @param array $options The options
- */
- public function buildForm(FormBuilder $builder, array $options)
- {
- $csrfProvider = $options['csrf_provider'];
- $intention = $options['intention'];
-
- $builder
- ->setData($csrfProvider->generateCsrfToken($intention))
- ->addEventSubscriber(new CsrfValidationListener($csrfProvider, $intention))
- ;
- }
-
- /**
- * {@inheritDoc}
- */
- public function getDefaultOptions()
- {
- return array(
- 'csrf_provider' => $this->csrfProvider,
- 'intention' => null,
- 'property_path' => false,
- );
- }
-
- /**
- * {@inheritDoc}
- */
- public function getParent(array $options)
- {
- return 'hidden';
- }
-
- /**
- * Returns the name of this form.
- *
- * @return string 'csrf'
- */
- public function getName()
- {
- return 'csrf';
- }
-}
View
27 src/Symfony/Component/Form/Extension/Csrf/Type/DateTypeCsrfExtension.php
@@ -1,27 +0,0 @@
-<?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\Type;
-
-use Symfony\Component\Form\AbstractTypeExtension;
-
-class DateTypeCsrfExtension extends AbstractTypeExtension
-{
- public function getDefaultOptions()
- {
- return array('csrf_protection' => false);
- }
-
- public function getExtendedType()
- {
- return 'date';
- }
-}
View
54 src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php
@@ -12,21 +12,27 @@
namespace Symfony\Component\Form\Extension\Csrf\Type;
use Symfony\Component\Form\AbstractTypeExtension;
-use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener;
+use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
+use Symfony\Component\Form\Extension\Csrf\EventListener\CsrfValidationListener;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
class FormTypeCsrfExtension extends AbstractTypeExtension
{
- private $enabled;
- private $fieldName;
+ private $defaultCsrfProvider;
+ private $defaultEnabled;
+ private $defaultFieldName;
- public function __construct($enabled = true, $fieldName = '_token')
+ public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token')
{
- $this->enabled = $enabled;
- $this->fieldName = $fieldName;
+ $this->defaultCsrfProvider = $defaultCsrfProvider;
+ $this->defaultEnabled = $defaultEnabled;
+ $this->defaultFieldName = $defaultFieldName;
}
/**
@@ -41,35 +47,35 @@ public function buildForm(FormBuilder $builder, array $options)
return;
}
- $listener = new EnsureCsrfFieldListener(
- $builder->getFormFactory(),
- $options['csrf_field_name'],
- $options['intention'],
- $options['csrf_provider']
- );
-
// use a low priority so higher priority listeners don't remove the field
$builder
->setAttribute('csrf_field_name', $options['csrf_field_name'])
- ->addEventListener(FormEvents::PRE_SET_DATA, array($listener, 'ensureCsrfField'), -10)
- ->addEventListener(FormEvents::PRE_BIND, array($listener, 'ensureCsrfField'), -10)
+ ->setAttribute('csrf_provider', $options['csrf_provider'])
+ ->setAttribute('csrf_intention', $options['intention'])
+ ->setAttribute('csrf_factory', $builder->getFormFactory())
+ ->addEventSubscriber(new CsrfValidationListener($options['csrf_field_name'], $options['csrf_provider'], $options['intention']))
;
}
/**
- * Removes CSRF fields from all the form views except the root one.
+ * Adds a CSRF field to the root form view.
*
* @param FormView $view The form view
* @param FormInterface $form The form
*/
- public function buildViewBottomUp(FormView $view, FormInterface $form)
+ public function buildView(FormView $view, FormInterface $form)
{
- if ($view->hasParent() && $form->hasAttribute('csrf_field_name')) {
+ if ($form->isRoot() && $form->hasChildren() && $form->hasAttribute('csrf_field_name')) {
$name = $form->getAttribute('csrf_field_name');
+ $csrfProvider = $form->getAttribute('csrf_provider');
+ $intention = $form->getAttribute('csrf_intention');
+ $factory = $form->getAttribute('csrf_factory');
+ $data = $csrfProvider->generateCsrfToken($intention);
+ $csrfForm = $factory->createNamed('hidden', $name, $data, array(
+ 'property_path' => false,
+ ));
- if (isset($view[$name])) {
- unset($view[$name]);
- }
+ $view->addChild($csrfForm->createView($view));
}
}
@@ -79,9 +85,9 @@ public function buildViewBottomUp(FormView $view, FormInterface $form)
public function getDefaultOptions()
{
return array(
- 'csrf_protection' => $this->enabled,
- 'csrf_field_name' => $this->fieldName,
- 'csrf_provider' => null,
+ 'csrf_protection' => $this->defaultEnabled,
+ 'csrf_field_name' => $this->defaultFieldName,
+ 'csrf_provider' => $this->defaultCsrfProvider,
'intention' => 'unknown',
);
}
View
27 src/Symfony/Component/Form/Extension/Csrf/Type/RepeatedTypeCsrfExtension.php
@@ -1,27 +0,0 @@
-<?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\Type;
-
-use Symfony\Component\Form\AbstractTypeExtension;
-
-class RepeatedTypeCsrfExtension extends AbstractTypeExtension
-{
- public function getDefaultOptions()
- {
- return array('csrf_protection' => false);
- }
-
- public function getExtendedType()
- {
- return 'repeated';
- }
-}
View
27 src/Symfony/Component/Form/Extension/Csrf/Type/TimeTypeCsrfExtension.php
@@ -1,27 +0,0 @@
-<?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\Type;
-
-use Symfony\Component\Form\AbstractTypeExtension;
-
-class TimeTypeCsrfExtension extends AbstractTypeExtension
-{
- public function getDefaultOptions()
- {
- return array('csrf_protection' => false);
- }
-
- public function getExtendedType()
- {
- return 'time';
- }
-}
View
10 src/Symfony/Component/Form/Form.php
@@ -975,7 +975,7 @@ public function createView(FormView $parent = null)
$parent = $this->parent->createView();
}
- $view = new FormView();
+ $view = new FormView($this->name);
$view->setParent($parent);
@@ -989,14 +989,10 @@ public function createView(FormView $parent = null)
}
}
- $childViews = array();
-
- foreach ($this->children as $key => $child) {
- $childViews[$key] = $child->createView($view);
+ foreach ($this->children as $child) {
+ $view->addChild($child->createView($view));
}
- $view->setChildren($childViews);
-
foreach ($types as $type) {
$type->buildViewBottomUp($view, $this);
View
55 src/Symfony/Component/Form/FormView.php
@@ -11,8 +11,17 @@
namespace Symfony\Component\Form;
-class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
+use ArrayAccess;
+use IteratorAggregate;
+use Countable;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class FormView implements ArrayAccess, IteratorAggregate, Countable
{
+ private $name;
+
private $vars = array(
'value' => null,
'attr' => array(),
@@ -33,6 +42,16 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
*/
private $rendered = false;
+ public function __construct($name)
+ {
+ $this->name = $name;
+ }
+
+ public function getName()
+ {
+ return $this->name;
+ }
+
/**
* @param string $name
* @param mixed $value
@@ -177,15 +196,29 @@ public function hasParent()
}
/**
- * Sets the children view.
+ * Adds a child view.
*
- * @param array $children The children as instances of FormView
+ * @param FormView $child The child view to add.
*
* @return FormView The current view
*/
- public function setChildren(array $children)
+ public function addChild(FormView $child)
{
- $this->children = $children;
+ $this->children[$child->getName()] = $child;
+
+ return $this;
+ }
+
+ /**
+ * Removes a child view.
+ *
+ * @param string $name The name of the removed child view.
+ *
+ * @return FormView The current view
+ */
+ public function removeChild($name)
+ {
+ unset($this->children[$name]);
return $this;
}
@@ -223,6 +256,18 @@ public function hasChildren()
}
/**
+ * Returns whether this view has a given child.
+ *
+ * @param string $name The name of the child
+ *
+ * @return Boolean Whether the child with the given name exists
+ */
+ public function hasChild($name)
+ {
+ return isset($this->children[$name]);
+ }
+
+ /**
* Returns a child by name (implements \ArrayAccess).
*
* @param string $name The child name
View
35 src/Symfony/Component/Form/Tests/AbstractDivLayoutTest.php
@@ -363,6 +363,31 @@ public function testNestedFormError()
);
}
+ public function testCsrf()
+ {
+ $this->csrfProvider->expects($this->any())
+ ->method('generateCsrfToken')
+ ->will($this->returnValue('foo&bar'));
+
+ $form = $this->factory->createNamedBuilder('form', 'name')
+ ->add($this->factory
+ // No CSRF protection on nested forms
+ ->createNamedBuilder('form', 'child')
+ ->add($this->factory->createNamedBuilder('text', 'grandchild'))
+ )
+ ->getForm();
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/div
+ [
+ ./input[@type="hidden"][@id="name__token"][@value="foo&bar"]
+ /following-sibling::div
+ ]
+ [count(.//input[@type="hidden"])=1]
+'
+ );
+ }
+
public function testRepeated()
{
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
@@ -372,7 +397,8 @@ public function testRepeated()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./div
+ ./input[@type="hidden"][@id="name__token"]
+ /following-sibling::div
[
./label[@for="name_first"]
/following-sibling::input[@type="text"][@id="name_first"]
@@ -383,7 +409,7 @@ public function testRepeated()
/following-sibling::input[@type="text"][@id="name_second"]
]
]
- [count(.//input)=2]
+ [count(.//input)=3]
'
);
}
@@ -399,7 +425,8 @@ public function testRepeatedWithCustomOptions()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./div
+ ./input[@type="hidden"][@id="name__token"]
+ /following-sibling::div
[
./label[@for="name_first"][.="[trans]Test[/trans]"]
/following-sibling::input[@type="text"][@id="name_first"][@required="required"]
@@ -410,7 +437,7 @@ public function testRepeatedWithCustomOptions()
/following-sibling::input[@type="text"][@id="name_second"][@required="required"]
]
]
- [count(.//input)=2]
+ [count(.//input)=3]
'
);
}
View
36 src/Symfony/Component/Form/Tests/AbstractLayoutTest.php
@@ -646,12 +646,13 @@ public function testSingleChoiceExpanded()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
+ ./input[@type="hidden"][@id="name__token"]
+ /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
]
- [count(./input)=2]
+ [count(./input)=3]
'
);
}
@@ -668,12 +669,13 @@ public function testSingleChoiceExpandedSkipEmptyValue()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="radio"][@name="name"][@id="name_0"][@checked]
+ ./input[@type="hidden"][@id="name__token"]
+ /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
]
- [count(./input)=2]
+ [count(./input)=3]
'
);
}
@@ -689,12 +691,13 @@ public function testSingleChoiceExpandedWithBooleanValue()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="radio"][@name="name"][@id="name_0"][@checked]
+ ./input[@type="hidden"][@id="name__token"]
+ /following-sibling::input[@type="radio"][@name="name"][@id="name_0"][@checked]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
]
- [count(./input)=2]
+ [count(./input)=3]
'
);
}
@@ -711,14 +714,15 @@ public function testMultipleChoiceExpanded()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
- ./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
+ ./input[@type="hidden"][@id="name__token"]
+ /following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
/following-sibling::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
/following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"]
]
- [count(./input)=3]
+ [count(./input)=4]
'
);
}
@@ -753,22 +757,6 @@ public function testCountryWithEmptyValue()
);
}
- public function testCsrf()
- {
- $this->csrfProvider->expects($this->any())
- ->method('generateCsrfToken')
- ->will($this->returnValue('foo&bar'));
-
- $form = $this->factory->createNamed('csrf', 'name');
-
- $this->assertWidgetMatchesXpath($form->createView(), array(),
-'/input
- [@type="hidden"]
- [@value="foo&bar"]
-'
- );
- }
-
public function testDateTime()
{
$form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
View
66 src/Symfony/Component/Form/Tests/AbstractTableLayoutTest.php
@@ -45,7 +45,12 @@ public function testRepeatedRow()
$html = $this->renderRow($form->createView());
$this->assertMatchesXpath($html,
-'/tr
+'/tr[@style="display: none"]
+ [./td[@colspan="2"]/input
+ [@type="hidden"]
+ [@id="name__token"]
+ ]
+/following-sibling::tr
[
./td
[./label[@for="name_first"]]
@@ -59,7 +64,7 @@ public function testRepeatedRow()
/following-sibling::td
[./input[@id="name_second"]]
]
- [count(../tr)=2]
+ [count(../tr)=3]
'
);
}
@@ -76,6 +81,11 @@ public function testRepeatedRowWithErrors()
[./td[@colspan="2"]/ul
[./li[.="[trans]Error![/trans]"]]
]
+/following-sibling::tr[@style="display: none"]
+ [./td[@colspan="2"]/input
+ [@type="hidden"]
+ [@id="name__token"]
+ ]
/following-sibling::tr
[
./td
@@ -90,7 +100,7 @@ public function testRepeatedRowWithErrors()
/following-sibling::td
[./input[@id="name_second"]]
]
- [count(../tr)=3]
+ [count(../tr)=4]
'
);
}
@@ -151,9 +161,9 @@ public function testCollection()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/table
[
- ./tr[./td/input[@type="text"][@value="a"]]
+ ./tr[@style="display: none"][./td[@colspan="2"]/input[@type="hidden"][@id="name__token"]]
+ /following-sibling::tr[./td/input[@type="text"][@value="a"]]
/following-sibling::tr[./td/input[@type="text"][@value="b"]]
- /following-sibling::tr[./td/input[@type="hidden"][@id="name__token"]]
]
[count(./tr[./td/input])=3]
'
@@ -217,6 +227,34 @@ public function testNestedFormError()
);
}
+ public function testCsrf()
+ {
+ $this->csrfProvider->expects($this->any())
+ ->method('generateCsrfToken')
+ ->will($this->returnValue('foo&bar'));
+
+ $form = $this->factory->createNamedBuilder('form', 'name')
+ ->add($this->factory
+ // No CSRF protection on nested forms
+ ->createNamedBuilder('form', 'child')
+ ->add($this->factory->createNamedBuilder('text', 'grandchild'))
+ )
+ ->getForm();
+
+ $this->assertWidgetMatchesXpath($form->createView(), array(),
+'/table
+ [
+ ./tr[@style="display: none"]
+ [./td[@colspan="2"]/input
+ [@type="hidden"]
+ [@id="name__token"]
+ ]
+ ]
+ [count(.//input[@type="hidden"])=1]
+'
+ );
+ }
+
public function testRepeated()
{
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
@@ -226,7 +264,12 @@ public function testRepeated()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/table
[
- ./tr
+ ./tr[@style="display: none"]
+ [./td[@colspan="2"]/input
+ [@type="hidden"]
+ [@id="name__token"]
+ ]
+ /following-sibling::tr
[
./td
[./label[@for="name_first"]]
@@ -241,7 +284,7 @@ public function testRepeated()
[./input[@type="text"][@id="name_second"]]
]
]
- [count(.//input)=2]
+ [count(.//input)=3]
'
);
}
@@ -257,7 +300,12 @@ public function testRepeatedWithCustomOptions()
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/table
[
- ./tr
+ ./tr[@style="display: none"]
+ [./td[@colspan="2"]/input
+ [@type="hidden"]
+ [@id="name__token"]
+ ]
+ /following-sibling::tr
[
./td
[./label[@for="name_first"][.="[trans]Test[/trans]"]]
@@ -272,7 +320,7 @@ public function testRepeatedWithCustomOptions()
[./input[@type="password"][@id="name_second"][@required="required"]]
]
]
- [count(.//input)=2]
+ [count(.//input)=3]
'
);
}
View
87 ...Symfony/Component/Form/Tests/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php
@@ -1,87 +0,0 @@
-<?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\EventListener;
-
-use Symfony\Component\Form\Event\DataEvent;
-use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener;
-
-class EnsureCsrfFieldListenerTest extends \PHPUnit_Framework_TestCase
-{
- private $form;
- private $formFactory;
- private $field;
- private $event;
-
- protected function setUp()
- {
- if (!class_exists('Symfony\Component\EventDispatcher\EventDispatcher')) {
- $this->markTestSkipped('The "EventDispatcher" component is not available');
- }
-
- $this->formFactory = $this->getMock('Symfony\\Component\\Form\\FormFactoryInterface');
- $this->form = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface');
- $this->field = $this->getMock('Symfony\\Component\\Form\\Tests\\FormInterface');
- $this->event = new DataEvent($this->form, array());
- }
-
- protected function tearDown()
- {
- $this->form = null;
- $this->formFactory = null;
- $this->field = null;
- $this->event = null;
- }
-
- public function testAddField()
- {
- $this->formFactory->expects($this->once())
- ->method('createNamed')
- ->with('csrf', '_token', null, array())
- ->will($this->returnValue($this->field));
- $this->form->expects($this->once())
- ->method('add')
- ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface'));
-
- $listener = new EnsureCsrfFieldListener($this->formFactory, '_token');
- $listener->ensureCsrfField($this->event);
- }
-
- public function testIntention()
- {
- $this->formFactory->expects($this->once())
- ->method('createNamed')
- ->with('csrf', '_token', null, array('intention' => 'something'))
- ->will($this->returnValue($this->field));
- $this->form->expects($this->once())
- ->method('add')
- ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface'));
-
- $listener = new EnsureCsrfFieldListener($this->formFactory, '_token', 'something');
- $listener->ensureCsrfField($this->event);
- }
-
- public function testProvider()
- {
- $provider = $this->getMock('Symfony\\Component\\Form\\Extension\\Csrf\\CsrfProvider\\CsrfProviderInterface');
-
- $this->formFactory->expects($this->once())
- ->method('createNamed')
- ->with('csrf', '_token', null, array('csrf_provider' => $provider))
- ->will($this->returnValue($this->field));
- $this->form->expects($this->once())
- ->method('add')
- ->with($this->isInstanceOf('Symfony\\Component\\Form\\Tests\\FormInterface'));
-
- $listener = new EnsureCsrfFieldListener($this->formFactory, '_token', null, $provider);
- $listener->ensureCsrfField($this->event);
- }
-}
View
112 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/CsrfTypeTest.php
@@ -1,112 +0,0 @@
-<?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\Type;
-
-class CsrfTypeTest extends TypeTestCase
-{
- protected $provider;
-
- protected function setUp()
- {
- parent::setUp();
-
- $this->provider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
- }
-
- protected function tearDown()
- {
- parent::tearDown();
-
- $this->provider = null;
- }
-
- protected function getNonRootForm()
- {
- $form = $this->getMock('Symfony\Component\Form\Tests\FormInterface');
- $form->expects($this->any())
- ->method('isRoot')
- ->will($this->returnValue(false));
-
- return $form;
- }
-
- public function testGenerateCsrfToken()
- {
- $this->provider->expects($this->once())
- ->method('generateCsrfToken')
- ->with('%INTENTION%')
- ->will($this->returnValue('token'));
-
- $form = $this->factory->create('csrf', null, array(
- 'csrf_provider' => $this->provider,
- 'intention' => '%INTENTION%'
- ));
-
- $this->assertEquals('token', $form->getData());
- }
-
- public function testValidateTokenOnBind()
- {
- $this->provider->expects($this->once())
- ->method('isCsrfTokenValid')
- ->with('%INTENTION%', 'token')
- ->will($this->returnValue(true));
-
- $form = $this->factory->create('csrf', null, array(
- 'csrf_provider' => $this->provider,
- 'intention' => '%INTENTION%'
- ));
- $form->bind('token');
-
- $this->assertEquals('token', $form->getData());
- }
-
- public function testDontValidateTokenIfParentIsNotRoot()
- {
- $this->provider->expects($this->never())
- ->method('isCsrfTokenValid');
-
- $form = $this->factory->create('csrf', null, array(
- 'csrf_provider' => $this->provider,
- 'intention' => '%INTENTION%'
- ));
- $form->setParent($this->getNonRootForm());
- $form->bind('token');
- }
-
- public function testCsrfTokenIsRegeneratedIfValidationFails()
- {
- $this->provider->expects($this->at(0))
- ->method('generateCsrfToken')
- ->with('%INTENTION%')
- ->will($this->returnValue('token1'));
- $this->provider->expects($this->at(1))
- ->method('isCsrfTokenValid')
- ->with('%INTENTION%', 'invalid')
- ->will($this->returnValue(false));
-
- // The token is regenerated to avoid stalled tokens, for example when
- // the session ID changed
- $this->provider->expects($this->at(2))
- ->method('generateCsrfToken')
- ->with('%INTENTION%')
- ->will($this->returnValue('token2'));
-
- $form = $this->factory->create('csrf', null, array(
- 'csrf_provider' => $this->provider,
- 'intention' => '%INTENTION%'
- ));
- $form->bind('invalid');
-
- $this->assertEquals('token2', $form->getData());
- }
-}
View
181 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/FormTypeCsrfExtensionTest.php
@@ -11,43 +11,188 @@
namespace Symfony\Component\Form\Tests\Extension\Csrf\Type;
+use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
+use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase;
+
class FormTypeCsrfExtensionTest extends TypeTestCase
{
- public function testCsrfProtectionByDefault()
+ protected $csrfProvider;
+
+ protected function setUp()
+ {
+ $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
+
+ parent::setUp();
+ }
+
+ protected function tearDown()
+ {
+ $this->csrfProvider = null;
+
+ parent::tearDown();
+ }
+
+ protected function getExtensions()
{
- $form = $this->factory->create('form', null, array(
- 'csrf_field_name' => 'csrf',
+ return array_merge(parent::getExtensions(), array(
+ new CsrfExtension($this->csrfProvider),
));
+ }
- $this->assertTrue($form->has('csrf'));
+ public function testCsrfProtectionByDefaultIfRootAndChildren()
+ {
+ $view = $this->factory
+ ->createBuilder('form', null, array(
+ 'csrf_field_name' => 'csrf',
+ ))
+ ->add($this->factory->createNamedBuilder('form', 'child'))
+ ->getForm()
+ ->createView();
+
+ $this->assertTrue($view->hasChild('csrf'));
+ }
+
+ public function testNoCsrfProtectionByDefaultIfChildrenButNotRoot()
+ {
+ $view = $this->factory
+ ->createNamedBuilder('form', 'root')
+ ->add($this->factory
+ ->createNamedBuilder('form', 'form', null, array(
+ 'csrf_field_name' => 'csrf',
+ ))
+ ->add($this->factory->createNamedBuilder('form', 'child'))
+ )
+ ->getForm()
+ ->get('form')
+ ->createView();
+
+ $this->assertFalse($view->hasChild('csrf'));
+ }
+
+ public function testNoCsrfProtectionByDefaultIfRootButNoChildren()
+ {
+ $view = $this->factory
+ ->createBuilder('form', null, array(
+ 'csrf_field_name' => 'csrf',
+ ))
+ ->getForm()
+ ->createView();
+
+ $this->assertFalse($view->hasChild('csrf'));
}
public function testCsrfProtectionCanBeDisabled()
{
- $form = $this->factory->create('form', null, array(
- 'csrf_protection' => false,
- ));
+ $view = $this->factory
+ ->createBuilder('form', null, array(
+ 'csrf_field_name' => 'csrf',
+ 'csrf_protection' => false,
+ ))
+ ->add($this->factory->createNamedBuilder('form', 'child'))
+ ->getForm()
+ ->createView();
+
+ $this->assertFalse($view->hasChild('csrf'));
+ }
+
+ public function testGenerateCsrfToken()
+ {
+ $this->csrfProvider->expects($this->once())
+ ->method('generateCsrfToken')
+ ->with('%INTENTION%')
+ ->will($this->returnValue('token'));
+
+ $view = $this->factory
+ ->createBuilder('form', null, array(
+ 'csrf_field_name' => 'csrf',
+ 'csrf_provider' => $this->csrfProvider,
+ 'intention' => '%INTENTION%'
+ ))
+ ->add($this->factory->createNamedBuilder('form', 'child'))
+ ->getForm()
+ ->createView();
- $this->assertCount(0, $form);
+ $this->assertEquals('token', $view->getChild('csrf')->get('value'));
}
- public function testCsrfTokenIsOnlyIncludedInRootView()
+ public function provideBoolean()
{
- $view =
- $this->factory->createBuilder('form', null, array(
+ return array(
+ array(true),
+ array(false),
+ );
+ }
+
+ /**
+ * @dataProvider provideBoolean
+ */
+ public function testValidateTokenOnBindIfRootAndChildren($valid)
+ {
+ $this->csrfProvider->expects($this->once())
+ ->method('isCsrfTokenValid')
+ ->with('%INTENTION%', 'token')
+ ->will($this->returnValue($valid));
+
+ $form = $this->factory
+ ->createBuilder('form', null, array(
'csrf_field_name' => 'csrf',
+ 'csrf_provider' => $this->csrfProvider,
+ 'intention' => '%INTENTION%'
))
- ->add('notCsrf', 'text')
- ->add(
- $this->factory->createNamedBuilder('form', 'child', null, array(
+ ->add($this->factory->createNamedBuilder('form', 'child'))
+ ->getForm();
+
+ $form->bind(array(
+ 'child' => 'foobar',
+ 'csrf' => 'token',
+ ));
+
+ // Remove token from data
+ $this->assertSame(array('child' => 'foobar'), $form->getData());
+
+ // Validate accordingly
+ $this->assertSame($valid, $form->isValid());
+ }
+
+ public function testDontValidateTokenIfChildrenButNoRoot()
+ {
+ $this->csrfProvider->expects($this->never())
+ ->method('isCsrfTokenValid');
+
+ $form = $this->factory
+ ->createNamedBuilder('form', 'root')
+ ->add($this->factory
+ ->createNamedBuilder('form', 'form', null, array(
'csrf_field_name' => 'csrf',
+ 'csrf_provider' => $this->csrfProvider,
+ 'intention' => '%INTENTION%'
))
- ->add('notCsrf', 'text')
+ ->add($this->factory->createNamedBuilder('form', 'child'))
)
->getForm()
- ->createView();
+ ->get('form');
- $this->assertEquals(array('csrf', 'notCsrf', 'child'), array_keys(iterator_to_array($view)));
- $this->assertEquals(array('notCsrf'), array_keys(iterator_to_array($view['child'])));
+ $form->bind(array(
+ 'child' => 'foobar',
+ 'csrf' => 'token',
+ ));
+ }
+
+ public function testDontValidateTokenIfRootButNoChildren()
+ {
+ $this->csrfProvider->expects($this->never())
+ ->method('isCsrfTokenValid');
+
+ $form = $this->factory
+ ->createBuilder('form', null, array(
+ 'csrf_field_name' => 'csrf',
+ 'csrf_provider' => $this->csrfProvider,
+ 'intention' => '%INTENTION%'
+ ))
+ ->getForm();
+
+ $form->bind(array(
+ 'csrf' => 'token',
+ ));
}
}
View
41 src/Symfony/Component/Form/Tests/Extension/Csrf/Type/TypeTestCase.php
@@ -1,41 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Type;
-
-use Symfony\Component\Form\Tests\Extension\Core\Type\TypeTestCase as BaseTestCase;
-use Symfony\Component\Form\Extension\Csrf\CsrfExtension;
-
-abstract class TypeTestCase extends BaseTestCase
-{
- protected $csrfProvider;
-
- protected function setUp()
- {
- $this->csrfProvider = $this->getMock('Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface');
-
- parent::setUp();
- }
-
- protected function tearDown()
- {
- $this->csrfProvider = null;
-
- parent::tearDown();
- }
-
- protected function getExtensions()
- {
- return array_merge(parent::getExtensions(), array(
- new CsrfExtension($this->csrfProvider),
- ));
- }
-}
View
2 src/Symfony/Component/Form/Tests/FormTest.php
@@ -1247,7 +1247,7 @@ public function testCreateView()
public function testCreateViewAcceptsParent()
{
- $parent = new FormView();
+ $parent = new FormView('form');
$form = $this->getBuilder()->getForm();
$view = $form->createView($parent);

0 comments on commit 2a49449

Please sign in to comment.
Something went wrong with that request. Please try again.