diff options
Diffstat (limited to 'Core')
44 files changed, 864 insertions, 83 deletions
diff --git a/Core/Authentication/AuthenticationProviderManager.php b/Core/Authentication/AuthenticationProviderManager.php index 7ca46c0..8b7474b 100644 --- a/Core/Authentication/AuthenticationProviderManager.php +++ b/Core/Authentication/AuthenticationProviderManager.php @@ -39,6 +39,8 @@ class AuthenticationProviderManager implements AuthenticationManagerInterface * * @param AuthenticationProviderInterface[] $providers An array of AuthenticationProviderInterface instances * @param Boolean $eraseCredentials Whether to erase credentials after authentication or not + * + * @throws \InvalidArgumentException */ public function __construct(array $providers, $eraseCredentials = true) { @@ -75,7 +77,7 @@ class AuthenticationProviderManager implements AuthenticationManagerInterface break; } } catch (AccountStatusException $e) { - $e->setExtraInformation($token); + $e->setToken($token); throw $e; } catch (AuthenticationException $e) { @@ -103,7 +105,7 @@ class AuthenticationProviderManager implements AuthenticationManagerInterface $this->eventDispatcher->dispatch(AuthenticationEvents::AUTHENTICATION_FAILURE, new AuthenticationFailureEvent($token, $lastException)); } - $lastException->setExtraInformation($token); + $lastException->setToken($token); throw $lastException; } diff --git a/Core/Authentication/Provider/DaoAuthenticationProvider.php b/Core/Authentication/Provider/DaoAuthenticationProvider.php index f22045f..a9a2205 100644 --- a/Core/Authentication/Provider/DaoAuthenticationProvider.php +++ b/Core/Authentication/Provider/DaoAuthenticationProvider.php @@ -88,9 +88,12 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider return $user; } catch (UsernameNotFoundException $notFound) { + $notFound->setUsername($username); throw $notFound; } catch (\Exception $repositoryProblem) { - throw new AuthenticationServiceException($repositoryProblem->getMessage(), $token, 0, $repositoryProblem); + $ex = new AuthenticationServiceException($repositoryProblem->getMessage(), 0, $repositoryProblem); + $ex->setToken($token); + throw $ex; } } } diff --git a/Core/Authentication/Provider/UserAuthenticationProvider.php b/Core/Authentication/Provider/UserAuthenticationProvider.php index 32d7971..626f50b 100644 --- a/Core/Authentication/Provider/UserAuthenticationProvider.php +++ b/Core/Authentication/Provider/UserAuthenticationProvider.php @@ -37,6 +37,8 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter * @param UserCheckerInterface $userChecker An UserCheckerInterface interface * @param string $providerKey A provider key * @param Boolean $hideUserNotFoundExceptions Whether to hide user not found exception or not + * + * @throws \InvalidArgumentException */ public function __construct(UserCheckerInterface $userChecker, $providerKey, $hideUserNotFoundExceptions = true) { @@ -69,6 +71,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter if ($this->hideUserNotFoundExceptions) { throw new BadCredentialsException('Bad credentials', 0, $notFound); } + $notFound->setUsername($username); throw $notFound; } diff --git a/Core/Authentication/RememberMe/PersistentToken.php b/Core/Authentication/RememberMe/PersistentToken.php index 88b0413..f3f6858 100644 --- a/Core/Authentication/RememberMe/PersistentToken.php +++ b/Core/Authentication/RememberMe/PersistentToken.php @@ -32,6 +32,8 @@ final class PersistentToken implements PersistentTokenInterface * @param string $series * @param string $tokenValue * @param \DateTime $lastUsed + * + * @throws \InvalidArgumentException */ public function __construct($class, $username, $series, $tokenValue, \DateTime $lastUsed) { diff --git a/Core/Authentication/Token/AbstractToken.php b/Core/Authentication/Token/AbstractToken.php index ed6e8de..f21aa76 100644 --- a/Core/Authentication/Token/AbstractToken.php +++ b/Core/Authentication/Token/AbstractToken.php @@ -33,7 +33,9 @@ abstract class AbstractToken implements TokenInterface /** * Constructor. * - * @param Role[] $roles An array of roles + * @param RoleInterface[] $roles An array of roles + * + * @throws \InvalidArgumentException */ public function __construct(array $roles = array()) { diff --git a/Core/Authentication/Token/AnonymousToken.php b/Core/Authentication/Token/AnonymousToken.php index ecdd4cc..9b0a084 100644 --- a/Core/Authentication/Token/AnonymousToken.php +++ b/Core/Authentication/Token/AnonymousToken.php @@ -24,9 +24,9 @@ class AnonymousToken extends AbstractToken /** * Constructor. * - * @param string $key The key shared with the authentication provider - * @param string $user The user - * @param Role[] $roles An array of roles + * @param string $key The key shared with the authentication provider + * @param string $user The user + * @param RoleInterface[] $roles An array of roles */ public function __construct($key, $user, array $roles = array()) { @@ -66,9 +66,9 @@ class AnonymousToken extends AbstractToken /** * {@inheritDoc} */ - public function unserialize($str) + public function unserialize($serialized) { - list($this->key, $parentStr) = unserialize($str); + list($this->key, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Authentication/Token/RememberMeToken.php b/Core/Authentication/Token/RememberMeToken.php index de50e5c..6f3d821 100644 --- a/Core/Authentication/Token/RememberMeToken.php +++ b/Core/Authentication/Token/RememberMeToken.php @@ -29,6 +29,8 @@ class RememberMeToken extends AbstractToken * @param UserInterface $user * @param string $providerKey * @param string $key + * + * @throws \InvalidArgumentException */ public function __construct(UserInterface $user, $providerKey, $key) { diff --git a/Core/Authentication/Token/TokenInterface.php b/Core/Authentication/Token/TokenInterface.php index dec31d5..11f69da 100644 --- a/Core/Authentication/Token/TokenInterface.php +++ b/Core/Authentication/Token/TokenInterface.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authentication\Token; +use Symfony\Component\Security\Core\Role\RoleInterface; + /** * TokenInterface is the interface for the user authentication information. * @@ -31,7 +33,7 @@ interface TokenInterface extends \Serializable /** * Returns the user roles. * - * @return Role[] An array of Role instances. + * @return RoleInterface[] An array of RoleInterface instances. */ public function getRoles(); diff --git a/Core/Authentication/Token/UsernamePasswordToken.php b/Core/Authentication/Token/UsernamePasswordToken.php index 95eec54..d6e3998 100644 --- a/Core/Authentication/Token/UsernamePasswordToken.php +++ b/Core/Authentication/Token/UsernamePasswordToken.php @@ -24,10 +24,10 @@ class UsernamePasswordToken extends AbstractToken /** * Constructor. * - * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. - * @param string $credentials This usually is the password of the user - * @param string $providerKey The provider key - * @param array $roles An array of roles + * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. + * @param string $credentials This usually is the password of the user + * @param string $providerKey The provider key + * @param RoleInterface[] $roles An array of roles * * @throws \InvalidArgumentException */ @@ -78,14 +78,20 @@ class UsernamePasswordToken extends AbstractToken $this->credentials = null; } + /** + * {@inheritdoc} + */ public function serialize() { return serialize(array($this->credentials, $this->providerKey, parent::serialize())); } - public function unserialize($str) + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { - list($this->credentials, $this->providerKey, $parentStr) = unserialize($str); + list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Authorization/AccessDecisionManager.php b/Core/Authorization/AccessDecisionManager.php index a8bb5cf..6028c42 100644 --- a/Core/Authorization/AccessDecisionManager.php +++ b/Core/Authorization/AccessDecisionManager.php @@ -34,6 +34,8 @@ class AccessDecisionManager implements AccessDecisionManagerInterface * @param string $strategy The vote strategy * @param Boolean $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not * @param Boolean $allowIfEqualGrantedDeniedDecisions Whether to grant access if result are equals + * + * @throws \InvalidArgumentException */ public function __construct(array $voters, $strategy = 'affirmative', $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true) { diff --git a/Core/Encoder/BCryptPasswordEncoder.php b/Core/Encoder/BCryptPasswordEncoder.php new file mode 100644 index 0000000..1b7572d --- /dev/null +++ b/Core/Encoder/BCryptPasswordEncoder.php @@ -0,0 +1,148 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder; +use Symfony\Component\Security\Core\Util\SecureRandomInterface; + +/** + * @author Elnur Abdurrakhimov <elnur@elnur.pro> + * @author Terje BrĂ¥ten <terje@braten.be> + */ +class BCryptPasswordEncoder extends BasePasswordEncoder +{ + /** + * @var SecureRandomInterface + */ + private $secureRandom; + + /** + * @var string + */ + private $cost; + + private static $prefix = null; + + /** + * Constructor. + * + * @param SecureRandomInterface $secureRandom A SecureRandomInterface instance + * @param integer $cost The algorithmic cost that should be used + * + * @throws \InvalidArgumentException if cost is out of range + */ + public function __construct(SecureRandomInterface $secureRandom, $cost) + { + $this->secureRandom = $secureRandom; + + $cost = (int) $cost; + if ($cost < 4 || $cost > 31) { + throw new \InvalidArgumentException('Cost must be in the range of 4-31.'); + } + $this->cost = sprintf('%02d', $cost); + + if (!self::$prefix) { + self::$prefix = '$'.(version_compare(phpversion(), '5.3.7', '>=') ? '2y' : '2a').'$'; + } + } + + /** + * {@inheritdoc} + */ + public function encodePassword($raw, $salt) + { + if (function_exists('password_hash')) { + return password_hash($raw, PASSWORD_BCRYPT, array('cost' => $this->cost)); + } + + $salt = self::$prefix.$this->cost.'$'.$this->encodeSalt($this->getRawSalt()); + $encoded = crypt($raw, $salt); + if (!is_string($encoded) || strlen($encoded) <= 13) { + return false; + } + + return $encoded; + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid($encoded, $raw, $salt) + { + if (function_exists('password_verify')) { + return password_verify($raw, $encoded); + } + + $crypted = crypt($raw, $encoded); + if (strlen($crypted) <= 13) { + return false; + } + + return $this->comparePasswords($encoded, $crypted); + } + + /** + * Encodes the salt to be used by Bcrypt. + * + * The blowfish/bcrypt algorithm used by PHP crypt expects a different + * set and order of characters than the usual base64_encode function. + * Regular b64: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ + * Bcrypt b64: ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 + * We care because the last character in our encoded string will + * only represent 2 bits. While two known implementations of + * bcrypt will happily accept and correct a salt string which + * has the 4 unused bits set to non-zero, we do not want to take + * chances and we also do not want to waste an additional byte + * of entropy. + * + * @param bytes $random a string of 16 random bytes + * + * @return string Properly encoded salt to use with php crypt function + * + * @throws \InvalidArgumentException if string of random bytes is too short + */ + protected function encodeSalt($random) + { + $len = strlen($random); + if ($len < 16) { + throw new \InvalidArgumentException('The bcrypt salt needs 16 random bytes.'); + } + if ($len > 16) { + $random = substr($random, 0, 16); + } + + $base64raw = str_replace('+', '.', base64_encode($random)); + $salt128bit = substr($base64raw, 0, 21); + $lastchar = substr($base64raw, 21, 1); + $lastchar = strtr($lastchar, 'AQgw', '.Oeu'); + $salt128bit .= $lastchar; + + return $salt128bit; + } + + /** + * @return bytes 16 random bytes to be used in the salt + */ + protected function getRawSalt() + { + $rawSalt = false; + $numBytes = 16; + if (function_exists('mcrypt_create_iv')) { + $rawSalt = mcrypt_create_iv($numBytes, MCRYPT_DEV_URANDOM); + } + if (!$rawSalt) { + $rawSalt = $this->secureRandom->nextBytes($numBytes); + } + + return $rawSalt; + } +} diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php index ae1c7d4..c26c9ce 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. * @@ -50,6 +52,8 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface * @param string $salt the salt to be used * * @return string a merged password and salt + * + * @throws \InvalidArgumentException */ protected function mergePasswordAndSalt($password, $salt) { @@ -77,15 +81,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/EncoderFactory.php b/Core/Encoder/EncoderFactory.php index 9429441..8bad61f 100644 --- a/Core/Encoder/EncoderFactory.php +++ b/Core/Encoder/EncoderFactory.php @@ -51,6 +51,8 @@ class EncoderFactory implements EncoderFactoryInterface * @param array $config * * @return PasswordEncoderInterface + * + * @throws \InvalidArgumentException */ private function createEncoder(array $config) { 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/Exception/AccountExpiredException.php b/Core/Exception/AccountExpiredException.php index f899b1b..a5618ce 100644 --- a/Core/Exception/AccountExpiredException.php +++ b/Core/Exception/AccountExpiredException.php @@ -15,7 +15,15 @@ namespace Symfony\Component\Security\Core\Exception; * AccountExpiredException is thrown when the user account has expired. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class AccountExpiredException extends AccountStatusException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Account has expired.'; + } } diff --git a/Core/Exception/AccountStatusException.php b/Core/Exception/AccountStatusException.php index 958f584..7819e4d 100644 --- a/Core/Exception/AccountStatusException.php +++ b/Core/Exception/AccountStatusException.php @@ -11,12 +11,57 @@ namespace Symfony\Component\Security\Core\Exception; +use Symfony\Component\Security\Core\User\UserInterface; + /** * AccountStatusException is the base class for authentication exceptions * caused by the user account status. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ abstract class AccountStatusException extends AuthenticationException { + private $user; + + /** + * Get the user. + * + * @return UserInterface + */ + public function getUser() + { + return $this->user; + } + + /** + * Set the user. + * + * @param UserInterface $user + */ + public function setUser(UserInterface $user) + { + $this->user = $user; + } + + /** + * {@inheritDoc} + */ + public function serialize() + { + return serialize(array( + $this->user, + parent::serialize(), + )); + } + + /** + * {@inheritDoc} + */ + public function unserialize($str) + { + list($this->user, $parentData) = unserialize($str); + + parent::unserialize($parentData); + } } diff --git a/Core/Exception/AuthenticationCredentialsNotFoundException.php b/Core/Exception/AuthenticationCredentialsNotFoundException.php index 16686ad..633b2be 100644 --- a/Core/Exception/AuthenticationCredentialsNotFoundException.php +++ b/Core/Exception/AuthenticationCredentialsNotFoundException.php @@ -16,7 +16,15 @@ namespace Symfony\Component\Security\Core\Exception; * because no Token is available. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class AuthenticationCredentialsNotFoundException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Authentication credentials could not be found.'; + } } diff --git a/Core/Exception/AuthenticationException.php b/Core/Exception/AuthenticationException.php index 074dad0..2b897c2 100644 --- a/Core/Exception/AuthenticationException.php +++ b/Core/Exception/AuthenticationException.php @@ -11,36 +11,42 @@ namespace Symfony\Component\Security\Core\Exception; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + /** * AuthenticationException is the base class for all authentication exceptions. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class AuthenticationException extends \RuntimeException implements \Serializable { - private $extraInformation; - - public function __construct($message, $extraInformation = null, $code = 0, \Exception $previous = null) - { - parent::__construct($message, $code, $previous); + private $token; - $this->extraInformation = $extraInformation; - } - - public function getExtraInformation() + /** + * Get the token. + * + * @return TokenInterface + */ + public function getToken() { - return $this->extraInformation; + return $this->token; } - public function setExtraInformation($extraInformation) + /** + * Set the token. + * + * @param TokenInterface $token + */ + public function setToken(TokenInterface $token) { - $this->extraInformation = $extraInformation; + $this->token = $token; } public function serialize() { return serialize(array( - $this->extraInformation, + $this->token, $this->code, $this->message, $this->file, @@ -51,11 +57,31 @@ class AuthenticationException extends \RuntimeException implements \Serializable public function unserialize($str) { list( - $this->extraInformation, + $this->token, $this->code, $this->message, $this->file, $this->line ) = unserialize($str); } + + /** + * Message key to be used by the translation component. + * + * @return string + */ + public function getMessageKey() + { + return 'An authentication exception occurred.'; + } + + /** + * Message data to be used by the translation component. + * + * @return array + */ + public function getMessageData() + { + return array(); + } } diff --git a/Core/Exception/AuthenticationServiceException.php b/Core/Exception/AuthenticationServiceException.php index 5b32d81..758a4f0 100644 --- a/Core/Exception/AuthenticationServiceException.php +++ b/Core/Exception/AuthenticationServiceException.php @@ -15,7 +15,15 @@ namespace Symfony\Component\Security\Core\Exception; * AuthenticationServiceException is thrown when an authentication request could not be processed due to a system problem. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class AuthenticationServiceException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Authentication request could not be processed due to a system problem.'; + } } diff --git a/Core/Exception/BadCredentialsException.php b/Core/Exception/BadCredentialsException.php index 2eae5b8..5deecca 100644 --- a/Core/Exception/BadCredentialsException.php +++ b/Core/Exception/BadCredentialsException.php @@ -15,11 +15,15 @@ namespace Symfony\Component\Security\Core\Exception; * BadCredentialsException is thrown when the user credentials are invalid. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class BadCredentialsException extends AuthenticationException { - public function __construct($message, $code = 0, \Exception $previous = null) + /** + * {@inheritDoc} + */ + public function getMessageKey() { - parent::__construct($message, null, $code, $previous); + return 'Invalid credentials.'; } } diff --git a/Core/Exception/CookieTheftException.php b/Core/Exception/CookieTheftException.php index 2ada78d..8d9e154 100644 --- a/Core/Exception/CookieTheftException.php +++ b/Core/Exception/CookieTheftException.php @@ -16,7 +16,15 @@ namespace Symfony\Component\Security\Core\Exception; * detects that a presented cookie has already been used by someone else. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> + * @author Alexander <iam.asm89@gmail.com> */ class CookieTheftException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Cookie has already been used by someone else.'; + } } diff --git a/Core/Exception/CredentialsExpiredException.php b/Core/Exception/CredentialsExpiredException.php index a4d42c8..b9bf2d1 100644 --- a/Core/Exception/CredentialsExpiredException.php +++ b/Core/Exception/CredentialsExpiredException.php @@ -15,7 +15,15 @@ namespace Symfony\Component\Security\Core\Exception; * CredentialsExpiredException is thrown when the user account credentials have expired. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class CredentialsExpiredException extends AccountStatusException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Credentials have expired.'; + } } diff --git a/Core/Exception/DisabledException.php b/Core/Exception/DisabledException.php index fd26221..5571ab1 100644 --- a/Core/Exception/DisabledException.php +++ b/Core/Exception/DisabledException.php @@ -15,7 +15,15 @@ namespace Symfony\Component\Security\Core\Exception; * DisabledException is thrown when the user account is disabled. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class DisabledException extends AccountStatusException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Account is disabled.'; + } } diff --git a/Core/Exception/InsufficientAuthenticationException.php b/Core/Exception/InsufficientAuthenticationException.php index bbf5517..74fc2b9 100644 --- a/Core/Exception/InsufficientAuthenticationException.php +++ b/Core/Exception/InsufficientAuthenticationException.php @@ -17,7 +17,15 @@ namespace Symfony\Component\Security\Core\Exception; * This is the case when a user is anonymous and the resource to be displayed has an access role. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class InsufficientAuthenticationException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Not privileged to request the resource.'; + } } diff --git a/Core/Exception/InvalidCsrfTokenException.php b/Core/Exception/InvalidCsrfTokenException.php index 4181bac..ce0e1f4 100644 --- a/Core/Exception/InvalidCsrfTokenException.php +++ b/Core/Exception/InvalidCsrfTokenException.php @@ -15,7 +15,15 @@ namespace Symfony\Component\Security\Core\Exception; * This exception is thrown when the csrf token is invalid. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> + * @author Alexander <iam.asm89@gmail.com> */ class InvalidCsrfTokenException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Invalid CSRF token.'; + } } diff --git a/Core/Exception/LockedException.php b/Core/Exception/LockedException.php index 6fa0b77..6532f70 100644 --- a/Core/Exception/LockedException.php +++ b/Core/Exception/LockedException.php @@ -15,7 +15,15 @@ namespace Symfony\Component\Security\Core\Exception; * LockedException is thrown if the user account is locked. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class LockedException extends AccountStatusException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Account is locked.'; + } } diff --git a/Core/Exception/NonceExpiredException.php b/Core/Exception/NonceExpiredException.php index 6a6a781..da6fba8 100644 --- a/Core/Exception/NonceExpiredException.php +++ b/Core/Exception/NonceExpiredException.php @@ -18,7 +18,15 @@ use Symfony\Component\Security\Core\Exception\AuthenticationException; * the digest nonce has expired. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class NonceExpiredException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Digest nonce has expired.'; + } } diff --git a/Core/Exception/ProviderNotFoundException.php b/Core/Exception/ProviderNotFoundException.php index e11c8aa..ea2b1fd 100644 --- a/Core/Exception/ProviderNotFoundException.php +++ b/Core/Exception/ProviderNotFoundException.php @@ -16,7 +16,15 @@ namespace Symfony\Component\Security\Core\Exception; * supports an authentication Token. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class ProviderNotFoundException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'No authentication provider found to support the authentication token.'; + } } diff --git a/Core/Exception/SessionUnavailableException.php b/Core/Exception/SessionUnavailableException.php index 519164a..4b47b18 100644 --- a/Core/Exception/SessionUnavailableException.php +++ b/Core/Exception/SessionUnavailableException.php @@ -21,7 +21,15 @@ namespace Symfony\Component\Security\Core\Exception; * request. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> + * @author Alexander <iam.asm89@gmail.com> */ class SessionUnavailableException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'No session available, it either timed out or cookies are not enabled.'; + } } diff --git a/Core/Exception/TokenNotFoundException.php b/Core/Exception/TokenNotFoundException.php index 593f3ad..fb85abf 100644 --- a/Core/Exception/TokenNotFoundException.php +++ b/Core/Exception/TokenNotFoundException.php @@ -1,5 +1,4 @@ <?php -namespace Symfony\Component\Security\Core\Exception; /* * This file is part of the Symfony package. @@ -10,11 +9,21 @@ namespace Symfony\Component\Security\Core\Exception; * file that was distributed with this source code. */ +namespace Symfony\Component\Security\Core\Exception; + /** * TokenNotFoundException is thrown if a Token cannot be found. * * @author Johannes M. Schmitt <schmittjoh@gmail.com> + * @author Alexander <iam.asm89@gmail.com> */ class TokenNotFoundException extends AuthenticationException { + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'No token could be found.'; + } } diff --git a/Core/Exception/UsernameNotFoundException.php b/Core/Exception/UsernameNotFoundException.php index 38533e7..f656bac 100644 --- a/Core/Exception/UsernameNotFoundException.php +++ b/Core/Exception/UsernameNotFoundException.php @@ -15,7 +15,58 @@ namespace Symfony\Component\Security\Core\Exception; * UsernameNotFoundException is thrown if a User cannot be found by its username. * * @author Fabien Potencier <fabien@symfony.com> + * @author Alexander <iam.asm89@gmail.com> */ class UsernameNotFoundException extends AuthenticationException { + private $username; + + /** + * {@inheritDoc} + */ + public function getMessageKey() + { + return 'Username could not be found.'; + } + + /** + * Get the username. + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Set the username. + * + * @param string $username + */ + public function setUsername($username) + { + $this->username = $username; + } + + /** + * {@inheritDoc} + */ + public function serialize() + { + return serialize(array( + $this->username, + parent::serialize(), + )); + } + + /** + * {@inheritDoc} + */ + public function unserialize($str) + { + list($this->username, $parentData) = unserialize($str); + + parent::unserialize($parentData); + } } diff --git a/Core/Role/RoleHierarchyInterface.php b/Core/Role/RoleHierarchyInterface.php index c495a7f..2ea6ca3 100644 --- a/Core/Role/RoleHierarchyInterface.php +++ b/Core/Role/RoleHierarchyInterface.php @@ -24,9 +24,9 @@ interface RoleHierarchyInterface * Reachable roles are the roles directly assigned but also all roles that * are transitively reachable from them in the role hierarchy. * - * @param array $roles An array of directly assigned roles + * @param RoleInterface[] $roles An array of directly assigned roles * - * @return array An array of all reachable roles + * @return RoleInterface[] An array of all reachable roles */ public function getReachableRoles(array $roles); } 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/User/ChainUserProvider.php b/Core/User/ChainUserProvider.php index 376ba1c..3ff1ea9 100644 --- a/Core/User/ChainUserProvider.php +++ b/Core/User/ChainUserProvider.php @@ -44,7 +44,9 @@ class ChainUserProvider implements UserProviderInterface } } - throw new UsernameNotFoundException(sprintf('There is no user with name "%s".', $username)); + $ex = new UsernameNotFoundException(sprintf('There is no user with name "%s".', $username)); + $ex->setUsername($username); + throw $ex; } /** @@ -66,7 +68,9 @@ class ChainUserProvider implements UserProviderInterface } if ($supportedUserFound) { - throw new UsernameNotFoundException(sprintf('There is no user with name "%s".', $user->getUsername())); + $ex = new UsernameNotFoundException(sprintf('There is no user with name "%s".', $user->getUsername())); + $ex->setUsername($user->getUsername()); + throw $ex; } else { throw new UnsupportedUserException(sprintf('The account "%s" is not supported.', get_class($user))); } diff --git a/Core/User/InMemoryUserProvider.php b/Core/User/InMemoryUserProvider.php index eae2083..e87f80c 100644 --- a/Core/User/InMemoryUserProvider.php +++ b/Core/User/InMemoryUserProvider.php @@ -50,6 +50,8 @@ class InMemoryUserProvider implements UserProviderInterface * Adds a new User to the provider. * * @param UserInterface $user A UserInterface instance + * + * @throws \LogicException */ public function createUser(UserInterface $user) { @@ -66,7 +68,10 @@ class InMemoryUserProvider implements UserProviderInterface public function loadUserByUsername($username) { if (!isset($this->users[strtolower($username)])) { - throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + $ex = new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + $ex->setUsername($username); + + throw $ex; } $user = $this->users[strtolower($username)]; diff --git a/Core/User/UserChecker.php b/Core/User/UserChecker.php index 93897a1..8dde3a6 100644 --- a/Core/User/UserChecker.php +++ b/Core/User/UserChecker.php @@ -33,7 +33,9 @@ class UserChecker implements UserCheckerInterface } if (!$user->isCredentialsNonExpired()) { - throw new CredentialsExpiredException('User credentials have expired.', $user); + $ex = new CredentialsExpiredException('User credentials have expired.'); + $ex->setUser($user); + throw $ex; } } @@ -47,15 +49,21 @@ class UserChecker implements UserCheckerInterface } if (!$user->isAccountNonLocked()) { - throw new LockedException('User account is locked.', $user); + $ex = new LockedException('User account is locked.'); + $ex->setUser($user); + throw $ex; } if (!$user->isEnabled()) { - throw new DisabledException('User account is disabled.', $user); + $ex = new DisabledException('User account is disabled.'); + $ex->setUser($user); + throw $ex; } if (!$user->isAccountNonExpired()) { - throw new AccountExpiredException('User account has expired.', $user); + $ex = new AccountExpiredException('User account has expired.'); + $ex->setUser($user); + throw $ex; } } } 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..841b9af --- /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 Psr\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..2c35a72 --- /dev/null +++ b/Core/Util/SecureRandomInterface.php @@ -0,0 +1,29 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\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..2e8925d --- /dev/null +++ b/Core/Util/StringUtils.php @@ -0,0 +1,60 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\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 $knownString The string of known length to compare against + * @param string $userInput The string that the user can control + * + * @return Boolean true if the two strings are the same, false otherwise + */ + public static function equals($knownString, $userInput) + { + // Prevent issues if string length is 0 + $knownString .= chr(0); + $userInput .= chr(0); + + $knownLen = strlen($knownString); + $userLen = strlen($userInput); + + // Set the result to the difference between the lengths + $result = $knownLen - $userLen; + + // Note that we ALWAYS iterate over the user-supplied length + // This is to prevent leaking length information + for ($i = 0; $i < $userLen; $i++) { + // Using % here is a trick to prevent notices + // It's safe, since if the lengths are different + // $result is already non-0 + $result |= (ord($knownString[$i % $knownLen]) ^ ord($userInput[$i])); + } + + // They are only identical strings if $result is exactly 0... + return 0 === $result; + } +} diff --git a/Core/Validator/Constraint/UserPassword.php b/Core/Validator/Constraint/UserPassword.php index 3279e02..93ca24d 100644 --- a/Core/Validator/Constraint/UserPassword.php +++ b/Core/Validator/Constraint/UserPassword.php @@ -11,17 +11,19 @@ namespace Symfony\Component\Security\Core\Validator\Constraint; -use Symfony\Component\Validator\Constraint; +use Symfony\Component\Security\Core\Validator\Constraints\UserPassword as BaseUserPassword; /** * @Annotation + * + * @deprecated Deprecated since version 2.2, to be removed in 2.3. */ -class UserPassword extends Constraint +class UserPassword extends BaseUserPassword { - public $message = 'This value should be the user current password.'; - - public function validatedBy() + public function __construct($options = null) { - return 'security.validator.user_password'; + trigger_error('UserPassword class in Symfony\Component\Security\Core\Validator\Constraint namespace is deprecated since version 2.2 and will be removed in 2.3. Use the Symfony\Component\Security\Core\Validator\Constraints\UserPassword class instead.', E_USER_DEPRECATED); + + parent::__construct($options); } } diff --git a/Core/Validator/Constraint/UserPasswordValidator.php b/Core/Validator/Constraint/UserPasswordValidator.php index a54906b..0195fe5 100644 --- a/Core/Validator/Constraint/UserPasswordValidator.php +++ b/Core/Validator/Constraint/UserPasswordValidator.php @@ -11,36 +11,19 @@ namespace Symfony\Component\Security\Core\Validator\Constraint; -use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\SecurityContextInterface; use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; -use Symfony\Component\Validator\Constraint; -use Symfony\Component\Validator\ConstraintValidator; -use Symfony\Component\Validator\Exception\ConstraintDefinitionException; +use Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator as BaseUserPasswordValidator; -class UserPasswordValidator extends ConstraintValidator +/** + * @deprecated Deprecated since version 2.2, to be removed in 2.3. + */ +class UserPasswordValidator extends BaseUserPasswordValidator { - private $securityContext; - private $encoderFactory; - public function __construct(SecurityContextInterface $securityContext, EncoderFactoryInterface $encoderFactory) { - $this->securityContext = $securityContext; - $this->encoderFactory = $encoderFactory; - } - - public function validate($password, Constraint $constraint) - { - $user = $this->securityContext->getToken()->getUser(); - - if (!$user instanceof UserInterface) { - throw new ConstraintDefinitionException('The User must extend UserInterface'); - } - - $encoder = $this->encoderFactory->getEncoder($user); + trigger_error('UserPasswordValidator class in Symfony\Component\Security\Core\Validator\Constraint namespace is deprecated since version 2.2 and will be removed in 2.3. Use the Symfony\Component\Security\Core\Validator\Constraints\UserPasswordValidator class instead.', E_USER_DEPRECATED); - if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) { - $this->context->addViolation($constraint->message); - } + parent::__construct($securityContext, $encoderFactory); } } diff --git a/Core/Validator/Constraints/UserPassword.php b/Core/Validator/Constraints/UserPassword.php new file mode 100644 index 0000000..ed29b0c --- /dev/null +++ b/Core/Validator/Constraints/UserPassword.php @@ -0,0 +1,28 @@ +<?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\Validator\Constraints; + +use Symfony\Component\Validator\Constraint; + +/** + * @Annotation + */ +class UserPassword extends Constraint +{ + public $message = 'This value should be the user current password.'; + public $service = 'security.validator.user_password'; + + public function validatedBy() + { + return $this->service; + } +} diff --git a/Core/Validator/Constraints/UserPasswordValidator.php b/Core/Validator/Constraints/UserPasswordValidator.php new file mode 100644 index 0000000..a4e0f90 --- /dev/null +++ b/Core/Validator/Constraints/UserPasswordValidator.php @@ -0,0 +1,46 @@ +<?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\Validator\Constraints; + +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\SecurityContextInterface; +use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; +use Symfony\Component\Validator\Constraint; +use Symfony\Component\Validator\ConstraintValidator; +use Symfony\Component\Validator\Exception\ConstraintDefinitionException; + +class UserPasswordValidator extends ConstraintValidator +{ + private $securityContext; + private $encoderFactory; + + public function __construct(SecurityContextInterface $securityContext, EncoderFactoryInterface $encoderFactory) + { + $this->securityContext = $securityContext; + $this->encoderFactory = $encoderFactory; + } + + public function validate($password, Constraint $constraint) + { + $user = $this->securityContext->getToken()->getUser(); + + if (!$user instanceof UserInterface) { + throw new ConstraintDefinitionException('The User object must implement the UserInterface interface.'); + } + + $encoder = $this->encoderFactory->getEncoder($user); + + if (!$encoder->isPasswordValid($user->getPassword(), $password, $user->getSalt())) { + $this->context->addViolation($constraint->message); + } + } +} |