diff options
28 files changed, 181 insertions, 412 deletions
diff --git a/Acl/Dbal/AclProvider.php b/Acl/Dbal/AclProvider.php index 1fade3b..bba5824 100644 --- a/Acl/Dbal/AclProvider.php +++ b/Acl/Dbal/AclProvider.php @@ -367,7 +367,7 @@ FINDCHILDREN; */ protected function getSelectObjectIdentityIdSql(ObjectIdentityInterface $oid) { - $query = <<<QUERY + $query = <<<'QUERY' SELECT o.id FROM %s o INNER JOIN %s c ON c.id = o.class_id @@ -571,7 +571,7 @@ QUERY; $oidCache[$oidLookupKey] = new ObjectIdentity($objectIdentifier, $classType); } - $acl = new Acl((int) $aclId, $oidCache[$oidLookupKey], $permissionGrantingStrategy, $emptyArray, !!$entriesInheriting); + $acl = new Acl((int) $aclId, $oidCache[$oidLookupKey], $permissionGrantingStrategy, $emptyArray, (bool) $entriesInheriting); // keep a local, and global reference to this ACL $loadedAcls[$classType][$objectIdentifier] = $acl; @@ -613,9 +613,9 @@ QUERY; } if (null === $fieldName) { - $loadedAces[$aceId] = new Entry((int) $aceId, $acl, $sids[$key], $grantingStrategy, (int) $mask, !!$granting, !!$auditFailure, !!$auditSuccess); + $loadedAces[$aceId] = new Entry((int) $aceId, $acl, $sids[$key], $grantingStrategy, (int) $mask, (bool) $granting, (bool) $auditFailure, (bool) $auditSuccess); } else { - $loadedAces[$aceId] = new FieldEntry((int) $aceId, $acl, $fieldName, $sids[$key], $grantingStrategy, (int) $mask, !!$granting, !!$auditFailure, !!$auditSuccess); + $loadedAces[$aceId] = new FieldEntry((int) $aceId, $acl, $fieldName, $sids[$key], $grantingStrategy, (int) $mask, (bool) $granting, (bool) $auditFailure, (bool) $auditSuccess); } } $ace = $loadedAces[$aceId]; diff --git a/Acl/Dbal/MutableAclProvider.php b/Acl/Dbal/MutableAclProvider.php index b871443..40ed471 100644 --- a/Acl/Dbal/MutableAclProvider.php +++ b/Acl/Dbal/MutableAclProvider.php @@ -433,7 +433,7 @@ class MutableAclProvider extends AclProvider implements MutableAclProviderInterf */ protected function getInsertAccessControlEntrySql($classId, $objectIdentityId, $field, $aceOrder, $securityIdentityId, $strategy, $mask, $granting, $auditSuccess, $auditFailure) { - $query = <<<QUERY + $query = <<<'QUERY' INSERT INTO %s ( class_id, object_identity_id, @@ -510,7 +510,7 @@ QUERY; */ protected function getInsertObjectIdentitySql($identifier, $classId, $entriesInheriting) { - $query = <<<QUERY + $query = <<<'QUERY' INSERT INTO %s (class_id, object_identifier, entries_inheriting) VALUES (%d, %s, %s) QUERY; @@ -856,7 +856,7 @@ QUERY; */ private function updateNewAceProperty($name, array $changes) { - list($old, $new) = $changes; + list(, $new) = $changes; $sids = new \SplObjectStorage(); $classIds = new \SplObjectStorage(); diff --git a/Core/Authorization/Voter/VoterInterface.php b/Core/Authorization/Voter/VoterInterface.php index d00ff1c..1032cb2 100644 --- a/Core/Authorization/Voter/VoterInterface.php +++ b/Core/Authorization/Voter/VoterInterface.php @@ -27,7 +27,7 @@ interface VoterInterface /** * Checks if the voter supports the given attribute. * - * @param string $attribute An attribute + * @param mixed $attribute An attribute (usually the attribute name string) * * @return bool true if this Voter supports the attribute, false otherwise */ diff --git a/Core/Encoder/BCryptPasswordEncoder.php b/Core/Encoder/BCryptPasswordEncoder.php index d2b0319..83ae334 100644 --- a/Core/Encoder/BCryptPasswordEncoder.php +++ b/Core/Encoder/BCryptPasswordEncoder.php @@ -19,6 +19,8 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException; */ class BCryptPasswordEncoder extends BasePasswordEncoder { + const MAX_PASSWORD_LENGTH = 72; + /** * @var string */ diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php index 1c9ada1..fcf2e47 100644 --- a/Core/Encoder/BasePasswordEncoder.php +++ b/Core/Encoder/BasePasswordEncoder.php @@ -95,6 +95,6 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface */ protected function isPasswordTooLong($password) { - return strlen($password) > self::MAX_PASSWORD_LENGTH; + return strlen($password) > static::MAX_PASSWORD_LENGTH; } } diff --git a/Core/User/InMemoryUserProvider.php b/Core/User/InMemoryUserProvider.php index 9aa39ca..c1981de 100644 --- a/Core/User/InMemoryUserProvider.php +++ b/Core/User/InMemoryUserProvider.php @@ -97,7 +97,7 @@ class InMemoryUserProvider implements UserProviderInterface /** * Returns the user by given username. * - * @param string $username The username. + * @param string $username The username. * * @return User * diff --git a/Core/User/UserInterface.php b/Core/User/UserInterface.php index 1b52dab..7478842 100644 --- a/Core/User/UserInterface.php +++ b/Core/User/UserInterface.php @@ -47,7 +47,7 @@ interface UserInterface * and populated in any number of different ways when the user object * is created. * - * @return Role[] The user roles + * @return (Role|string)[] The user roles */ public function getRoles(); diff --git a/Core/Util/SecureRandom.php b/Core/Util/SecureRandom.php index 3461b4e..478f556 100644 --- a/Core/Util/SecureRandom.php +++ b/Core/Util/SecureRandom.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Core\Util; -use Psr\Log\LoggerInterface; - /** * A secure random number generator implementation. * @@ -21,98 +19,11 @@ use Psr\Log\LoggerInterface; */ 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; - - $isUnsupportedPhp = '\\' === DIRECTORY_SEPARATOR && PHP_VERSION_ID < 50304; - - // determine whether to use OpenSSL - if (!function_exists('random_bytes') && ($isUnsupportedPhp || !function_exists('openssl_random_pseudo_bytes'))) { - if (null !== $this->logger) { - $this->logger->notice('It is recommended that you install the "paragonie/random_compat" library or enable the "openssl" extension for random number generation.'); - } - $this->useOpenSsl = false; - } else { - $this->useOpenSsl = true; - } - } - /** * {@inheritdoc} */ public function nextBytes($nbBytes) { - if (function_exists('random_bytes')) { - return random_bytes($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; + return random_bytes($nbBytes); } } diff --git a/Http/EntryPoint/AuthenticationEntryPointInterface.php b/Http/EntryPoint/AuthenticationEntryPointInterface.php index 0d7595d..c8e43e5 100644 --- a/Http/EntryPoint/AuthenticationEntryPointInterface.php +++ b/Http/EntryPoint/AuthenticationEntryPointInterface.php @@ -24,7 +24,17 @@ use Symfony\Component\HttpFoundation\Response; interface AuthenticationEntryPointInterface { /** - * Starts the authentication scheme. + * Returns a response that directs the user to authenticate. + * + * This is called when an anonymous request accesses a resource that + * requires authentication. The job of this method is to return some + * response that "helps" the user start into the authentication process. + * + * Examples: + * A) For a form login, you might redirect to the login page + * return new RedirectResponse('/login'); + * B) For an API token authentication system, you return a 401 response + * return new Response('Auth header required', 401); * * @param Request $request The request that resulted in an AuthenticationException * @param AuthenticationException $authException The exception that started the authentication process diff --git a/Http/Firewall/DigestAuthenticationListener.php b/Http/Firewall/DigestAuthenticationListener.php index a88250b..4e57667 100644 --- a/Http/Firewall/DigestAuthenticationListener.php +++ b/Http/Firewall/DigestAuthenticationListener.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Firewall; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Core\Util\StringUtils; use Symfony\Component\Security\Http\EntryPoint\DigestAuthenticationEntryPoint; use Psr\Log\LoggerInterface; use Symfony\Component\HttpKernel\Event\GetResponseEvent; @@ -99,7 +100,7 @@ class DigestAuthenticationListener implements ListenerInterface return; } - if ($serverDigestMd5 !== $digestAuth->getResponse()) { + if (!StringUtils::equals($serverDigestMd5, $digestAuth->getResponse())) { if (null !== $this->logger) { $this->logger->debug(sprintf('Expected response: "%s" but received: "%s"; is AuthenticationDao returning clear text passwords?', $serverDigestMd5, $digestAuth->getResponse())); } @@ -171,10 +172,8 @@ class DigestData throw new BadCredentialsException(sprintf('Missing mandatory digest value; received header "%s" (%s)', $this->header, implode(', ', $keys))); } - if ('auth' === $this->elements['qop']) { - if (!isset($this->elements['nc']) || !isset($this->elements['cnonce'])) { - throw new BadCredentialsException(sprintf('Missing mandatory digest value; received header "%s"', $this->header)); - } + if ('auth' === $this->elements['qop'] && !isset($this->elements['nc'], $this->elements['cnonce'])) { + throw new BadCredentialsException(sprintf('Missing mandatory digest value; received header "%s"', $this->header)); } if ($expectedRealm !== $this->elements['realm']) { diff --git a/Http/Firewall/RememberMeListener.php b/Http/Firewall/RememberMeListener.php index 942e537..52a231c 100644 --- a/Http/Firewall/RememberMeListener.php +++ b/Http/Firewall/RememberMeListener.php @@ -20,6 +20,7 @@ use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; use Symfony\Component\Security\Http\SecurityEvents; use Symfony\Component\EventDispatcher\EventDispatcherInterface; +use Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy; /** * RememberMeListener implements authentication capabilities via a cookie. @@ -33,6 +34,7 @@ class RememberMeListener implements ListenerInterface private $authenticationManager; private $logger; private $dispatcher; + private $sessionStrategy; /** * Constructor. @@ -50,6 +52,7 @@ class RememberMeListener implements ListenerInterface $this->authenticationManager = $authenticationManager; $this->logger = $logger; $this->dispatcher = $dispatcher; + $this->sessionStrategy = new SessionAuthenticationStrategy(SessionAuthenticationStrategy::MIGRATE); } /** @@ -70,6 +73,11 @@ class RememberMeListener implements ListenerInterface try { $token = $this->authenticationManager->authenticate($token); + + if ($request->hasSession() && $request->getSession()->isStarted()) { + $this->sessionStrategy->onAuthentication($request, $token); + } + $this->securityContext->setToken($token); if (null !== $this->dispatcher) { diff --git a/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/Http/RememberMe/PersistentTokenBasedRememberMeServices.php index f800668..0fffbfe 100644 --- a/Http/RememberMe/PersistentTokenBasedRememberMeServices.php +++ b/Http/RememberMe/PersistentTokenBasedRememberMeServices.php @@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Util\SecureRandomInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\Security\Core\Util\StringUtils; /** * Concrete implementation of the RememberMeServicesInterface which needs @@ -90,7 +91,7 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices list($series, $tokenValue) = $cookieParts; $persistentToken = $this->tokenProvider->loadTokenBySeries($series); - if ($persistentToken->getTokenValue() !== $tokenValue) { + if (!StringUtils::equals($persistentToken->getTokenValue(), $tokenValue)) { throw new CookieTheftException('This token was already used. The account is possibly compromised.'); } diff --git a/Http/RememberMe/TokenBasedRememberMeServices.php b/Http/RememberMe/TokenBasedRememberMeServices.php index de662fb..1aea5fd 100644 --- a/Http/RememberMe/TokenBasedRememberMeServices.php +++ b/Http/RememberMe/TokenBasedRememberMeServices.php @@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Exception\AuthenticationException; use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\Util\StringUtils; /** * Concrete implementation of the RememberMeServicesInterface providing @@ -53,7 +54,7 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices throw new \RuntimeException(sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', get_class($user))); } - if (true !== $this->compareHashes($hash, $this->generateCookieHash($class, $username, $expires, $user->getPassword()))) { + if (!StringUtils::equals($this->generateCookieHash($class, $username, $expires, $user->getPassword()), $hash)) { throw new AuthenticationException('The cookie\'s hash is invalid.'); } @@ -65,31 +66,6 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices } /** - * Compares two hashes using a constant-time algorithm to avoid (remote) - * timing attacks. - * - * This is the same implementation as used in the BasePasswordEncoder. - * - * @param string $hash1 The first hash - * @param string $hash2 The second hash - * - * @return bool true if the two hashes are the same, false otherwise - */ - private function compareHashes($hash1, $hash2) - { - if (strlen($hash1) !== $c = strlen($hash2)) { - return false; - } - - $result = 0; - for ($i = 0; $i < $c; ++$i) { - $result |= ord($hash1[$i]) ^ ord($hash2[$i]); - } - - return 0 === $result; - } - - /** * {@inheritdoc} */ protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token) @@ -1,4 +1,4 @@ -Copyright (c) 2004-2015 Fabien Potencier +Copyright (c) 2004-2016 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 @@ -1,23 +1,18 @@ Security Component ================== -Security provides an infrastructure for sophisticated authorization systems, -which makes it possible to easily separate the actual authorization logic from -so called user providers that hold the users credentials. It is inspired by -the Java Spring framework. +The Security component provides a complete security system for your web +application. It ships with facilities for authenticating using HTTP basic or +digest authentication, interactive form login or X.509 certificate login, but +also allows you to implement your own authentication strategies. Furthermore, +the component provides ways to authorize authenticated users based on their +roles, and it contains an advanced ACL system. Resources --------- -Documentation: - -https://symfony.com/doc/2.3/book/security.html - -Resources ---------- - -You can run the unit tests with the following command: - - $ cd path/to/Symfony/Component/Security/ - $ composer install - $ phpunit + * [Documentation](https://symfony.com/doc/current/components/security/index.html) + * [Contributing](https://symfony.com/doc/current/contributing/index.html) + * [Report issues](https://github.com/symfony/symfony/issues) and + [send Pull Requests](https://github.com/symfony/symfony/pulls) + in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/Resources/translations/security.no.xlf b/Resources/translations/security.no.xlf index 3369d43..3635916 100644 --- a/Resources/translations/security.no.xlf +++ b/Resources/translations/security.no.xlf @@ -4,7 +4,7 @@ <body> <trans-unit id="1"> <source>An authentication exception occurred.</source> - <target>En autentiserings feil har skjedd.</target> + <target>En autentiseringsfeil har skjedd.</target> </trans-unit> <trans-unit id="2"> <source>Authentication credentials could not be found.</source> @@ -24,7 +24,7 @@ </trans-unit> <trans-unit id="6"> <source>Not privileged to request the resource.</source> - <target>Ingen tilgang til å be om gitt kilde.</target> + <target>Ingen tilgang til å be om gitt ressurs.</target> </trans-unit> <trans-unit id="7"> <source>Invalid CSRF token.</source> diff --git a/Tests/Core/Authentication/Token/AbstractTokenTest.php b/Tests/Core/Authentication/Token/AbstractTokenTest.php index b8be628..efdad4c 100644 --- a/Tests/Core/Authentication/Token/AbstractTokenTest.php +++ b/Tests/Core/Authentication/Token/AbstractTokenTest.php @@ -85,10 +85,6 @@ class AbstractTokenTest extends \PHPUnit_Framework_TestCase $token->eraseCredentials(); } - /** - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::serialize - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::unserialize - */ public function testSerialize() { $token = $this->getToken(array('ROLE_FOO')); @@ -114,9 +110,6 @@ class AbstractTokenTest extends \PHPUnit_Framework_TestCase ); } - /** - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::__construct - */ public function testConstructor() { $token = $this->getToken(array('ROLE_FOO')); @@ -129,10 +122,6 @@ class AbstractTokenTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array(new Role('ROLE_FOO'), new Role('ROLE_BAR')), $token->getRoles()); } - /** - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::isAuthenticated - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::setAuthenticated - */ public function testAuthenticatedFlag() { $token = $this->getToken(); @@ -145,13 +134,6 @@ class AbstractTokenTest extends \PHPUnit_Framework_TestCase $this->assertFalse($token->isAuthenticated()); } - /** - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::getAttributes - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::setAttributes - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::hasAttribute - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::getAttribute - * @covers Symfony\Component\Security\Core\Authentication\Token\AbstractToken::setAttribute - */ public function testAttributes() { $attributes = array('foo' => 'bar'); diff --git a/Tests/Core/Encoder/BCryptPasswordEncoderTest.php b/Tests/Core/Encoder/BCryptPasswordEncoderTest.php index 076d954..9894c6f 100644 --- a/Tests/Core/Encoder/BCryptPasswordEncoderTest.php +++ b/Tests/Core/Encoder/BCryptPasswordEncoderTest.php @@ -73,13 +73,18 @@ class BCryptPasswordEncoderTest extends \PHPUnit_Framework_TestCase { $encoder = new BCryptPasswordEncoder(self::VALID_COST); - $encoder->encodePassword(str_repeat('a', 5000), 'salt'); + $encoder->encodePassword(str_repeat('a', 73), 'salt'); } + /** + * @requires PHP 5.3.7 + */ public function testCheckPasswordLength() { $encoder = new BCryptPasswordEncoder(self::VALID_COST); + $result = $encoder->encodePassword(str_repeat('a', 72), null); - $this->assertFalse($encoder->isPasswordValid('encoded', str_repeat('a', 5000), 'salt')); + $this->assertFalse($encoder->isPasswordValid($result, str_repeat('a', 73), 'salt')); + $this->assertTrue($encoder->isPasswordValid($result, str_repeat('a', 72), 'salt')); } } diff --git a/Tests/Core/Encoder/EncoderFactoryTest.php b/Tests/Core/Encoder/EncoderFactoryTest.php index 85d4e91..4fe60ad 100644 --- a/Tests/Core/Encoder/EncoderFactoryTest.php +++ b/Tests/Core/Encoder/EncoderFactoryTest.php @@ -85,15 +85,19 @@ class SomeUser implements UserInterface public function getRoles() { } + public function getPassword() { } + public function getSalt() { } + public function getUsername() { } + public function eraseCredentials() { } diff --git a/Tests/Core/SecurityContextTest.php b/Tests/Core/SecurityContextTest.php index 6695889..3fba8d9 100644 --- a/Tests/Core/SecurityContextTest.php +++ b/Tests/Core/SecurityContextTest.php @@ -92,6 +92,6 @@ class SecurityContextTest extends \PHPUnit_Framework_TestCase public function testTranslationsAreNotInCore() { - $this->assertFalse(file_exists(__DIR__.'/../../Core/Resources/translations/')); + $this->assertFileNotExists(__DIR__.'/../../Core/Resources/translations/'); } } diff --git a/Tests/Core/User/UserTest.php b/Tests/Core/User/UserTest.php index d05f491..eb21503 100644 --- a/Tests/Core/User/UserTest.php +++ b/Tests/Core/User/UserTest.php @@ -16,7 +16,6 @@ use Symfony\Component\Security\Core\User\User; class UserTest extends \PHPUnit_Framework_TestCase { /** - * @covers Symfony\Component\Security\Core\User\User::__construct * @expectedException \InvalidArgumentException */ public function testConstructorException() @@ -24,10 +23,6 @@ class UserTest extends \PHPUnit_Framework_TestCase new User('', 'superpass'); } - /** - * @covers Symfony\Component\Security\Core\User\User::__construct - * @covers Symfony\Component\Security\Core\User\User::getRoles - */ public function testGetRoles() { $user = new User('fabien', 'superpass'); @@ -37,38 +32,24 @@ class UserTest extends \PHPUnit_Framework_TestCase $this->assertEquals(array('ROLE_ADMIN'), $user->getRoles()); } - /** - * @covers Symfony\Component\Security\Core\User\User::__construct - * @covers Symfony\Component\Security\Core\User\User::getPassword - */ public function testGetPassword() { $user = new User('fabien', 'superpass'); $this->assertEquals('superpass', $user->getPassword()); } - /** - * @covers Symfony\Component\Security\Core\User\User::__construct - * @covers Symfony\Component\Security\Core\User\User::getUsername - */ public function testGetUsername() { $user = new User('fabien', 'superpass'); $this->assertEquals('fabien', $user->getUsername()); } - /** - * @covers Symfony\Component\Security\Core\User\User::getSalt - */ public function testGetSalt() { $user = new User('fabien', 'superpass'); $this->assertEquals('', $user->getSalt()); } - /** - * @covers Symfony\Component\Security\Core\User\User::isAccountNonExpired - */ public function testIsAccountNonExpired() { $user = new User('fabien', 'superpass'); @@ -78,9 +59,6 @@ class UserTest extends \PHPUnit_Framework_TestCase $this->assertFalse($user->isAccountNonExpired()); } - /** - * @covers Symfony\Component\Security\Core\User\User::isCredentialsNonExpired - */ public function testIsCredentialsNonExpired() { $user = new User('fabien', 'superpass'); @@ -90,9 +68,6 @@ class UserTest extends \PHPUnit_Framework_TestCase $this->assertFalse($user->isCredentialsNonExpired()); } - /** - * @covers Symfony\Component\Security\Core\User\User::isAccountNonLocked - */ public function testIsAccountNonLocked() { $user = new User('fabien', 'superpass'); @@ -102,9 +77,6 @@ class UserTest extends \PHPUnit_Framework_TestCase $this->assertFalse($user->isAccountNonLocked()); } - /** - * @covers Symfony\Component\Security\Core\User\User::isEnabled - */ public function testIsEnabled() { $user = new User('fabien', 'superpass'); @@ -114,9 +86,6 @@ class UserTest extends \PHPUnit_Framework_TestCase $this->assertFalse($user->isEnabled()); } - /** - * @covers Symfony\Component\Security\Core\User\User::eraseCredentials - */ public function testEraseCredentials() { $user = new User('fabien', 'superpass'); diff --git a/Tests/Core/Util/SecureRandomTest.php b/Tests/Core/Util/SecureRandomTest.php deleted file mode 100644 index 9fd1c16..0000000 --- a/Tests/Core/Util/SecureRandomTest.php +++ /dev/null @@ -1,201 +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\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]]; - } - - $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]; - }; - - $currentRun = 0; - $lastBit = null; - for ($i = 0; $i < 20000; ++$i) { - if ($lastBit === $b[$i]) { - ++$currentRun; - } 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 = $currentRun = 0; - $lastBit = null; - for ($i = 0; $i < 20000; ++$i) { - if ($lastBit === $b[$i]) { - ++$currentRun; - } 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/Http/Firewall/RememberMeListenerTest.php b/Tests/Http/Firewall/RememberMeListenerTest.php index 067cacb..8316a8c 100644 --- a/Tests/Http/Firewall/RememberMeListenerTest.php +++ b/Tests/Http/Firewall/RememberMeListenerTest.php @@ -138,6 +138,69 @@ class RememberMeListenerTest extends \PHPUnit_Framework_TestCase $listener->handle($event); } + public function testSessionStrategy() + { + list($listener, $tokenStorage, $service, $manager) = $this->getListener(); + + $tokenStorage + ->expects($this->once()) + ->method('getToken') + ->will($this->returnValue(null)) + ; + + $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + $service + ->expects($this->once()) + ->method('autoLogin') + ->will($this->returnValue($token)) + ; + + $tokenStorage + ->expects($this->once()) + ->method('setToken') + ->with($this->equalTo($token)) + ; + + $manager + ->expects($this->once()) + ->method('authenticate') + ->will($this->returnValue($token)) + ; + + $session = $this->getMock('\Symfony\Component\HttpFoundation\Session\SessionInterface'); + $session + ->expects($this->once()) + ->method('isStarted') + ->will($this->returnValue(true)) + ; + $session + ->expects($this->once()) + ->method('migrate') + ; + + $request = $this->getMock('\Symfony\Component\HttpFoundation\Request'); + $request + ->expects($this->any()) + ->method('hasSession') + ->will($this->returnValue(true)) + ; + + $request + ->expects($this->any()) + ->method('getSession') + ->will($this->returnValue($session)) + ; + + $event = $this->getGetResponseEvent(); + $event + ->expects($this->once()) + ->method('getRequest') + ->will($this->returnValue($request)) + ; + + $listener->handle($event); + } + protected function getGetResponseEvent() { return $this->getMock('Symfony\Component\HttpKernel\Event\GetResponseEvent', array(), array(), '', false); diff --git a/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php b/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php index 61c3559..3ba8f99 100644 --- a/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php +++ b/Tests/Http/RememberMe/PersistentTokenBasedRememberMeServicesTest.php @@ -24,6 +24,15 @@ use Symfony\Component\Security\Core\Util\SecureRandom; class PersistentTokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase { + public static function setUpBeforeClass() + { + try { + random_bytes(1); + } catch (\Exception $e) { + throw new \PHPUnit_Framework_SkippedTestError($e->getMessage()); + } + } + public function testAutoLoginReturnsNullWhenNoCookie() { $service = $this->getService(null, array('name' => 'foo')); diff --git a/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php b/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php index b988c7d..d1ec9b2 100644 --- a/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php +++ b/Tests/Http/RememberMe/TokenBasedRememberMeServicesTest.php @@ -172,9 +172,8 @@ class TokenBasedRememberMeServicesTest extends \PHPUnit_Framework_TestCase { $service = $this->getService(null, array('name' => 'foo', 'path' => '/foo', 'domain' => 'foodomain.foo')); $request = new Request(); - $response = new Response(); - $service->loginFail($request, $response); + $service->loginFail($request); $cookie = $request->attributes->get(RememberMeServicesInterface::COOKIE_ATTR_NAME); $this->assertTrue($cookie->isCleared()); diff --git a/Tests/Resources/TranslationFilesTest.php b/Tests/Resources/TranslationFilesTest.php new file mode 100644 index 0000000..341ec87 --- /dev/null +++ b/Tests/Resources/TranslationFilesTest.php @@ -0,0 +1,31 @@ +<?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\Resources; + +class TranslationFilesTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider provideTranslationFiles + */ + public function testTranslationFileIsValid($filePath) + { + \PHPUnit_Util_XML::loadfile($filePath, false, false, true); + } + + public function provideTranslationFiles() + { + return array_map( + function ($filePath) { return (array) $filePath; }, + glob(dirname(dirname(__DIR__)).'/Resources/translations/*.xlf') + ); + } +} diff --git a/composer.json b/composer.json index bc15cc8..4f265fe 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ ], "require": { "php": ">=5.3.3", + "paragonie/random_compat": "~1.0", "symfony/event-dispatcher": "~2.2", "symfony/http-foundation": "~2.1", "symfony/http-kernel": "~2.1" @@ -43,11 +44,13 @@ "symfony/validator": "", "symfony/routing": "", "doctrine/dbal": "to use the built-in ACL implementation", - "ircmaxell/password-compat": "", - "paragonie/random_compat": "" + "ircmaxell/password-compat": "" }, "autoload": { - "psr-0": { "Symfony\\Component\\Security\\": "" } + "psr-0": { "Symfony\\Component\\Security\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "target-dir": "Symfony/Component/Security", "minimum-stability": "dev", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9a20f91..9d3cf4a 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -9,6 +9,7 @@ <php> <ini name="error_reporting" value="-1" /> </php> + <testsuites> <testsuite name="Symfony Security Component Test Suite"> <directory>./Tests/</directory> @@ -19,8 +20,10 @@ <whitelist> <directory>./</directory> <exclude> - <directory>./vendor</directory> + <directory>./Acl/Resources</directory> + <directory>./Resources</directory> <directory>./Tests</directory> + <directory>./vendor</directory> </exclude> </whitelist> </filter> |