merged kriswallsmith/form/csrf-intention

This commit is contained in:
Fabien Potencier 2011-05-17 15:25:50 +02:00
commit eb202bb7b7
8 changed files with 48 additions and 45 deletions

View File

@ -23,6 +23,9 @@ beta1 to beta2
Symfony\Component\Routing\Exception\NotFoundException
Symfony\Component\Routing\Exception\MethodNotAllowedException
* The form component's ``csrf_page_id`` option has been renamed to
``csrf_intention``.
* The ``error_handler`` setting has been removed. The ``ErrorHandler`` class
is now managed directly by Symfony SE in ``AppKernel``.

View File

@ -30,7 +30,7 @@ class FormLoginFactory extends AbstractFactory
$this->addOption('username_parameter', '_username');
$this->addOption('password_parameter', '_password');
$this->addOption('csrf_parameter', '_csrf_token');
$this->addOption('csrf_page_id', 'form_login');
$this->addOption('intention', 'authenticate');
$this->addOption('post_only', true);
}

View File

@ -21,31 +21,29 @@ namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider;
* secret information.
*
* If you want to secure a form submission against CSRF attacks, you could
* use the class name of the form as page ID. This way you make sure that the
* form can only be bound to pages that are designed to handle the form,
* that is, that use the same class name to validate the CSRF token with
* isCsrfTokenValid().
* supply an "intention" string. This way you make sure that the form can only
* be bound to pages that are designed to handle the form, that is, that use
* the same intention string to validate the CSRF token with isCsrfTokenValid().
*
* @author Bernhard Schussek <bernhard.schussek@symfony.com>
*/
interface CsrfProviderInterface
{
/**
* Generates a CSRF token for a page of your application
* Generates a CSRF token for a page of your application.
*
* @param string $pageId Some value that identifies the page (for example,
* the class name of the form). Doesn't have to be
* a secret value.
* @param string $intention Some value that identifies the action intention
* (i.e. "authenticate"). Doesn't have to be a secret value.
*/
public function generateCsrfToken($pageId);
public function generateCsrfToken($intention);
/**
* Validates a CSRF token
* Validates a CSRF token.
*
* @param string $pageId The page ID used when generating the CSRF token
* @param string $token The token supplied by the browser
* @return Boolean Whether the token supplied by the browser is
* correct
* @param string $intention The intention used when generating the CSRF token
* @param string $token The token supplied by the browser
*
* @return Boolean Whether the token supplied by the browser is correct
*/
public function isCsrfTokenValid($pageId, $token);
public function isCsrfTokenValid($intention, $token);
}

View File

@ -43,17 +43,17 @@ class DefaultCsrfProvider implements CsrfProviderInterface
/**
* {@inheritDoc}
*/
public function generateCsrfToken($pageId)
public function generateCsrfToken($intention)
{
return sha1($this->secret.$pageId.$this->getSessionId());
return sha1($this->secret.$intention.$this->getSessionId());
}
/**
* {@inheritDoc}
*/
public function isCsrfTokenValid($pageId, $token)
public function isCsrfTokenValid($intention, $token)
{
return $token === $this->generateCsrfToken($pageId);
return $token === $this->generateCsrfToken($intention);
}
/**

View File

@ -30,26 +30,28 @@ class CsrfType extends AbstractType
public function buildForm(FormBuilder $builder, array $options)
{
$csrfProvider = $options['csrf_provider'];
$pageId = $options['page_id'];
$intention = $options['intention'];
$validator = function (FormInterface $form) use ($csrfProvider, $intention)
{
if ((!$form->hasParent() || $form->getParent()->isRoot())
&& !$csrfProvider->isCsrfTokenValid($intention, $form->getData())) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
$form->setData($csrfProvider->generateCsrfToken($intention));
}
};
$builder
->setData($csrfProvider->generateCsrfToken($pageId))
->addValidator(new CallbackValidator(
function (FormInterface $form) use ($csrfProvider, $pageId) {
if ((!$form->hasParent() || $form->getParent()->isRoot())
&& !$csrfProvider->isCsrfTokenValid($pageId, $form->getData())) {
$form->addError(new FormError('The CSRF token is invalid. Please try to resubmit the form'));
$form->setData($csrfProvider->generateCsrfToken($pageId));
}
}
));
->setData($csrfProvider->generateCsrfToken($intention))
->addValidator(new CallbackValidator($validator))
;
}
public function getDefaultOptions(array $options)
{
return array(
'csrf_provider' => $this->csrfProvider,
'page_id' => null,
'intention' => null,
'property_path' => false,
);
}

View File

@ -30,7 +30,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
public function buildForm(FormBuilder $builder, array $options)
{
if ($options['csrf_protection']) {
$csrfOptions = array('page_id' => $options['csrf_page_id']);
$csrfOptions = array('intention' => $options['intention']);
if ($options['csrf_provider']) {
$csrfOptions['csrf_provider'] = $options['csrf_provider'];
@ -58,7 +58,7 @@ class FormTypeCsrfExtension extends AbstractTypeExtension
'csrf_protection' => $this->enabled,
'csrf_field_name' => $this->fieldName,
'csrf_provider' => null,
'csrf_page_id' => get_class($this),
'intention' => 'unknown',
);
}

View File

@ -42,7 +42,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
'username_parameter' => '_username',
'password_parameter' => '_password',
'csrf_parameter' => '_csrf_token',
'csrf_page_id' => 'form_login',
'intention' => 'authenticate',
'post_only' => true,
), $options), $successHandler, $failureHandler, $logger, $dispatcher);
@ -65,7 +65,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
if (null !== $this->csrfProvider) {
$csrfToken = $request->get($this->options['csrf_parameter']);
if (false === $this->csrfProvider->isCsrfTokenValid($this->options['csrf_page_id'], $csrfToken)) {
if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}

View File

@ -36,12 +36,12 @@ class CsrfTypeTest extends TypeTestCase
{
$this->provider->expects($this->once())
->method('generateCsrfToken')
->with('%PAGE_ID%')
->with('%INTENTION%')
->will($this->returnValue('token'));
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'page_id' => '%PAGE_ID%'
'intention' => '%INTENTION%'
));
$this->assertEquals('token', $form->getData());
@ -51,12 +51,12 @@ class CsrfTypeTest extends TypeTestCase
{
$this->provider->expects($this->once())
->method('isCsrfTokenValid')
->with('%PAGE_ID%', 'token')
->with('%INTENTION%', 'token')
->will($this->returnValue(true));
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'page_id' => '%PAGE_ID%'
'intention' => '%INTENTION%'
));
$form->bind('token');
@ -70,7 +70,7 @@ class CsrfTypeTest extends TypeTestCase
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'page_id' => '%PAGE_ID%'
'intention' => '%INTENTION%'
));
$form->setParent($this->getNonRootForm());
$form->bind('token');
@ -80,23 +80,23 @@ class CsrfTypeTest extends TypeTestCase
{
$this->provider->expects($this->at(0))
->method('generateCsrfToken')
->with('%PAGE_ID%')
->with('%INTENTION%')
->will($this->returnValue('token1'));
$this->provider->expects($this->at(1))
->method('isCsrfTokenValid')
->with('%PAGE_ID%', 'invalid')
->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('%PAGE_ID%')
->with('%INTENTION%')
->will($this->returnValue('token2'));
$form = $this->factory->create('csrf', null, array(
'csrf_provider' => $this->provider,
'page_id' => '%PAGE_ID%'
'intention' => '%INTENTION%'
));
$form->bind('invalid');