[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.
This commit is contained in:
parent
e7470ffebb
commit
2a49449862
@ -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.
|
don't receive an options array anymore.
|
||||||
* Deprecated FormValidatorInterface and substituted its implementations
|
* Deprecated FormValidatorInterface and substituted its implementations
|
||||||
by event subscribers
|
by event subscribers
|
||||||
|
* simplified CSRF protection and removed the csrf type
|
||||||
|
|
||||||
### HttpFoundation
|
### HttpFoundation
|
||||||
|
|
||||||
|
@ -14,12 +14,9 @@
|
|||||||
<argument>%kernel.secret%</argument>
|
<argument>%kernel.secret%</argument>
|
||||||
</service>
|
</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">
|
<service id="form.type_extension.csrf" class="Symfony\Component\Form\Extension\Csrf\Type\FormTypeCsrfExtension">
|
||||||
<tag name="form.type_extension" alias="form" />
|
<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.enabled%</argument>
|
||||||
<argument>%form.type_extension.csrf.field_name%</argument>
|
<argument>%form.type_extension.csrf.field_name%</argument>
|
||||||
</service>
|
</service>
|
||||||
|
@ -27,9 +27,9 @@ abstract class FrameworkExtensionTest extends TestCase
|
|||||||
$def = $container->getDefinition('form.type_extension.csrf');
|
$def = $container->getDefinition('form.type_extension.csrf');
|
||||||
|
|
||||||
$this->assertTrue($container->getParameter('form.type_extension.csrf.enabled'));
|
$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('_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)));
|
$this->assertEquals('s3cr3t', $container->getParameterBag()->resolveValue($container->findDefinition('form.csrf_provider')->getArgument(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,27 +32,13 @@ class CsrfExtension extends AbstractExtension
|
|||||||
$this->csrfProvider = $csrfProvider;
|
$this->csrfProvider = $csrfProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
protected function loadTypes()
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
new Type\CsrfType($this->csrfProvider),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
protected function loadTypeExtensions()
|
protected function loadTypeExtensions()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
new Type\ChoiceTypeCsrfExtension(),
|
new Type\FormTypeCsrfExtension($this->csrfProvider),
|
||||||
new Type\DateTypeCsrfExtension(),
|
|
||||||
new Type\FormTypeCsrfExtension(),
|
|
||||||
new Type\RepeatedTypeCsrfExtension(),
|
|
||||||
new Type\TimeTypeCsrfExtension(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace Symfony\Component\Form\Extension\Csrf\EventListener;
|
|||||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
use Symfony\Component\Form\FormEvents;
|
use Symfony\Component\Form\FormEvents;
|
||||||
use Symfony\Component\Form\FormError;
|
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;
|
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,6 +22,12 @@ use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
|
|||||||
*/
|
*/
|
||||||
class CsrfValidationListener implements EventSubscriberInterface
|
class CsrfValidationListener implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* The name of the CSRF field
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $fieldName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The provider for generating and validating CSRF tokens
|
* The provider for generating and validating CSRF tokens
|
||||||
* @var CsrfProviderInterface
|
* @var CsrfProviderInterface
|
||||||
@ -45,24 +51,26 @@ class CsrfValidationListener implements EventSubscriberInterface
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(CsrfProviderInterface $csrfProvider, $intention)
|
public function __construct($fieldName, CsrfProviderInterface $csrfProvider, $intention)
|
||||||
{
|
{
|
||||||
|
$this->fieldName = $fieldName;
|
||||||
$this->csrfProvider = $csrfProvider;
|
$this->csrfProvider = $csrfProvider;
|
||||||
$this->intention = $intention;
|
$this->intention = $intention;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onBindClientData(DataEvent $event)
|
public function onBindClientData(FilterDataEvent $event)
|
||||||
{
|
{
|
||||||
$form = $event->getForm();
|
$form = $event->getForm();
|
||||||
$data = $event->getData();
|
$data = $event->getData();
|
||||||
|
|
||||||
if ((!$form->hasParent() || $form->getParent()->isRoot())
|
if ($form->isRoot() && $form->hasChildren() && isset($data[$this->fieldName])) {
|
||||||
&& !$this->csrfProvider->isCsrfTokenValid($this->intention, $data)) {
|
if (!$this->csrfProvider->isCsrfTokenValid($this->intention, $data[$this->fieldName])) {
|
||||||
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
|
$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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,21 +12,27 @@
|
|||||||
namespace Symfony\Component\Form\Extension\Csrf\Type;
|
namespace Symfony\Component\Form\Extension\Csrf\Type;
|
||||||
|
|
||||||
use Symfony\Component\Form\AbstractTypeExtension;
|
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\FormBuilder;
|
||||||
use Symfony\Component\Form\FormView;
|
use Symfony\Component\Form\FormView;
|
||||||
use Symfony\Component\Form\FormEvents;
|
use Symfony\Component\Form\FormEvents;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||||
|
*/
|
||||||
class FormTypeCsrfExtension extends AbstractTypeExtension
|
class FormTypeCsrfExtension extends AbstractTypeExtension
|
||||||
{
|
{
|
||||||
private $enabled;
|
private $defaultCsrfProvider;
|
||||||
private $fieldName;
|
private $defaultEnabled;
|
||||||
|
private $defaultFieldName;
|
||||||
|
|
||||||
public function __construct($enabled = true, $fieldName = '_token')
|
public function __construct(CsrfProviderInterface $defaultCsrfProvider, $defaultEnabled = true, $defaultFieldName = '_token')
|
||||||
{
|
{
|
||||||
$this->enabled = $enabled;
|
$this->defaultCsrfProvider = $defaultCsrfProvider;
|
||||||
$this->fieldName = $fieldName;
|
$this->defaultEnabled = $defaultEnabled;
|
||||||
|
$this->defaultFieldName = $defaultFieldName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,35 +47,35 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
|
|||||||
return;
|
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
|
// use a low priority so higher priority listeners don't remove the field
|
||||||
$builder
|
$builder
|
||||||
->setAttribute('csrf_field_name', $options['csrf_field_name'])
|
->setAttribute('csrf_field_name', $options['csrf_field_name'])
|
||||||
->addEventListener(FormEvents::PRE_SET_DATA, array($listener, 'ensureCsrfField'), -10)
|
->setAttribute('csrf_provider', $options['csrf_provider'])
|
||||||
->addEventListener(FormEvents::PRE_BIND, array($listener, 'ensureCsrfField'), -10)
|
->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 FormView $view The form view
|
||||||
* @param FormInterface $form The form
|
* @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');
|
$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])) {
|
$view->addChild($csrfForm->createView($view));
|
||||||
unset($view[$name]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,9 +85,9 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
|
|||||||
public function getDefaultOptions()
|
public function getDefaultOptions()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
'csrf_protection' => $this->enabled,
|
'csrf_protection' => $this->defaultEnabled,
|
||||||
'csrf_field_name' => $this->fieldName,
|
'csrf_field_name' => $this->defaultFieldName,
|
||||||
'csrf_provider' => null,
|
'csrf_provider' => $this->defaultCsrfProvider,
|
||||||
'intention' => 'unknown',
|
'intention' => 'unknown',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
@ -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';
|
|
||||||
}
|
|
||||||
}
|
|
@ -975,7 +975,7 @@ class Form implements \IteratorAggregate, FormInterface
|
|||||||
$parent = $this->parent->createView();
|
$parent = $this->parent->createView();
|
||||||
}
|
}
|
||||||
|
|
||||||
$view = new FormView();
|
$view = new FormView($this->name);
|
||||||
|
|
||||||
$view->setParent($parent);
|
$view->setParent($parent);
|
||||||
|
|
||||||
@ -989,14 +989,10 @@ class Form implements \IteratorAggregate, FormInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$childViews = array();
|
foreach ($this->children as $child) {
|
||||||
|
$view->addChild($child->createView($view));
|
||||||
foreach ($this->children as $key => $child) {
|
|
||||||
$childViews[$key] = $child->createView($view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$view->setChildren($childViews);
|
|
||||||
|
|
||||||
foreach ($types as $type) {
|
foreach ($types as $type) {
|
||||||
$type->buildViewBottomUp($view, $this);
|
$type->buildViewBottomUp($view, $this);
|
||||||
|
|
||||||
|
@ -11,8 +11,17 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Form;
|
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(
|
private $vars = array(
|
||||||
'value' => null,
|
'value' => null,
|
||||||
'attr' => array(),
|
'attr' => array(),
|
||||||
@ -33,6 +42,16 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||||||
*/
|
*/
|
||||||
private $rendered = false;
|
private $rendered = false;
|
||||||
|
|
||||||
|
public function __construct($name)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $name
|
* @param string $name
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
@ -177,15 +196,29 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
* @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;
|
return $this;
|
||||||
}
|
}
|
||||||
@ -222,6 +255,18 @@ class FormView implements \ArrayAccess, \IteratorAggregate, \Countable
|
|||||||
return count($this->children) > 0;
|
return count($this->children) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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).
|
* Returns a child by name (implements \ArrayAccess).
|
||||||
*
|
*
|
||||||
|
@ -363,6 +363,31 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
public function testRepeated()
|
||||||
{
|
{
|
||||||
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
|
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
|
||||||
@ -372,7 +397,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/div
|
'/div
|
||||||
[
|
[
|
||||||
./div
|
./input[@type="hidden"][@id="name__token"]
|
||||||
|
/following-sibling::div
|
||||||
[
|
[
|
||||||
./label[@for="name_first"]
|
./label[@for="name_first"]
|
||||||
/following-sibling::input[@type="text"][@id="name_first"]
|
/following-sibling::input[@type="text"][@id="name_first"]
|
||||||
@ -383,7 +409,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
|
|||||||
/following-sibling::input[@type="text"][@id="name_second"]
|
/following-sibling::input[@type="text"][@id="name_second"]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[count(.//input)=2]
|
[count(.//input)=3]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -399,7 +425,8 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/div
|
'/div
|
||||||
[
|
[
|
||||||
./div
|
./input[@type="hidden"][@id="name__token"]
|
||||||
|
/following-sibling::div
|
||||||
[
|
[
|
||||||
./label[@for="name_first"][.="[trans]Test[/trans]"]
|
./label[@for="name_first"][.="[trans]Test[/trans]"]
|
||||||
/following-sibling::input[@type="text"][@id="name_first"][@required="required"]
|
/following-sibling::input[@type="text"][@id="name_first"][@required="required"]
|
||||||
@ -410,7 +437,7 @@ abstract class AbstractDivLayoutTest extends AbstractLayoutTest
|
|||||||
/following-sibling::input[@type="text"][@id="name_second"][@required="required"]
|
/following-sibling::input[@type="text"][@id="name_second"][@required="required"]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[count(.//input)=2]
|
[count(.//input)=3]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -646,12 +646,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/div
|
'/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::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
|
||||||
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
|
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
|
||||||
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
||||||
]
|
]
|
||||||
[count(./input)=2]
|
[count(./input)=3]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -668,12 +669,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/div
|
'/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::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
|
||||||
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
|
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
|
||||||
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
||||||
]
|
]
|
||||||
[count(./input)=2]
|
[count(./input)=3]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -689,12 +691,13 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/div
|
'/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::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
|
||||||
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
|
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][not(@checked)]
|
||||||
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
/following-sibling::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
||||||
]
|
]
|
||||||
[count(./input)=2]
|
[count(./input)=3]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -711,14 +714,15 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/div
|
'/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::label[@for="name_0"][.="[trans]Choice&A[/trans]"]
|
||||||
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
|
/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::label[@for="name_1"][.="[trans]Choice&B[/trans]"]
|
||||||
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
|
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
|
||||||
/following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"]
|
/following-sibling::label[@for="name_2"][.="[trans]Choice&C[/trans]"]
|
||||||
]
|
]
|
||||||
[count(./input)=3]
|
[count(./input)=4]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -753,22 +757,6 @@ abstract class AbstractLayoutTest extends \PHPUnit_Framework_TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
public function testDateTime()
|
||||||
{
|
{
|
||||||
$form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
|
$form = $this->factory->createNamed('datetime', 'name', '2011-02-03 04:05:06', array(
|
||||||
|
@ -45,36 +45,10 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
$html = $this->renderRow($form->createView());
|
$html = $this->renderRow($form->createView());
|
||||||
|
|
||||||
$this->assertMatchesXpath($html,
|
$this->assertMatchesXpath($html,
|
||||||
'/tr
|
'/tr[@style="display: none"]
|
||||||
[
|
[./td[@colspan="2"]/input
|
||||||
./td
|
[@type="hidden"]
|
||||||
[./label[@for="name_first"]]
|
[@id="name__token"]
|
||||||
/following-sibling::td
|
|
||||||
[./input[@id="name_first"]]
|
|
||||||
]
|
|
||||||
/following-sibling::tr
|
|
||||||
[
|
|
||||||
./td
|
|
||||||
[./label[@for="name_second"]]
|
|
||||||
/following-sibling::td
|
|
||||||
[./input[@id="name_second"]]
|
|
||||||
]
|
|
||||||
[count(../tr)=2]
|
|
||||||
'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testRepeatedRowWithErrors()
|
|
||||||
{
|
|
||||||
$form = $this->factory->createNamed('repeated', 'name');
|
|
||||||
$form->addError(new FormError('Error!'));
|
|
||||||
$view = $form->createView();
|
|
||||||
$html = $this->renderRow($view);
|
|
||||||
|
|
||||||
$this->assertMatchesXpath($html,
|
|
||||||
'/tr
|
|
||||||
[./td[@colspan="2"]/ul
|
|
||||||
[./li[.="[trans]Error![/trans]"]]
|
|
||||||
]
|
]
|
||||||
/following-sibling::tr
|
/following-sibling::tr
|
||||||
[
|
[
|
||||||
@ -95,6 +69,42 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRepeatedRowWithErrors()
|
||||||
|
{
|
||||||
|
$form = $this->factory->createNamed('repeated', 'name');
|
||||||
|
$form->addError(new FormError('Error!'));
|
||||||
|
$view = $form->createView();
|
||||||
|
$html = $this->renderRow($view);
|
||||||
|
|
||||||
|
$this->assertMatchesXpath($html,
|
||||||
|
'/tr
|
||||||
|
[./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
|
||||||
|
[./label[@for="name_first"]]
|
||||||
|
/following-sibling::td
|
||||||
|
[./input[@id="name_first"]]
|
||||||
|
]
|
||||||
|
/following-sibling::tr
|
||||||
|
[
|
||||||
|
./td
|
||||||
|
[./label[@for="name_second"]]
|
||||||
|
/following-sibling::td
|
||||||
|
[./input[@id="name_second"]]
|
||||||
|
]
|
||||||
|
[count(../tr)=4]
|
||||||
|
'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function testRest()
|
public function testRest()
|
||||||
{
|
{
|
||||||
$view = $this->factory->createNamedBuilder('form', 'name')
|
$view = $this->factory->createNamedBuilder('form', 'name')
|
||||||
@ -151,9 +161,9 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/table
|
'/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="text"][@value="b"]]
|
||||||
/following-sibling::tr[./td/input[@type="hidden"][@id="name__token"]]
|
|
||||||
]
|
]
|
||||||
[count(./tr[./td/input])=3]
|
[count(./tr[./td/input])=3]
|
||||||
'
|
'
|
||||||
@ -217,6 +227,34 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
public function testRepeated()
|
||||||
{
|
{
|
||||||
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
|
$form = $this->factory->createNamed('repeated', 'name', 'foobar', array(
|
||||||
@ -226,7 +264,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/table
|
'/table
|
||||||
[
|
[
|
||||||
./tr
|
./tr[@style="display: none"]
|
||||||
|
[./td[@colspan="2"]/input
|
||||||
|
[@type="hidden"]
|
||||||
|
[@id="name__token"]
|
||||||
|
]
|
||||||
|
/following-sibling::tr
|
||||||
[
|
[
|
||||||
./td
|
./td
|
||||||
[./label[@for="name_first"]]
|
[./label[@for="name_first"]]
|
||||||
@ -241,7 +284,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
[./input[@type="text"][@id="name_second"]]
|
[./input[@type="text"][@id="name_second"]]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[count(.//input)=2]
|
[count(.//input)=3]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -257,7 +300,12 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
$this->assertWidgetMatchesXpath($form->createView(), array(),
|
||||||
'/table
|
'/table
|
||||||
[
|
[
|
||||||
./tr
|
./tr[@style="display: none"]
|
||||||
|
[./td[@colspan="2"]/input
|
||||||
|
[@type="hidden"]
|
||||||
|
[@id="name__token"]
|
||||||
|
]
|
||||||
|
/following-sibling::tr
|
||||||
[
|
[
|
||||||
./td
|
./td
|
||||||
[./label[@for="name_first"][.="[trans]Test[/trans]"]]
|
[./label[@for="name_first"][.="[trans]Test[/trans]"]]
|
||||||
@ -272,7 +320,7 @@ abstract class AbstractTableLayoutTest extends AbstractLayoutTest
|
|||||||
[./input[@type="password"][@id="name_second"][@required="required"]]
|
[./input[@type="password"][@id="name_second"][@required="required"]]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
[count(.//input)=2]
|
[count(.//input)=3]
|
||||||
'
|
'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,43 +11,188 @@
|
|||||||
|
|
||||||
namespace Symfony\Component\Form\Tests\Extension\Csrf\Type;
|
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
|
class FormTypeCsrfExtensionTest extends TypeTestCase
|
||||||
{
|
{
|
||||||
public function testCsrfProtectionByDefault()
|
protected $csrfProvider;
|
||||||
{
|
|
||||||
$form = $this->factory->create('form', null, array(
|
|
||||||
'csrf_field_name' => 'csrf',
|
|
||||||
));
|
|
||||||
|
|
||||||
$this->assertTrue($form->has('csrf'));
|
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),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
public function testCsrfProtectionCanBeDisabled()
|
||||||
{
|
{
|
||||||
$form = $this->factory->create('form', null, array(
|
$view = $this->factory
|
||||||
|
->createBuilder('form', null, array(
|
||||||
|
'csrf_field_name' => 'csrf',
|
||||||
'csrf_protection' => false,
|
'csrf_protection' => false,
|
||||||
));
|
|
||||||
|
|
||||||
$this->assertCount(0, $form);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function testCsrfTokenIsOnlyIncludedInRootView()
|
|
||||||
{
|
|
||||||
$view =
|
|
||||||
$this->factory->createBuilder('form', null, array(
|
|
||||||
'csrf_field_name' => 'csrf',
|
|
||||||
))
|
))
|
||||||
->add('notCsrf', 'text')
|
->add($this->factory->createNamedBuilder('form', 'child'))
|
||||||
->add(
|
|
||||||
$this->factory->createNamedBuilder('form', 'child', null, array(
|
|
||||||
'csrf_field_name' => 'csrf',
|
|
||||||
))
|
|
||||||
->add('notCsrf', 'text')
|
|
||||||
)
|
|
||||||
->getForm()
|
->getForm()
|
||||||
->createView();
|
->createView();
|
||||||
|
|
||||||
$this->assertEquals(array('csrf', 'notCsrf', 'child'), array_keys(iterator_to_array($view)));
|
$this->assertFalse($view->hasChild('csrf'));
|
||||||
$this->assertEquals(array('notCsrf'), array_keys(iterator_to_array($view['child'])));
|
}
|
||||||
|
|
||||||
|
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->assertEquals('token', $view->getChild('csrf')->get('value'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function provideBoolean()
|
||||||
|
{
|
||||||
|
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($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($this->factory->createNamedBuilder('form', 'child'))
|
||||||
|
)
|
||||||
|
->getForm()
|
||||||
|
->get('form');
|
||||||
|
|
||||||
|
$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',
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1247,7 +1247,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
|
|||||||
|
|
||||||
public function testCreateViewAcceptsParent()
|
public function testCreateViewAcceptsParent()
|
||||||
{
|
{
|
||||||
$parent = new FormView();
|
$parent = new FormView('form');
|
||||||
|
|
||||||
$form = $this->getBuilder()->getForm();
|
$form = $this->getBuilder()->getForm();
|
||||||
$view = $form->createView($parent);
|
$view = $form->createView($parent);
|
||||||
|
Reference in New Issue
Block a user