summaryrefslogtreecommitdiffstats
path: root/Csrf
diff options
context:
space:
mode:
Diffstat (limited to 'Csrf')
-rw-r--r--Csrf/.gitignore3
-rw-r--r--Csrf/CsrfToken.php72
-rw-r--r--Csrf/CsrfTokenManager.php97
-rw-r--r--Csrf/CsrfTokenManagerInterface.php69
-rw-r--r--Csrf/Exception/TokenNotFoundException.php21
-rw-r--r--Csrf/LICENSE19
-rw-r--r--Csrf/README.md21
-rw-r--r--Csrf/Tests/CsrfTokenManagerTest.php164
-rw-r--r--Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php71
-rw-r--r--Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php127
-rw-r--r--Csrf/Tests/TokenStorage/SessionTokenStorageTest.php262
-rw-r--r--Csrf/TokenGenerator/TokenGeneratorInterface.php29
-rw-r--r--Csrf/TokenGenerator/UriSafeTokenGenerator.php66
-rw-r--r--Csrf/TokenStorage/NativeSessionTokenStorage.php123
-rw-r--r--Csrf/TokenStorage/SessionTokenStorage.php109
-rw-r--r--Csrf/TokenStorage/TokenStorageInterface.php60
-rw-r--r--Csrf/composer.json37
-rw-r--r--Csrf/phpunit.xml.dist37
18 files changed, 1387 insertions, 0 deletions
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/CsrfToken.php b/Csrf/CsrfToken.php
new file mode 100644
index 0000000..9ccaaeb
--- /dev/null
+++ b/Csrf/CsrfToken.php
@@ -0,0 +1,72 @@
+<?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;
+
+/**
+ * A CSRF token.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class CsrfToken
+{
+ /**
+ * @var string
+ */
+ private $id;
+
+ /**
+ * @var string
+ */
+ private $value;
+
+ /**
+ * Constructor.
+ *
+ * @param string $id The token ID
+ * @param string $value The actual token value
+ */
+ public function __construct($id, $value)
+ {
+ $this->id = (string) $id;
+ $this->value = (string) $value;
+ }
+
+ /**
+ * Returns the ID of the CSRF token.
+ *
+ * @return string The token ID
+ */
+ public function getId()
+ {
+ return $this->id;
+ }
+
+ /**
+ * Returns the value of the CSRF token.
+ *
+ * @return string The token value
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Returns the value of the CSRF token.
+ *
+ * @return string The token value
+ */
+ public function __toString()
+ {
+ return $this->value;
+ }
+}
diff --git a/Csrf/CsrfTokenManager.php b/Csrf/CsrfTokenManager.php
new file mode 100644
index 0000000..e129502
--- /dev/null
+++ b/Csrf/CsrfTokenManager.php
@@ -0,0 +1,97 @@
+<?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\StringUtils;
+use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
+use Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface;
+use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
+use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
+
+/**
+ * Default implementation of {@link CsrfTokenManagerInterface}.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class CsrfTokenManager implements CsrfTokenManagerInterface
+{
+ /**
+ * @var TokenGeneratorInterface
+ */
+ private $generator;
+
+ /**
+ * @var TokenStorageInterface
+ */
+ private $storage;
+
+ /**
+ * Creates a new CSRF provider using PHP's native session storage.
+ *
+ * @param TokenGeneratorInterface|null $generator The token generator
+ * @param TokenStorageInterface|null $storage The storage for storing
+ * generated CSRF tokens
+ */
+ public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null)
+ {
+ $this->generator = $generator ?: new UriSafeTokenGenerator();
+ $this->storage = $storage ?: new NativeSessionTokenStorage();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getToken($tokenId)
+ {
+ if ($this->storage->hasToken($tokenId)) {
+ $value = $this->storage->getToken($tokenId);
+ } else {
+ $value = $this->generator->generateToken();
+
+ $this->storage->setToken($tokenId, $value);
+ }
+
+ return new CsrfToken($tokenId, $value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function refreshToken($tokenId)
+ {
+ $value = $this->generator->generateToken();
+
+ $this->storage->setToken($tokenId, $value);
+
+ return new CsrfToken($tokenId, $value);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeToken($tokenId)
+ {
+ return $this->storage->removeToken($tokenId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isTokenValid(CsrfToken $token)
+ {
+ if (!$this->storage->hasToken($token->getId())) {
+ return false;
+ }
+
+ return StringUtils::equals($this->storage->getToken($token->getId()), $token->getValue());
+ }
+}
diff --git a/Csrf/CsrfTokenManagerInterface.php b/Csrf/CsrfTokenManagerInterface.php
new file mode 100644
index 0000000..bccabe6
--- /dev/null
+++ b/Csrf/CsrfTokenManagerInterface.php
@@ -0,0 +1,69 @@
+<?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;
+
+/**
+ * Manages CSRF tokens.
+ *
+ * @since 2.4
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface CsrfTokenManagerInterface
+{
+ /**
+ * Returns a CSRF token for the given ID.
+ *
+ * If previously no token existed for the given ID, a new token is
+ * generated. Otherwise the existing token is returned (with the same value,
+ * not the same instance).
+ *
+ * @param string $tokenId The token ID. You may choose an arbitrary value
+ * for the ID
+ *
+ * @return CsrfToken The CSRF token
+ */
+ public function getToken($tokenId);
+
+ /**
+ * Generates a new token value for the given ID.
+ *
+ * This method will generate a new token for the given token ID, independent
+ * of whether a token value previously existed or not. It can be used to
+ * enforce once-only tokens in environments with high security needs.
+ *
+ * @param string $tokenId The token ID. You may choose an arbitrary value
+ * for the ID
+ *
+ * @return CsrfToken The CSRF token
+ */
+ public function refreshToken($tokenId);
+
+ /**
+ * Invalidates the CSRF token with the given ID, if one exists.
+ *
+ * @param string $tokenId The token ID
+ *
+ * @return string|null Returns the removed token value if one existed, NULL
+ * otherwise
+ */
+ public function removeToken($tokenId);
+
+ /**
+ * Returns whether the given CSRF token is valid.
+ *
+ * @param CsrfToken $token A CSRF token
+ *
+ * @return bool Returns true if the token is valid, false otherwise
+ */
+ public function isTokenValid(CsrfToken $token);
+}
diff --git a/Csrf/Exception/TokenNotFoundException.php b/Csrf/Exception/TokenNotFoundException.php
new file mode 100644
index 0000000..936afde
--- /dev/null
+++ b/Csrf/Exception/TokenNotFoundException.php
@@ -0,0 +1,21 @@
+<?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\Exception;
+
+use Symfony\Component\Security\Core\Exception\RuntimeException;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class TokenNotFoundException extends RuntimeException
+{
+}
diff --git a/Csrf/LICENSE b/Csrf/LICENSE
new file mode 100644
index 0000000..43028bc
--- /dev/null
+++ b/Csrf/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-2015 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..3ae550b
--- /dev/null
+++ b/Csrf/README.md
@@ -0,0 +1,21 @@
+Security Component - CSRF
+=========================
+
+The Security CSRF (cross-site request forgery) component provides a class
+`CsrfTokenManager` for generating and validating CSRF tokens.
+
+Resources
+---------
+
+Documentation:
+
+https://symfony.com/doc/2.7/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/CsrfTokenManagerTest.php b/Csrf/Tests/CsrfTokenManagerTest.php
new file mode 100644
index 0000000..3112038
--- /dev/null
+++ b/Csrf/Tests/CsrfTokenManagerTest.php
@@ -0,0 +1,164 @@
+<?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\Tests;
+
+use Symfony\Component\Security\Csrf\CsrfToken;
+use Symfony\Component\Security\Csrf\CsrfTokenManager;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class CsrfTokenManagerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $generator;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $storage;
+
+ /**
+ * @var CsrfTokenManager
+ */
+ private $manager;
+
+ protected function setUp()
+ {
+ $this->generator = $this->getMock('Symfony\Component\Security\Csrf\TokenGenerator\TokenGeneratorInterface');
+ $this->storage = $this->getMock('Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface');
+ $this->manager = new CsrfTokenManager($this->generator, $this->storage);
+ }
+
+ protected function tearDown()
+ {
+ $this->generator = null;
+ $this->storage = null;
+ $this->manager = null;
+ }
+
+ public function testGetNonExistingToken()
+ {
+ $this->storage->expects($this->once())
+ ->method('hasToken')
+ ->with('token_id')
+ ->will($this->returnValue(false));
+
+ $this->generator->expects($this->once())
+ ->method('generateToken')
+ ->will($this->returnValue('TOKEN'));
+
+ $this->storage->expects($this->once())
+ ->method('setToken')
+ ->with('token_id', 'TOKEN');
+
+ $token = $this->manager->getToken('token_id');
+
+ $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
+ $this->assertSame('token_id', $token->getId());
+ $this->assertSame('TOKEN', $token->getValue());
+ }
+
+ public function testUseExistingTokenIfAvailable()
+ {
+ $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'));
+
+ $token = $this->manager->getToken('token_id');
+
+ $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
+ $this->assertSame('token_id', $token->getId());
+ $this->assertSame('TOKEN', $token->getValue());
+ }
+
+ public function testRefreshTokenAlwaysReturnsNewToken()
+ {
+ $this->storage->expects($this->never())
+ ->method('hasToken');
+
+ $this->generator->expects($this->once())
+ ->method('generateToken')
+ ->will($this->returnValue('TOKEN'));
+
+ $this->storage->expects($this->once())
+ ->method('setToken')
+ ->with('token_id', 'TOKEN');
+
+ $token = $this->manager->refreshToken('token_id');
+
+ $this->assertInstanceOf('Symfony\Component\Security\Csrf\CsrfToken', $token);
+ $this->assertSame('token_id', $token->getId());
+ $this->assertSame('TOKEN', $token->getValue());
+ }
+
+ 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->manager->isTokenValid(new CsrfToken('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->manager->isTokenValid(new CsrfToken('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->manager->isTokenValid(new CsrfToken('token_id', 'FOOBAR')));
+ }
+
+ public function testRemoveToken()
+ {
+ $this->storage->expects($this->once())
+ ->method('removeToken')
+ ->with('token_id')
+ ->will($this->returnValue('REMOVED_TOKEN'));
+
+ $this->assertSame('REMOVED_TOKEN', $this->manager->removeToken('token_id'));
+ }
+}
diff --git a/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php b/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php
new file mode 100644
index 0000000..1b325e5
--- /dev/null
+++ b/Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php
@@ -0,0 +1,71 @@
+<?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\Tests\TokenGenerator;
+
+use Symfony\Component\Security\Csrf\TokenGenerator\UriSafeTokenGenerator;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class UriSafeTokenGeneratorTest extends \PHPUnit_Framework_TestCase
+{
+ const ENTROPY = 1000;
+
+ /**
+ * A non alpha-numeric byte string.
+ *
+ * @var string
+ */
+ private static $bytes;
+
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $random;
+
+ /**
+ * @var UriSafeTokenGenerator
+ */
+ 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->generator = new UriSafeTokenGenerator($this->random, self::ENTROPY);
+ }
+
+ protected function tearDown()
+ {
+ $this->random = null;
+ $this->generator = null;
+ }
+
+ public function testGenerateToken()
+ {
+ $this->random->expects($this->once())
+ ->method('nextBytes')
+ ->with(self::ENTROPY / 8)
+ ->will($this->returnValue(self::$bytes));
+
+ $token = $this->generator->generateToken();
+
+ $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');
+ }
+}
diff --git a/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php b/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php
new file mode 100644
index 0000000..0039deb
--- /dev/null
+++ b/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php
@@ -0,0 +1,127 @@
+<?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\Tests\TokenStorage;
+
+use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
+
+/**
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ *
+ * @runTestsInSeparateProcesses
+ * @preserveGlobalState disabled
+ */
+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()
+ {
+ if (PHP_VERSION_ID < 50400) {
+ $this->markTestSkipped('This test requires PHP 5.4 or later.');
+ }
+
+ 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'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException
+ */
+ public function testGetNonExistingToken()
+ {
+ $this->storage->getToken('token_id');
+ }
+
+ /**
+ * @depends testCheckToken
+ */
+ public function testRemoveNonExistingToken()
+ {
+ $this->assertNull($this->storage->removeToken('token_id'));
+ $this->assertFalse($this->storage->hasToken('token_id'));
+ }
+
+ /**
+ * @depends testCheckToken
+ */
+ public function testRemoveExistingToken()
+ {
+ $this->storage->setToken('token_id', 'TOKEN');
+
+ $this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
+ $this->assertFalse($this->storage->hasToken('token_id'));
+ }
+}
diff --git a/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php b/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php
new file mode 100644
index 0000000..4166c1e
--- /dev/null
+++ b/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php
@@ -0,0 +1,262 @@
+<?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\Tests\TokenStorage;
+
+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 (!interface_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 testGetExistingTokenFromClosedSession()
+ {
+ $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(true));
+
+ $this->session->expects($this->once())
+ ->method('get')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue('RESULT'));
+
+ $this->assertSame('RESULT', $this->storage->getToken('token_id'));
+ }
+
+ public function testGetExistingTokenFromActiveSession()
+ {
+ $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(true));
+
+ $this->session->expects($this->once())
+ ->method('get')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue('RESULT'));
+
+ $this->assertSame('RESULT', $this->storage->getToken('token_id'));
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException
+ */
+ public function testGetNonExistingTokenFromClosedSession()
+ {
+ $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(false));
+
+ $this->storage->getToken('token_id');
+ }
+
+ /**
+ * @expectedException \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException
+ */
+ public function testGetNonExistingTokenFromActiveSession()
+ {
+ $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(false));
+
+ $this->storage->getToken('token_id');
+ }
+
+ public function testRemoveNonExistingTokenFromClosedSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(false));
+
+ $this->session->expects($this->once())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('remove')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue(null));
+
+ $this->assertNull($this->storage->removeToken('token_id'));
+ }
+
+ public function testRemoveNonExistingTokenFromActiveSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(true));
+
+ $this->session->expects($this->never())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('remove')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue(null));
+
+ $this->assertNull($this->storage->removeToken('token_id'));
+ }
+
+ public function testRemoveExistingTokenFromClosedSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(false));
+
+ $this->session->expects($this->once())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('remove')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue('TOKEN'));
+
+ $this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
+ }
+
+ public function testRemoveExistingTokenFromActiveSession()
+ {
+ $this->session->expects($this->any())
+ ->method('isStarted')
+ ->will($this->returnValue(true));
+
+ $this->session->expects($this->never())
+ ->method('start');
+
+ $this->session->expects($this->once())
+ ->method('remove')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
+ ->will($this->returnValue('TOKEN'));
+
+ $this->assertSame('TOKEN', $this->storage->removeToken('token_id'));
+ }
+}
diff --git a/Csrf/TokenGenerator/TokenGeneratorInterface.php b/Csrf/TokenGenerator/TokenGeneratorInterface.php
new file mode 100644
index 0000000..1405b84
--- /dev/null
+++ b/Csrf/TokenGenerator/TokenGeneratorInterface.php
@@ -0,0 +1,29 @@
+<?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\TokenGenerator;
+
+/**
+ * Generates CSRF tokens.
+ *
+ * @since 2.4
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+interface TokenGeneratorInterface
+{
+ /**
+ * Generates a CSRF token.
+ *
+ * @return string The generated CSRF token
+ */
+ public function generateToken();
+}
diff --git a/Csrf/TokenGenerator/UriSafeTokenGenerator.php b/Csrf/TokenGenerator/UriSafeTokenGenerator.php
new file mode 100644
index 0000000..edeb435
--- /dev/null
+++ b/Csrf/TokenGenerator/UriSafeTokenGenerator.php
@@ -0,0 +1,66 @@
+<?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\TokenGenerator;
+
+use Symfony\Component\Security\Core\Util\SecureRandomInterface;
+use Symfony\Component\Security\Core\Util\SecureRandom;
+
+/**
+ * Generates CSRF tokens.
+ *
+ * @since 2.4
+ *
+ * @author Bernhard Schussek <bernhard.schussek@symfony.com>
+ */
+class UriSafeTokenGenerator implements TokenGeneratorInterface
+{
+ /**
+ * The generator for random values.
+ *
+ * @var SecureRandomInterface
+ */
+ private $random;
+
+ /**
+ * The amount of entropy collected for each token (in bits).
+ *
+ * @var int
+ */
+ private $entropy;
+
+ /**
+ * Generates URI-safe CSRF tokens.
+ *
+ * @param SecureRandomInterface|null $random The random value generator used for
+ * generating entropy
+ * @param int $entropy The amount of entropy collected for
+ * each token (in bits)
+ */
+ public function __construct(SecureRandomInterface $random = null, $entropy = 256)
+ {
+ $this->random = $random ?: new SecureRandom();
+ $this->entropy = $entropy;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generateToken()
+ {
+ // 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);
+
+ return rtrim(strtr(base64_encode($bytes), '+/', '-_'), '=');
+ }
+}
diff --git a/Csrf/TokenStorage/NativeSessionTokenStorage.php b/Csrf/TokenStorage/NativeSessionTokenStorage.php
new file mode 100644
index 0000000..2620156
--- /dev/null
+++ b/Csrf/TokenStorage/NativeSessionTokenStorage.php
@@ -0,0 +1,123 @@
+<?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\Security\Csrf\Exception\TokenNotFoundException;
+
+/**
+ * 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 bool
+ */
+ 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)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ if (!isset($_SESSION[$this->namespace][$tokenId])) {
+ throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.');
+ }
+
+ return (string) $_SESSION[$this->namespace][$tokenId];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setToken($tokenId, $token)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ $_SESSION[$this->namespace][$tokenId] = (string) $token;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasToken($tokenId)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ return isset($_SESSION[$this->namespace][$tokenId]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeToken($tokenId)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ $token = isset($_SESSION[$this->namespace][$tokenId])
+ ? (string) $_SESSION[$this->namespace][$tokenId]
+ : null;
+
+ unset($_SESSION[$this->namespace][$tokenId]);
+
+ return $token;
+ }
+
+ private function startSession()
+ {
+ if (PHP_VERSION_ID >= 50400) {
+ 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..a6a6ea3
--- /dev/null
+++ b/Csrf/TokenStorage/SessionTokenStorage.php
@@ -0,0 +1,109 @@
+<?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;
+use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException;
+
+/**
+ * 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)
+ {
+ if (!$this->session->isStarted()) {
+ $this->session->start();
+ }
+
+ if (!$this->session->has($this->namespace.'/'.$tokenId)) {
+ throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.');
+ }
+
+ return (string) $this->session->get($this->namespace.'/'.$tokenId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setToken($tokenId, $token)
+ {
+ if (!$this->session->isStarted()) {
+ $this->session->start();
+ }
+
+ $this->session->set($this->namespace.'/'.$tokenId, (string) $token);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasToken($tokenId)
+ {
+ if (!$this->session->isStarted()) {
+ $this->session->start();
+ }
+
+ return $this->session->has($this->namespace.'/'.$tokenId);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function removeToken($tokenId)
+ {
+ if (!$this->session->isStarted()) {
+ $this->session->start();
+ }
+
+ return $this->session->remove($this->namespace.'/'.$tokenId);
+ }
+}
diff --git a/Csrf/TokenStorage/TokenStorageInterface.php b/Csrf/TokenStorage/TokenStorageInterface.php
new file mode 100644
index 0000000..5efe72f
--- /dev/null
+++ b/Csrf/TokenStorage/TokenStorageInterface.php
@@ -0,0 +1,60 @@
+<?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
+ *
+ * @return string The stored token
+ *
+ * @throws \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException If the token ID does not exist
+ */
+ public function getToken($tokenId);
+
+ /**
+ * Stores a CSRF token.
+ *
+ * @param string $tokenId The token ID
+ * @param string $token The CSRF token
+ */
+ public function setToken($tokenId, $token);
+
+ /**
+ * Removes a CSRF token.
+ *
+ * @param string $tokenId The token ID
+ *
+ * @return string|null Returns the removed token if one existed, NULL
+ * otherwise
+ */
+ public function removeToken($tokenId);
+
+ /**
+ * Checks whether a token with the given token ID exists.
+ *
+ * @param string $tokenId The token ID
+ *
+ * @return bool Whether a token exists with the given ID
+ */
+ public function hasToken($tokenId);
+}
diff --git a/Csrf/composer.json b/Csrf/composer.json
new file mode 100644
index 0000000..12077d4
--- /dev/null
+++ b/Csrf/composer.json
@@ -0,0 +1,37 @@
+{
+ "name": "symfony/security-csrf",
+ "type": "library",
+ "description": "Symfony Security Component - CSRF Library",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.9",
+ "symfony/security-core": "~2.4"
+ },
+ "require-dev": {
+ "symfony/http-foundation": "~2.1"
+ },
+ "suggest": {
+ "symfony/http-foundation": "For using the class SessionTokenStorage."
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Component\\Security\\Csrf\\": "" }
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.7-dev"
+ }
+ }
+}
diff --git a/Csrf/phpunit.xml.dist b/Csrf/phpunit.xml.dist
new file mode 100644
index 0000000..3e3d430
--- /dev/null
+++ b/Csrf/phpunit.xml.dist
@@ -0,0 +1,37 @@
+<?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"
+>
+ <php>
+ <ini name="error_reporting" value="-1" />
+ </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>
+
+ <listeners>
+ <listener class="Symfony\Bridge\PhpUnit\SkippedTestsListener" />
+ </listeners>
+</phpunit>