summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--CHANGELOG.md9
-rw-r--r--Core/Encoder/BasePasswordEncoder.php13
-rw-r--r--Core/Encoder/Pbkdf2PasswordEncoder.php97
-rw-r--r--Core/Role/RoleInterface.php2
-rw-r--r--Core/Util/ClassUtils.php5
-rw-r--r--Core/Util/SecureRandom.php114
-rw-r--r--Core/Util/SecureRandomInterface.php30
-rw-r--r--Core/Util/StringUtils.php49
-rw-r--r--Http/EntryPoint/BasicAuthenticationEntryPoint.php2
-rw-r--r--Http/EntryPoint/DigestAuthenticationEntryPoint.php2
-rw-r--r--Http/Firewall.php9
-rw-r--r--Http/Firewall/UsernamePasswordFormAuthenticationListener.php19
-rw-r--r--Http/RememberMe/PersistentTokenBasedRememberMeServices.php47
-rw-r--r--Http/RememberMe/ResponseListener.php9
-rw-r--r--README.md9
-rw-r--r--Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php45
-rw-r--r--Tests/Core/Util/ClassUtilsTest.php9
-rwxr-xr-xTests/Core/Util/SecureRandomTest.php201
-rwxr-xr-xTests/Core/Util/StringUtilsTest.php23
-rw-r--r--Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php2
-rw-r--r--Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php3
-rw-r--r--Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php3
-rw-r--r--Tests/bootstrap.php22
-rw-r--r--composer.json26
-rw-r--r--phpunit.xml.dist2
26 files changed, 655 insertions, 99 deletions
diff --git a/.gitignore b/.gitignore
index d1502b0..44de97a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
vendor/
composer.lock
+phpunit.xml
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c570ac3..1305b28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,15 @@
CHANGELOG
=========
+2.2.0
+-----
+
+ * `Symfony\Component\Security\Http\Firewall` and
+ `Symfony\Component\Security\Http\RememberMe\ResponseListener` now
+ implements EventSubscriberInterface
+ * added secure random number generator
+ * added PBKDF2 Password encoder
+
2.1.0
-----
diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php
index ae1c7d4..1ef134b 100644
--- a/Core/Encoder/BasePasswordEncoder.php
+++ b/Core/Encoder/BasePasswordEncoder.php
@@ -11,6 +11,8 @@
namespace Symfony\Component\Security\Core\Encoder;
+use Symfony\Component\Security\Core\Util\StringUtils;
+
/**
* BasePasswordEncoder is the base class for all password encoders.
*
@@ -77,15 +79,6 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface
*/
protected function comparePasswords($password1, $password2)
{
- if (strlen($password1) !== strlen($password2)) {
- return false;
- }
-
- $result = 0;
- for ($i = 0; $i < strlen($password1); $i++) {
- $result |= ord($password1[$i]) ^ ord($password2[$i]);
- }
-
- return 0 === $result;
+ return StringUtils::equals($password1, $password2);
}
}
diff --git a/Core/Encoder/Pbkdf2PasswordEncoder.php b/Core/Encoder/Pbkdf2PasswordEncoder.php
new file mode 100644
index 0000000..656545f
--- /dev/null
+++ b/Core/Encoder/Pbkdf2PasswordEncoder.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\Core\Encoder;
+
+/**
+ * Pbkdf2PasswordEncoder uses the PBKDF2 (Password-Based Key Derivation Function 2).
+ *
+ * Providing a high level of Cryptographic security,
+ * PBKDF2 is recommended by the National Institute of Standards and Technology (NIST).
+ *
+ * But also warrants a warning, using PBKDF2 (with a high number of iterations) slows down the process.
+ * PBKDF2 should be used with caution and care.
+ *
+ * @author Sebastiaan Stok <s.stok@rollerscapes.net>
+ * @author Andrew Johnson
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class Pbkdf2PasswordEncoder extends BasePasswordEncoder
+{
+ private $algorithm;
+ private $encodeHashAsBase64;
+ private $iterations;
+ private $length;
+
+ /**
+ * Constructor.
+ *
+ * @param string $algorithm The digest algorithm to use
+ * @param Boolean $encodeHashAsBase64 Whether to base64 encode the password hash
+ * @param integer $iterations The number of iterations to use to stretch the password hash
+ * @param integer $length Length of derived key to create
+ */
+ public function __construct($algorithm = 'sha512', $encodeHashAsBase64 = true, $iterations = 1000, $length = 40)
+ {
+ $this->algorithm = $algorithm;
+ $this->encodeHashAsBase64 = $encodeHashAsBase64;
+ $this->iterations = $iterations;
+ $this->length = $length;
+ }
+
+ /**
+ * {@inheritdoc}
+ *
+ * @throws \LogicException when the algorithm is not supported
+ */
+ public function encodePassword($raw, $salt)
+ {
+ if (!in_array($this->algorithm, hash_algos(), true)) {
+ throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm));
+ }
+
+ if (function_exists('hash_pbkdf2')) {
+ $digest = hash_pbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length, true);
+ } else {
+ $digest = $this->hashPbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length);
+ }
+
+ return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isPasswordValid($encoded, $raw, $salt)
+ {
+ return $this->comparePasswords($encoded, $this->encodePassword($raw, $salt));
+ }
+
+ private function hashPbkdf2($algorithm, $password, $salt, $iterations, $length = 0)
+ {
+ // Number of blocks needed to create the derived key
+ $blocks = ceil($length / strlen(hash($algorithm, null, true)));
+ $digest = '';
+
+ for ($i = 1; $i <= $blocks; $i++) {
+ $ib = $block = hash_hmac($algorithm, $salt . pack('N', $i), $password, true);
+
+ // Iterations
+ for ($j = 1; $j < $iterations; $j++) {
+ $ib ^= ($block = hash_hmac($algorithm, $block, $password, true));
+ }
+
+ $digest .= $ib;
+ }
+
+ return substr($digest, 0, $this->length);
+ }
+}
diff --git a/Core/Role/RoleInterface.php b/Core/Role/RoleInterface.php
index a3cb266..3d4cbea 100644
--- a/Core/Role/RoleInterface.php
+++ b/Core/Role/RoleInterface.php
@@ -15,7 +15,7 @@ namespace Symfony\Component\Security\Core\Role;
* RoleInterface represents a role granted to a user.
*
* A role must either have a string representation or it needs to be explicitly
- * supported by an at least one AccessDecisionManager.
+ * supported by at least one AccessDecisionManager.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
diff --git a/Core/Util/ClassUtils.php b/Core/Util/ClassUtils.php
index 7b583a3..26bf1a1 100644
--- a/Core/Util/ClassUtils.php
+++ b/Core/Util/ClassUtils.php
@@ -37,6 +37,11 @@ class ClassUtils
const MARKER_LENGTH = 6;
/**
+ * This class should not be instantiated
+ */
+ private function __construct() {}
+
+ /**
* Gets the real class name of a class name that could be a proxy.
*
* @param string|object
diff --git a/Core/Util/SecureRandom.php b/Core/Util/SecureRandom.php
new file mode 100644
index 0000000..77f1d8c
--- /dev/null
+++ b/Core/Util/SecureRandom.php
@@ -0,0 +1,114 @@
+<?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\Core\Util;
+
+use Symfony\Component\HttpKernel\Log\LoggerInterface;
+
+/**
+ * A secure random number generator implementation.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Johannes M. Schmitt <schmittjoh@gmail.com>
+ */
+final class SecureRandom implements SecureRandomInterface
+{
+ private $logger;
+ private $useOpenSsl;
+ private $seed;
+ private $seedUpdated;
+ private $seedLastUpdatedAt;
+ private $seedFile;
+
+ /**
+ * Constructor.
+ *
+ * Be aware that a guessable seed will severely compromise the PRNG
+ * algorithm that is employed.
+ *
+ * @param string $seedFile
+ * @param LoggerInterface $logger
+ */
+ public function __construct($seedFile = null, LoggerInterface $logger = null)
+ {
+ $this->seedFile = $seedFile;
+ $this->logger = $logger;
+
+ // determine whether to use OpenSSL
+ if (defined('PHP_WINDOWS_VERSION_BUILD') && version_compare(PHP_VERSION, '5.3.4', '<')) {
+ $this->useOpenSsl = false;
+ } elseif (!function_exists('openssl_random_pseudo_bytes')) {
+ if (null !== $this->logger) {
+ $this->logger->notice('It is recommended that you enable the "openssl" extension for random number generation.');
+ }
+ $this->useOpenSsl = false;
+ } else {
+ $this->useOpenSsl = true;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function nextBytes($nbBytes)
+ {
+ // try OpenSSL
+ if ($this->useOpenSsl) {
+ $bytes = openssl_random_pseudo_bytes($nbBytes, $strong);
+
+ if (false !== $bytes && true === $strong) {
+ return $bytes;
+ }
+
+ if (null !== $this->logger) {
+ $this->logger->info('OpenSSL did not produce a secure random number.');
+ }
+ }
+
+ // initialize seed
+ if (null === $this->seed) {
+ if (null === $this->seedFile) {
+ throw new \RuntimeException('You need to specify a file path to store the seed.');
+ }
+
+ if (is_file($this->seedFile)) {
+ list($this->seed, $this->seedLastUpdatedAt) = $this->readSeed();
+ } else {
+ $this->seed = uniqid(mt_rand(), true);
+ $this->updateSeed();
+ }
+ }
+
+ $bytes = '';
+ while (strlen($bytes) < $nbBytes) {
+ static $incr = 1;
+ $bytes .= hash('sha512', $incr++.$this->seed.uniqid(mt_rand(), true).$nbBytes, true);
+ $this->seed = base64_encode(hash('sha512', $this->seed.$bytes.$nbBytes, true));
+ $this->updateSeed();
+ }
+
+ return substr($bytes, 0, $nbBytes);
+ }
+
+ private function readSeed()
+ {
+ return json_decode(file_get_contents($this->seedFile));
+ }
+
+ private function updateSeed()
+ {
+ if (!$this->seedUpdated && $this->seedLastUpdatedAt < time() - mt_rand(1, 10)) {
+ file_put_contents($this->seedFile, json_encode(array($this->seed, microtime(true))));
+ }
+
+ $this->seedUpdated = true;
+ }
+}
diff --git a/Core/Util/SecureRandomInterface.php b/Core/Util/SecureRandomInterface.php
new file mode 100644
index 0000000..bb70a7e
--- /dev/null
+++ b/Core/Util/SecureRandomInterface.php
@@ -0,0 +1,30 @@
+<?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\Core\Util;
+
+
+/**
+ * Interface that needs to be implemented by all secure random number generators.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+interface SecureRandomInterface
+{
+ /**
+ * Generates the specified number of secure random bytes.
+ *
+ * @param integer $nbBytes
+ *
+ * @return string
+ */
+ public function nextBytes($nbBytes);
+}
diff --git a/Core/Util/StringUtils.php b/Core/Util/StringUtils.php
new file mode 100644
index 0000000..d21efd3
--- /dev/null
+++ b/Core/Util/StringUtils.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\Core\Util;
+
+/**
+ * String utility functions.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ */
+class StringUtils
+{
+ /**
+ * This class should not be instantiated
+ */
+ private function __construct() {}
+
+ /**
+ * Compares two strings.
+ *
+ * This method implements a constant-time algorithm to compare strings.
+ *
+ * @param string $str1 The first string
+ * @param string $str2 The second string
+ *
+ * @return Boolean true if the two strings are the same, false otherwise
+ */
+ public static function equals($str1, $str2)
+ {
+ if (strlen($str1) !== $c = strlen($str2)) {
+ return false;
+ }
+
+ $result = 0;
+ for ($i = 0; $i < $c; $i++) {
+ $result |= ord($str1[$i]) ^ ord($str2[$i]);
+ }
+
+ return 0 === $result;
+ }
+}
diff --git a/Http/EntryPoint/BasicAuthenticationEntryPoint.php b/Http/EntryPoint/BasicAuthenticationEntryPoint.php
index 6ba3872..44ece5e 100644
--- a/Http/EntryPoint/BasicAuthenticationEntryPoint.php
+++ b/Http/EntryPoint/BasicAuthenticationEntryPoint.php
@@ -34,7 +34,7 @@ class BasicAuthenticationEntryPoint implements AuthenticationEntryPointInterface
{
$response = new Response();
$response->headers->set('WWW-Authenticate', sprintf('Basic realm="%s"', $this->realmName));
- $response->setStatusCode(401, $authException ? $authException->getMessage() : null);
+ $response->setStatusCode(401);
return $response;
}
diff --git a/Http/EntryPoint/DigestAuthenticationEntryPoint.php b/Http/EntryPoint/DigestAuthenticationEntryPoint.php
index ec92419..37fba85 100644
--- a/Http/EntryPoint/DigestAuthenticationEntryPoint.php
+++ b/Http/EntryPoint/DigestAuthenticationEntryPoint.php
@@ -57,7 +57,7 @@ class DigestAuthenticationEntryPoint implements AuthenticationEntryPointInterfac
$response = new Response();
$response->headers->set('WWW-Authenticate', $authenticateHeader);
- $response->setStatusCode(401, $authException ? $authException->getMessage() : null);
+ $response->setStatusCode(401);
return $response;
}
diff --git a/Http/Firewall.php b/Http/Firewall.php
index a590fd9..e083fdb 100644
--- a/Http/Firewall.php
+++ b/Http/Firewall.php
@@ -12,8 +12,10 @@
namespace Symfony\Component\Security\Http;
use Symfony\Component\HttpKernel\HttpKernelInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Firewall uses a FirewallMap to register security listeners for the given
@@ -25,7 +27,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface;
*
* @author Fabien Potencier <fabien@symfony.com>
*/
-class Firewall
+class Firewall implements EventSubscriberInterface
{
private $map;
private $dispatcher;
@@ -68,4 +70,9 @@ class Firewall
}
}
}
+
+ public static function getSubscribedEvents()
+ {
+ return array(KernelEvents::REQUEST => array('onKernelRequest', 8));
+ }
}
diff --git a/Http/Firewall/UsernamePasswordFormAuthenticationListener.php b/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
index 22330a8..388c014 100644
--- a/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
+++ b/Http/Firewall/UsernamePasswordFormAuthenticationListener.php
@@ -55,7 +55,7 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
*/
protected function requiresAuthentication(Request $request)
{
- if ($this->options['post_only'] && !$request->isMethod('post')) {
+ if ($this->options['post_only'] && !$request->isMethod('POST')) {
return false;
}
@@ -67,14 +67,6 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
*/
protected function attemptAuthentication(Request $request)
{
- if ($this->options['post_only'] && 'post' !== strtolower($request->getMethod())) {
- if (null !== $this->logger) {
- $this->logger->debug(sprintf('Authentication method not supported: %s.', $request->getMethod()));
- }
-
- return null;
- }
-
if (null !== $this->csrfProvider) {
$csrfToken = $request->get($this->options['csrf_parameter'], null, true);
@@ -83,8 +75,13 @@ class UsernamePasswordFormAuthenticationListener extends AbstractAuthenticationL
}
}
- $username = trim($request->get($this->options['username_parameter'], null, true));
- $password = $request->get($this->options['password_parameter'], null, true);
+ if ($this->options['post_only']) {
+ $username = trim($request->request->get($this->options['username_parameter'], null, true));
+ $password = $request->request->get($this->options['password_parameter'], null, true);
+ } else {
+ $username = trim($request->get($this->options['username_parameter'], null, true));
+ $password = $request->get($this->options['password_parameter'], null, true);
+ }
$request->getSession()->set(SecurityContextInterface::LAST_USERNAME, $username);
diff --git a/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/Http/RememberMe/PersistentTokenBasedRememberMeServices.php
index 8944672..dbb6429 100644
--- a/Http/RememberMe/PersistentTokenBasedRememberMeServices.php
+++ b/Http/RememberMe/PersistentTokenBasedRememberMeServices.php
@@ -19,6 +19,7 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CookieTheftException;
use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
+use Symfony\Component\Security\Core\Util\SecureRandomInterface;
/**
* Concrete implementation of the RememberMeServicesInterface which needs
@@ -30,6 +31,24 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
{
private $tokenProvider;
+ private $secureRandom;
+
+ /**
+ * Constructor.
+ *
+ * @param array $userProviders
+ * @param string $key
+ * @param string $providerKey
+ * @param array $options
+ * @param LoggerInterface $logger
+ * @param SecureRandomInterface $secureRandom
+ */
+ public function __construct(array $userProviders, $key, $providerKey, array $options = array(), LoggerInterface $logger = null, SecureRandomInterface $secureRandom)
+ {
+ parent::__construct($userProviders, $key, $providerKey, $options, $logger);
+
+ $this->secureRandom = $secureRandom;
+ }
/**
* Sets the token provider
@@ -79,7 +98,7 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
}
$series = $persistentToken->getSeries();
- $tokenValue = $this->generateRandomValue();
+ $tokenValue = $this->secureRandom->nextBytes(64);
$this->tokenProvider->updateToken($series, $tokenValue, new \DateTime());
$request->attributes->set(self::COOKIE_ATTR_NAME,
new Cookie(
@@ -101,8 +120,8 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
*/
protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
{
- $series = $this->generateRandomValue();
- $tokenValue = $this->generateRandomValue();
+ $series = $this->secureRandom->nextBytes(64);
+ $tokenValue = $this->secureRandom->nextBytes(64);
$this->tokenProvider->createNewToken(
new PersistentToken(
@@ -126,26 +145,4 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
)
);
}
-
- /**
- * Generates a cryptographically strong random value
- *
- * @return string
- */
- protected function generateRandomValue()
- {
- if (function_exists('openssl_random_pseudo_bytes')) {
- $bytes = openssl_random_pseudo_bytes(64, $strong);
-
- if (true === $strong && false !== $bytes) {
- return base64_encode($bytes);
- }
- }
-
- if (null !== $this->logger) {
- $this->logger->warn('Could not produce a cryptographically strong random value. Please install/update the OpenSSL extension.');
- }
-
- return base64_encode(hash('sha512', uniqid(mt_rand(), true), true));
- }
}
diff --git a/Http/RememberMe/ResponseListener.php b/Http/RememberMe/ResponseListener.php
index 6cbdcb3..03c71c7 100644
--- a/Http/RememberMe/ResponseListener.php
+++ b/Http/RememberMe/ResponseListener.php
@@ -12,13 +12,15 @@
namespace Symfony\Component\Security\Http\RememberMe;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Adds remember-me cookies to the Response.
*
* @author Johannes M. Schmitt <schmittjoh@gmail.com>
*/
-class ResponseListener
+class ResponseListener implements EventSubscriberInterface
{
public function onKernelResponse(FilterResponseEvent $event)
{
@@ -29,4 +31,9 @@ class ResponseListener
$response->headers->setCookie($request->attributes->get(RememberMeServicesInterface::COOKIE_ATTR_NAME));
}
}
+
+ public static function getSubscribedEvents()
+ {
+ return array(KernelEvents::RESPONSE => 'onKernelResponse');
+ }
}
diff --git a/README.md b/README.md
index 7ae97b1..057f844 100644
--- a/README.md
+++ b/README.md
@@ -18,9 +18,6 @@ Resources
You can run the unit tests with the following command:
- phpunit
-
-If you also want to run the unit tests that depend on other Symfony
-Components, install dev dependencies before running PHPUnit:
-
- php composer.phar install --dev
+ $ cd path/to/Symfony/Component/Security/
+ $ composer.phar install --dev
+ $ phpunit
diff --git a/Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php b/Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php
new file mode 100644
index 0000000..2c98543
--- /dev/null
+++ b/Tests/Core/Encoder/Pbkdf2PasswordEncoderTest.php
@@ -0,0 +1,45 @@
+<?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\Tests\Core\Encoder;
+
+use Symfony\Component\Security\Core\Encoder\Pbkdf2PasswordEncoder;
+
+class Pbkdf2PasswordEncoderTest extends \PHPUnit_Framework_TestCase
+{
+ public function testIsPasswordValid()
+ {
+ $encoder = new Pbkdf2PasswordEncoder('sha256', false, 1, 40);
+
+ $this->assertTrue($encoder->isPasswordValid('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', 'password', ''));
+ }
+
+ public function testEncodePassword()
+ {
+ $encoder = new Pbkdf2PasswordEncoder('sha256', false, 1, 40);
+ $this->assertSame('c1232f10f62715fda06ae7c0a2037ca19b33cf103b727ba56d870c11f290a2ab106974c75607c8a3', $encoder->encodePassword('password', ''));
+
+ $encoder = new Pbkdf2PasswordEncoder('sha256', true, 1, 40);
+ $this->assertSame('wSMvEPYnFf2gaufAogN8oZszzxA7cnulbYcMEfKQoqsQaXTHVgfIow==', $encoder->encodePassword('password', ''));
+
+ $encoder = new Pbkdf2PasswordEncoder('sha256', false, 2, 40);
+ $this->assertSame('8bc2f9167a81cdcfad1235cd9047f1136271c1f978fcfcb35e22dbeafa4634f6fd2214218ed63ebb', $encoder->encodePassword('password', ''));
+ }
+
+ /**
+ * @expectedException LogicException
+ */
+ public function testEncodePasswordAlgorithmDoesNotExist()
+ {
+ $encoder = new Pbkdf2PasswordEncoder('foobar');
+ $encoder->encodePassword('password', '');
+ }
+}
diff --git a/Tests/Core/Util/ClassUtilsTest.php b/Tests/Core/Util/ClassUtilsTest.php
index 16378a6..edfd779 100644
--- a/Tests/Core/Util/ClassUtilsTest.php
+++ b/Tests/Core/Util/ClassUtilsTest.php
@@ -1,5 +1,14 @@
<?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\Tests\Core\Util
{
use Symfony\Component\Security\Core\Util\ClassUtils;
diff --git a/Tests/Core/Util/SecureRandomTest.php b/Tests/Core/Util/SecureRandomTest.php
new file mode 100755
index 0000000..c7ed016
--- /dev/null
+++ b/Tests/Core/Util/SecureRandomTest.php
@@ -0,0 +1,201 @@
+<?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\Tests\Core\Util;
+
+use Symfony\Component\Security\Core\Util\SecureRandom;
+
+class SecureRandomTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * T1: Monobit test
+ *
+ * @dataProvider getSecureRandoms
+ */
+ public function testMonobit($secureRandom)
+ {
+ $nbOnBits = substr_count($this->getBitSequence($secureRandom, 20000), '1');
+ $this->assertTrue($nbOnBits > 9654 && $nbOnBits < 10346, 'Monobit test failed, number of turned on bits: '.$nbOnBits);
+ }
+
+ /**
+ * T2: Chi-square test with 15 degrees of freedom (chi-Quadrat-Anpassungstest)
+ *
+ * @dataProvider getSecureRandoms
+ */
+ public function testPoker($secureRandom)
+ {
+ $b = $this->getBitSequence($secureRandom, 20000);
+ $c = array();
+ for ($i = 0; $i <= 15; $i++) {
+ $c[$i] = 0;
+ }
+
+ for ($j = 1; $j <= 5000; $j++) {
+ $k = 4 * $j - 1;
+ $c[8 * $b[$k - 3] + 4 * $b[$k - 2] + 2 * $b[$k - 1] + $b[$k]] += 1;
+ }
+
+ $f = 0;
+ for ($i = 0; $i <= 15; $i++) {
+ $f += $c[$i] * $c[$i];
+ }
+
+ $Y = 16/5000 * $f - 5000;
+
+ $this->assertTrue($Y > 1.03 && $Y < 57.4, 'Poker test failed, Y = '.$Y);
+ }
+
+ /**
+ * Run test
+ *
+ * @dataProvider getSecureRandoms
+ */
+ public function testRun($secureRandom)
+ {
+ $b = $this->getBitSequence($secureRandom, 20000);
+
+ $runs = array();
+ for ($i = 1; $i <= 6; $i++) {
+ $runs[$i] = 0;
+ }
+
+ $addRun = function($run) use (&$runs) {
+ if ($run > 6) {
+ $run = 6;
+ }
+
+ $runs[$run] += 1;
+ };
+
+ $currentRun = 0;
+ $lastBit = null;
+ for ($i = 0; $i < 20000; $i++) {
+ if ($lastBit === $b[$i]) {
+ $currentRun += 1;
+ } else {
+ if ($currentRun > 0) {
+ $addRun($currentRun);
+ }
+
+ $lastBit = $b[$i];
+ $currentRun = 0;
+ }
+ }
+ if ($currentRun > 0) {
+ $addRun($currentRun);
+ }
+
+ $this->assertTrue($runs[1] > 2267 && $runs[1] < 2733, 'Runs of length 1 outside of defined interval: '.$runs[1]);
+ $this->assertTrue($runs[2] > 1079 && $runs[2] < 1421, 'Runs of length 2 outside of defined interval: '.$runs[2]);
+ $this->assertTrue($runs[3] > 502 && $runs[3] < 748, 'Runs of length 3 outside of defined interval: '.$runs[3]);
+ $this->assertTrue($runs[4] > 233 && $runs[4] < 402, 'Runs of length 4 outside of defined interval: '.$runs[4]);
+ $this->assertTrue($runs[5] > 90 && $runs[5] < 223, 'Runs of length 5 outside of defined interval: '.$runs[5]);
+ $this->assertTrue($runs[6] > 90 && $runs[6] < 233, 'Runs of length 6 outside of defined interval: '.$runs[6]);
+ }
+
+ /**
+ * Long-run test
+ *
+ * @dataProvider getSecureRandoms
+ */
+ public function testLongRun($secureRandom)
+ {
+ $b = $this->getBitSequence($secureRandom, 20000);
+
+ $longestRun = 0;
+ $currentRun = $lastBit = null;
+ for ($i = 0; $i < 20000; $i++) {
+ if ($lastBit === $b[$i]) {
+ $currentRun += 1;
+ } else {
+ if ($currentRun > $longestRun) {
+ $longestRun = $currentRun;
+ }
+ $lastBit = $b[$i];
+ $currentRun = 0;
+ }
+ }
+ if ($currentRun > $longestRun) {
+ $longestRun = $currentRun;
+ }
+
+ $this->assertTrue($longestRun < 34, 'Failed longest run test: '.$longestRun);
+ }
+
+ /**
+ * Serial Correlation (Autokorrelationstest)
+ *
+ * @dataProvider getSecureRandoms
+ */
+ public function testSerialCorrelation($secureRandom)
+ {
+ $shift = rand(1, 5000);
+ $b = $this->getBitSequence($secureRandom, 20000);
+
+ $Z = 0;
+ for ($i = 0; $i < 5000; $i++) {
+ $Z += $b[$i] === $b[$i + $shift] ? 1 : 0;
+ }
+
+ $this->assertTrue($Z > 2326 && $Z < 2674, 'Failed serial correlation test: '.$Z);
+ }
+
+ public function getSecureRandoms()
+ {
+ $secureRandoms = array();
+
+ // only add if openssl is indeed present
+ $secureRandom = new SecureRandom();
+ if ($this->hasOpenSsl($secureRandom)) {
+ $secureRandoms[] = array($secureRandom);
+ }
+
+ // no-openssl with custom seed provider
+ $secureRandom = new SecureRandom(sys_get_temp_dir().'/_sf2.seed');
+ $this->disableOpenSsl($secureRandom);
+ $secureRandoms[] = array($secureRandom);
+
+ return $secureRandoms;
+ }
+
+ protected function disableOpenSsl($secureRandom)
+ {
+ $ref = new \ReflectionProperty($secureRandom, 'useOpenSsl');
+ $ref->setAccessible(true);
+ $ref->setValue($secureRandom, false);
+ $ref->setAccessible(false);
+ }
+
+ protected function hasOpenSsl($secureRandom)
+ {
+ $ref = new \ReflectionProperty($secureRandom, 'useOpenSsl');
+ $ref->setAccessible(true);
+
+ $ret = $ref->getValue($secureRandom);
+
+ $ref->setAccessible(false);
+
+ return $ret;
+ }
+
+ private function getBitSequence($secureRandom, $length)
+ {
+ $bitSequence = '';
+ for ($i = 0; $i < $length; $i += 40) {
+ $value = unpack('H*', $secureRandom->nextBytes(5));
+ $value = str_pad(base_convert($value[1], 16, 2), 40, '0', STR_PAD_LEFT);
+ $bitSequence .= $value;
+ }
+
+ return substr($bitSequence, 0, $length);
+ }
+}
diff --git a/Tests/Core/Util/StringUtilsTest.php b/Tests/Core/Util/StringUtilsTest.php
new file mode 100755
index 0000000..aac4139
--- /dev/null
+++ b/Tests/Core/Util/StringUtilsTest.php
@@ -0,0 +1,23 @@
+<?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\Tests\Core\Util;
+
+use Symfony\Component\Security\Core\Util\StringUtils;
+
+class StringUtilsTest extends \PHPUnit_Framework_TestCase
+{
+ public function testEquals()
+ {
+ $this->assertTrue(StringUtils::equals('password', 'password'));
+ $this->assertFalse(StringUtils::equals('password', 'foo'));
+ }
+}
diff --git a/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php b/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php
index b442309..b9e289d 100644
--- a/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php
+++ b/Tests/Http/EntryPoint/BasicAuthenticationEntryPointTest.php
@@ -34,7 +34,6 @@ class BasicAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('Basic realm="TheRealmName"', $response->headers->get('WWW-Authenticate'));
$this->assertEquals(401, $response->getStatusCode());
- $this->assertAttributeEquals('The exception message', 'statusText', $response);
}
public function testStartWithoutAuthException()
@@ -47,6 +46,5 @@ class BasicAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('Basic realm="TheRealmName"', $response->headers->get('WWW-Authenticate'));
$this->assertEquals(401, $response->getStatusCode());
- $this->assertAttributeEquals('Unauthorized', 'statusText', $response);
}
}
diff --git a/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php b/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php
index ae0e3cc..8dfd618 100644
--- a/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php
+++ b/Tests/Http/EntryPoint/DigestAuthenticationEntryPointTest.php
@@ -34,7 +34,6 @@ class DigestAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase
$response = $entryPoint->start($request, $authenticationException);
$this->assertEquals(401, $response->getStatusCode());
- $this->assertAttributeEquals('TheAuthenticationExceptionMessage', 'statusText', $response);
$this->assertRegExp('/^Digest realm="TheRealmName", qop="auth", nonce="[a-zA-Z0-9\/+]+={0,2}"$/', $response->headers->get('WWW-Authenticate'));
}
@@ -46,7 +45,6 @@ class DigestAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase
$response = $entryPoint->start($request);
$this->assertEquals(401, $response->getStatusCode());
- $this->assertAttributeEquals('Unauthorized', 'statusText', $response);
$this->assertRegExp('/^Digest realm="TheRealmName", qop="auth", nonce="[a-zA-Z0-9\/+]+={0,2}"$/', $response->headers->get('WWW-Authenticate'));
}
@@ -60,7 +58,6 @@ class DigestAuthenticationEntryPointTest extends \PHPUnit_Framework_TestCase
$response = $entryPoint->start($request, $nonceExpiredException);
$this->assertEquals(401, $response->getStatusCode());
- $this->assertAttributeEquals('TheNonceExpiredExceptionMessage', 'statusText', $response);
$this->assertRegExp('/^Digest realm="TheRealmName", qop="auth", nonce="[a-zA-Z0-9\/+]+={0,2}", stale="true"$/', $response->headers->get('WWW-Authenticate'));
}
}
diff --git a/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php b/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php
index 3b3691d..7fc3021 100644
--- a/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php
+++ b/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php
@@ -22,6 +22,7 @@ use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\Security\Http\RememberMe\PersistentTokenBasedRememberMeServices;
use Symfony\Component\Security\Core\Exception\TokenNotFoundException;
use Symfony\Component\Security\Core\Exception\CookieTheftException;
+use Symfony\Component\Security\Core\Util\SecureRandom;
class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase
{
@@ -318,7 +319,7 @@ class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_Test
$userProvider = $this->getProvider();
}
- return new PersistentTokenBasedRememberMeServices(array($userProvider), 'fookey', 'fookey', $options, $logger);
+ return new PersistentTokenBasedRememberMeServices(array($userProvider), 'fookey', 'fookey', $options, $logger, new SecureRandom(sys_get_temp_dir().'/_sf2.seed'));
}
protected function getProvider()
diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php
deleted file mode 100644
index 84ae3a6..0000000
--- a/Tests/bootstrap.php
+++ /dev/null
@@ -1,22 +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.
- */
-
-spl_autoload_register(function ($class) {
- if (0 === strpos(ltrim($class, '/'), 'Symfony\Component\Security')) {
- if (file_exists($file = __DIR__.'/../'.substr(str_replace('\\', '/', $class), strlen('Symfony\Component\Security')).'.php')) {
- require_once $file;
- }
- }
-});
-
-if (file_exists($loader = __DIR__.'/../vendor/autoload.php')) {
- require_once $loader;
-}
diff --git a/composer.json b/composer.json
index 0cf0a30..73e07b5 100644
--- a/composer.json
+++ b/composer.json
@@ -17,33 +17,33 @@
],
"require": {
"php": ">=5.3.3",
- "symfony/event-dispatcher": "2.1.*",
- "symfony/http-foundation": "2.1.*",
- "symfony/http-kernel": "2.1.*"
+ "symfony/event-dispatcher": "2.2.*",
+ "symfony/http-foundation": "2.2.*",
+ "symfony/http-kernel": "2.2.*"
},
"require-dev": {
- "symfony/form": "2.1.*",
- "symfony/routing": "2.1.*",
- "symfony/validator": "2.1.*",
+ "symfony/form": "2.2.*",
+ "symfony/routing": "2.2.*",
+ "symfony/validator": "2.2.*",
"doctrine/common": ">=2.2,<2.4-dev",
"doctrine/dbal": ">=2.2,<2.4-dev"
},
"suggest": {
- "symfony/class-loader": "2.1.*",
- "symfony/finder": "2.1.*",
- "symfony/form": "2.1.*",
- "symfony/validator": "2.1.*",
- "symfony/routing": "2.1.*",
+ "symfony/class-loader": "2.2.*",
+ "symfony/finder": "2.2.*",
+ "symfony/form": "2.2.*",
+ "symfony/validator": "2.2.*",
+ "symfony/routing": "2.2.*",
"doctrine/dbal": "to use the built-in ACL implementation"
},
"autoload": {
- "psr-0": { "Symfony\\Component\\Security": "" }
+ "psr-0": { "Symfony\\Component\\Security\\": "" }
},
"target-dir": "Symfony/Component/Security",
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "2.1-dev"
+ "dev-master": "2.2-dev"
}
}
}
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index 0560cf5..f45a44e 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -9,7 +9,7 @@
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
- bootstrap="Tests/bootstrap.php"
+ bootstrap="vendor/autoload.php"
>
<testsuites>
<testsuite name="Symfony Security Component Test Suite">