[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');
}
if ($this->container->hasParameter('csrf_secret')) {
FormConfiguration::setDefaultCsrfSecret($this->container->getParameter('csrf_secret'));
FormConfiguration::addDefaultCsrfSecret($this->container->getParameter('csrf_secret'));
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)

View File

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

View File

@ -18,7 +18,7 @@ namespace Symfony\Component\Form;
*/
class FormConfiguration
{
protected static $defaultCsrfSecret = null;
protected static $defaultCsrfSecrets = array();
protected static $defaultCsrfProtection = false;
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
*/
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();
}
/**
* Returns the session ID
*
* @return mixed The session ID
*/
public function getId()
{
return $this->storage->getId();
}
/**
* Returns the locale
*

View File

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

View File

@ -83,6 +83,18 @@ class NativeSessionStorage implements SessionStorageInterface
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.
*

View File

@ -23,6 +23,15 @@ interface SessionStorageInterface
*/
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.
*

View File

@ -60,7 +60,7 @@ class FormTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
FormConfiguration::disableDefaultCsrfProtection();
FormConfiguration::setDefaultCsrfSecret(null);
FormConfiguration::setDefaultCsrfSecrets(array());
$this->validator = $this->createMockValidator();
$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);
}
public function testDefaultCsrfSecretCanBeSet()
public function testDefaultCsrfSecretsCanBeAdded()
{
FormConfiguration::setDefaultCsrfSecret('foobar');
$form = new Form('author', new Author(), $this->validator);
$form->enableCsrfProtection();
FormConfiguration::addDefaultCsrfSecret('foobar');
$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()