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 năm trước cách đây
mục cha
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);
+    }
+}