summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--Csrf/.gitignore3
-rw-r--r--Csrf/CsrfTokenGenerator.php105
-rw-r--r--Csrf/CsrfTokenGeneratorInterface.php52
-rw-r--r--Csrf/LICENSE19
-rw-r--r--Csrf/README.md21
-rw-r--r--Csrf/Tests/CsrfTokenGeneratorTest.php148
-rw-r--r--Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php99
-rw-r--r--Csrf/Tests/TokenStorage/SessionTokenStorageTest.php144
-rw-r--r--Csrf/TokenStorage/NativeSessionTokenStorage.php101
-rw-r--r--Csrf/TokenStorage/SessionTokenStorage.php89
-rw-r--r--Csrf/TokenStorage/TokenStorageInterface.php49
-rw-r--r--Csrf/composer.json38
-rw-r--r--Csrf/phpunit.xml.dist29
-rw-r--r--Http/Firewall/LogoutListener.php22
-rw-r--r--Http/Firewall/SimpleFormAuthenticationListener.php15
-rw-r--r--Http/Firewall/UsernamePasswordFormAuthenticationListener.php12
-rw-r--r--Http/composer.json3
-rw-r--r--composer.json1
19 files changed, 928 insertions, 25 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6802055..9ab61d0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,9 @@ CHANGELOG
* The switch user listener now preserves the query string when switching a user
* The remember-me cookie hashes now use HMAC, which means that current cookies will be invalidated
* added simpler customization options
+ * structured component into three sub-components Acl, Core and Http
+ * added Csrf sub-component
+ * changed Http sub-component to depend on Csrf sub-component instead of the Form component
2.3.0
-----
diff --git a/Csrf/.gitignore b/Csrf/.gitignore
new file mode 100644
index 0000000..c49a5d8
--- /dev/null
+++ b/Csrf/.gitignore
@@ -0,0 +1,3 @@
+vendor/
+composer.lock
+phpunit.xml
diff --git a/Csrf/CsrfTokenGenerator.php b/Csrf/CsrfTokenGenerator.php
new file mode 100644
index 0000000..8ff3462
--- /dev/null
+++ b/Csrf/CsrfTokenGenerator.php
@@ -0,0 +1,105 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Csrf;
+
+use Symfony\Component\Security\Core\Util\SecureRandomInterface;
+use Symfony\Component\Security\Core\Util\SecureRandom;
+use Symfony\Component\Security\Core\Util\StringUtils;
+use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
+use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
+
+/**
+ * Generates and validates CSRF tokens.
+ *
+ * @since 2.4
+ * @author Bernhard Schussek <bernhard.schussek@symfony.com>
+ */
+class CsrfTokenGenerator implements CsrfTokenGeneratorInterface
+{
+ /**
+ * The entropy of the token in bits.
+ * @var integer
+ */
+ const TOKEN_ENTROPY = 256;
+
+ /**
+ * @var TokenStorageInterface
+ */
+ private $storage;
+
+ /**
+ * The generator for random values.
+ * @var SecureRandomInterface
+ */
+ private $random;
+
+ /**
+ * Creates a new CSRF provider using PHP's native session storage.
+ *
+ * @param TokenStorageInterface $storage The storage for storing generated
+ * CSRF tokens
+ * @param SecureRandomInterface $random The used random value generator
+ * @param integer $entropy The amount of entropy collected for
+ * newly generated tokens (in bits)
+ *
+ */
+ public function __construct(TokenStorageInterface $storage = null, SecureRandomInterface $random = null, $entropy = self::TOKEN_ENTROPY)
+ {
+ if (null === $storage) {
+ $storage = new NativeSessionTokenStorage();
+ }
+
+ if (null === $random) {
+ $random = new SecureRandom();
+ }
+
+ $this->storage = $storage;
+ $this->random = $random;
+ $this->entropy = $entropy;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function generateCsrfToken($tokenId)
+ {
+ $currentToken = $this->storage->getToken($tokenId, false);
+
+ // Token exists and is still valid
+ if (false !== $currentToken) {
+ return $currentToken;
+ }
+
+ // Token needs to be (re)generated
+ // Generate an URI safe base64 encoded string that does not contain "+",
+ // "/" or "=" which need to be URL encoded and make URLs unnecessarily
+ // longer.
+ $bytes = $this->random->nextBytes($this->entropy / 8);
+ $token = rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
+
+ $this->storage->setToken($tokenId, $token);
+
+ return $token;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function isCsrfTokenValid($tokenId, $token)
+ {
+ if (!$this->storage->hasToken($tokenId)) {
+ return false;
+ }
+
+ return StringUtils::equals((string) $this->storage->getToken($tokenId), $token);
+ }
+}
diff --git a/Csrf/CsrfTokenGeneratorInterface.php b/Csrf/CsrfTokenGeneratorInterface.php
new file mode 100644
index 0000000..c34549f
--- /dev/null
+++ b/Csrf/CsrfTokenGeneratorInterface.php
@@ -0,0 +1,52 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Csrf;
+
+/**
+ * Generates and validates CSRF tokens.
+ *
+ * You can generate a CSRF token by using the method {@link generateCsrfToken()}.
+ * This method expects a unique token ID as argument. The token ID can later be
+ * used to validate a token provided by the user.
+ *
+ * Token IDs do not necessarily have to be secret, but they should NEVER be
+ * created from data provided by the client. A good practice is to hard-code the
+ * token IDs for the various CSRF tokens used by your application.
+ *
+ * You should use the method {@link isCsrfTokenValid()} to check a CSRF token
+ * submitted by the client. This method will return true if the CSRF token is
+ * valid.
+ *
+ * @since 2.4
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface CsrfTokenGeneratorInterface
+{
+ /**
+ * Generates a CSRF token with the given token ID.
+ *
+ * @param string $tokenId An ID that identifies the token
+ *
+ * @return string The generated CSRF token
+ */
+ public function generateCsrfToken($tokenId);
+
+ /**
+ * Validates a CSRF token.
+ *
+ * @param string $tokenId The token ID used when generating the token
+ * @param string $token The token supplied by the client
+ *
+ * @return Boolean Whether the token supplied by the client is correct
+ */
+ public function isCsrfTokenValid($tokenId, $token);
+}
diff --git a/Csrf/LICENSE b/Csrf/LICENSE
new file mode 100644
index 0000000..88a57f8
--- /dev/null
+++ b/Csrf/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2013 Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Csrf/README.md b/Csrf/README.md
new file mode 100644
index 0000000..394a29e
--- /dev/null
+++ b/Csrf/README.md
@@ -0,0 +1,21 @@
+Security Component - CSRF
+=========================
+
+The Security CSRF (cross-site request forgery) component provides a class
+`CsrfTokenGenerator` for generating and validating CSRF tokens.
+
+Resources
+---------
+
+Documentation:
+
+http://symfony.com/doc/2.4/book/security.html
+
+Tests
+-----
+
+You can run the unit tests with the following command:
+
+ $ cd path/to/Symfony/Component/Security/Csrf/
+ $ composer.phar install --dev
+ $ phpunit
diff --git a/Csrf/Tests/CsrfTokenGeneratorTest.php b/Csrf/Tests/CsrfTokenGeneratorTest.php
new file mode 100644
index 0000000..f5f9507
--- /dev/null
+++ b/Csrf/Tests/CsrfTokenGeneratorTest.php
@@ -0,0 +1,148 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider;
+
+use Symfony\Component\Security\Csrf\CsrfTokenGenerator;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class CsrfTokenGeneratorTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * A non alpha-numeric byte string
+ * @var string
+ */
+ private static $bytes;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $random;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $storage;
+
+ /**
+ * @var CsrfTokenGenerator
+ */
+ private $generator;
+
+ public static function setUpBeforeClass()
+ {
+ self::$bytes = base64_decode('aMf+Tct/RLn2WQ==');
+ }
+
+ protected function setUp()
+ {
+ $this->random = $this->getMock('Symfony\Component\Security\Core\Util\SecureRandomInterface');
+ $this->storage = $this->getMock('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface');
+ $this->generator = new CsrfTokenGenerator($this->storage, $this->random);
+ }
+
+ protected function tearDown()
+ {
+ $this->random = null;
+ $this->storage = null;
+ $this->generator = null;
+ }
+
+ public function testGenerateNewToken()
+ {
+ $this->storage->expects($this->once())
+ ->method('getToken')
+ ->with('token_id', false)
+ ->will($this->returnValue(false));
+
+ $this->storage->expects($this->once())
+ ->method('setToken')
+ ->with('token_id', $this->anything())
+ ->will($this->returnCallback(function ($tokenId, $token) use (&$storedToken) {
+ $storedToken = $token;
+ }));
+
+ $this->random->expects($this->once())
+ ->method('nextBytes')
+ ->will($this->returnValue(self::$bytes));
+
+ $token = $this->generator->generateCsrfToken('token_id');
+
+ $this->assertSame($token, $storedToken);
+ $this->assertTrue(ctype_print($token), 'is printable');
+ $this->assertStringNotMatchesFormat('%S+%S', $token, 'is URI safe');
+ $this->assertStringNotMatchesFormat('%S/%S', $token, 'is URI safe');
+ $this->assertStringNotMatchesFormat('%S=%S', $token, 'is URI safe');
+ }
+
+ public function testUseExistingTokenIfAvailable()
+ {
+ $this->storage->expects($this->once())
+ ->method('getToken')
+ ->with('token_id', false)
+ ->will($this->returnValue('TOKEN'));
+
+ $this->storage->expects($this->never())
+ ->method('setToken');
+
+ $this->random->expects($this->never())
+ ->method('nextBytes');
+
+ $token = $this->generator->generateCsrfToken('token_id');
+
+ $this->assertEquals('TOKEN', $token);
+ }
+
+ public function testMatchingTokenIsValid()
+ {
+ $this->storage->expects($this->once())
+ ->method('hasToken')
+ ->with('token_id')
+ ->will($this->returnValue(true));
+
+ $this->storage->expects($this->once())
+ ->method('getToken')
+ ->with('token_id')
+ ->will($this->returnValue('TOKEN'));
+
+ $this->assertTrue($this->generator->isCsrfTokenValid('token_id', 'TOKEN'));
+ }
+
+ public function testNonMatchingTokenIsNotValid()
+ {
+ $this->storage->expects($this->once())
+ ->method('hasToken')
+ ->with('token_id')
+ ->will($this->returnValue(true));
+
+ $this->storage->expects($this->once())
+ ->method('getToken')
+ ->with('token_id')
+ ->will($this->returnValue('TOKEN'));
+
+ $this->assertFalse($this->generator->isCsrfTokenValid('token_id', 'FOOBAR'));
+ }
+
+ public function testNonExistingTokenIsNotValid()
+ {
+ $this->storage->expects($this->once())
+ ->method('hasToken')
+ ->with('token_id')
+ ->will($this->returnValue(false));
+
+ $this->storage->expects($this->never())
+ ->method('getToken');
+
+ $this->assertFalse($this->generator->isCsrfTokenValid('token_id', 'FOOBAR'));
+ }
+}
diff --git a/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php b/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php
new file mode 100644
index 0000000..69df061
--- /dev/null
+++ b/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider;
+
+use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @runTestsInSeparateProcesses
+ */
+class NativeSessionTokenStorageTest extends \PHPUnit_Framework_TestCase
+{
+ const SESSION_NAMESPACE = 'foobar';
+
+ /**
+ * @var NativeSessionTokenStorage
+ */
+ private $storage;
+
+ public static function setUpBeforeClass()
+ {
+ ini_set('session.save_handler', 'files');
+ ini_set('session.save_path', sys_get_temp_dir());
+
+ parent::setUpBeforeClass();
+ }
+
+ protected function setUp()
+ {
+ $_SESSION = array();
+
+ $this->storage = new NativeSessionTokenStorage(self::SESSION_NAMESPACE);
+ }
+
+ public function testStoreTokenInClosedSession()
+ {
+ $this->storage->setToken('token_id', 'TOKEN');
+
+ $this->assertSame(array(self::SESSION_NAMESPACE => array('token_id' => 'TOKEN')), $_SESSION);
+ }
+
+ public function testStoreTokenInClosedSessionWithExistingSessionId()
+ {
+ session_id('foobar');
+
+ $this->assertSame(PHP_SESSION_NONE, session_status());
+
+ $this->storage->setToken('token_id', 'TOKEN');
+
+ $this->assertSame(PHP_SESSION_ACTIVE, session_status());
+ $this->assertSame(array(self::SESSION_NAMESPACE => array('token_id' => 'TOKEN')), $_SESSION);
+ }
+
+ public function testStoreTokenInActiveSession()
+ {
+ session_start();
+
+ $this->storage->setToken('token_id', 'TOKEN');
+
+ $this->assertSame(array(self::SESSION_NAMESPACE => array('token_id' => 'TOKEN')), $_SESSION);
+ }
+
+ /**
+ * @depends testStoreTokenInClosedSession
+ */
+ public function testCheckToken()
+ {
+ $this->assertFalse($this->storage->hasToken('token_id'));
+
+ $this->storage->setToken('token_id', 'TOKEN');
+
+ $this->assertTrue($this->storage->hasToken('token_id'));
+ }
+
+ /**
+ * @depends testStoreTokenInClosedSession
+ */
+ public function testGetExistingToken()
+ {
+ $this->storage->setToken('token_id', 'TOKEN');
+
+ $this->assertSame('TOKEN', $this->storage->getToken('token_id'));
+ }
+
+ public function testGetNonExistingToken()
+ {
+ $this->assertSame('DEFAULT', $this->storage->getToken('token_id', 'DEFAULT'));
+ }
+}
diff --git a/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php b/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php
new file mode 100644
index 0000000..5c8c173
--- /dev/null
+++ b/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php
@@ -0,0 +1,144 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Form\Tests\Extension\Csrf\CsrfProvider;
+
+use Symfony\Component\Security\Csrf\TokenStorage\SessionTokenStorage;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase
+{
+ const SESSION_NAMESPACE = 'foobar';
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $session;
+
+ /**
+ * @var SessionTokenStorage
+ */
+ private $storage;
+
+ protected function setUp()
+ {
+ if (!class_exists('Symfony\Component\HttpFoundation\Session\SessionInterface')) {
+ $this->markTestSkipped('The "HttpFoundation" component is not available');
+ }
+
+ $this->session = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\SessionInterface')
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->storage = new SessionTokenStorage($this->session, self::SESSION_NAMESPACE);
+ }
+
+ public function testStoreTokenInClosedSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(false));
+
+ $this->session->expects($this->once())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('set')
+ ->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
+
+ $this->storage->setToken('token_id', 'TOKEN');
+ }
+
+ public function testStoreTokenInActiveSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(true));
+
+ $this->session->expects($this->never())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('set')
+ ->with(self::SESSION_NAMESPACE.'/token_id', 'TOKEN');
+
+ $this->storage->setToken('token_id', 'TOKEN');
+ }
+
+ public function testCheckTokenInClosedSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(false));
+
+ $this->session->expects($this->once())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('has')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue('RESULT'));
+
+ $this->assertSame('RESULT', $this->storage->hasToken('token_id'));
+ }
+
+ public function testCheckTokenInActiveSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(true));
+
+ $this->session->expects($this->never())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('has')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue('RESULT'));
+
+ $this->assertSame('RESULT', $this->storage->hasToken('token_id'));
+ }
+
+ public function testGetTokenFromClosedSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(false));
+
+ $this->session->expects($this->once())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('get')
+ ->with(self::SESSION_NAMESPACE.'/token_id', 'DEFAULT')
+ ->will($this->returnValue('RESULT'));
+
+ $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT'));
+ }
+
+ public function testGetTokenFromActiveSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(true));
+
+ $this->session->expects($this->never())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('get')
+ ->with(self::SESSION_NAMESPACE.'/token_id', 'DEFAULT')
+ ->will($this->returnValue('RESULT'));
+
+ $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT'));
+ }
+}
diff --git a/Csrf/TokenStorage/NativeSessionTokenStorage.php b/Csrf/TokenStorage/NativeSessionTokenStorage.php
new file mode 100644
index 0000000..8956743
--- /dev/null
+++ b/Csrf/TokenStorage/NativeSessionTokenStorage.php
@@ -0,0 +1,101 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Csrf\TokenStorage;
+
+/**
+ * Token storage that uses PHP's native session handling.
+ *
+ * @since 2.4
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class NativeSessionTokenStorage implements TokenStorageInterface
+{
+ /**
+ * The namespace used to store values in the session.
+ * @var string
+ */
+ const SESSION_NAMESPACE = '_csrf';
+
+ /**
+ * @var Boolean
+ */
+ private $sessionStarted = false;
+
+ /**
+ * @var string
+ */
+ private $namespace;
+
+ /**
+ * Initializes the storage with a session namespace.
+ *
+ * @param string $namespace The namespace under which the token is stored
+ * in the session
+ */
+ public function __construct($namespace = self::SESSION_NAMESPACE)
+ {
+ $this->namespace = $namespace;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getToken($tokenId, $default = null)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ if (isset($_SESSION[$this->namespace][$tokenId])) {
+ return $_SESSION[$this->namespace][$tokenId];
+ }
+
+ return $default;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setToken($tokenId, $token)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ $_SESSION[$this->namespace][$tokenId] = $token;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasToken($tokenId)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ return isset($_SESSION[$this->namespace][$tokenId]);
+ }
+
+ private function startSession()
+ {
+ if (version_compare(PHP_VERSION, '5.4', '>=')) {
+ if (PHP_SESSION_NONE === session_status()) {
+ session_start();
+ }
+ } elseif (!session_id()) {
+ session_start();
+ }
+
+ $this->sessionStarted = true;
+ }
+}
diff --git a/Csrf/TokenStorage/SessionTokenStorage.php b/Csrf/TokenStorage/SessionTokenStorage.php
new file mode 100644
index 0000000..3878e4c
--- /dev/null
+++ b/Csrf/TokenStorage/SessionTokenStorage.php
@@ -0,0 +1,89 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Csrf\TokenStorage;
+
+use Symfony\Component\HttpFoundation\Session\SessionInterface;
+
+/**
+ * Token storage that uses a Symfony2 Session object.
+ *
+ * @since 2.4
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class SessionTokenStorage implements TokenStorageInterface
+{
+ /**
+ * The namespace used to store values in the session.
+ * @var string
+ */
+ const SESSION_NAMESPACE = '_csrf';
+
+ /**
+ * The user session from which the session ID is returned
+ * @var SessionInterface
+ */
+ private $session;
+
+ /**
+ * @var string
+ */
+ private $namespace;
+
+ /**
+ * Initializes the storage with a Session object and a session namespace.
+ *
+ * @param SessionInterface $session The user session
+ * @param string $namespace The namespace under which the token
+ * is stored in the session
+ */
+ public function __construct(SessionInterface $session, $namespace = self::SESSION_NAMESPACE)
+ {
+ $this->session = $session;
+ $this->namespace = $namespace;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getToken($tokenId, $default = null)
+ {
+ if (!$this->session->isStarted()) {
+ $this->session->start();
+ }
+
+ return $this->session->get($this->namespace . '/' . $tokenId, $default);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setToken($tokenId, $token)
+ {
+ if (!$this->session->isStarted()) {
+ $this->session->start();
+ }
+
+ $this->session->set($this->namespace . '/' . $tokenId, $token);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasToken($tokenId)
+ {
+ if (!$this->session->isStarted()) {
+ $this->session->start();
+ }
+
+ return $this->session->has($this->namespace . '/' . $tokenId);
+ }
+}
diff --git a/Csrf/TokenStorage/TokenStorageInterface.php b/Csrf/TokenStorage/TokenStorageInterface.php
new file mode 100644
index 0000000..7dba9e5
--- /dev/null
+++ b/Csrf/TokenStorage/TokenStorageInterface.php
@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Security\Csrf\TokenStorage;
+
+/**
+ * Stores CSRF tokens.
+ *
+ * @since 2.4
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface TokenStorageInterface
+{
+ /**
+ * Reads a stored CSRF token.
+ *
+ * @param string $tokenId The token ID
+ * @param mixed $default The value to be returned if no token is set
+ *
+ * @return mixed The stored token or the default value, if no token is set
+ */
+ public function getToken($tokenId, $default = null);
+
+ /**
+ * Stores a CSRF token.
+ *
+ * @param string $tokenId The token ID
+ * @param mixed $token The CSRF token
+ */
+ public function setToken($tokenId, $token);
+
+ /**
+ * Checks whether a token with the given token ID exists.
+ *
+ * @param string $tokenId The token ID
+ *
+ * @return Boolean Returns true if a token is stored for the given token ID,
+ * false otherwise.
+ */
+ public function hasToken($tokenId);
+}
diff --git a/Csrf/composer.json b/Csrf/composer.json
new file mode 100644
index 0000000..3cfc2b4
--- /dev/null
+++ b/Csrf/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "symfony/security-csrf",
+ "type": "library",
+ "description": "Symfony Security Component - CSRF Library",
+ "keywords": [],
+ "homepage": "http://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/security-core": "~2.4"
+ },
+ "require-dev": {
+ "symfony/http-foundation": "~2.1"
+ },
+ "suggest": {
+ "symfony/http-foundation": "For using the class SessionTokenStorage."
+ },
+ "autoload": {
+ "psr-0": { "Symfony\\Component\\Security\\Csrf\\": "" }
+ },
+ "target-dir": "Symfony/Component/Security/Csrf",
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.4-dev"
+ }
+ }
+}
diff --git a/Csrf/phpunit.xml.dist b/Csrf/phpunit.xml.dist
new file mode 100644
index 0000000..0718c76
--- /dev/null
+++ b/Csrf/phpunit.xml.dist
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit backupGlobals="false"
+ backupStaticAttributes="false"
+ colors="true"
+ convertErrorsToExceptions="true"
+ convertNoticesToExceptions="true"
+ convertWarningsToExceptions="true"
+ processIsolation="false"
+ stopOnFailure="false"
+ syntaxCheck="false"
+ bootstrap="vendor/autoload.php"
+>
+ <testsuites>
+ <testsuite name="Symfony Security Component CSRF Test Suite">
+ <directory>./Tests/</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory>./</directory>
+ <exclude>
+ <directory>./vendor</directory>
+ <directory>./Tests</directory>
+ </exclude>
+ </whitelist>
+ </filter>
+</phpunit>
diff --git a/Http/Firewall/LogoutListener.php b/Http/Firewall/LogoutListener.php
index 983eab0..073a48c 100644
--- a/Http/Firewall/LogoutListener.php
+++ b/Http/Firewall/LogoutListener.php
@@ -11,12 +11,12 @@
namespace Symfony\Component\Security\Http\Firewall;
-use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\LogoutException;
+use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;
@@ -34,18 +34,18 @@ class LogoutListener implements ListenerInterface
private $handlers;
private $successHandler;
private $httpUtils;
- private $csrfProvider;
+ private $csrfTokenGenerator;
/**
* Constructor.
*
* @param SecurityContextInterface $securityContext
- * @param HttpUtils $httpUtils An HttpUtilsInterface instance
- * @param LogoutSuccessHandlerInterface $successHandler A LogoutSuccessHandlerInterface instance
- * @param array $options An array of options to process a logout attempt
- * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance
+ * @param HttpUtils $httpUtils An HttpUtilsInterface instance
+ * @param LogoutSuccessHandlerInterface $successHandler A LogoutSuccessHandlerInterface instance
+ * @param array $options An array of options to process a logout attempt
+ * @param CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface instance
*/
- public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), CsrfProviderInterface $csrfProvider = null)
+ public function __construct(SecurityContextInterface $securityContext, HttpUtils $httpUtils, LogoutSuccessHandlerInterface $successHandler, array $options = array(), CsrfTokenGeneratorInterface $csrfTokenGenerator = null)
{
$this->securityContext = $securityContext;
$this->httpUtils = $httpUtils;
@@ -55,7 +55,7 @@ class LogoutListener implements ListenerInterface
'logout_path' => '/logout',
), $options);
$this->successHandler = $successHandler;
- $this->csrfProvider = $csrfProvider;
+ $this->csrfTokenGenerator = $csrfTokenGenerator;
$this->handlers = array();
}
@@ -72,7 +72,7 @@ class LogoutListener implements ListenerInterface
/**
* Performs the logout if requested
*
- * If a CsrfProviderInterface instance is available, it will be used to
+ * If a CsrfTokenGeneratorInterface instance is available, it will be used to
* validate the request.
*
* @param GetResponseEvent $event A GetResponseEvent instance
@@ -88,10 +88,10 @@ class LogoutListener implements ListenerInterface
return;
}
- if (null !== $this->csrfProvider) {
+ if (null !== $this->csrfTokenGenerator) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
- if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
+ if (false === $this->csrfTokenGenerator->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
throw new LogoutException('Invalid CSRF token.');
}
}
diff --git a/Http/Firewall/SimpleFormAuthenticationListener.php b/Http/Firewall/SimpleFormAuthenticationListener.php
index 054616b..c09cbdb 100644
--- a/Http/Firewall/SimpleFormAuthenticationListener.php
+++ b/Http/Firewall/SimpleFormAuthenticationListener.php
@@ -13,10 +13,11 @@ namespace Symfony\Component\Security\Http\Firewall;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\Security\Core\Exception\InvalidCsrfTokenException;
+use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
-use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Http\HttpUtils;
@@ -29,7 +30,7 @@ use Psr\Log\LoggerInterface;
class SimpleFormAuthenticationListener extends AbstractAuthenticationListener
{
private $simpleAuthenticator;
- private $csrfProvider;
+ private $csrfTokenGenerator;
/**
* Constructor.
@@ -46,16 +47,16 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener
* @param LoggerInterface $logger A LoggerInterface instance
* @param EventDispatcherInterface $dispatcher An EventDispatcherInterface instance
* @param SimpleFormAuthenticatorInterface $simpleAuthenticator A SimpleFormAuthenticatorInterface instance
- * @param CsrfProviderInterface $csrfProvider A CsrfProviderInterface instance
+ * @param CsrfTokenGeneratorInterface $csrfTokenGenerator A CsrfTokenGeneratorInterface instance
*/
- public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfProviderInterface $csrfProvider = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null)
+ public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenGeneratorInterface $csrfTokenGenerator = null, SimpleFormAuthenticatorInterface $simpleAuthenticator = null)
{
if (!$simpleAuthenticator) {
throw new \InvalidArgumentException('Missing simple authenticator');
}
$this->simpleAuthenticator = $simpleAuthenticator;
- $this->csrfProvider = $csrfProvider;
+ $this->csrfTokenGenerator = $csrfTokenGenerator;
$options = array_merge(array(
'username_parameter' => '_username',
@@ -84,10 +85,10 @@ class SimpleFormAuthenticationListener extends AbstractAuthenticationListener
*/
protected function attemptAuthentication(Request $request)
{
- if (null !== $this->csrfProvider) {
+ if (null !== $this->csrfTokenGenerator) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
- if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
+ if (false === $this->csrfTokenGenerator->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
diff --git a/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
index 81c2b37..7c42dec 100644
--- a/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
+++ b/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
@@ -11,9 +11,9 @@
namespace Symfony\Component\Security\Http\Firewall;
-use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;
+use Symfony\Component\Security\Csrf\CsrfTokenGeneratorInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface;
use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface;
@@ -32,12 +32,12 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*/
class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationListener
{
- private $csrfProvider;
+ private $csrfTokenGenerator;
/**
* {@inheritdoc}
*/
- public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfProviderInterface $csrfProvider = null)
+ public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager, SessionAuthenticationStrategyInterface $sessionStrategy, HttpUtils $httpUtils, $providerKey, AuthenticationSuccessHandlerInterface $successHandler, AuthenticationFailureHandlerInterface $failureHandler, array $options = array(), LoggerInterface $logger = null, EventDispatcherInterface $dispatcher = null, CsrfTokenGeneratorInterface $csrfTokenGenerator = null)
{
parent::__construct($securityContext, $authenticationManager, $sessionStrategy, $httpUtils, $providerKey, $successHandler, $failureHandler, array_merge(array(
'username_parameter' => '_username',
@@ -47,7 +47,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
'post_only' => true,
), $options), $logger, $dispatcher);
- $this->csrfProvider = $csrfProvider;
+ $this->csrfTokenGenerator = $csrfTokenGenerator;
}
/**
@@ -67,10 +67,10 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
*/
protected function attemptAuthentication(Request $request)
{
- if (null !== $this->csrfProvider) {
+ if (null !== $this->csrfTokenGenerator) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
- if (false === $this->csrfProvider->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
+ if (false === $this->csrfTokenGenerator->isCsrfTokenValid($this->options['intention'], $csrfToken)) {
throw new InvalidCsrfTokenException('Invalid CSRF token.');
}
}
diff --git a/Http/composer.json b/Http/composer.json
index 6b610a0..4dfd985 100644
--- a/Http/composer.json
+++ b/Http/composer.json
@@ -25,10 +25,11 @@
"require-dev": {
"symfony/form": "~2.0",
"symfony/routing": "~2.2",
+ "symfony/security-csrf": "~2.4",
"psr/log": "~1.0"
},
"suggest": {
- "symfony/form": "",
+ "symfony/security-csrf": "",
"symfony/routing": ""
},
"autoload": {
diff --git a/composer.json b/composer.json
index 164deb3..6be3886 100644
--- a/composer.json
+++ b/composer.json
@@ -24,6 +24,7 @@
"replace": {
"symfony/security-acl": "self.version",
"symfony/security-core": "self.version",
+ "symfony/security-csrf": "self.version",
"symfony/security-http": "self.version"
},
"require-dev": {