summaryrefslogtreecommitdiffstats
path: root/Csrf
diff options
context:
space:
mode:
Diffstat (limited to 'Csrf')
-rw-r--r--Csrf/CsrfToken.php66
-rw-r--r--Csrf/CsrfTokenGenerator.php105
-rw-r--r--Csrf/CsrfTokenManager.php106
-rw-r--r--Csrf/CsrfTokenManagerInterface.php67
-rw-r--r--Csrf/Exception/TokenNotFoundException.php21
-rw-r--r--Csrf/README.md2
-rw-r--r--Csrf/Tests/CsrfTokenGeneratorTest.php148
-rw-r--r--Csrf/Tests/CsrfTokenManagerTest.php164
-rw-r--r--Csrf/Tests/TokenGenerator/UriSafeTokenGeneratorTest.php71
-rw-r--r--Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php25
-rw-r--r--Csrf/Tests/TokenStorage/SessionTokenStorageTest.php130
-rw-r--r--Csrf/TokenGenerator/TokenGeneratorInterface.php (renamed from Csrf/CsrfTokenGeneratorInterface.php)20
-rw-r--r--Csrf/TokenGenerator/UriSafeTokenGenerator.php73
-rw-r--r--Csrf/TokenStorage/NativeSessionTokenStorage.php30
-rw-r--r--Csrf/TokenStorage/SessionTokenStorage.php25
-rw-r--r--Csrf/TokenStorage/TokenStorageInterface.php22
16 files changed, 783 insertions, 292 deletions
diff --git a/Csrf/CsrfToken.php b/Csrf/CsrfToken.php
new file mode 100644
index 0000000..aa3da45
--- /dev/null
+++ b/Csrf/CsrfToken.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;
+
+/**
+ * A CSRF token.
+ *
+ * @author Bernhard Schussek <bschussek@gmail.com>
+ */
+class CsrfToken
+{
+ /**
+ * @var string
+ */
+ private $id;
+
+ /**
+ * @var string
+ */
+ private $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/CsrfTokenGenerator.php b/Csrf/CsrfTokenGenerator.php
deleted file mode 100644
index 8ff3462..0000000
--- a/Csrf/CsrfTokenGenerator.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?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/CsrfTokenManager.php b/Csrf/CsrfTokenManager.php
new file mode 100644
index 0000000..fa6e19e
--- /dev/null
+++ b/Csrf/CsrfTokenManager.php
@@ -0,0 +1,106 @@
+<?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 $generator The token generator
+ * @param TokenStorageInterface $storage The storage for storing
+ * generated CSRF tokens
+ *
+ */
+ public function __construct(TokenGeneratorInterface $generator = null, TokenStorageInterface $storage = null)
+ {
+ if (null === $generator) {
+ $generator = new UriSafeTokenGenerator();
+ }
+
+ if (null === $storage) {
+ $storage = new NativeSessionTokenStorage();
+ }
+
+ $this->generator = $generator;
+ $this->storage = $storage;
+ }
+
+ /**
+ * {@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((string) $this->storage->getToken($token->getId()), $token->getValue());
+ }
+}
diff --git a/Csrf/CsrfTokenManagerInterface.php b/Csrf/CsrfTokenManagerInterface.php
new file mode 100644
index 0000000..878237b
--- /dev/null
+++ b/Csrf/CsrfTokenManagerInterface.php
@@ -0,0 +1,67 @@
+<?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.
+ *
+ * @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 Boolean Returns true if a token existed for this ID, false
+ * otherwise
+ */
+ public function removeToken($tokenId);
+
+ /**
+ * Returns whether the given CSRF token is valid.
+ *
+ * @param CsrfToken $token A CSRF token
+ *
+ * @return Boolean 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/README.md b/Csrf/README.md
index 394a29e..2b51362 100644
--- a/Csrf/README.md
+++ b/Csrf/README.md
@@ -2,7 +2,7 @@ Security Component - CSRF
=========================
The Security CSRF (cross-site request forgery) component provides a class
-`CsrfTokenGenerator` for generating and validating CSRF tokens.
+`CsrfTokenManager` for generating and validating CSRF tokens.
Resources
---------
diff --git a/Csrf/Tests/CsrfTokenGeneratorTest.php b/Csrf/Tests/CsrfTokenGeneratorTest.php
deleted file mode 100644
index f5f9507..0000000
--- a/Csrf/Tests/CsrfTokenGeneratorTest.php
+++ /dev/null
@@ -1,148 +0,0 @@
-<?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/CsrfTokenManagerTest.php b/Csrf/Tests/CsrfTokenManagerTest.php
new file mode 100644
index 0000000..67c66fb
--- /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\Form\Tests\Extension\Csrf\CsrfProvider;
+
+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..a55056f
--- /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\Form\Tests\Extension\Csrf\CsrfProvider\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
index b62eeef..ada04c8 100644
--- a/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php
+++ b/Csrf/Tests/TokenStorage/NativeSessionTokenStorageTest.php
@@ -96,8 +96,31 @@ class NativeSessionTokenStorageTest extends \PHPUnit_Framework_TestCase
$this->assertSame('TOKEN', $this->storage->getToken('token_id'));
}
+ /**
+ * @expectedException \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException
+ */
public function testGetNonExistingToken()
{
- $this->assertSame('DEFAULT', $this->storage->getToken('token_id', 'DEFAULT'));
+ $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
index 9ba7edb..799b16d 100644
--- a/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php
+++ b/Csrf/Tests/TokenStorage/SessionTokenStorageTest.php
@@ -108,7 +108,7 @@ class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase
$this->assertSame('RESULT', $this->storage->hasToken('token_id'));
}
- public function testGetTokenFromClosedSession()
+ public function testGetExistingTokenFromClosedSession()
{
$this->session->expects($this->any())
->method('isStarted')
@@ -118,14 +118,19 @@ class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase
->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', 'DEFAULT')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
- $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT'));
+ $this->assertSame('RESULT', $this->storage->getToken('token_id'));
}
- public function testGetTokenFromActiveSession()
+ public function testGetExistingTokenFromActiveSession()
{
$this->session->expects($this->any())
->method('isStarted')
@@ -135,10 +140,123 @@ class SessionTokenStorageTest extends \PHPUnit_Framework_TestCase
->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', 'DEFAULT')
+ ->with(self::SESSION_NAMESPACE.'/token_id')
->will($this->returnValue('RESULT'));
- $this->assertSame('RESULT', $this->storage->getToken('token_id', 'DEFAULT'));
+ $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/CsrfTokenGeneratorInterface.php b/Csrf/TokenGenerator/TokenGeneratorInterface.php
index c34549f..4d81da9 100644
--- a/Csrf/CsrfTokenGeneratorInterface.php
+++ b/Csrf/TokenGenerator/TokenGeneratorInterface.php
@@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Security\Csrf;
+namespace Symfony\Component\Security\Csrf\TokenGenerator;
/**
* Generates and validates CSRF tokens.
@@ -29,24 +29,12 @@ namespace Symfony\Component\Security\Csrf;
* @since 2.4
* @author Bernhard Schussek <bschussek@gmail.com>
*/
-interface CsrfTokenGeneratorInterface
+interface TokenGeneratorInterface
{
/**
- * Generates a CSRF token with the given token ID.
- *
- * @param string $tokenId An ID that identifies the token
+ * Generates a CSRF 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);
+ public function generateToken();
}
diff --git a/Csrf/TokenGenerator/UriSafeTokenGenerator.php b/Csrf/TokenGenerator/UriSafeTokenGenerator.php
new file mode 100644
index 0000000..9d24f1c
--- /dev/null
+++ b/Csrf/TokenGenerator/UriSafeTokenGenerator.php
@@ -0,0 +1,73 @@
+<?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;
+use Symfony\Component\Security\Core\Util\StringUtils;
+use Symfony\Component\Security\Csrf\TokenStorage\NativeSessionTokenStorage;
+use Symfony\Component\Security\Csrf\TokenStorage\TokenStorageInterface;
+
+/**
+ * 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 integer
+ */
+ private $entropy;
+
+ /**
+ * Generates URI-safe CSRF tokens.
+ *
+ * @param SecureRandomInterface $random The random value generator used for
+ * generating entropy
+ * @param integer $entropy The amount of entropy collected for
+ * each token (in bits)
+ *
+ */
+ public function __construct(SecureRandomInterface $random = null, $entropy = 256)
+ {
+ if (null === $random) {
+ $random = new SecureRandom();
+ }
+
+ $this->random = $random;
+ $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
index 8956743..c01967c 100644
--- a/Csrf/TokenStorage/NativeSessionTokenStorage.php
+++ b/Csrf/TokenStorage/NativeSessionTokenStorage.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Security\Csrf\TokenStorage;
+use Symfony\Component\Security\Csrf\Exception\TokenNotFoundException;
+
/**
* Token storage that uses PHP's native session handling.
*
@@ -49,17 +51,17 @@ class NativeSessionTokenStorage implements TokenStorageInterface
/**
* {@inheritdoc}
*/
- public function getToken($tokenId, $default = null)
+ public function getToken($tokenId)
{
if (!$this->sessionStarted) {
$this->startSession();
}
- if (isset($_SESSION[$this->namespace][$tokenId])) {
- return $_SESSION[$this->namespace][$tokenId];
+ if (!isset($_SESSION[$this->namespace][$tokenId])) {
+ throw new TokenNotFoundException('The CSRF token with ID '.$tokenId.' does not exist.');
}
- return $default;
+ return (string) $_SESSION[$this->namespace][$tokenId];
}
/**
@@ -71,7 +73,7 @@ class NativeSessionTokenStorage implements TokenStorageInterface
$this->startSession();
}
- $_SESSION[$this->namespace][$tokenId] = $token;
+ $_SESSION[$this->namespace][$tokenId] = (string) $token;
}
/**
@@ -86,6 +88,24 @@ class NativeSessionTokenStorage implements TokenStorageInterface
return isset($_SESSION[$this->namespace][$tokenId]);
}
+ /**
+ * {@inheritdoc}
+ */
+ public function removeToken($tokenId)
+ {
+ if (!$this->sessionStarted) {
+ $this->startSession();
+ }
+
+ $token = isset($_SESSION[$this->namespace][$tokenId])
+ ? $_SESSION[$this->namespace][$tokenId]
+ : null;
+
+ unset($_SESSION[$this->namespace][$tokenId]);
+
+ return $token;
+ }
+
private function startSession()
{
if (version_compare(PHP_VERSION, '5.4', '>=')) {
diff --git a/Csrf/TokenStorage/SessionTokenStorage.php b/Csrf/TokenStorage/SessionTokenStorage.php
index 3878e4c..f08eb96 100644
--- a/Csrf/TokenStorage/SessionTokenStorage.php
+++ b/Csrf/TokenStorage/SessionTokenStorage.php
@@ -12,6 +12,7 @@
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.
@@ -54,13 +55,17 @@ class SessionTokenStorage implements TokenStorageInterface
/**
* {@inheritdoc}
*/
- public function getToken($tokenId, $default = null)
+ public function getToken($tokenId)
{
if (!$this->session->isStarted()) {
$this->session->start();
}
- return $this->session->get($this->namespace . '/' . $tokenId, $default);
+ 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);
}
/**
@@ -72,7 +77,7 @@ class SessionTokenStorage implements TokenStorageInterface
$this->session->start();
}
- $this->session->set($this->namespace . '/' . $tokenId, $token);
+ $this->session->set($this->namespace.'/'.$tokenId, (string) $token);
}
/**
@@ -84,6 +89,18 @@ class SessionTokenStorage implements TokenStorageInterface
$this->session->start();
}
- return $this->session->has($this->namespace . '/' . $tokenId);
+ 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
index 7dba9e5..3fb3191 100644
--- a/Csrf/TokenStorage/TokenStorageInterface.php
+++ b/Csrf/TokenStorage/TokenStorageInterface.php
@@ -23,27 +23,37 @@ 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
+ * @return string The stored token
+ *
+ * @throws \Symfony\Component\Security\Csrf\Exception\TokenNotFoundException If the token ID does not exist
*/
- public function getToken($tokenId, $default = null);
+ public function getToken($tokenId);
/**
* Stores a CSRF token.
*
* @param string $tokenId The token ID
- * @param mixed $token The CSRF token
+ * @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 Boolean Returns true if a token is stored for the given token ID,
- * false otherwise.
+ * @return Boolean Whether a token exists with the given ID
*/
public function hasToken($tokenId);
}