diff options
Diffstat (limited to 'Core')
39 files changed, 812 insertions, 330 deletions
diff --git a/Core/Authentication/Provider/AnonymousAuthenticationProvider.php b/Core/Authentication/Provider/AnonymousAuthenticationProvider.php index 7fbbf85..ff3d15f 100644 --- a/Core/Authentication/Provider/AnonymousAuthenticationProvider.php +++ b/Core/Authentication/Provider/AnonymousAuthenticationProvider.php @@ -22,16 +22,22 @@ use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; */ class AnonymousAuthenticationProvider implements AuthenticationProviderInterface { - private $key; + /** + * Used to determine if the token is created by the application + * instead of a malicious client. + * + * @var string + */ + private $secret; /** * Constructor. * - * @param string $key The key shared with the authentication token + * @param string $secret The secret shared with the AnonymousToken */ - public function __construct($key) + public function __construct($secret) { - $this->key = $key; + $this->secret = $secret; } /** @@ -43,7 +49,7 @@ class AnonymousAuthenticationProvider implements AuthenticationProviderInterface return; } - if ($this->key !== $token->getKey()) { + if ($this->secret !== $token->getSecret()) { throw new BadCredentialsException('The Token does not contain the expected key.'); } diff --git a/Core/Authentication/Provider/LdapBindAuthenticationProvider.php b/Core/Authentication/Provider/LdapBindAuthenticationProvider.php new file mode 100644 index 0000000..fab7d80 --- /dev/null +++ b/Core/Authentication/Provider/LdapBindAuthenticationProvider.php @@ -0,0 +1,85 @@ +<?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\Authentication\Provider; + +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\User\UserCheckerInterface; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Ldap\LdapClientInterface; +use Symfony\Component\Ldap\Exception\ConnectionException; + +/** + * LdapBindAuthenticationProvider authenticates a user against an LDAP server. + * + * The only way to check user credentials is to try to connect the user with its + * credentials to the ldap. + * + * @author Charles Sarrazin <charles@sarraz.in> + */ +class LdapBindAuthenticationProvider extends UserAuthenticationProvider +{ + private $userProvider; + private $ldap; + private $dnString; + + /** + * Constructor. + * + * @param UserProviderInterface $userProvider A UserProvider + * @param UserCheckerInterface $userChecker A UserChecker + * @param string $providerKey The provider key + * @param LdapClientInterface $ldap An Ldap client + * @param string $dnString A string used to create the bind DN + * @param bool $hideUserNotFoundExceptions Whether to hide user not found exception or not + */ + public function __construct(UserProviderInterface $userProvider, UserCheckerInterface $userChecker, $providerKey, LdapClientInterface $ldap, $dnString = '{username}', $hideUserNotFoundExceptions = true) + { + parent::__construct($userChecker, $providerKey, $hideUserNotFoundExceptions); + + $this->userProvider = $userProvider; + $this->ldap = $ldap; + $this->dnString = $dnString; + } + + /** + * {@inheritdoc} + */ + protected function retrieveUser($username, UsernamePasswordToken $token) + { + if ('NONE_PROVIDED' === $username) { + throw new UsernameNotFoundException('Username can not be null'); + } + + return $this->userProvider->loadUserByUsername($username); + } + + /** + * {@inheritdoc} + */ + protected function checkAuthentication(UserInterface $user, UsernamePasswordToken $token) + { + $username = $token->getUsername(); + $password = $token->getCredentials(); + + try { + $username = $this->ldap->escape($username, '', LdapClientInterface::LDAP_ESCAPE_DN); + $dn = str_replace('{username}', $username, $this->dnString); + + $this->ldap->bind($dn, $password); + } catch (ConnectionException $e) { + throw new BadCredentialsException('The presented password is invalid.'); + } + } +} diff --git a/Core/Authentication/Provider/RememberMeAuthenticationProvider.php b/Core/Authentication/Provider/RememberMeAuthenticationProvider.php index 82be1d1..f0a74eb 100644 --- a/Core/Authentication/Provider/RememberMeAuthenticationProvider.php +++ b/Core/Authentication/Provider/RememberMeAuthenticationProvider.php @@ -19,20 +19,20 @@ use Symfony\Component\Security\Core\Exception\BadCredentialsException; class RememberMeAuthenticationProvider implements AuthenticationProviderInterface { private $userChecker; - private $key; + private $secret; private $providerKey; /** * Constructor. * * @param UserCheckerInterface $userChecker An UserCheckerInterface interface - * @param string $key A key - * @param string $providerKey A provider key + * @param string $secret A secret + * @param string $providerKey A provider secret */ - public function __construct(UserCheckerInterface $userChecker, $key, $providerKey) + public function __construct(UserCheckerInterface $userChecker, $secret, $providerKey) { $this->userChecker = $userChecker; - $this->key = $key; + $this->secret = $secret; $this->providerKey = $providerKey; } @@ -45,14 +45,14 @@ class RememberMeAuthenticationProvider implements AuthenticationProviderInterfac return; } - if ($this->key !== $token->getKey()) { - throw new BadCredentialsException('The presented key does not match.'); + if ($this->secret !== $token->getSecret()) { + throw new BadCredentialsException('The presented secret does not match.'); } $user = $token->getUser(); $this->userChecker->checkPreAuth($user); - $authenticatedToken = new RememberMeToken($user, $this->providerKey, $this->key); + $authenticatedToken = new RememberMeToken($user, $this->providerKey, $this->secret); $authenticatedToken->setAttributes($token->getAttributes()); return $authenticatedToken; diff --git a/Core/Authentication/SimpleFormAuthenticatorInterface.php b/Core/Authentication/SimpleFormAuthenticatorInterface.php index 95ee881..ae2b58b 100644 --- a/Core/Authentication/SimpleFormAuthenticatorInterface.php +++ b/Core/Authentication/SimpleFormAuthenticatorInterface.php @@ -14,6 +14,8 @@ namespace Symfony\Component\Security\Core\Authentication; use Symfony\Component\HttpFoundation\Request; /** + * @deprecated Deprecated since version 2.8, to be removed in 3.0. Use the same interface from Security\Http\Authentication instead. + * * @author Jordi Boggiano <j.boggiano@seld.be> */ interface SimpleFormAuthenticatorInterface extends SimpleAuthenticatorInterface diff --git a/Core/Authentication/SimplePreAuthenticatorInterface.php b/Core/Authentication/SimplePreAuthenticatorInterface.php index 6164e7d..c01f064 100644 --- a/Core/Authentication/SimplePreAuthenticatorInterface.php +++ b/Core/Authentication/SimplePreAuthenticatorInterface.php @@ -14,6 +14,8 @@ namespace Symfony\Component\Security\Core\Authentication; use Symfony\Component\HttpFoundation\Request; /** + * @deprecated Since version 2.8, to be removed in 3.0. Use the same interface from Security\Http\Authentication instead. + * * @author Jordi Boggiano <j.boggiano@seld.be> */ interface SimplePreAuthenticatorInterface extends SimpleAuthenticatorInterface diff --git a/Core/Authentication/Token/AnonymousToken.php b/Core/Authentication/Token/AnonymousToken.php index 571816c..22fc611 100644 --- a/Core/Authentication/Token/AnonymousToken.php +++ b/Core/Authentication/Token/AnonymousToken.php @@ -20,20 +20,20 @@ use Symfony\Component\Security\Core\Role\RoleInterface; */ class AnonymousToken extends AbstractToken { - private $key; + private $secret; /** * Constructor. * - * @param string $key The key shared with the authentication provider - * @param string $user The user - * @param RoleInterface[] $roles An array of roles + * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client + * @param string $user The user + * @param RoleInterface[] $roles An array of roles */ - public function __construct($key, $user, array $roles = array()) + public function __construct($secret, $user, array $roles = array()) { parent::__construct($roles); - $this->key = $key; + $this->secret = $secret; $this->setUser($user); $this->setAuthenticated(true); } @@ -47,13 +47,23 @@ class AnonymousToken extends AbstractToken } /** - * Returns the key. - * - * @return string The Key + * @deprecated Since version 2.8, to be removed in 3.0. Use getSecret() instead. */ public function getKey() { - return $this->key; + @trigger_error(__method__.'() is deprecated since version 2.8 and will be removed in 3.0. Use getSecret() instead.', E_USER_DEPRECATED); + + return $this->getSecret(); + } + + /** + * Returns the secret. + * + * @return string + */ + public function getSecret() + { + return $this->secret; } /** @@ -61,7 +71,7 @@ class AnonymousToken extends AbstractToken */ public function serialize() { - return serialize(array($this->key, parent::serialize())); + return serialize(array($this->secret, parent::serialize())); } /** @@ -69,7 +79,7 @@ class AnonymousToken extends AbstractToken */ public function unserialize($serialized) { - list($this->key, $parentStr) = unserialize($serialized); + list($this->secret, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Authentication/Token/RememberMeToken.php b/Core/Authentication/Token/RememberMeToken.php index 609fdad..60e36f2 100644 --- a/Core/Authentication/Token/RememberMeToken.php +++ b/Core/Authentication/Token/RememberMeToken.php @@ -20,7 +20,7 @@ use Symfony\Component\Security\Core\User\UserInterface; */ class RememberMeToken extends AbstractToken { - private $key; + private $secret; private $providerKey; /** @@ -28,16 +28,16 @@ class RememberMeToken extends AbstractToken * * @param UserInterface $user * @param string $providerKey - * @param string $key + * @param string $secret A secret used to make sure the token is created by the app and not by a malicious client * * @throws \InvalidArgumentException */ - public function __construct(UserInterface $user, $providerKey, $key) + public function __construct(UserInterface $user, $providerKey, $secret) { parent::__construct($user->getRoles()); - if (empty($key)) { - throw new \InvalidArgumentException('$key must not be empty.'); + if (empty($secret)) { + throw new \InvalidArgumentException('$secret must not be empty.'); } if (empty($providerKey)) { @@ -45,7 +45,7 @@ class RememberMeToken extends AbstractToken } $this->providerKey = $providerKey; - $this->key = $key; + $this->secret = $secret; $this->setUser($user); parent::setAuthenticated(true); @@ -64,9 +64,9 @@ class RememberMeToken extends AbstractToken } /** - * Returns the provider key. + * Returns the provider secret. * - * @return string The provider key + * @return string The provider secret */ public function getProviderKey() { @@ -74,13 +74,23 @@ class RememberMeToken extends AbstractToken } /** - * Returns the key. - * - * @return string The Key + * @deprecated Since version 2.8, to be removed in 3.0. Use getSecret() instead. */ public function getKey() { - return $this->key; + @trigger_error(__method__.'() is deprecated since version 2.8 and will be removed in 3.0. Use getSecret() instead.', E_USER_DEPRECATED); + + return $this->getSecret(); + } + + /** + * Returns the secret. + * + * @return string + */ + public function getSecret() + { + return $this->secret; } /** @@ -97,7 +107,7 @@ class RememberMeToken extends AbstractToken public function serialize() { return serialize(array( - $this->key, + $this->secret, $this->providerKey, parent::serialize(), )); @@ -108,7 +118,7 @@ class RememberMeToken extends AbstractToken */ public function unserialize($serialized) { - list($this->key, $this->providerKey, $parentStr) = unserialize($serialized); + list($this->secret, $this->providerKey, $parentStr) = unserialize($serialized); parent::unserialize($parentStr); } } diff --git a/Core/Authorization/AccessDecisionManager.php b/Core/Authorization/AccessDecisionManager.php index b8b6a77..7cefef1 100644 --- a/Core/Authorization/AccessDecisionManager.php +++ b/Core/Authorization/AccessDecisionManager.php @@ -41,12 +41,8 @@ class AccessDecisionManager implements AccessDecisionManagerInterface * * @throws \InvalidArgumentException */ - public function __construct(array $voters, $strategy = self::STRATEGY_AFFIRMATIVE, $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true) + public function __construct(array $voters = array(), $strategy = self::STRATEGY_AFFIRMATIVE, $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true) { - if (!$voters) { - throw new \InvalidArgumentException('You must at least add one voter.'); - } - $strategyMethod = 'decide'.ucfirst($strategy); if (!is_callable(array($this, $strategyMethod))) { throw new \InvalidArgumentException(sprintf('The strategy "%s" is not supported.', $strategy)); @@ -59,6 +55,16 @@ class AccessDecisionManager implements AccessDecisionManagerInterface } /** + * Configures the voters. + * + * @param VoterInterface[] $voters An array of VoterInterface instances + */ + public function setVoters(array $voters) + { + $this->voters = $voters; + } + + /** * {@inheritdoc} */ public function decide(TokenInterface $token, array $attributes, $object = null) @@ -71,6 +77,8 @@ class AccessDecisionManager implements AccessDecisionManagerInterface */ public function supportsAttribute($attribute) { + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); + foreach ($this->voters as $voter) { if ($voter->supportsAttribute($attribute)) { return true; @@ -85,6 +93,8 @@ class AccessDecisionManager implements AccessDecisionManagerInterface */ public function supportsClass($class) { + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); + foreach ($this->voters as $voter) { if ($voter->supportsClass($class)) { return true; @@ -144,7 +154,6 @@ class AccessDecisionManager implements AccessDecisionManagerInterface { $grant = 0; $deny = 0; - $abstain = 0; foreach ($this->voters as $voter) { $result = $voter->vote($token, $object, $attributes); @@ -158,11 +167,6 @@ class AccessDecisionManager implements AccessDecisionManagerInterface ++$deny; break; - - default: - ++$abstain; - - break; } } @@ -174,7 +178,7 @@ class AccessDecisionManager implements AccessDecisionManagerInterface return false; } - if ($grant == $deny && $grant != 0) { + if ($grant > 0) { return $this->allowIfEqualGrantedDeniedDecisions; } diff --git a/Core/Authorization/AccessDecisionManagerInterface.php b/Core/Authorization/AccessDecisionManagerInterface.php index 16209ba..d18b5e3 100644 --- a/Core/Authorization/AccessDecisionManagerInterface.php +++ b/Core/Authorization/AccessDecisionManagerInterface.php @@ -37,6 +37,8 @@ interface AccessDecisionManagerInterface * @param string $attribute An attribute * * @return bool true if this decision manager supports the attribute, false otherwise + * + * @deprecated since version 2.8, to be removed in 3.0. */ public function supportsAttribute($attribute); @@ -46,6 +48,8 @@ interface AccessDecisionManagerInterface * @param string $class A class name * * @return true if this decision manager can process the class + * + * @deprecated since version 2.8, to be removed in 3.0. */ public function supportsClass($class); } diff --git a/Core/Authorization/Voter/AbstractVoter.php b/Core/Authorization/Voter/AbstractVoter.php index efa1562..7b04222 100644 --- a/Core/Authorization/Voter/AbstractVoter.php +++ b/Core/Authorization/Voter/AbstractVoter.php @@ -26,6 +26,8 @@ abstract class AbstractVoter implements VoterInterface */ public function supportsAttribute($attribute) { + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); + return in_array($attribute, $this->getSupportedAttributes()); } @@ -34,6 +36,8 @@ abstract class AbstractVoter implements VoterInterface */ public function supportsClass($class) { + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); + foreach ($this->getSupportedClasses() as $supportedClass) { if ($supportedClass === $class || is_subclass_of($class, $supportedClass)) { return true; @@ -58,7 +62,7 @@ abstract class AbstractVoter implements VoterInterface */ public function vote(TokenInterface $token, $object, array $attributes) { - if (!$object || !$this->supportsClass(get_class($object))) { + if (!$object) { return self::ACCESS_ABSTAIN; } @@ -66,14 +70,14 @@ abstract class AbstractVoter implements VoterInterface $vote = self::ACCESS_ABSTAIN; foreach ($attributes as $attribute) { - if (!$this->supportsAttribute($attribute)) { + if (!$this->supports($attribute, $object)) { continue; } // as soon as at least one attribute is supported, default is to deny access $vote = self::ACCESS_DENIED; - if ($this->isGranted($attribute, $object, $token->getUser())) { + if ($this->voteOnAttribute($attribute, $object, $token)) { // grant access as soon as at least one voter returns a positive response return self::ACCESS_GRANTED; } @@ -83,18 +87,61 @@ abstract class AbstractVoter implements VoterInterface } /** + * Determines if the attribute and object are supported by this voter. + * + * This method will become abstract in 3.0. + * + * @param string $attribute An attribute + * @param string $object The object to secure + * + * @return bool True if the attribute and object is supported, false otherwise + */ + protected function supports($attribute, $object) + { + @trigger_error('The getSupportedClasses and getSupportedAttributes methods are deprecated since version 2.8 and will be removed in version 3.0. Overwrite supports instead.', E_USER_DEPRECATED); + + $classIsSupported = false; + foreach ($this->getSupportedClasses() as $supportedClass) { + if ($object instanceof $supportedClass) { + $classIsSupported = true; + break; + } + } + + if (!$classIsSupported) { + return false; + } + + if (!in_array($attribute, $this->getSupportedAttributes())) { + return false; + } + + return true; + } + + /** * Return an array of supported classes. This will be called by supportsClass. * * @return array an array of supported classes, i.e. array('Acme\DemoBundle\Model\Product') + * + * @deprecated since version 2.8, to be removed in 3.0. Use supports() instead. */ - abstract protected function getSupportedClasses(); + protected function getSupportedClasses() + { + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); + } /** * Return an array of supported attributes. This will be called by supportsAttribute. * * @return array an array of supported attributes, i.e. array('CREATE', 'READ') + * + * @deprecated since version 2.8, to be removed in 3.0. Use supports() instead. */ - abstract protected function getSupportedAttributes(); + protected function getSupportedAttributes() + { + @trigger_error('The '.__METHOD__.' is deprecated since version 2.8 and will be removed in version 3.0.', E_USER_DEPRECATED); + } /** * Perform a single access check operation on a given attribute, object and (optionally) user @@ -107,7 +154,33 @@ abstract class AbstractVoter implements VoterInterface * @param object $object * @param UserInterface|string $user * + * @deprecated This method will be removed in 3.0 - override voteOnAttribute instead. + * * @return bool */ - abstract protected function isGranted($attribute, $object, $user = null); + protected function isGranted($attribute, $object, $user = null) + { + // forces isGranted() or voteOnAttribute() to be overridden + throw new \BadMethodCallException(sprintf('You must override the voteOnAttribute() method in "%s".', get_class($this))); + } + + /** + * Perform a single access check operation on a given attribute, object and token. + * It is safe to assume that $attribute and $object's class pass supports method call. + * + * This method will become abstract in 3.0. + * + * @param string $attribute + * @param object $object + * @param TokenInterface $token + * + * @return bool + */ + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + // the user should override this method, and not rely on the deprecated isGranted() + @trigger_error(sprintf("The AbstractVoter::isGranted() method is deprecated since 2.8 and won't be called anymore in 3.0. Override voteOnAttribute() in %s instead.", get_class($this)), E_USER_DEPRECATED); + + return $this->isGranted($attribute, $object, $token->getUser()); + } } diff --git a/Core/Authorization/Voter/VoterInterface.php b/Core/Authorization/Voter/VoterInterface.php index d00ff1c..7e243f9 100644 --- a/Core/Authorization/Voter/VoterInterface.php +++ b/Core/Authorization/Voter/VoterInterface.php @@ -30,6 +30,8 @@ interface VoterInterface * @param string $attribute An attribute * * @return bool true if this Voter supports the attribute, false otherwise + * + * @deprecated since version 2.8, to be removed in 3.0. */ public function supportsAttribute($attribute); @@ -39,6 +41,8 @@ interface VoterInterface * @param string $class A class name * * @return bool true if this Voter can process the class + * + * @deprecated since version 2.8, to be removed in 3.0. */ public function supportsClass($class); diff --git a/Core/Encoder/BCryptPasswordEncoder.php b/Core/Encoder/BCryptPasswordEncoder.php index d2b0319..c0c8fe0 100644 --- a/Core/Encoder/BCryptPasswordEncoder.php +++ b/Core/Encoder/BCryptPasswordEncoder.php @@ -34,10 +34,6 @@ class BCryptPasswordEncoder extends BasePasswordEncoder */ public function __construct($cost) { - if (!function_exists('password_hash')) { - throw new \RuntimeException('To use the BCrypt encoder, you need to upgrade to PHP 5.5 or install the "ircmaxell/password-compat" via Composer.'); - } - $cost = (int) $cost; if ($cost < 4 || $cost > 31) { throw new \InvalidArgumentException('Cost must be in the range of 4-31.'); diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php index 1c9ada1..12126d8 100644 --- a/Core/Encoder/BasePasswordEncoder.php +++ b/Core/Encoder/BasePasswordEncoder.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Security\Core\Encoder; -use Symfony\Component\Security\Core\Util\StringUtils; - /** * BasePasswordEncoder is the base class for all password encoders. * @@ -83,7 +81,7 @@ abstract class BasePasswordEncoder implements PasswordEncoderInterface */ protected function comparePasswords($password1, $password2) { - return StringUtils::equals($password1, $password2); + return hash_equals($password1, $password2); } /** diff --git a/Core/Encoder/Pbkdf2PasswordEncoder.php b/Core/Encoder/Pbkdf2PasswordEncoder.php index 6f24c4f..8422a4b 100644 --- a/Core/Encoder/Pbkdf2PasswordEncoder.php +++ b/Core/Encoder/Pbkdf2PasswordEncoder.php @@ -64,11 +64,7 @@ class Pbkdf2PasswordEncoder extends BasePasswordEncoder 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); - } + $digest = hash_pbkdf2($this->algorithm, $raw, $salt, $this->iterations, $this->length, true); return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); } @@ -80,24 +76,4 @@ class Pbkdf2PasswordEncoder extends BasePasswordEncoder { return !$this->isPasswordTooLong($raw) && $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/AuthenticationExpiredException.php b/Core/Exception/AuthenticationExpiredException.php new file mode 100644 index 0000000..caf2e6c --- /dev/null +++ b/Core/Exception/AuthenticationExpiredException.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\Core\Exception; + +/** + * AuthenticationServiceException is thrown when an authenticated token becomes un-authentcated between requests. + * + * In practice, this is due to the User changing between requests (e.g. password changes), + * causes the token to become un-authenticated. + * + * @author Ryan Weaver <ryan@knpuniversity.com> + */ +class AuthenticationExpiredException extends AccountStatusException +{ + /** + * {@inheritdoc} + */ + public function getMessageKey() + { + return 'Authentication expired because your account information has changed.'; + } +} diff --git a/Core/Exception/CustomUserMessageAuthenticationException.php b/Core/Exception/CustomUserMessageAuthenticationException.php new file mode 100644 index 0000000..9f5071f --- /dev/null +++ b/Core/Exception/CustomUserMessageAuthenticationException.php @@ -0,0 +1,79 @@ +<?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\Exception; + +/** + * An authentication exception where you can control the message shown to the user. + * + * Be sure that the message passed to this exception is something that + * can be shown safely to your user. In other words, avoid catching + * other exceptions and passing their message directly to this class. + * + * @author Ryan Weaver <ryan@knpuniversity.com> + */ +class CustomUserMessageAuthenticationException extends AuthenticationException +{ + private $messageKey; + + private $messageData = array(); + + public function __construct($message = '', array $messageData = array(), $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->setSafeMessage($message, $messageData); + } + + /** + * Set a message that will be shown to the user. + * + * @param string $messageKey The message or message key + * @param array $messageData Data to be passed into the translator + */ + public function setSafeMessage($messageKey, array $messageData = array()) + { + $this->messageKey = $messageKey; + $this->messageData = $messageData; + } + + public function getMessageKey() + { + return $this->messageKey; + } + + public function getMessageData() + { + return $this->messageData; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array( + parent::serialize(), + $this->messageKey, + $this->messageData, + )); + } + + /** + * {@inheritdoc} + */ + public function unserialize($str) + { + list($parentData, $this->messageKey, $this->messageData) = unserialize($str); + + parent::unserialize($parentData); + } +} diff --git a/Core/README.md b/Core/README.md index b0d1749..f1da5b1 100644 --- a/Core/README.md +++ b/Core/README.md @@ -11,7 +11,7 @@ Resources Documentation: -https://symfony.com/doc/2.7/book/security.html +https://symfony.com/doc/2.8/book/security.html Tests ----- diff --git a/Core/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php b/Core/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php index 5a189b0..5b71747 100644 --- a/Core/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php +++ b/Core/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php @@ -37,7 +37,7 @@ class AnonymousAuthenticationProviderTest extends \PHPUnit_Framework_TestCase { $provider = $this->getProvider('foo'); - $this->assertNull($provider->authenticate($this->getSupportedToken('bar'))); + $provider->authenticate($this->getSupportedToken('bar')); } public function testAuthenticate() @@ -50,9 +50,9 @@ class AnonymousAuthenticationProviderTest extends \PHPUnit_Framework_TestCase protected function getSupportedToken($key) { - $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', array('getKey'), array(), '', false); + $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\AnonymousToken', array('getSecret'), array(), '', false); $token->expects($this->any()) - ->method('getKey') + ->method('getSecret') ->will($this->returnValue($key)) ; diff --git a/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php b/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php new file mode 100644 index 0000000..f1b5c03 --- /dev/null +++ b/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php @@ -0,0 +1,61 @@ +<?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\Tests\Authentication\Provider; + +use Symfony\Component\Security\Core\Authentication\Provider\LdapBindAuthenticationProvider; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\User\User; +use Symfony\Component\Ldap\Exception\ConnectionException; + +class LdapBindAuthenticationProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException + * @expectedExceptionMessage The presented password is invalid. + */ + public function testBindFailureShouldThrowAnException() + { + $userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface'); + $ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface'); + $ldap + ->expects($this->once()) + ->method('bind') + ->will($this->throwException(new ConnectionException())) + ; + $userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface'); + + $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap); + $reflection = new \ReflectionMethod($provider, 'checkAuthentication'); + $reflection->setAccessible(true); + + $reflection->invoke($provider, new User('foo', null), new UsernamePasswordToken('foo', '', 'key')); + } + + public function testRetrieveUser() + { + $userProvider = $this->getMock('Symfony\Component\Security\Core\User\UserProviderInterface'); + $userProvider + ->expects($this->once()) + ->method('loadUserByUsername') + ->with('foo') + ; + $ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface'); + + $userChecker = $this->getMock('Symfony\Component\Security\Core\User\UserCheckerInterface'); + + $provider = new LdapBindAuthenticationProvider($userProvider, $userChecker, 'key', $ldap); + $reflection = new \ReflectionMethod($provider, 'retrieveUser'); + $reflection->setAccessible(true); + + $reflection->invoke($provider, 'foo', new UsernamePasswordToken('foo', 'bar', 'key')); + } +} diff --git a/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php b/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php index a6fff4b..735d195 100644 --- a/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php +++ b/Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php @@ -36,10 +36,10 @@ class RememberMeAuthenticationProviderTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException */ - public function testAuthenticateWhenKeysDoNotMatch() + public function testAuthenticateWhenSecretsDoNotMatch() { - $provider = $this->getProvider(null, 'key1'); - $token = $this->getSupportedToken(null, 'key2'); + $provider = $this->getProvider(null, 'secret1'); + $token = $this->getSupportedToken(null, 'secret2'); $provider->authenticate($token); } @@ -77,7 +77,7 @@ class RememberMeAuthenticationProviderTest extends \PHPUnit_Framework_TestCase $this->assertEquals('', $authToken->getCredentials()); } - protected function getSupportedToken($user = null, $key = 'test') + protected function getSupportedToken($user = null, $secret = 'test') { if (null === $user) { $user = $this->getMock('Symfony\Component\Security\Core\User\UserInterface'); @@ -87,7 +87,7 @@ class RememberMeAuthenticationProviderTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue(array())); } - $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\RememberMeToken', array('getProviderKey'), array($user, 'foo', $key)); + $token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\RememberMeToken', array('getProviderKey'), array($user, 'foo', $secret)); $token ->expects($this->once()) ->method('getProviderKey') diff --git a/Core/Tests/Authentication/Token/AnonymousTokenTest.php b/Core/Tests/Authentication/Token/AnonymousTokenTest.php index b5cf006..cac2039 100644 --- a/Core/Tests/Authentication/Token/AnonymousTokenTest.php +++ b/Core/Tests/Authentication/Token/AnonymousTokenTest.php @@ -28,7 +28,7 @@ class AnonymousTokenTest extends \PHPUnit_Framework_TestCase public function testGetKey() { $token = new AnonymousToken('foo', 'bar'); - $this->assertEquals('foo', $token->getKey()); + $this->assertEquals('foo', $token->getSecret()); } public function testGetCredentials() diff --git a/Core/Tests/Authentication/Token/RememberMeTokenTest.php b/Core/Tests/Authentication/Token/RememberMeTokenTest.php index 7449204..b83de4a 100644 --- a/Core/Tests/Authentication/Token/RememberMeTokenTest.php +++ b/Core/Tests/Authentication/Token/RememberMeTokenTest.php @@ -22,7 +22,7 @@ class RememberMeTokenTest extends \PHPUnit_Framework_TestCase $token = new RememberMeToken($user, 'fookey', 'foo'); $this->assertEquals('fookey', $token->getProviderKey()); - $this->assertEquals('foo', $token->getKey()); + $this->assertEquals('foo', $token->getSecret()); $this->assertEquals(array(new Role('ROLE_FOO')), $token->getRoles()); $this->assertSame($user, $token->getUser()); $this->assertTrue($token->isAuthenticated()); @@ -31,7 +31,7 @@ class RememberMeTokenTest extends \PHPUnit_Framework_TestCase /** * @expectedException \InvalidArgumentException */ - public function testConstructorKeyCannotBeNull() + public function testConstructorSecretCannotBeNull() { new RememberMeToken( $this->getUser(), @@ -43,7 +43,7 @@ class RememberMeTokenTest extends \PHPUnit_Framework_TestCase /** * @expectedException \InvalidArgumentException */ - public function testConstructorKeyCannotBeEmptyString() + public function testConstructorSecretCannotBeEmptyString() { new RememberMeToken( $this->getUser(), diff --git a/Core/Tests/Authorization/AccessDecisionManagerTest.php b/Core/Tests/Authorization/AccessDecisionManagerTest.php index 7a9ab08..412af91 100644 --- a/Core/Tests/Authorization/AccessDecisionManagerTest.php +++ b/Core/Tests/Authorization/AccessDecisionManagerTest.php @@ -16,6 +16,9 @@ use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; class AccessDecisionManagerTest extends \PHPUnit_Framework_TestCase { + /** + * @group legacy + */ public function testSupportsClass() { $manager = new AccessDecisionManager(array( @@ -31,6 +34,9 @@ class AccessDecisionManagerTest extends \PHPUnit_Framework_TestCase $this->assertFalse($manager->supportsClass('FooClass')); } + /** + * @group legacy + */ public function testSupportsAttribute() { $manager = new AccessDecisionManager(array( @@ -49,14 +55,6 @@ class AccessDecisionManagerTest extends \PHPUnit_Framework_TestCase /** * @expectedException \InvalidArgumentException */ - public function testSetVotersEmpty() - { - $manager = new AccessDecisionManager(array()); - } - - /** - * @expectedException \InvalidArgumentException - */ public function testSetUnsupportedStrategy() { new AccessDecisionManager(array($this->getVoter(VoterInterface::ACCESS_GRANTED)), 'fooBar'); diff --git a/Core/Tests/Authorization/Voter/AbstractVoterTest.php b/Core/Tests/Authorization/Voter/AbstractVoterTest.php index 2ab943b..5ea7732 100644 --- a/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/Core/Tests/Authorization/Voter/AbstractVoterTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; @@ -53,10 +54,44 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } + + /** + * @dataProvider getTests + * @group legacy + */ + public function testVoteLegacy(array $attributes, $expectedVote, $object, $message) + { + $voter = new AbstractVoterTest_LegacyVoter(); + + $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); + } + + /** + * @group legacy + * @expectedException \BadMethodCallException + */ + public function testNoOverriddenMethodsThrowsException() + { + $voter = new AbstractVoterTest_NothingImplementedVoter(); + $voter->vote($this->token, new \stdClass(), array('EDIT')); + } } class AbstractVoterTest_Voter extends AbstractVoter { + protected function voteOnAttribute($attribute, $object, TokenInterface $token) + { + return 'EDIT' === $attribute; + } + + protected function supports($attribute, $object) + { + return $object instanceof \stdClass && in_array($attribute, array('EDIT', 'CREATE')); + } +} + +class AbstractVoterTest_LegacyVoter extends AbstractVoter +{ protected function getSupportedClasses() { return array('stdClass'); @@ -72,3 +107,18 @@ class AbstractVoterTest_Voter extends AbstractVoter return 'EDIT' === $attribute; } } + +class AbstractVoterTest_NothingImplementedVoter extends AbstractVoter +{ + protected function getSupportedClasses() + { + return array('stdClass'); + } + + protected function getSupportedAttributes() + { + return array('EDIT', 'CREATE'); + } + + // this is a bad voter that hasn't overridden isGranted or voteOnAttribute +} diff --git a/Core/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php b/Core/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php new file mode 100644 index 0000000..408dd2a --- /dev/null +++ b/Core/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php @@ -0,0 +1,26 @@ +<?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\Tests\Exception; + +use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; + +class CustomUserMessageAuthenticationExceptionTest extends \PHPUnit_Framework_TestCase +{ + public function testConstructWithSAfeMessage() + { + $e = new CustomUserMessageAuthenticationException('SAFE MESSAGE', array('foo' => true)); + + $this->assertEquals('SAFE MESSAGE', $e->getMessageKey()); + $this->assertEquals(array('foo' => true), $e->getMessageData()); + $this->assertEquals('SAFE MESSAGE', $e->getMessage()); + } +} diff --git a/Core/Tests/LegacySecurityContextInterfaceTest.php b/Core/Tests/LegacySecurityContextInterfaceTest.php deleted file mode 100644 index a45ecf9..0000000 --- a/Core/Tests/LegacySecurityContextInterfaceTest.php +++ /dev/null @@ -1,31 +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\Core\Tests; - -use Symfony\Component\Security\Core\SecurityContextInterface; -use Symfony\Component\Security\Core\Security; - -/** - * @group legacy - */ -class LegacySecurityContextInterfaceTest extends \PHPUnit_Framework_TestCase -{ - /** - * Test if the BC Layer is working as intended. - */ - public function testConstantSync() - { - $this->assertSame(Security::ACCESS_DENIED_ERROR, SecurityContextInterface::ACCESS_DENIED_ERROR); - $this->assertSame(Security::AUTHENTICATION_ERROR, SecurityContextInterface::AUTHENTICATION_ERROR); - $this->assertSame(Security::LAST_USERNAME, SecurityContextInterface::LAST_USERNAME); - } -} diff --git a/Core/Tests/LegacySecurityContextTest.php b/Core/Tests/LegacySecurityContextTest.php index 92d7c16..4502261 100644 --- a/Core/Tests/LegacySecurityContextTest.php +++ b/Core/Tests/LegacySecurityContextTest.php @@ -11,9 +11,9 @@ namespace Symfony\Component\Security\Core\Tests; -use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage; -use Symfony\Component\Security\Core\Authorization\AuthorizationChecker; +use Symfony\Component\Security\Core\Security; use Symfony\Component\Security\Core\SecurityContext; +use Symfony\Component\Security\Core\SecurityContextInterface; /** * @group legacy @@ -119,4 +119,14 @@ class LegacySecurityContextTest extends \PHPUnit_Framework_TestCase array(true, null), ); } + + /** + * Test if the BC Layer is working as intended. + */ + public function testConstantSync() + { + $this->assertSame(Security::ACCESS_DENIED_ERROR, SecurityContextInterface::ACCESS_DENIED_ERROR); + $this->assertSame(Security::AUTHENTICATION_ERROR, SecurityContextInterface::AUTHENTICATION_ERROR); + $this->assertSame(Security::LAST_USERNAME, SecurityContextInterface::LAST_USERNAME); + } } diff --git a/Core/Tests/User/LdapUserProviderTest.php b/Core/Tests/User/LdapUserProviderTest.php new file mode 100644 index 0000000..f56648b --- /dev/null +++ b/Core/Tests/User/LdapUserProviderTest.php @@ -0,0 +1,102 @@ +<?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\Tests\User; + +use Symfony\Component\Security\Core\User\LdapUserProvider; +use Symfony\Component\Ldap\Exception\ConnectionException; + +class LdapUserProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException + */ + public function testLoadUserByUsernameFailsIfCantConnectToLdap() + { + $ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface'); + $ldap + ->expects($this->once()) + ->method('bind') + ->will($this->throwException(new ConnectionException())) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $provider->loadUserByUsername('foo'); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException + */ + public function testLoadUserByUsernameFailsIfNoLdapEntries() + { + $ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface'); + $ldap + ->expects($this->once()) + ->method('escape') + ->will($this->returnValue('foo')) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $provider->loadUserByUsername('foo'); + } + + /** + * @expectedException \Symfony\Component\Security\Core\Exception\UsernameNotFoundException + */ + public function testLoadUserByUsernameFailsIfMoreThanOneLdapEntry() + { + $ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface'); + $ldap + ->expects($this->once()) + ->method('escape') + ->will($this->returnValue('foo')) + ; + $ldap + ->expects($this->once()) + ->method('find') + ->will($this->returnValue(array( + array(), + array(), + 'count' => 2, + ))) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $provider->loadUserByUsername('foo'); + } + + public function testSuccessfulLoadUserByUsername() + { + $ldap = $this->getMock('Symfony\Component\Ldap\LdapClientInterface'); + $ldap + ->expects($this->once()) + ->method('escape') + ->will($this->returnValue('foo')) + ; + $ldap + ->expects($this->once()) + ->method('find') + ->will($this->returnValue(array( + array( + 'sAMAccountName' => 'foo', + 'userpassword' => 'bar', + ), + 'count' => 1, + ))) + ; + + $provider = new LdapUserProvider($ldap, 'ou=MyBusiness,dc=symfony,dc=com'); + $this->assertInstanceOf( + 'Symfony\Component\Security\Core\User\User', + $provider->loadUserByUsername('foo') + ); + } +} diff --git a/Core/Tests/Util/ClassUtilsTest.php b/Core/Tests/Util/ClassUtilsTest.php index e8f0143..b048206 100644 --- a/Core/Tests/Util/ClassUtilsTest.php +++ b/Core/Tests/Util/ClassUtilsTest.php @@ -13,6 +13,9 @@ namespace Symfony\Component\Security\Core\Tests\Util { use Symfony\Component\Security\Core\Util\ClassUtils; + /** + * @group legacy + */ class ClassUtilsTest extends \PHPUnit_Framework_TestCase { public static function dataGetClass() diff --git a/Core/Tests/Util/SecureRandomTest.php b/Core/Tests/Util/SecureRandomTest.php index 2e94cc1..a78d5a2 100644 --- a/Core/Tests/Util/SecureRandomTest.php +++ b/Core/Tests/Util/SecureRandomTest.php @@ -13,26 +13,27 @@ namespace Symfony\Component\Security\Core\Tests\Util; use Symfony\Component\Security\Core\Util\SecureRandom; +/** + * @group legacy + */ class SecureRandomTest extends \PHPUnit_Framework_TestCase { /** * T1: Monobit test. - * - * @dataProvider getSecureRandoms */ - public function testMonobit($secureRandom) + public function testMonobit() { + $secureRandom = new 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) + public function testPoker() { + $secureRandom = new SecureRandom(); $b = $this->getBitSequence($secureRandom, 20000); $c = array(); for ($i = 0; $i <= 15; ++$i) { @@ -56,11 +57,10 @@ class SecureRandomTest extends \PHPUnit_Framework_TestCase /** * Run test. - * - * @dataProvider getSecureRandoms */ - public function testRun($secureRandom) + public function testRun() { + $secureRandom = new SecureRandom(); $b = $this->getBitSequence($secureRandom, 20000); $runs = array(); @@ -104,11 +104,10 @@ class SecureRandomTest extends \PHPUnit_Framework_TestCase /** * Long-run test. - * - * @dataProvider getSecureRandoms */ - public function testLongRun($secureRandom) + public function testLongRun() { + $secureRandom = new SecureRandom(); $b = $this->getBitSequence($secureRandom, 20000); $longestRun = $currentRun = 0; @@ -133,11 +132,10 @@ class SecureRandomTest extends \PHPUnit_Framework_TestCase /** * Serial Correlation (Autokorrelationstest). - * - * @dataProvider getSecureRandoms */ - public function testSerialCorrelation($secureRandom) + public function testSerialCorrelation() { + $secureRandom = new SecureRandom(); $shift = mt_rand(1, 5000); $b = $this->getBitSequence($secureRandom, 20000); @@ -149,44 +147,6 @@ class SecureRandomTest extends \PHPUnit_Framework_TestCase $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 = ''; diff --git a/Core/Tests/Util/StringUtilsTest.php b/Core/Tests/Util/StringUtilsTest.php index faeaf25..78d9b05 100644 --- a/Core/Tests/Util/StringUtilsTest.php +++ b/Core/Tests/Util/StringUtilsTest.php @@ -15,6 +15,8 @@ use Symfony\Component\Security\Core\Util\StringUtils; /** * Data from PHP.net's hash_equals tests. + * + * @group legacy */ class StringUtilsTest extends \PHPUnit_Framework_TestCase { diff --git a/Core/User/LdapUserProvider.php b/Core/User/LdapUserProvider.php new file mode 100644 index 0000000..ec699fc --- /dev/null +++ b/Core/User/LdapUserProvider.php @@ -0,0 +1,109 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\User; + +use Symfony\Component\Security\Core\Exception\UnsupportedUserException; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Ldap\Exception\ConnectionException; +use Symfony\Component\Ldap\LdapClientInterface; + +/** + * LdapUserProvider is a simple user provider on top of ldap. + * + * @author Grégoire Pineau <lyrixx@lyrixx.info> + * @author Charles Sarrazin <charles@sarraz.in> + */ +class LdapUserProvider implements UserProviderInterface +{ + private $ldap; + private $baseDn; + private $searchDn; + private $searchPassword; + private $defaultRoles; + private $defaultSearch; + + /** + * @param LdapClientInterface $ldap + * @param string $baseDn + * @param string $searchDn + * @param string $searchPassword + * @param array $defaultRoles + * @param string $uidKey + * @param string $filter + */ + public function __construct(LdapClientInterface $ldap, $baseDn, $searchDn = null, $searchPassword = null, array $defaultRoles = array(), $uidKey = 'sAMAccountName', $filter = '({uid_key}={username})') + { + $this->ldap = $ldap; + $this->baseDn = $baseDn; + $this->searchDn = $searchDn; + $this->searchPassword = $searchPassword; + $this->defaultRoles = $defaultRoles; + $this->defaultSearch = str_replace('{uid_key}', $uidKey, $filter); + } + + /** + * {@inheritdoc} + */ + public function loadUserByUsername($username) + { + try { + $this->ldap->bind($this->searchDn, $this->searchPassword); + $username = $this->ldap->escape($username, '', LdapClientInterface::LDAP_ESCAPE_FILTER); + $query = str_replace('{username}', $username, $this->defaultSearch); + $search = $this->ldap->find($this->baseDn, $query); + } catch (ConnectionException $e) { + throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username), 0, $e); + } + + if (!$search) { + throw new UsernameNotFoundException(sprintf('User "%s" not found.', $username)); + } + + if ($search['count'] > 1) { + throw new UsernameNotFoundException('More than one user found'); + } + + $user = $search[0]; + + return $this->loadUser($username, $user); + } + + public function loadUser($username, $user) + { + $password = isset($user['userpassword']) ? $user['userpassword'] : null; + + $roles = $this->defaultRoles; + + return new User($username, $password, $roles); + } + + /** + * {@inheritdoc} + */ + public function refreshUser(UserInterface $user) + { + if (!$user instanceof User) { + throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user))); + } + + return new User($user->getUsername(), null, $user->getRoles()); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + return $class === 'Symfony\Component\Security\Core\User\User'; + } + +} diff --git a/Core/User/UserCheckerInterface.php b/Core/User/UserCheckerInterface.php index 3dd8d51..62ea9f0 100644 --- a/Core/User/UserCheckerInterface.php +++ b/Core/User/UserCheckerInterface.php @@ -11,10 +11,13 @@ namespace Symfony\Component\Security\Core\User; +use Symfony\Component\Security\Core\Exception\AccountStatusException; + /** - * UserCheckerInterface checks user account when authentication occurs. + * Implement to throw AccountStatusException during the authentication process. * - * This should not be used to make authentication decisions. + * Can be used when you want to check the account status, e.g when the account is + * disabled or blocked. This should not be used to make authentication decisions. * * @author Fabien Potencier <fabien@symfony.com> */ @@ -24,6 +27,8 @@ interface UserCheckerInterface * Checks the user account before authentication. * * @param UserInterface $user a UserInterface instance + * + * @throws AccountStatusException */ public function checkPreAuth(UserInterface $user); @@ -31,6 +36,8 @@ interface UserCheckerInterface * Checks the user account after authentication. * * @param UserInterface $user a UserInterface instance + * + * @throws AccountStatusException */ public function checkPostAuth(UserInterface $user); } diff --git a/Core/User/UserProviderInterface.php b/Core/User/UserProviderInterface.php index d17e3b7..146ed65 100644 --- a/Core/User/UserProviderInterface.php +++ b/Core/User/UserProviderInterface.php @@ -43,8 +43,6 @@ interface UserProviderInterface * * @return UserInterface * - * @see UsernameNotFoundException - * * @throws UsernameNotFoundException if the user is not found */ public function loadUserByUsername($username); diff --git a/Core/Util/ClassUtils.php b/Core/Util/ClassUtils.php index 6c87096..06186ef 100644 --- a/Core/Util/ClassUtils.php +++ b/Core/Util/ClassUtils.php @@ -11,13 +11,15 @@ namespace Symfony\Component\Security\Core\Util; -use Doctrine\Common\Util\ClassUtils as DoctrineClassUtils; +use Symfony\Component\Security\Acl\Util\ClassUtils as AclClassUtils; + +@trigger_error('The '.__NAMESPACE__.'\ClassUtils class is deprecated since version 2.8, to be removed in 3.0. Use Symfony\Component\Security\Acl\Util\ClassUtils instead.', E_USER_DEPRECATED); /** * Class related functionality for objects that * might or might not be proxy objects at the moment. * - * @see DoctrineClassUtils + * @deprecated ClassUtils is deprecated since version 2.8, to be removed in 3.0. Use Acl ClassUtils instead. * * @author Benjamin Eberlei <kontakt@beberlei.de> * @author Johannes Schmitt <schmittjoh@gmail.com> @@ -54,6 +56,11 @@ class ClassUtils */ public static function getRealClass($object) { + if (class_exists('Symfony\Component\Security\Acl\Util\ClassUtils')) { + return AclClassUtils::getRealClass($object); + } + + // fallback in case security-acl is not installed $class = is_object($object) ? get_class($object) : $object; if (false === $pos = strrpos($class, '\\'.self::MARKER.'\\')) { diff --git a/Core/Util/SecureRandom.php b/Core/Util/SecureRandom.php index 65722ce..06ed893 100644 --- a/Core/Util/SecureRandom.php +++ b/Core/Util/SecureRandom.php @@ -11,106 +11,23 @@ namespace Symfony\Component\Security\Core\Util; -use Psr\Log\LoggerInterface; +@trigger_error('The '.__NAMESPACE__.'\SecureRandom class is deprecated since version 2.8 and will be removed in 3.0. Use the random_bytes() function instead.', E_USER_DEPRECATED); /** * A secure random number generator implementation. * * @author Fabien Potencier <fabien@symfony.com> * @author Johannes M. Schmitt <schmittjoh@gmail.com> + * + * @deprecated since version 2.8, to be removed in 3.0. Use the random_bytes function instead */ 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 (!function_exists('random_bytes') && !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/Core/Util/SecureRandomInterface.php b/Core/Util/SecureRandomInterface.php index 87d3ace..df5509b 100644 --- a/Core/Util/SecureRandomInterface.php +++ b/Core/Util/SecureRandomInterface.php @@ -15,6 +15,8 @@ namespace Symfony\Component\Security\Core\Util; * Interface that needs to be implemented by all secure random number generators. * * @author Fabien Potencier <fabien@symfony.com> + * + * @deprecated since version 2.8, to be removed in 3.0. Use the random_bytes function instead */ interface SecureRandomInterface { diff --git a/Core/Util/StringUtils.php b/Core/Util/StringUtils.php index 343585c..5900812 100644 --- a/Core/Util/StringUtils.php +++ b/Core/Util/StringUtils.php @@ -11,10 +11,16 @@ namespace Symfony\Component\Security\Core\Util; +@trigger_error('The '.__NAMESPACE__.'\\StringUtils class is deprecated since version 2.8 and will be removed in 3.0. Use hash_equals() instead.', E_USER_DEPRECATED); + +use Symfony\Component\Polyfill\Util\Binary; + /** * String utility functions. * * @author Fabien Potencier <fabien@symfony.com> + * + * @deprecated since 2.8, to be removed in 3.0. */ class StringUtils { @@ -47,25 +53,7 @@ class StringUtils $userInput = (string) $userInput; } - if (function_exists('hash_equals')) { - return hash_equals($knownString, $userInput); - } - - $knownLen = self::safeStrlen($knownString); - $userLen = self::safeStrlen($userInput); - - if ($userLen !== $knownLen) { - return false; - } - - $result = 0; - - for ($i = 0; $i < $knownLen; ++$i) { - $result |= (ord($knownString[$i]) ^ ord($userInput[$i])); - } - - // They are only identical strings if $result is exactly 0... - return 0 === $result; + return hash_equals($knownString, $userInput); } /** @@ -77,17 +65,6 @@ class StringUtils */ public static function safeStrlen($string) { - // Premature optimization - // Since this cannot be changed at runtime, we can cache it - static $funcExists = null; - if (null === $funcExists) { - $funcExists = function_exists('mb_strlen'); - } - - if ($funcExists) { - return mb_strlen($string, '8bit'); - } - - return strlen($string); + return Binary::strlen($string); } } diff --git a/Core/composer.json b/Core/composer.json index 5672a28..e89fb8d 100644 --- a/Core/composer.json +++ b/Core/composer.json @@ -16,23 +16,27 @@ } ], "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-php55": "~1.0", + "symfony/polyfill-php56": "~1.0", + "symfony/polyfill-php70": "~1.0", + "symfony/polyfill-util": "~1.0" }, "require-dev": { - "symfony/event-dispatcher": "~2.1", - "symfony/expression-language": "~2.6", - "symfony/http-foundation": "~2.4", - "symfony/translation": "~2.0,>=2.0.5", - "symfony/validator": "~2.5,>=2.5.5", - "psr/log": "~1.0", - "ircmaxell/password-compat": "1.0.*" + "symfony/event-dispatcher": "~2.1|~3.0.0", + "symfony/expression-language": "~2.6|~3.0.0", + "symfony/http-foundation": "~2.4|~3.0.0", + "symfony/ldap": "~2.8|~3.0.0", + "symfony/translation": "~2.0,>=2.0.5|~3.0.0", + "symfony/validator": "~2.5,>=2.5.5|~3.0.0", + "psr/log": "~1.0" }, "suggest": { "symfony/event-dispatcher": "", "symfony/http-foundation": "", "symfony/validator": "For using the user password constraint", "symfony/expression-language": "For using the expression voter", - "ircmaxell/password-compat": "For using the BCrypt password encoder in PHP <5.5" + "symfony/ldap": "For using LDAP integration" }, "autoload": { "psr-4": { "Symfony\\Component\\Security\\Core\\": "" }, @@ -43,7 +47,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } |