[Form][FrameworkBundle][HttpFoundation] The session is now automatically started when a form is CSRF protected

This commit is contained in:
Bernhard Schussek 2011-01-03 17:00:28 +01:00 committed by Fabien Potencier
parent e9a7531a26
commit 48af2fc86e
8 changed files with 97 additions and 24 deletions

View File

@ -35,9 +35,21 @@ class FrameworkBundle extends Bundle
$this->container->get('error_handler'); $this->container->get('error_handler');
} }
if ($this->container->hasParameter('csrf_secret')) { if ($this->container->hasParameter('csrf_secret')) {
FormConfiguration::setDefaultCsrfSecret($this->container->getParameter('csrf_secret')); FormConfiguration::addDefaultCsrfSecret($this->container->getParameter('csrf_secret'));
FormConfiguration::enableDefaultCsrfProtection(); FormConfiguration::enableDefaultCsrfProtection();
} }
$container = $this->container;
// the session ID should always be included in the CSRF token, even
// if default CSRF protection is not enabled
FormConfiguration::addDefaultCsrfSecret(function () use ($container) {
// automatically starts the session when the CSRF token is
// generated
$container->get('session')->start();
return $container->get('session')->getId();
});
} }
public function registerExtensions(ContainerBuilder $container) public function registerExtensions(ContainerBuilder $container)

View File

@ -161,11 +161,18 @@ class Form extends FieldGroup
*/ */
protected function generateCsrfToken($secret) protected function generateCsrfToken($secret)
{ {
$sessId = session_id(); $secret .= get_class($this);
if (!$sessId) { $defaultSecrets = FormConfiguration::getDefaultCsrfSecrets();
throw new \LogicException('The session must be started in order to generate a proper CSRF Token');
foreach ($defaultSecrets as $defaultSecret) {
if ($defaultSecret instanceof \Closure) {
$defaultSecret = $defaultSecret();
}
$secret .= $defaultSecret;
} }
return md5($secret.$sessId.get_class($this));
return md5($secret);
} }
/** /**
@ -187,11 +194,7 @@ class Form extends FieldGroup
} }
if (null === $csrfSecret) { if (null === $csrfSecret) {
if (FormConfiguration::getDefaultCsrfSecret() !== null) { $csrfSecret = md5(__FILE__.php_uname());
$csrfSecret = FormConfiguration::getDefaultCsrfSecret();
} else {
$csrfSecret = md5(__FILE__.php_uname());
}
} }
$field = new HiddenField($csrfFieldName, array( $field = new HiddenField($csrfFieldName, array(

View File

@ -18,7 +18,7 @@ namespace Symfony\Component\Form;
*/ */
class FormConfiguration class FormConfiguration
{ {
protected static $defaultCsrfSecret = null; protected static $defaultCsrfSecrets = array();
protected static $defaultCsrfProtection = false; protected static $defaultCsrfProtection = false;
protected static $defaultCsrfFieldName = '_token'; protected static $defaultCsrfFieldName = '_token';
@ -89,22 +89,32 @@ class FormConfiguration
} }
/** /**
* Sets the CSRF secret used in all new CSRF protected forms * Sets the default CSRF secrets to be used in all new CSRF protected forms
* *
* @param string $secret * @param array $secrets
*/ */
static public function setDefaultCsrfSecret($secret) static public function setDefaultCsrfSecrets(array $secrets)
{ {
self::$defaultCsrfSecret = $secret; self::$defaultCsrfSecrets = $secrets;
} }
/** /**
* Returns the default CSRF secret * Adds CSRF secrets to be used in all new CSRF protected forms
*
* @param string $secret
*/
static public function addDefaultCsrfSecret($secret)
{
self::$defaultCsrfSecrets[] = $secret;
}
/**
* Returns the default CSRF secrets
* *
* @return string * @return string
*/ */
static public function getDefaultCsrfSecret() static public function getDefaultCsrfSecrets()
{ {
return self::$defaultCsrfSecret; return self::$defaultCsrfSecrets;
} }
} }

View File

@ -168,6 +168,16 @@ class Session implements \Serializable
$this->storage->regenerate(); $this->storage->regenerate();
} }
/**
* Returns the session ID
*
* @return mixed The session ID
*/
public function getId()
{
return $this->storage->getId();
}
/** /**
* Returns the locale * Returns the locale
* *

View File

@ -44,6 +44,10 @@ class ArraySessionStorage implements SessionStorageInterface
{ {
} }
public function getId()
{
}
public function write($key, $data) public function write($key, $data)
{ {
$this->data[$key] = $data; $this->data[$key] = $data;

View File

@ -83,6 +83,18 @@ class NativeSessionStorage implements SessionStorageInterface
self::$sessionStarted = true; self::$sessionStarted = true;
} }
/**
* @inheritDoc
*/
public function getId()
{
if (!self::$sessionStarted) {
throw new \RuntimeException('The session must be started before reading its ID');
}
return session_id();
}
/** /**
* Reads data from this storage. * Reads data from this storage.
* *

View File

@ -23,6 +23,15 @@ interface SessionStorageInterface
*/ */
function start(); function start();
/**
* Returns the session ID
*
* @return mixed The session ID
*
* @throws \RuntimeException If the session was not started yet
*/
function getId();
/** /**
* Reads data from this storage. * Reads data from this storage.
* *

View File

@ -60,7 +60,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
protected function setUp() protected function setUp()
{ {
FormConfiguration::disableDefaultCsrfProtection(); FormConfiguration::disableDefaultCsrfProtection();
FormConfiguration::setDefaultCsrfSecret(null); FormConfiguration::setDefaultCsrfSecrets(array());
$this->validator = $this->createMockValidator(); $this->validator = $this->createMockValidator();
$this->form = new Form('author', new Author(), $this->validator); $this->form = new Form('author', new Author(), $this->validator);
} }
@ -111,13 +111,26 @@ class FormTest extends \PHPUnit_Framework_TestCase
$this->assertTrue(strlen($form->getCsrfSecret()) >= 32); $this->assertTrue(strlen($form->getCsrfSecret()) >= 32);
} }
public function testDefaultCsrfSecretCanBeSet() public function testDefaultCsrfSecretsCanBeAdded()
{ {
FormConfiguration::setDefaultCsrfSecret('foobar'); FormConfiguration::addDefaultCsrfSecret('foobar');
$form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection();
$this->assertEquals('foobar', $form->getCsrfSecret()); $form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection('_token', 'secret');
$this->assertEquals(md5('secret'.get_class($form).'foobar'), $form['_token']->getData());
}
public function testDefaultCsrfSecretsCanBeAddedAsClosures()
{
FormConfiguration::addDefaultCsrfSecret(function () {
return 'foobar';
});
$form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection('_token', 'secret');
$this->assertEquals(md5('secret'.get_class($form).'foobar'), $form['_token']->getData());
} }
public function testDefaultCsrfFieldNameCanBeSet() public function testDefaultCsrfFieldNameCanBeSet()