Sfoglia il codice sorgente

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

Bernhard Schussek 14 anni fa
parent
commit
48af2fc86e

+ 13 - 1
src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

@@ -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)

+ 12 - 9
src/Symfony/Component/Form/Form.php

@@ -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(

+ 17 - 7
src/Symfony/Component/Form/FormConfiguration.php

@@ -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 array $secrets
+     */
+    static public function setDefaultCsrfSecrets(array $secrets)
+    {
+        self::$defaultCsrfSecrets = $secrets;
+    }
+
+    /**
+     * Adds CSRF secrets to be used in all new CSRF protected forms
      *
      * @param string $secret
      */
-    static public function setDefaultCsrfSecret($secret)
+    static public function addDefaultCsrfSecret($secret)
     {
-        self::$defaultCsrfSecret = $secret;
+        self::$defaultCsrfSecrets[] = $secret;
     }
 
     /**
-     * Returns the default CSRF secret
+     * Returns the default CSRF secrets
      *
      * @return string
      */
-    static public function getDefaultCsrfSecret()
+    static public function getDefaultCsrfSecrets()
     {
-        return self::$defaultCsrfSecret;
+        return self::$defaultCsrfSecrets;
     }
 }

+ 10 - 0
src/Symfony/Component/HttpFoundation/Session.php

@@ -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
      *

+ 4 - 0
src/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php

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

+ 12 - 0
src/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php

@@ -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.
      *

+ 9 - 0
src/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php

@@ -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.
      *

+ 18 - 5
tests/Symfony/Tests/Component/Form/FormTest.php

@@ -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');
+        FormConfiguration::addDefaultCsrfSecret('foobar');
+
         $form = new Form('author', new Author(), $this->validator);
-        $form->enableCsrfProtection();
+        $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('foobar', $form->getCsrfSecret());
+        $this->assertEquals(md5('secret'.get_class($form).'foobar'), $form['_token']->getData());
     }
 
     public function testDefaultCsrfFieldNameCanBeSet()