Browse Source

Merge remote branch 'kriswallsmith/form/lazier-csrf-token'

* kriswallsmith/form/lazier-csrf-token:
  [Form] fixed xpath
  [Form] moved csrf listener to its own class
  fix issue with csrf token not present on collection fields because of resize listener
Fabien Potencier 14 years ago
parent
commit
c79e51c9aa

+ 66 - 0
src/Symfony/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListener.php

@@ -0,0 +1,66 @@
+<?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));
+    }
+}

+ 17 - 10
src/Symfony/Component/Form/Extension/Csrf/Type/FormTypeCsrfExtension.php

@@ -12,8 +12,10 @@
 namespace Symfony\Component\Form\Extension\Csrf\Type;
 
 use Symfony\Component\Form\AbstractTypeExtension;
+use Symfony\Component\Form\Extension\Csrf\EventListener\EnsureCsrfFieldListener;
 use Symfony\Component\Form\FormBuilder;
 use Symfony\Component\Form\FormView;
+use Symfony\Component\Form\FormEvents;
 use Symfony\Component\Form\FormInterface;
 
 class FormTypeCsrfExtension extends AbstractTypeExtension
@@ -35,18 +37,23 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
      */
     public function buildForm(FormBuilder $builder, array $options)
     {
-        if ($options['csrf_protection']) {
-            $csrfOptions = array('intention' => $options['intention']);
+        if (!$options['csrf_protection']) {
+            return;
+        }
 
-            if ($options['csrf_provider']) {
-                $csrfOptions['csrf_provider'] = $options['csrf_provider'];
-            }
+        $listener = new EnsureCsrfFieldListener(
+            $builder->getFormFactory(),
+            $options['csrf_field_name'],
+            $options['intention'],
+            $options['csrf_provider']
+        );
 
-            $builder
-                ->add($options['csrf_field_name'], 'csrf', $csrfOptions)
-                ->setAttribute('csrf_field_name', $options['csrf_field_name'])
-            ;
-        }
+        // 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)
+        ;
     }
 
     /**

+ 2 - 1
tests/Symfony/Tests/Component/Form/AbstractTableLayoutTest.php

@@ -153,8 +153,9 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
     [
         ./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])=2]
+    [count(./tr[./td/input])=3]
 '
         );
     }

+ 73 - 0
tests/Symfony/Tests/Component/Form/Extension/Csrf/EventListener/EnsureCsrfFieldListenerTest.php

@@ -0,0 +1,73 @@
+<?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\Tests\Component\Form\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;
+
+    protected function setUp()
+    {
+        $this->formFactory = $this->getMock('Symfony\\Component\\Form\\FormFactoryInterface');
+        $this->form = $this->getMock('Symfony\\Tests\\Component\\Form\\FormInterface');
+        $this->field = $this->getMock('Symfony\\Tests\\Component\\Form\\FormInterface');
+        $this->event = new DataEvent($this->form, array());
+    }
+
+    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\\Tests\\Component\\Form\\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\\Tests\\Component\\Form\\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\\Tests\\Component\\Form\\FormInterface'));
+
+        $listener = new EnsureCsrfFieldListener($this->formFactory, '_token', null, $provider);
+        $listener->ensureCsrfField($this->event);
+    }
+}