summaryrefslogtreecommitdiffstats
path: root/Core
diff options
context:
space:
mode:
Diffstat (limited to 'Core')
-rw-r--r--Core/Authentication/Provider/AnonymousAuthenticationProvider.php16
-rw-r--r--Core/Authentication/Provider/LdapBindAuthenticationProvider.php85
-rw-r--r--Core/Authentication/Provider/RememberMeAuthenticationProvider.php16
-rw-r--r--Core/Authentication/SimpleFormAuthenticatorInterface.php2
-rw-r--r--Core/Authentication/SimplePreAuthenticatorInterface.php2
-rw-r--r--Core/Authentication/Token/AnonymousToken.php34
-rw-r--r--Core/Authentication/Token/RememberMeToken.php38
-rw-r--r--Core/Authorization/AccessDecisionManager.php28
-rw-r--r--Core/Authorization/AccessDecisionManagerInterface.php4
-rw-r--r--Core/Authorization/Voter/AbstractVoter.php4
-rw-r--r--Core/Authorization/Voter/ExpressionVoter.php1
-rw-r--r--Core/Authorization/Voter/Voter.php85
-rw-r--r--Core/Authorization/Voter/VoterInterface.php4
-rw-r--r--Core/Encoder/BCryptPasswordEncoder.php6
-rw-r--r--Core/Encoder/BasePasswordEncoder.php4
-rw-r--r--Core/Encoder/Pbkdf2PasswordEncoder.php26
-rw-r--r--Core/Exception/AuthenticationExpiredException.php31
-rw-r--r--Core/Exception/CustomUserMessageAuthenticationException.php79
-rw-r--r--Core/README.md2
-rw-r--r--Core/Tests/Authentication/Provider/AnonymousAuthenticationProviderTest.php16
-rw-r--r--Core/Tests/Authentication/Provider/LdapBindAuthenticationProviderTest.php64
-rw-r--r--Core/Tests/Authentication/Provider/RememberMeAuthenticationProviderTest.php10
-rw-r--r--Core/Tests/Authentication/Token/AnonymousTokenTest.php2
-rw-r--r--Core/Tests/Authentication/Token/RememberMeTokenTest.php6
-rw-r--r--Core/Tests/Authorization/AccessDecisionManagerTest.php14
-rw-r--r--Core/Tests/Authorization/Voter/AbstractVoterTest.php24
-rw-r--r--Core/Tests/Authorization/Voter/Fixtures/MyVoter.php27
-rw-r--r--Core/Tests/Authorization/Voter/RoleHierarchyVoterTest.php15
-rw-r--r--Core/Tests/Authorization/Voter/VoterTest.php70
-rw-r--r--Core/Tests/Exception/CustomUserMessageAuthenticationExceptionTest.php26
-rw-r--r--Core/Tests/LegacySecurityContextInterfaceTest.php31
-rw-r--r--Core/Tests/LegacySecurityContextTest.php14
-rw-r--r--Core/Tests/User/LdapUserProviderTest.php105
-rw-r--r--Core/Tests/Util/ClassUtilsTest.php3
-rw-r--r--Core/Tests/Util/StringUtilsTest.php2
-rw-r--r--Core/User/LdapUserProvider.php108
-rw-r--r--Core/User/UserCheckerInterface.php11
-rw-r--r--Core/User/UserProviderInterface.php2
-rw-r--r--Core/Util/ClassUtils.php11
-rw-r--r--Core/Util/SecureRandom.php4
-rw-r--r--Core/Util/SecureRandomInterface.php2
-rw-r--r--Core/Util/StringUtils.php39
-rw-r--r--Core/composer.json21
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"
}
}
}