diff options
Diffstat (limited to 'Core')
43 files changed, 886 insertions, 208 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..adc42ef --- /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, '', 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..5dcf787 100644 --- a/Core/Authorization/Voter/AbstractVoter.php +++ b/Core/Authorization/Voter/AbstractVoter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Core\Authorization\Voter; +@trigger_error('The '.__NAMESPACE__.'\AbstractVoter class is deprecated since version 2.8, to be removed in 3.0. Upgrade to Symfony\Component\Security\Core\Authorization\Voter\Voter instead.', E_USER_DEPRECATED); + use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; @@ -18,6 +20,8 @@ use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; * Abstract Voter implementation that reduces boilerplate code required to create a custom Voter. * * @author Roman Marintšenko <inoryy@gmail.com> + * + * @deprecated since version 2.8, to be removed in 3.0. Upgrade to Symfony\Component\Security\Core\Authorization\Voter\Voter instead. */ abstract class AbstractVoter implements VoterInterface { diff --git a/Core/Authorization/Voter/ExpressionVoter.php b/Core/Authorization/Voter/ExpressionVoter.php index 98b8f50..96a7ece 100644 --- a/Core/Authorization/Voter/ExpressionVoter.php +++ b/Core/Authorization/Voter/ExpressionVoter.php @@ -102,6 +102,7 @@ class ExpressionVoter implements VoterInterface 'token' => $token, 'user' => $token->getUser(), 'object' => $object, + 'subject' => $object, 'roles' => array_map(function ($role) { return $role->getRole(); }, $roles), 'trust_resolver' => $this->trustResolver, ); diff --git a/Core/Authorization/Voter/Voter.php b/Core/Authorization/Voter/Voter.php new file mode 100644 index 0000000..8d36fd8 --- /dev/null +++ b/Core/Authorization/Voter/Voter.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\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Voter is an abstract default implementation of a voter. + * + * @author Roman Marintšenko <inoryy@gmail.com> + * @author Grégoire Pineau <lyrixx@lyrixx.info> + */ +abstract class Voter implements VoterInterface +{ + /** + * {@inheritdoc} + */ + public function supportsAttribute($attribute) + { + throw new \BadMethodCallException('supportsAttribute method is deprecated since version 2.8, to be removed in 3.0'); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + throw new \BadMethodCallException('supportsClass method is deprecated since version 2.8, to be removed in 3.0'); + } + + /** + * {@inheritdoc} + */ + public function vote(TokenInterface $token, $object, array $attributes) + { + // abstain vote by default in case none of the attributes are supported + $vote = self::ACCESS_ABSTAIN; + + foreach ($attributes as $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->voteOnAttribute($attribute, $object, $token)) { + // grant access as soon as at least one attribute returns a positive response + return self::ACCESS_GRANTED; + } + } + + return $vote; + } + + /** + * Determines if the attribute and subject are supported by this voter. + * + * @param string $attribute An attribute + * @param mixed $subject The subject to secure, e.g. an object the user wants to access or any other PHP type + * + * @return bool True if the attribute and subject are supported, false otherwise + */ + abstract protected function supports($attribute, $subject); + + /** + * Perform a single access check operation on a given attribute, subject and token. + * + * @param string $attribute + * @param mixed $subject + * @param TokenInterface $token + * + * @return bool + */ + abstract protected function voteOnAttribute($attribute, $subject, TokenInterface $token); +} diff --git a/Core/Authorization/Voter/VoterInterface.php b/Core/Authorization/Voter/VoterInterface.php index 1032cb2..91ddc1f 100644 --- a/Core/Authorization/Voter/VoterInterface.php +++ b/Core/Authorization/Voter/VoterInterface.php @@ -30,6 +30,8 @@ interface VoterInterface * @param mixed $attribute An attribute (usually the attribute name string) * * @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 83ae334..b992765 100644 --- a/Core/Encoder/BCryptPasswordEncoder.php +++ b/Core/Encoder/BCryptPasswordEncoder.php @@ -36,10 +36,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.'); @@ -77,6 +73,8 @@ class BCryptPasswordEncoder extends BasePasswordEncoder $options = array('cost' => $this->cost); if ($salt) { + @trigger_error('Passing a $salt to '.__METHOD__.'() is deprecated since version 2.8 and will be ignored in 3.0.', E_USER_DEPRECATED); + $options['salt'] = $salt; } diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php index fcf2e47..d86f260 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..8f4b392 100644 --- a/Core/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php +++ b/Core/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php @@ -33,11 +33,11 @@ class AnonymousAuthenticationProviderTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Symfony\Component\Security\Core\Exception\BadCredentialsException */ - public function testAuthenticateWhenKeyIsNotValid() + public function testAuthenticateWhenSecretIsNotValid() { $provider = $this->getProvider('foo'); - $this->assertNull($provider->authenticate($this->getSupportedToken('bar'))); + $provider->authenticate($this->getSupportedToken('bar')); } public function testAuthenticate() @@ -48,19 +48,19 @@ class AnonymousAuthenticationProviderTest extends \PHPUnit_Framework_TestCase $this->assertSame($token, $provider->authenticate($token)); } - protected function getSupportedToken($key) + protected function getSupportedToken($secret) { - $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') - ->will($this->returnValue($key)) + ->method('getSecret') + ->will($this->returnValue($secret)) ; return $token; } - protected function getProvider($key) + protected function getProvider($secret) { - return new AnonymousAuthenticationProvider($key); + return new AnonymousAuthenticationProvider($secret); } } diff --git a/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php b/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php new file mode 100644 index 0000000..844bcef --- /dev/null +++ b/Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php @@ -0,0 +1,64 @@ +<?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; + +/** + * @requires extension ldap + */ +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..b537c1b 100644 --- a/Core/Tests/Authorization/Voter/AbstractVoterTest.php +++ b/Core/Tests/Authorization/Voter/AbstractVoterTest.php @@ -11,9 +11,11 @@ namespace Symfony\Component\Security\Core\Tests\Authorization\Voter; -use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +/** + * @group legacy + */ class AbstractVoterTest extends \PHPUnit_Framework_TestCase { protected $token; @@ -49,26 +51,8 @@ class AbstractVoterTest extends \PHPUnit_Framework_TestCase */ public function testVote(array $attributes, $expectedVote, $object, $message) { - $voter = new AbstractVoterTest_Voter(); + $voter = new Fixtures\MyVoter(); $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); } } - -class AbstractVoterTest_Voter extends AbstractVoter -{ - protected function getSupportedClasses() - { - return array('stdClass'); - } - - protected function getSupportedAttributes() - { - return array('EDIT', 'CREATE'); - } - - protected function isGranted($attribute, $object, $user = null) - { - return 'EDIT' === $attribute; - } -} diff --git a/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php b/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php new file mode 100644 index 0000000..b75f798 --- /dev/null +++ b/Core/Tests/Authorization/Voter/Fixtures/MyVoter.php @@ -0,0 +1,27 @@ +<?php + +namespace Symfony\Component\Security\Core\Tests\Authorization\Voter\Fixtures; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\AbstractVoter; + +/** + * @group legacy + */ +class MyVoter extends AbstractVoter +{ + protected function getSupportedClasses() + { + return array('stdClass'); + } + + protected function getSupportedAttributes() + { + return array('EDIT', 'CREATE'); + } + + protected function isGranted($attribute, $object, $user = null) + { + return 'EDIT' === $attribute; + } +} diff --git a/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php b/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php index c50ecf3..4b03bac 100644 --- a/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php +++ b/Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php @@ -33,4 +33,19 @@ class RoleHierarchyVoterTest extends RoleVoterTest array(array('ROLE_FOO'), array('ROLE_FOOBAR'), VoterInterface::ACCESS_GRANTED), )); } + + /** + * @dataProvider getVoteWithEmptyHierarchyTests + */ + public function testVoteWithEmptyHierarchy($roles, $attributes, $expected) + { + $voter = new RoleHierarchyVoter(new RoleHierarchy(array())); + + $this->assertSame($expected, $voter->vote($this->getToken($roles), null, $attributes)); + } + + public function getVoteWithEmptyHierarchyTests() + { + return parent::getVoteTests(); + } } diff --git a/Core/Tests/Authorization/Voter/VoterTest.php b/Core/Tests/Authorization/Voter/VoterTest.php new file mode 100644 index 0000000..4bac44d --- /dev/null +++ b/Core/Tests/Authorization/Voter/VoterTest.php @@ -0,0 +1,70 @@ +<?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\Authorization\Voter; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authorization\Voter\Voter; +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; + +class VoterTest extends \PHPUnit_Framework_TestCase +{ + protected $token; + + protected function setUp() + { + $this->token = $this->getMock('Symfony\Component\Security\Core\Authentication\Token\TokenInterface'); + } + + public function getTests() + { + return array( + array(array('EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if attribute and class are supported and attribute grants access'), + array(array('CREATE'), VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if attribute and class are supported and attribute does not grant access'), + + array(array('DELETE', 'EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute is supported and grants access'), + array(array('DELETE', 'CREATE'), VoterInterface::ACCESS_DENIED, new \stdClass(), 'ACCESS_DENIED if one attribute is supported and denies access'), + + array(array('CREATE', 'EDIT'), VoterInterface::ACCESS_GRANTED, new \stdClass(), 'ACCESS_GRANTED if one attribute grants access'), + + array(array('DELETE'), VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attribute is supported'), + + array(array('EDIT'), VoterInterface::ACCESS_ABSTAIN, $this, 'ACCESS_ABSTAIN if class is not supported'), + + array(array('EDIT'), VoterInterface::ACCESS_ABSTAIN, null, 'ACCESS_ABSTAIN if object is null'), + + array(array(), VoterInterface::ACCESS_ABSTAIN, new \stdClass(), 'ACCESS_ABSTAIN if no attributes were provided'), + ); + } + + /** + * @dataProvider getTests + */ + public function testVote(array $attributes, $expectedVote, $object, $message) + { + $voter = new VoterTest_Voter(); + + $this->assertEquals($expectedVote, $voter->vote($this->token, $object, $attributes), $message); + } +} + +class VoterTest_Voter extends Voter +{ + 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')); + } +} 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..9b126e9 --- /dev/null +++ b/Core/Tests/User/LdapUserProviderTest.php @@ -0,0 +1,105 @@ +<?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; + +/** + * @requires extension ldap + */ +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/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..1593564 --- /dev/null +++ b/Core/User/LdapUserProvider.php @@ -0,0 +1,108 @@ +<?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, '', 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 478f556..06ed893 100644 --- a/Core/Util/SecureRandom.php +++ b/Core/Util/SecureRandom.php @@ -11,11 +11,15 @@ namespace Symfony\Component\Security\Core\Util; +@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 { 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..bb0c8b2 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\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 354c55e..3362971 100644 --- a/Core/composer.json +++ b/Core/composer.json @@ -17,22 +17,25 @@ ], "require": { "php": ">=5.3.9", - "paragonie/random_compat": "~1.0" + "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/validator": "~2.5,>=2.5.9", - "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/validator": "~2.5,>=2.5.9|~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 +46,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "2.7-dev" + "dev-master": "2.8-dev" } } } |