merged kriswallsmith/form/csrf-intention
This commit is contained in:
commit
eb202bb7b7
@ -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``.
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
||||
|
Reference in New Issue
Block a user