diff options
author | Johannes M. Schmitt <schmittjoh@gmail.com> | 2011-01-26 21:34:11 +0100 |
---|---|---|
committer | Fabien Potencier <fabien.potencier@gmail.com> | 2011-01-26 22:23:20 +0100 |
commit | bebc09870cb0a7720e2c6a8c5c74585e69e8bb24 (patch) | |
tree | 0c399647cdbe504be405017e7cc04c70c53482f2 /Core | |
parent | c85f3d708d2c9b00d73ca1234ccfaf50336d94b1 (diff) | |
download | symfony-security-bebc09870cb0a7720e2c6a8c5c74585e69e8bb24.zip symfony-security-bebc09870cb0a7720e2c6a8c5c74585e69e8bb24.tar.gz symfony-security-bebc09870cb0a7720e2c6a8c5c74585e69e8bb24.tar.bz2 |
namespace changes
Symfony\Component\Security -> Symfony\Component\Security\Core
Symfony\Component\Security\Acl remains unchanged
Symfony\Component\HttpKernel\Security -> Symfony\Component\Security\Http
Diffstat (limited to 'Core')
63 files changed, 3726 insertions, 0 deletions
diff --git a/Core/Authentication/AuthenticationManagerInterface.php b/Core/Authentication/AuthenticationManagerInterface.php new file mode 100644 index 0000000..280377a --- /dev/null +++ b/Core/Authentication/AuthenticationManagerInterface.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Exception\AuthenticationException; + +/** + * AuthenticationManagerInterface is the interface for authentication managers, + * which process Token authentication. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface AuthenticationManagerInterface +{ + /** + * Attempts to authenticates a TokenInterface object. + * + * @param TokenInterface $token The TokenInterface instance to authenticate + * + * @return TokenInterface An authenticated TokenInterface instance + * + * @throws AuthenticationException if the authentication fails + */ + function authenticate(TokenInterface $token); +} diff --git a/Core/Authentication/AuthenticationProviderManager.php b/Core/Authentication/AuthenticationProviderManager.php new file mode 100644 index 0000000..187a81b --- /dev/null +++ b/Core/Authentication/AuthenticationProviderManager.php @@ -0,0 +1,120 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +use Symfony\Component\Security\Core\Exception\AccountStatusException; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\ProviderNotFoundException; +use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * AuthenticationProviderManager uses a list of AuthenticationProviderInterface + * instances to authenticate a Token. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AuthenticationProviderManager implements AuthenticationManagerInterface +{ + protected $providers; + protected $eraseCredentials; + + /** + * Constructor. + * + * @param AuthenticationProviderInterface[] $providers An array of AuthenticationProviderInterface instances + * @param Boolean $eraseCredentials Whether to erase credentials after authentication or not + */ + public function __construct(array $providers = array(), $eraseCredentials = true) + { + $this->setProviders($providers); + $this->eraseCredentials = (Boolean) $eraseCredentials; + } + + /** + * {@inheritdoc} + */ + public function authenticate(TokenInterface $token) + { + if (!count($this->providers)) { + throw new \LogicException('You must add at least one provider.'); + } + + $lastException = null; + $result = null; + + foreach ($this->providers as $provider) { + if (!$provider->supports($token)) { + continue; + } + + try { + $result = $provider->authenticate($token); + } catch (AccountStatusException $e) { + $e->setExtraInformation($token); + + throw $e; + } catch (AuthenticationException $e) { + $lastException = $e; + } + } + + if (null !== $result) { + if (true === $this->eraseCredentials) { + $result->eraseCredentials(); + } + + return $result; + } + + if (null === $lastException) { + $lastException = new ProviderNotFoundException(sprintf('No Authentication Provider found for token of class "%s".', get_class($token))); + } + + $lastException->setExtraInformation($token); + + throw $lastException; + } + + /** + * Returns the list of current providers. + * + * @return AuthenticationProviderInterface[] An array of AuthenticationProviderInterface instances + */ + public function all() + { + return $this->providers; + } + + /** + * Sets the providers instances. + * + * @param AuthenticationProviderInterface[] $providers An array of AuthenticationProviderInterface instances + */ + public function setProviders(array $providers) + { + $this->providers = array(); + foreach ($providers as $provider) { + $this->add($provider); + } + } + + /** + * Adds a provider. + * + * @param AuthenticationProviderInterface $provider A AuthenticationProviderInterface instance + */ + public function add(AuthenticationProviderInterface $provider) + { + $this->providers[] = $provider; + } +} diff --git a/Core/Authentication/AuthenticationTrustResolver.php b/Core/Authentication/AuthenticationTrustResolver.php new file mode 100644 index 0000000..95b8cb4 --- /dev/null +++ b/Core/Authentication/AuthenticationTrustResolver.php @@ -0,0 +1,75 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * The default implementation of the authentication trust resolver. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class AuthenticationTrustResolver implements AuthenticationTrustResolverInterface +{ + protected $anonymousClass; + protected $rememberMeClass; + + /** + * Constructor + * + * @param string $anonymousClass + * @param string $rememberMeClass + * + * @return void + */ + public function __construct($anonymousClass, $rememberMeClass) + { + $this->anonymousClass = $anonymousClass; + $this->rememberMeClass = $rememberMeClass; + } + + /** + * {@inheritDoc} + */ + public function isAnonymous(TokenInterface $token = null) + { + if (null === $token) { + return false; + } + + return $token instanceof $this->anonymousClass; + } + + /** + * {@inheritDoc} + */ + public function isRememberMe(TokenInterface $token = null) + { + if (null === $token) { + return false; + } + + return $token instanceof $this->rememberMeClass; + } + + /** + * {@inheritDoc} + */ + public function isFullFledged(TokenInterface $token = null) + { + if (null === $token) { + return false; + } + + return !$this->isAnonymous($token) && !$this->isRememberMe($token); + } +} diff --git a/Core/Authentication/AuthenticationTrustResolverInterface.php b/Core/Authentication/AuthenticationTrustResolverInterface.php new file mode 100644 index 0000000..1f29465 --- /dev/null +++ b/Core/Authentication/AuthenticationTrustResolverInterface.php @@ -0,0 +1,53 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Interface for resolving the authentication status of a given token. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +interface AuthenticationTrustResolverInterface +{ + /** + * Resolves whether the passed token implementation is authenticated + * anonymously. + * + * If null is passed, the method must return false. + * + * @param TokenInterface $token + * + * @return Boolean + */ + function isAnonymous(TokenInterface $token = null); + + /** + * Resolves whether the passed token implementation is authenticated + * using remember-me capabilities. + * + * @param TokenInterface $token + * + * @return Boolean + */ + function isRememberMe(TokenInterface $token = null); + + /** + * Resolves whether the passed token implementation is fully authenticated. + * + * @param TokenInterface $token + * + * @return Boolean + */ + function isFullFledged(TokenInterface $token = null); +} diff --git a/Core/Authentication/EntryPoint/AuthenticationEntryPointInterface.php b/Core/Authentication/EntryPoint/AuthenticationEntryPointInterface.php new file mode 100644 index 0000000..7fd64bf --- /dev/null +++ b/Core/Authentication/EntryPoint/AuthenticationEntryPointInterface.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\EntryPoint; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\HttpFoundation\Request; + +/** + * AuthenticationEntryPointInterface is the interface used to start the + * authentication scheme. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface AuthenticationEntryPointInterface +{ + /** + * Starts the authentication scheme. + * + * @param object $request The request that resulted in an AuthenticationException + * @param AuthenticationException $authException The exception that started the authentication process + */ + function start(Request $request, AuthenticationException $authException = null); +} diff --git a/Core/Authentication/Provider/AnonymousAuthenticationProvider.php b/Core/Authentication/Provider/AnonymousAuthenticationProvider.php new file mode 100644 index 0000000..821e17e --- /dev/null +++ b/Core/Authentication/Provider/AnonymousAuthenticationProvider.php @@ -0,0 +1,60 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\TokenInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; + +/** + * AnonymousAuthenticationProvider validates AnonymousToken instances. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AnonymousAuthenticationProvider implements AuthenticationProviderInterface +{ + protected $key; + + /** + * Constructor. + * + * @param string $key The key shared with the authentication token + */ + public function __construct($key) + { + $this->key = $key; + } + + /** + * {@inheritdoc} + */ + public function authenticate(TokenInterface $token) + { + if (!$this->supports($token)) { + return null; + } + + if ($this->key != $token->getKey()) { + throw new BadCredentialsException('The Token does not contain the expected key.'); + } + + return $token; + } + + /** + * {@inheritdoc} + */ + public function supports(TokenInterface $token) + { + return $token instanceof AnonymousToken; + } +} diff --git a/Core/Authentication/Provider/AuthenticationProviderInterface.php b/Core/Authentication/Provider/AuthenticationProviderInterface.php new file mode 100644 index 0000000..89d5ed5 --- /dev/null +++ b/Core/Authentication/Provider/AuthenticationProviderInterface.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\TokenInterface; +use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; + +/** + * AuthenticationProviderInterface is the interface for for all authentication + * providers. + * + * Concrete implementations processes specific Token instances. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface AuthenticationProviderInterface extends AuthenticationManagerInterface +{ + /** + * Checks whether this provider supports the given token. + * + * @param TokenInterface $token A TokenInterface instance + * + * @return Boolean true if the implementation supports the Token, false otherwise + */ + function supports(TokenInterface $token); +} diff --git a/Core/Authentication/Provider/DaoAuthenticationProvider.php b/Core/Authentication/Provider/DaoAuthenticationProvider.php new file mode 100644 index 0000000..398f586 --- /dev/null +++ b/Core/Authentication/Provider/DaoAuthenticationProvider.php @@ -0,0 +1,95 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\TokenInterface; +use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Core\User\AccountCheckerInterface; +use Symfony\Component\Security\Core\User\AccountInterface; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; + +/** + * DaoAuthenticationProvider uses a UserProviderInterface to retrieve the user + * for a UsernamePasswordToken. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class DaoAuthenticationProvider extends UserAuthenticationProvider +{ + protected $encoderFactory; + protected $userProvider; + + /** + * Constructor. + * + * @param UserProviderInterface $userProvider A UserProviderInterface instance + * @param AccountCheckerInterface $accountChecker An AccountCheckerInterface instance + * @param EncoderFactoryInterface $encoderFactory A EncoderFactoryInterface instance + */ + public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true) + { + parent::__construct($accountChecker, $providerKey, $hideUserNotFoundExceptions); + + $this->encoderFactory = $encoderFactory; + $this->userProvider = $userProvider; + } + + /** + * {@inheritdoc} + */ + protected function checkAuthentication(AccountInterface $account, UsernamePasswordToken $token) + { + $user = $token->getUser(); + if ($user instanceof AccountInterface) { + if ($account->getPassword() !== $user->getPassword()) { + throw new BadCredentialsException('The credentials were changed from another session.'); + } + } else { + if (!$presentedPassword = (string) $token->getCredentials()) { + throw new BadCredentialsException('Bad credentials'); + } + + if (!$this->encoderFactory->getEncoder($account)->isPasswordValid($account->getPassword(), $presentedPassword, $account->getSalt())) { + throw new BadCredentialsException('Bad credentials'); + } + } + } + + /** + * {@inheritdoc} + */ + protected function retrieveUser($username, UsernamePasswordToken $token) + { + $user = $token->getUser(); + if ($user instanceof AccountInterface) { + return $user; + } + + try { + $user = $this->userProvider->loadUserByUsername($username); + + if (!$user instanceof AccountInterface) { + throw new AuthenticationServiceException('The user provider must return an AccountInterface object.'); + } + + return $user; + } catch (UsernameNotFoundException $notFound) { + throw $notFound; + } catch (\Exception $repositoryProblem) { + throw new AuthenticationServiceException($repositoryProblem->getMessage(), $token, 0, $repositoryProblem); + } + } +} diff --git a/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php b/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php new file mode 100644 index 0000000..7fda9d4 --- /dev/null +++ b/Core/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php @@ -0,0 +1,81 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\User\AccountInterface; +use Symfony\Component\Security\Core\User\UserProviderInterface; +use Symfony\Component\Security\Core\User\AccountCheckerInterface; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * Processes a pre-authenticated authentication request. + * + * This authentication provider will not perform any checks on authentication + * requests, as they should already be pre-authenticated. However, the + * UserProviderInterface implementation may still throw a + * UsernameNotFoundException, for example. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class PreAuthenticatedAuthenticationProvider implements AuthenticationProviderInterface +{ + protected $userProvider; + protected $accountChecker; + protected $providerKey; + + /** + * Constructor. + * + * @param UserProviderInterface $userProvider A UserProviderInterface instance + * @param AccountCheckerInterface $accountChecker An AccountCheckerInterface instance + */ + public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker, $providerKey) + { + $this->userProvider = $userProvider; + $this->accountChecker = $accountChecker; + $this->providerKey = $providerKey; + } + + /** + * {@inheritdoc} + */ + public function authenticate(TokenInterface $token) + { + if (!$this->supports($token)) { + return null; + } + + if (!$user = $token->getUser()) { + throw new BadCredentialsException('No pre-authenticated principal found in request.'); + } +/* + if (null === $token->getCredentials()) { + throw new BadCredentialsException('No pre-authenticated credentials found in request.'); + } +*/ + $user = $this->userProvider->loadUserByUsername($user); + + $this->accountChecker->checkPostAuth($user); + + return new PreAuthenticatedToken($user, $token->getCredentials(), $user->getRoles()); + } + + /** + * {@inheritdoc} + */ + public function supports(TokenInterface $token) + { + return $token instanceof PreAuthenticatedToken && $this->providerKey === $token->getProviderKey(); + } +} diff --git a/Core/Authentication/Provider/RememberMeAuthenticationProvider.php b/Core/Authentication/Provider/RememberMeAuthenticationProvider.php new file mode 100644 index 0000000..95ee588 --- /dev/null +++ b/Core/Authentication/Provider/RememberMeAuthenticationProvider.php @@ -0,0 +1,45 @@ +<?php +namespace Symfony\Component\Security\Core\Authentication\Provider; + +use Symfony\Component\Security\Core\User\AccountCheckerInterface; +use Symfony\Component\Security\Core\User\AccountInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Core\Authentication\Token\RememberMeToken; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; + +class RememberMeAuthenticationProvider implements AuthenticationProviderInterface +{ + protected $accountChecker; + protected $key; + protected $providerKey; + + public function __construct(AccountCheckerInterface $accountChecker, $key, $providerKey) + { + $this->accountChecker = $accountChecker; + $this->key = $key; + $this->providerKey = $providerKey; + } + + public function authenticate(TokenInterface $token) + { + if (!$this->supports($token)) { + return; + } + + if ($this->key !== $token->getKey()) { + throw new BadCredentialsException('The presented key does not match.'); + } + + $user = $token->getUser(); + $this->accountChecker->checkPreAuth($user); + $this->accountChecker->checkPostAuth($user); + $token->setAuthenticated(true); + + return $token; + } + + public function supports(TokenInterface $token) + { + return $token instanceof RememberMeToken && $token->getProviderKey() === $this->providerKey; + } +}
\ No newline at end of file diff --git a/Core/Authentication/Provider/UserAuthenticationProvider.php b/Core/Authentication/Provider/UserAuthenticationProvider.php new file mode 100644 index 0000000..6947de3 --- /dev/null +++ b/Core/Authentication/Provider/UserAuthenticationProvider.php @@ -0,0 +1,113 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\User\AccountInterface; +use Symfony\Component\Security\Core\User\AccountCheckerInterface; +use Symfony\Component\Security\Core\Exception\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Exception\BadCredentialsException; +use Symfony\Component\Security\Core\Exception\AuthenticationServiceException; +use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * UserProviderInterface retrieves users for UsernamePasswordToken tokens. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +abstract class UserAuthenticationProvider implements AuthenticationProviderInterface +{ + protected $hideUserNotFoundExceptions; + protected $accountChecker; + protected $providerKey; + + /** + * Constructor. + * + * @param AccountCheckerInterface $accountChecker An AccountCheckerInterface interface + * @param Boolean $hideUserNotFoundExceptions Whether to hide user not found exception or not + */ + public function __construct(AccountCheckerInterface $accountChecker, $providerKey, $hideUserNotFoundExceptions = true) + { + if (empty($providerKey)) { + throw new \InvalidArgumentException('$providerKey must not be empty.'); + } + + $this->accountChecker = $accountChecker; + $this->providerKey = $providerKey; + $this->hideUserNotFoundExceptions = $hideUserNotFoundExceptions; + } + + /** + * {@inheritdoc} + */ + public function authenticate(TokenInterface $token) + { + if (!$this->supports($token)) { + return null; + } + + $username = null === $token->getUser() ? 'NONE_PROVIDED' : (string) $token; + + try { + $user = $this->retrieveUser($username, $token); + + if (!$user instanceof AccountInterface) { + throw new AuthenticationServiceException('retrieveUser() must return an AccountInterface.'); + } + + $this->accountChecker->checkPreAuth($user); + $this->checkAuthentication($user, $token); + $this->accountChecker->checkPostAuth($user); + + return new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles()); + } catch (UsernameNotFoundException $notFound) { + if ($this->hideUserNotFoundExceptions) { + throw new BadCredentialsException('Bad credentials', 0, $notFound); + } + + throw $notFound; + } + } + + /** + * {@inheritdoc} + */ + public function supports(TokenInterface $token) + { + return $token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey(); + } + + /** + * Retrieves the user from an implementation-specific location. + * + * @param string $username The username to retrieve + * @param UsernamePasswordToken $token The Token + * + * @return array The user + * + * @throws AuthenticationException if the credentials could not be validated + */ + abstract protected function retrieveUser($username, UsernamePasswordToken $token); + + /** + * Does additional checks on the user and token (like validating the + * credentials). + * + * @param AccountInterface $account The retrieved AccountInterface instance + * @param UsernamePasswordToken $token The UsernamePasswordToken token to be authenticated + * + * @throws AuthenticationException if the credentials could not be validated + */ + abstract protected function checkAuthentication(AccountInterface $account, UsernamePasswordToken $token); +} diff --git a/Core/Authentication/RememberMe/InMemoryTokenProvider.php b/Core/Authentication/RememberMe/InMemoryTokenProvider.php new file mode 100644 index 0000000..80c10d1 --- /dev/null +++ b/Core/Authentication/RememberMe/InMemoryTokenProvider.php @@ -0,0 +1,50 @@ +<?php + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +use Symfony\Component\Security\Core\Exception\TokenNotFoundException; + +/** + * This class is used for testing purposes, and is not really suited for production. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class InMemoryTokenProvider implements TokenProviderInterface +{ + protected $tokens = array(); + + public function loadTokenBySeries($series) + { + if (!isset($this->tokens[$series])) { + throw new TokenNotFoundException('No token found.'); + } + + return $this->tokens[$series]; + } + + public function updateToken($series, $tokenValue, \DateTime $lastUsed) + { + if (!isset($this->tokens[$series])) { + throw new TokenNotFoundException('No token found.'); + } + + $token = new PersistentToken( + $this->tokens[$series]->getClass(), + $this->tokens[$series]->getUsername(), + $series, + $tokenValue, + $lastUsed + ); + $this->tokens[$series] = $token; + } + + public function deleteTokenBySeries($series) + { + unset($this->tokens[$series]); + } + + public function createNewToken(PersistentTokenInterface $token) + { + $this->tokens[$token->getSeries()] = $token; + } +}
\ No newline at end of file diff --git a/Core/Authentication/RememberMe/PersistentToken.php b/Core/Authentication/RememberMe/PersistentToken.php new file mode 100644 index 0000000..9b9bb93 --- /dev/null +++ b/Core/Authentication/RememberMe/PersistentToken.php @@ -0,0 +1,107 @@ +<?php + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * This class is only used by PersistentTokenRememberMeServices internally. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +final class PersistentToken implements PersistentTokenInterface +{ + private $class; + private $username; + private $series; + private $tokenValue; + private $lastUsed; + + /** + * Constructor + * + * @param string $class + * @param string $username + * @param string $series + * @param string $tokenValue + * @param DateTime $lastUsed + */ + public function __construct($class, $username, $series, $tokenValue, \DateTime $lastUsed) + { + if (empty($class)) { + throw new \InvalidArgumentException('$class must not be empty.'); + } + if (empty($username)) { + throw new \InvalidArgumentException('$username must not be empty.'); + } + if (empty($series)) { + throw new \InvalidArgumentException('$series must not be empty.'); + } + if (empty($tokenValue)) { + throw new \InvalidArgumentException('$tokenValue must not be empty.'); + } + + $this->class = $class; + $this->username = $username; + $this->series = $series; + $this->tokenValue = $tokenValue; + $this->lastUsed = $lastUsed; + } + + /** + * Returns the class of the user + * + * @return string + */ + public function getClass() + { + return $this->class; + } + + /** + * Returns the username + * + * @return string + */ + public function getUsername() + { + return $this->username; + } + + /** + * Returns the series + * + * @return string + */ + public function getSeries() + { + return $this->series; + } + + /** + * Returns the token value + * + * @return string + */ + public function getTokenValue() + { + return $this->tokenValue; + } + + /** + * Returns the time the token was last used + * + * @return DateTime + */ + public function getLastUsed() + { + return $this->lastUsed; + } +}
\ No newline at end of file diff --git a/Core/Authentication/RememberMe/PersistentTokenInterface.php b/Core/Authentication/RememberMe/PersistentTokenInterface.php new file mode 100644 index 0000000..3696d1f --- /dev/null +++ b/Core/Authentication/RememberMe/PersistentTokenInterface.php @@ -0,0 +1,45 @@ +<?php + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Interface to be implemented by persistent token classes (such as + * Doctrine entities representing a remember-me token) + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +interface PersistentTokenInterface +{ + /** + * Returns the username + * @return string + */ + function getUsername(); + + /** + * Returns the series + * @return string + */ + function getSeries(); + + /** + * Returns the token value + * @return string + */ + function getTokenValue(); + + /** + * Returns the last time the cookie was used + * @return \DateTime + */ + function getLastUsed(); +}
\ No newline at end of file diff --git a/Core/Authentication/RememberMe/TokenProviderInterface.php b/Core/Authentication/RememberMe/TokenProviderInterface.php new file mode 100644 index 0000000..e77e68a --- /dev/null +++ b/Core/Authentication/RememberMe/TokenProviderInterface.php @@ -0,0 +1,51 @@ +<?php + +namespace Symfony\Component\Security\Core\Authentication\RememberMe; + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Interface for TokenProviders + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +interface TokenProviderInterface +{ + /** + * Loads the active token for the given series + * + * @throws TokenNotFoundException if the token is not found + * + * @param string $series + * @return PersistentTokenInterface + */ + function loadTokenBySeries($series); + + /** + * Deletes all tokens belonging to series + * @param string $series + */ + function deleteTokenBySeries($series); + + /** + * Updates the token according to this data + * + * @param string $series + * @param string $tokenValue + * @param DateTime $lastUsed + */ + function updateToken($series, $tokenValue, \DateTime $lastUsed); + + /** + * Creates a new token + * @param PersistentTokenInterface $token + */ + function createNewToken(PersistentTokenInterface $token); +}
\ No newline at end of file diff --git a/Core/Authentication/Token/AnonymousToken.php b/Core/Authentication/Token/AnonymousToken.php new file mode 100644 index 0000000..7735925 --- /dev/null +++ b/Core/Authentication/Token/AnonymousToken.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Token; + +/** + * AnonymousToken represents an anonymous token. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AnonymousToken extends Token +{ + protected $user; + protected $key; + + /** + * Constructor. + * + * @param string $key The key shared with the authentication provider + * @param string $user The user + * @param Role[] $roles An array of roles + */ + public function __construct($key, $user, array $roles = array()) + { + parent::__construct($roles); + + $this->key = $key; + $this->user = $user; + + parent::setAuthenticated(true); + } + + /** + * {@inheritdoc} + */ + public function getCredentials() + { + return ''; + } + + /** + * Returns the key. + * + * @return string The Key + */ + public function getKey() + { + return $this->key; + } +} diff --git a/Core/Authentication/Token/PreAuthenticatedToken.php b/Core/Authentication/Token/PreAuthenticatedToken.php new file mode 100644 index 0000000..c84ea10 --- /dev/null +++ b/Core/Authentication/Token/PreAuthenticatedToken.php @@ -0,0 +1,52 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Token; + +/** + * PreAuthenticatedToken implements a pre-authenticated token. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class PreAuthenticatedToken extends Token +{ + protected $providerKey; + + /** + * Constructor. + */ + public function __construct($user, $credentials, $providerKey, array $roles = null) + { + parent::__construct(null === $roles ? array() : $roles); + if (null !== $roles) { + $this->setAuthenticated(true); + } + + $this->user = $user; + $this->credentials = $credentials; + $this->providerKey = $providerKey; + } + + public function getProviderKey() + { + return $this->providerKey; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + parent::eraseCredentials(); + + $this->credentials = null; + } +} diff --git a/Core/Authentication/Token/RememberMeToken.php b/Core/Authentication/Token/RememberMeToken.php new file mode 100644 index 0000000..81bf1e0 --- /dev/null +++ b/Core/Authentication/Token/RememberMeToken.php @@ -0,0 +1,75 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Token; + +use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentTokenInterface; +use Symfony\Component\Security\Core\User\AccountInterface; + +/** + * Base class for "Remember Me" tokens + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class RememberMeToken extends Token +{ + protected $key; + protected $providerKey; + + /** + * The persistent token which resulted in this authentication token. + * + * @var PersistentTokenInterface + */ + protected $persistentToken; + + /** + * Constructor. + * + * @param string $username + * @param string $key + */ + public function __construct(AccountInterface $user, $providerKey, $key) { + parent::__construct($user->getRoles()); + + if (empty($key)) { + throw new \InvalidArgumentException('$key must not be empty.'); + } + if (empty($providerKey)) { + throw new \InvalidArgumentException('$providerKey must not be empty.'); + } + + $this->setUser($user); + $this->providerKey = $providerKey; + $this->key = $key; + $this->setAuthenticated(true); + } + + public function getProviderKey() + { + return $this->providerKey; + } + + public function getKey() + { + return $this->key; + } + + public function getPersistentToken() + { + return $this->persistentToken; + } + + public function setPersistentToken(PersistentTokenInterface $persistentToken) + { + $this->persistentToken = $persistentToken; + } +}
\ No newline at end of file diff --git a/Core/Authentication/Token/Token.php b/Core/Authentication/Token/Token.php new file mode 100644 index 0000000..d41bab5 --- /dev/null +++ b/Core/Authentication/Token/Token.php @@ -0,0 +1,199 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Token; + +use Symfony\Component\Security\Core\Role\RoleInterface; +use Symfony\Component\Security\Core\Role\Role; +use Symfony\Component\Security\Core\User\AccountInterface; + +/** + * Base class for Token instances. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +abstract class Token implements TokenInterface +{ + protected $roles; + protected $authenticated; + protected $user; + protected $credentials; + protected $immutable; + + /** + * Constructor. + * + * @param Role[] $roles An array of roles + */ + public function __construct(array $roles = array()) + { + $this->setRoles($roles); + $this->authenticated = false; + $this->immutable = false; + } + + /** + * Adds a Role to the token. + * + * @param RoleInterface $role A RoleInterface instance + */ + public function addRole(RoleInterface $role) + { + if ($this->immutable) { + throw new \LogicException('This token is considered immutable.'); + } + + $this->roles[] = $role; + } + + /** + * {@inheritdoc} + */ + public function getRoles() + { + return $this->roles; + } + + /** + * {@inheritDoc} + */ + public function setRoles(array $roles) + { + $this->roles = array(); + + foreach ($roles as $role) { + if (is_string($role)) { + $role = new Role($role); + } + + $this->addRole($role); + } + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + if (!is_object($this->user)) { + return (string) $this->user; + } elseif ($this->user instanceof AccountInterface) { + return $this->user->getUsername(); + } else { + return 'n/a'; + } + } + + /** + * {@inheritdoc} + */ + public function isAuthenticated() + { + return $this->authenticated; + } + + /** + * {@inheritdoc} + */ + public function setAuthenticated($authenticated) + { + if ($this->immutable) { + throw new \LogicException('This token is considered immutable.'); + } + + $this->authenticated = (Boolean) $authenticated; + } + + /** + * {@inheritdoc} + */ + public function getCredentials() + { + return $this->credentials; + } + + /** + * {@inheritdoc} + */ + public function getUser() + { + return $this->user; + } + + /** + * {@inheritDoc} + */ + public function setUser($user) + { + if ($this->immutable) { + throw new \LogicException('This token is considered immutable.'); + } + + if (!is_string($user) && !is_object($user)) { + throw new \InvalidArgumentException('$user must be an object, or a primitive string.'); + } else if (is_object($user) && !method_exists($user, '__toString')) { + throw new \InvalidArgumentException('If $user is an object, it must implement __toString().'); + } + + $this->user = $user; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + if ($this->immutable) { + throw new \LogicException('This token is considered immutable.'); + } + + if ($this->getCredentials() instanceof AccountInterface) { + $this->getCredentials()->eraseCredentials(); + } + + if ($this->getUser() instanceof AccountInterface) { + $this->getUser()->eraseCredentials(); + } + } + + /** + * {@inheritdoc} + */ + public function isImmutable() + { + return $this->immutable; + } + + /** + * {@inheritdoc} + */ + public function setImmutable() + { + $this->immutable = true; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array($this->user, $this->credentials, $this->authenticated, $this->roles, $this->immutable)); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + list($this->user, $this->credentials, $this->authenticated, $this->roles, $this->immutable) = unserialize($serialized); + } +} diff --git a/Core/Authentication/Token/TokenInterface.php b/Core/Authentication/Token/TokenInterface.php new file mode 100644 index 0000000..b6ac31c --- /dev/null +++ b/Core/Authentication/Token/TokenInterface.php @@ -0,0 +1,102 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Token; + +use Symfony\Component\Security\Core\User\AccountInterface; + +/** + * TokenInterface is the interface for the user authentication information. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface TokenInterface extends \Serializable +{ + /** + * Returns a string representation of the token. + * + * @return string A string representation + */ + function __toString(); + + /** + * Returns the user roles. + * + * @return Role[] An array of Role instances. + */ + function getRoles(); + + /** + * Sets the user's roles + * + * @param array $roles + * @return void + */ + function setRoles(array $roles); + + /** + * Returns the user credentials. + * + * @return mixed The user credentials + */ + function getCredentials(); + + /** + * Returns a user representation. + * + * @return mixed either returns an object which implements __toString(), or + * a primitive string is returned. + */ + function getUser(); + + /** + * Sets the user. + * + * @param mixed $user can either be an object which implements __toString(), or + * only a primitive string + */ + function setUser($user); + + /** + * Checks if the user is authenticated or not. + * + * @return Boolean true if the token has been authenticated, false otherwise + */ + function isAuthenticated(); + + /** + * Sets the authenticated flag. + * + * @param Boolean $isAuthenticated The authenticated flag + */ + function setAuthenticated($isAuthenticated); + + /** + * Whether this token is considered immutable + * + * @return Boolean + */ + function isImmutable(); + + /** + * Marks this token as immutable. This change cannot be reversed. + * + * You'll need to create a new token if you want a mutable token again. + * + * @return void + */ + function setImmutable(); + + /** + * Removes sensitive information from the token. + */ + function eraseCredentials(); +} diff --git a/Core/Authentication/Token/UsernamePasswordToken.php b/Core/Authentication/Token/UsernamePasswordToken.php new file mode 100644 index 0000000..a61acd4 --- /dev/null +++ b/Core/Authentication/Token/UsernamePasswordToken.php @@ -0,0 +1,66 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Token; + +/** + * UsernamePasswordToken implements a username and password token. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class UsernamePasswordToken extends Token +{ + protected $providerKey; + + /** + * Constructor. + * + * @param string $user The username (like a nickname, email address, etc.) + * @param string $credentials This usually is the password of the user + */ + public function __construct($user, $credentials, $providerKey, array $roles = array()) + { + parent::__construct($roles); + + $this->setUser($user); + $this->credentials = $credentials; + $this->providerKey = $providerKey; + + parent::setAuthenticated((Boolean) count($roles)); + } + + public function getProviderKey() + { + return $this->providerKey; + } + + /** + * {@inheritdoc} + */ + public function setAuthenticated($isAuthenticated) + { + if ($isAuthenticated) { + throw new \LogicException('Cannot set this token to trusted after instantiation.'); + } + + parent::setAuthenticated(false); + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + parent::eraseCredentials(); + + $this->credentials = null; + } +} diff --git a/Core/Authorization/AccessDecisionManager.php b/Core/Authorization/AccessDecisionManager.php new file mode 100644 index 0000000..d6e642c --- /dev/null +++ b/Core/Authorization/AccessDecisionManager.php @@ -0,0 +1,240 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * AccessDecisionManager is the base class for all access decision managers + * that use decision voters. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AccessDecisionManager implements AccessDecisionManagerInterface +{ + protected $voters; + protected $strategy; + protected $allowIfAllAbstainDecisions; + protected $allowIfEqualGrantedDeniedDecisions; + + /** + * Constructor. + * + * @param VoterInterface[] $voters An array of VoterInterface instances + * @param string $strategy The vote strategy + * @param Boolean $allowIfAllAbstainDecisions Whether to grant access if all voters abstained or not + */ + public function __construct(array $voters = array(), $strategy = 'affirmative', $allowIfAllAbstainDecisions = false, $allowIfEqualGrantedDeniedDecisions = true) + { + $this->voters = $voters; + $this->strategy = 'decide'.ucfirst($strategy); + $this->allowIfAllAbstainDecisions = (Boolean) $allowIfAllAbstainDecisions; + $this->allowIfEqualGrantedDeniedDecisions = (Boolean) $allowIfEqualGrantedDeniedDecisions; + } + + /** + * {@inheritdoc} + */ + public function decide(TokenInterface $token, array $attributes, $object = null) + { + return $this->{$this->strategy}($token, $attributes, $object); + } + + /** + * Returns all voters. + * + * @return VoterInterface[] $voters An array of VoterInterface instances + */ + public function getVoters() + { + return $this->voters; + } + + /** + * Sets voters. + * + * @param VoterInterface[] $voters An array of VoterInterface instances + */ + public function setVoters(array $voters) + { + if (!count($voters)) { + throw new \LogicException('You must have at least one voter.'); + } + + $this->voters = array(); + foreach ($voters as $voter) { + $this->addVoter($voter); + } + } + + /** + * Adds a voter. + * + * @param VoterInterface $voter A VoterInterface instance + */ + public function addVoter(VoterInterface $voter) + { + $this->voters[] = $voter; + } + + /** + * {@inheritdoc} + */ + public function supportsAttribute($attribute) + { + foreach ($this->voters as $voter) { + if ($voter->supportsAttribute($attribute)) { + return true; + } + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + foreach ($this->voters as $voter) { + if ($voter->supportsClass($class)) { + return true; + } + } + + return false; + } + + /** + * Grants access if any voter returns an affirmative response. + * + * If all voters abstained from voting, the decision will be based on the + * allowIfAllAbstainDecisions property value (defaults to false). + */ + protected function decideAffirmative(TokenInterface $token, array $attributes, $object = null) + { + $deny = 0; + foreach ($this->voters as $voter) { + $result = $voter->vote($token, $object, $attributes); + switch ($result) { + case VoterInterface::ACCESS_GRANTED: + return true; + + case VoterInterface::ACCESS_DENIED: + ++$deny; + + break; + + default: + break; + } + } + + if ($deny > 0) { + return false; + } + + return $this->allowIfAllAbstainDecisions; + } + + /** + * Grants access if there is consensus of granted against denied responses. + * + * Consensus means majority-rule (ignoring abstains) rather than unanimous + * agreement (ignoring abstains). If you require unanimity, see + * UnanimousBased. + * + * If there were an equal number of grant and deny votes, the decision will + * be based on the allowIfEqualGrantedDeniedDecisions property value + * (defaults to true). + * + * If all voters abstained from voting, the decision will be based on the + * allowIfAllAbstainDecisions property value (defaults to false). + */ + protected function decideConsensus(TokenInterface $token, array $attributes, $object = null) + { + $grant = 0; + $deny = 0; + $abstain = 0; + foreach ($this->voters as $voter) { + $result = $voter->vote($token, $object, $attributes); + + switch ($result) { + case VoterInterface::ACCESS_GRANTED: + ++$grant; + + break; + + case VoterInterface::ACCESS_DENIED: + ++$deny; + + break; + + default: + ++$abstain; + + break; + } + } + + if ($grant > $deny) { + return true; + } + + if ($deny > $grant) { + return false; + } + + if ($grant == $deny && $grant != 0) { + return $this->allowIfEqualGrantedDeniedDecisions; + } + + return $this->allowIfAllAbstainDecisions; + } + + /** + * Grants access if only grant (or abstain) votes were received. + * + * If all voters abstained from voting, the decision will be based on the + * allowIfAllAbstainDecisions property value (defaults to false). + */ + protected function decideUnanimous(TokenInterface $token, array $attributes, $object = null) + { + $grant = 0; + foreach ($attributes as $attribute) { + foreach ($this->voters as $voter) { + $result = $voter->vote($token, $object, array($attribute)); + + switch ($result) { + case VoterInterface::ACCESS_GRANTED: + ++$grant; + + break; + + case VoterInterface::ACCESS_DENIED: + return false; + + default: + break; + } + } + } + + // no deny votes + if ($grant > 0) { + return true; + } + + return $this->allowIfAllAbstainDecisions; + } +} diff --git a/Core/Authorization/AccessDecisionManagerInterface.php b/Core/Authorization/AccessDecisionManagerInterface.php new file mode 100644 index 0000000..7648a3b --- /dev/null +++ b/Core/Authorization/AccessDecisionManagerInterface.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * AccessDecisionManagerInterface makes authorization decisions. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface AccessDecisionManagerInterface +{ + /** + * Decides whether the access is possible or not. + * + * @param TokenInterface $token A TokenInterface instance + * @param array $attributes An array of attributes associated with the method being invoked + * @param object $object The object to secure + * + * @return Boolean true if the access is granted, false otherwise + */ + function decide(TokenInterface $token, array $attributes, $object = null); + + /** + * Checks if the access decision manager supports the given attribute. + * + * @param string $attribute An attribute + * + * @return Boolean true if this decision manager supports the attribute, false otherwise + */ + function supportsAttribute($attribute); + + /** + * Checks if the access decision manager supports the given class. + * + * @param string $class A class name + * + * @return true if this decision manager can process the class + */ + function supportsClass($class); +} diff --git a/Core/Authorization/Voter/AuthenticatedVoter.php b/Core/Authorization/Voter/AuthenticatedVoter.php new file mode 100644 index 0000000..a400e4d --- /dev/null +++ b/Core/Authorization/Voter/AuthenticatedVoter.php @@ -0,0 +1,96 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\AuthenticationTrustResolverInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * AuthenticatedVoter votes if an attribute like IS_AUTHENTICATED_FULLY, + * IS_AUTHENTICATED_REMEMBERED, or IS_AUTHENTICATED_ANONYMOUSLY is present. + * + * This list is most restrictive to least restrictive checking. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class AuthenticatedVoter implements VoterInterface +{ + const IS_AUTHENTICATED_FULLY = 'IS_AUTHENTICATED_FULLY'; + const IS_AUTHENTICATED_REMEMBERED = 'IS_AUTHENTICATED_REMEMBERED'; + const IS_AUTHENTICATED_ANONYMOUSLY = 'IS_AUTHENTICATED_ANONYMOUSLY'; + + protected $authenticationTrustResolver; + + /** + * Constructor. + * + * @param AuthenticationTrustResolverInterface $authenticationTrustResolver + * + * @return void + */ + public function __construct(AuthenticationTrustResolverInterface $authenticationTrustResolver) + { + $this->authenticationTrustResolver = $authenticationTrustResolver; + } + + /** + * {@inheritdoc} + */ + public function supportsAttribute($attribute) + { + return null !== $attribute && (self::IS_AUTHENTICATED_FULLY === $attribute || self::IS_AUTHENTICATED_REMEMBERED === $attribute || self::IS_AUTHENTICATED_ANONYMOUSLY === $attribute); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function vote(TokenInterface $token, $object, array $attributes) + { + $result = VoterInterface::ACCESS_ABSTAIN; + foreach ($attributes as $attribute) { + if (!$this->supportsAttribute($attribute)) { + continue; + } + + $result = VoterInterface::ACCESS_DENIED; + + if (self::IS_AUTHENTICATED_FULLY === $attribute + && $this->authenticationTrustResolver->isFullFledged($token)) { + return VoterInterface::ACCESS_GRANTED; + } + + if (self::IS_AUTHENTICATED_REMEMBERED === $attribute + && ($this->authenticationTrustResolver->isRememberMe($token) + || $this->authenticationTrustResolver->isFullFledged($token))) { + return VoterInterface::ACCESS_GRANTED; + } + + if (self::IS_AUTHENTICATED_ANONYMOUSLY === $attribute + && ($this->authenticationTrustResolver->isAnonymous($token) + || $this->authenticationTrustResolver->isRememberMe($token) + || $this->authenticationTrustResolver->isFullFledged($token))) { + return VoterInterface::ACCESS_GRANTED; + } + } + + return $result; + } +} diff --git a/Core/Authorization/Voter/RoleHierarchyVoter.php b/Core/Authorization/Voter/RoleHierarchyVoter.php new file mode 100644 index 0000000..7bdff3d --- /dev/null +++ b/Core/Authorization/Voter/RoleHierarchyVoter.php @@ -0,0 +1,41 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; +use Symfony\Component\Security\Core\Role\RoleHierarchyInterface; + +/** + * RoleHierarchyVoter uses a RoleHierarchy to determine the roles granted to + * the user before voting. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class RoleHierarchyVoter extends RoleVoter +{ + protected $roleHierarchy; + + public function __construct(RoleHierarchyInterface $roleHierarchy, $prefix = 'ROLE_') + { + $this->roleHierarchy = $roleHierarchy; + + parent::__construct($prefix); + } + + /** + * {@inheritdoc} + */ + protected function extractRoles(TokenInterface $token) + { + return $this->roleHierarchy->getReachableRoles($token->getRoles()); + } +} diff --git a/Core/Authorization/Voter/RoleVoter.php b/Core/Authorization/Voter/RoleVoter.php new file mode 100644 index 0000000..426093a --- /dev/null +++ b/Core/Authorization/Voter/RoleVoter.php @@ -0,0 +1,79 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * RoleVoter votes if any attribute starts with a given prefix. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class RoleVoter implements VoterInterface +{ + protected $prefix; + + /** + * Constructor. + * + * @param string $prefix The role prefix + */ + public function __construct($prefix = 'ROLE_') + { + $this->prefix = $prefix; + } + + /** + * {@inheritdoc} + */ + public function supportsAttribute($attribute) + { + return 0 === strpos($attribute, $this->prefix); + } + + /** + * {@inheritdoc} + */ + public function supportsClass($class) + { + return true; + } + + /** + * {@inheritdoc} + */ + public function vote(TokenInterface $token, $object, array $attributes) + { + $result = VoterInterface::ACCESS_ABSTAIN; + $roles = $this->extractRoles($token); + + foreach ($attributes as $attribute) { + if (!$this->supportsAttribute($attribute)) { + continue; + } + + $result = VoterInterface::ACCESS_DENIED; + foreach ($roles as $role) { + if ($attribute === $role->getRole()) { + return VoterInterface::ACCESS_GRANTED; + } + } + } + + return $result; + } + + protected function extractRoles(TokenInterface $token) + { + return $token->getRoles(); + } +} diff --git a/Core/Authorization/Voter/VoterInterface.php b/Core/Authorization/Voter/VoterInterface.php new file mode 100644 index 0000000..add6e19 --- /dev/null +++ b/Core/Authorization/Voter/VoterInterface.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * VoterInterface is the interface implemented by all voters. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface VoterInterface +{ + const ACCESS_GRANTED = 1; + const ACCESS_ABSTAIN = 0; + const ACCESS_DENIED = -1; + + /** + * Checks if the voter supports the given attribute. + * + * @param string $attribute An attribute + * + * @return Boolean true if this Voter supports the attribute, false otherwise + */ + function supportsAttribute($attribute); + + /** + * Checks if the voter supports the given class. + * + * @param string $class A class name + * + * @return true if this Voter can process the class + */ + function supportsClass($class); + + /** + * Returns the vote for the given parameters. + * + * This method must return one of the following constant: + * ACCESS_GRANTED, ACCESS_DENIED, or ACCESS_ABSTAIN. + * + * @param TokenInterface $token A TokenInterface instance + * @param object $object The object to secure + * @param array $attributes An array of attributes associated with the method being invoked + * + * @return integer either ACCESS_GRANTED, ACCESS_ABSTAIN, or ACCESS_DENIED + */ + function vote(TokenInterface $token, $object, array $attributes); +} diff --git a/Core/Encoder/BasePasswordEncoder.php b/Core/Encoder/BasePasswordEncoder.php new file mode 100644 index 0000000..01f471c --- /dev/null +++ b/Core/Encoder/BasePasswordEncoder.php @@ -0,0 +1,91 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +/** + * BasePasswordEncoder is the base class for all password encoders. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +abstract class BasePasswordEncoder implements PasswordEncoderInterface +{ + /** + * Demerges a merge password and salt string. + * + * @param string $mergedPasswordSalt The merged password and salt string + * + * @return array An array where the first element is the password and the second the salt + */ + protected function demergePasswordAndSalt($mergedPasswordSalt) + { + if (empty($mergedPasswordSalt)) { + return array('', ''); + } + + $password = $mergedPasswordSalt; + $salt = ''; + $saltBegins = strrpos($mergedPasswordSalt, '{'); + + if (false !== $saltBegins && $saltBegins + 1 < strlen($mergedPasswordSalt)) { + $salt = substr($mergedPasswordSalt, $saltBegins + 1, -1); + $password = substr($mergedPasswordSalt, 0, $saltBegins); + } + + return array($password, $salt); + } + + /** + * Merges a password and a salt. + * + * @param string $password the password to be used + * @param string $salt the salt to be used + * + * @return string a merged password and salt + */ + protected function mergePasswordAndSalt($password, $salt) + { + if (empty($salt)) { + return $password; + } + + if (false !== strrpos($salt, '{') || false !== strrpos($salt, '}')) { + throw new \InvalidArgumentException('Cannot use { or } in salt.'); + } + + return $password.'{'.$salt.'}'; + } + + /** + * Compares two passwords. + * + * This method implements a constant-time algorithm to compare passwords to + * avoid (remote) timing attacks. + * + * @param string $password1 The first password + * @param string $password2 The second password + * + * @return Boolean true if the two passwords are the same, false otherwise + */ + protected function comparePasswords($password1, $password2) + { + if (strlen($password1) !== strlen($password2)) { + return false; + } + + $result = 0; + for ($i = 0; $i < strlen($password1); $i++) { + $result |= ord($password1[$i]) ^ ord($password2[$i]); + } + + return 0 === $result; + } +} diff --git a/Core/Encoder/EncoderFactory.php b/Core/Encoder/EncoderFactory.php new file mode 100644 index 0000000..0f218fe --- /dev/null +++ b/Core/Encoder/EncoderFactory.php @@ -0,0 +1,77 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +use Symfony\Component\Security\Core\User\AccountInterface; + +/** + * A generic encoder factory implementation + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class EncoderFactory implements EncoderFactoryInterface +{ + protected $encoders; + protected $encoderMap; + + public function __construct(array $encoderMap) + { + $this->encoders = array(); + $this->encoderMap = $encoderMap; + } + + /** + * {@inheritDoc} + */ + public function getEncoder(AccountInterface $account) + { + foreach ($this->encoders as $class => $encoder) { + if ($account instanceof $class) { + return $encoder; + } + } + + return $this->createEncoder($account); + } + + /** + * Adds an encoder instance to the factory + * + * @param string $class + * @param PasswordEncoderInterface $encoder + * @return void + */ + public function addEncoder($class, PasswordEncoderInterface $encoder) + { + $this->encoders[$class] = $encoder; + } + + /** + * Creates the actual encoder instance + * + * @param AccountInterface $account + * @return PasswordEncoderInterface + */ + protected function createEncoder($account) + { + foreach ($this->encoderMap as $class => $config) { + if ($account instanceof $class) { + $reflection = new \ReflectionClass($config['class']); + $this->encoders[$class] = $reflection->newInstanceArgs($config['arguments']); + + return $this->encoders[$class]; + } + } + + throw new \InvalidArgumentException(sprintf('No encoder has been configured for account "%s".', get_class($account))); + } +}
\ No newline at end of file diff --git a/Core/Encoder/EncoderFactoryInterface.php b/Core/Encoder/EncoderFactoryInterface.php new file mode 100644 index 0000000..2bdf6fc --- /dev/null +++ b/Core/Encoder/EncoderFactoryInterface.php @@ -0,0 +1,30 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +use Symfony\Component\Security\Core\User\AccountInterface; + +/** + * EncoderFactoryInterface to support different encoders for different accounts. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +interface EncoderFactoryInterface +{ + /** + * Returns the password encoder to use for the given account + * + * @param AccountInterface $account + * @return PasswordEncoderInterface never null + */ + function getEncoder(AccountInterface $account); +}
\ No newline at end of file diff --git a/Core/Encoder/MessageDigestPasswordEncoder.php b/Core/Encoder/MessageDigestPasswordEncoder.php new file mode 100644 index 0000000..811dd4c --- /dev/null +++ b/Core/Encoder/MessageDigestPasswordEncoder.php @@ -0,0 +1,65 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +/** + * MessageDigestPasswordEncoder uses a message digest algorithm. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class MessageDigestPasswordEncoder extends BasePasswordEncoder +{ + protected $algorithm; + protected $encodeHashAsBase64; + + /** + * Constructor. + * + * @param string $algorithm The digest algorithm to use + * @param Boolean $encodeHashAsBase64 Whether to base64 encode the password hash + * @param integer $iterations The number of iterations to use to stretch the password hash + */ + public function __construct($algorithm = 'sha256', $encodeHashAsBase64 = false, $iterations = 1) + { + $this->algorithm = $algorithm; + $this->encodeHashAsBase64 = $encodeHashAsBase64; + $this->iterations = $iterations; + } + + /** + * {@inheritdoc} + */ + public function encodePassword($raw, $salt) + { + if (!in_array($this->algorithm, hash_algos(), true)) { + throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $this->algorithm)); + } + + $salted = $this->mergePasswordAndSalt($raw, $salt); + $digest = hash($this->algorithm, $salted, true); + + // "stretch" hash + for ($i = 1; $i < $this->iterations; $i++) { + $digest = hash($this->algorithm, $digest, true); + } + + return $this->encodeHashAsBase64 ? base64_encode($digest) : bin2hex($digest); + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid($encoded, $raw, $salt) + { + return $this->comparePasswords($encoded, $this->encodePassword($raw, $salt)); + } +} diff --git a/Core/Encoder/PasswordEncoderInterface.php b/Core/Encoder/PasswordEncoderInterface.php new file mode 100644 index 0000000..393b779 --- /dev/null +++ b/Core/Encoder/PasswordEncoderInterface.php @@ -0,0 +1,41 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +/** + * PasswordEncoderInterface is the interface for all encoders. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface PasswordEncoderInterface +{ + /** + * Encodes the raw password. + * + * @param string $raw The password to encode + * @param string $salt The salt + * + * @return string The encoded password + */ + function encodePassword($raw, $salt); + + /** + * Checks a raw password against an encoded password. + * + * @param string $encoded An encoded password + * @param string $raw A raw password + * @param string $salt The salt + * + * @return Boolean true if the password is valid, false otherwise + */ + function isPasswordValid($encoded, $raw, $salt); +} diff --git a/Core/Encoder/PlaintextPasswordEncoder.php b/Core/Encoder/PlaintextPasswordEncoder.php new file mode 100644 index 0000000..98982b0 --- /dev/null +++ b/Core/Encoder/PlaintextPasswordEncoder.php @@ -0,0 +1,49 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Security\Core\Encoder; + +/** + * PlaintextPasswordEncoder does not do any encoding. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class PlaintextPasswordEncoder extends BasePasswordEncoder +{ + protected $ignorePasswordCase; + + public function __construct($ignorePasswordCase = false) + { + $this->ignorePasswordCase = $ignorePasswordCase; + } + + /** + * {@inheritdoc} + */ + public function encodePassword($raw, $salt) + { + return $this->mergePasswordAndSalt($raw, $salt); + } + + /** + * {@inheritdoc} + */ + public function isPasswordValid($encoded, $raw, $salt) + { + $pass2 = $this->mergePasswordAndSalt($raw, $salt); + + if (!$this->ignorePasswordCase) { + return $this->comparePasswords($encoded, $pass2); + } else { + return $this->comparePasswords(strtolower($encoded), strtolower($pass2)); + } + } +} diff --git a/Core/Exception/AccessDeniedException.php b/Core/Exception/AccessDeniedException.php new file mode 100644 index 0000000..d065ed7 --- /dev/null +++ b/Core/Exception/AccessDeniedException.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AccessDeniedException is thrown when the account has not the required role. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AccessDeniedException extends \RuntimeException +{ + public function __construct($message = 'Access Denied', \Exception $previous = null) + { + parent::__construct($message, 403, $previous); + } +} diff --git a/Core/Exception/AccountExpiredException.php b/Core/Exception/AccountExpiredException.php new file mode 100644 index 0000000..f0a09f7 --- /dev/null +++ b/Core/Exception/AccountExpiredException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AccountExpiredException is thrown when the user account has expired. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AccountExpiredException extends AccountStatusException +{ +} diff --git a/Core/Exception/AccountStatusException.php b/Core/Exception/AccountStatusException.php new file mode 100644 index 0000000..4828d20 --- /dev/null +++ b/Core/Exception/AccountStatusException.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AccountStatusException is the base class for authentication exceptions + * caused by the user account status. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +abstract class AccountStatusException extends AuthenticationException +{ +} diff --git a/Core/Exception/AuthenticationCredentialsNotFoundException.php b/Core/Exception/AuthenticationCredentialsNotFoundException.php new file mode 100644 index 0000000..4f95127 --- /dev/null +++ b/Core/Exception/AuthenticationCredentialsNotFoundException.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AuthenticationCredentialsNotFoundException is thrown when an authentication is rejected + * because no Token is available. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AuthenticationCredentialsNotFoundException extends AuthenticationException +{ +} diff --git a/Core/Exception/AuthenticationException.php b/Core/Exception/AuthenticationException.php new file mode 100644 index 0000000..a43b998 --- /dev/null +++ b/Core/Exception/AuthenticationException.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AuthenticationException is the base class for all authentication exceptions. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AuthenticationException extends \RuntimeException +{ + protected $extraInformation; + + public function __construct($message, $extraInformation = null, $code = 0, \Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->extraInformation = $extraInformation; + } + + public function getExtraInformation() + { + return $this->extraInformation; + } + + public function setExtraInformation($extraInformation) + { + $this->extraInformation = $extraInformation; + } +} diff --git a/Core/Exception/AuthenticationServiceException.php b/Core/Exception/AuthenticationServiceException.php new file mode 100644 index 0000000..02fcc2f --- /dev/null +++ b/Core/Exception/AuthenticationServiceException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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 authentication request could not be processed due to a system problem. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AuthenticationServiceException extends AuthenticationException +{ +} diff --git a/Core/Exception/BadCredentialsException.php b/Core/Exception/BadCredentialsException.php new file mode 100644 index 0000000..797a806 --- /dev/null +++ b/Core/Exception/BadCredentialsException.php @@ -0,0 +1,25 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * BadCredentialsException is thrown when the user credentials are invalid. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class BadCredentialsException extends AuthenticationException +{ + public function __construct($message, $code = 0, \Exception $previous = null) + { + parent::__construct($message, null, $code, $previous); + } +} diff --git a/Core/Exception/CookieTheftException.php b/Core/Exception/CookieTheftException.php new file mode 100644 index 0000000..64a06ca --- /dev/null +++ b/Core/Exception/CookieTheftException.php @@ -0,0 +1,22 @@ +<?php + +namespace Symfony\Component\Security\Core\Exception; + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * This exception is thrown when the RememberMeServices implementation + * detects that a presented cookie has already been used by someone else. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class CookieTheftException extends AuthenticationException +{ +}
\ No newline at end of file diff --git a/Core/Exception/CredentialsExpiredException.php b/Core/Exception/CredentialsExpiredException.php new file mode 100644 index 0000000..43ba982 --- /dev/null +++ b/Core/Exception/CredentialsExpiredException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * CredentialsExpiredException is thrown when the user account credentials have expired. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class CredentialsExpiredException extends AccountStatusException +{ +} diff --git a/Core/Exception/DisabledException.php b/Core/Exception/DisabledException.php new file mode 100644 index 0000000..fd87947 --- /dev/null +++ b/Core/Exception/DisabledException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * DisabledException is thrown when the user account is disabled. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class DisabledException extends AccountStatusException +{ +} diff --git a/Core/Exception/InsufficientAuthenticationException.php b/Core/Exception/InsufficientAuthenticationException.php new file mode 100644 index 0000000..3fbba35 --- /dev/null +++ b/Core/Exception/InsufficientAuthenticationException.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * InsufficientAuthenticationException is thrown if the user credentials are not sufficiently trusted. + * + * This is the case when a user is anonymous and the resource to be displayed has an access role. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class InsufficientAuthenticationException extends AuthenticationException +{ +} diff --git a/Core/Exception/LockedException.php b/Core/Exception/LockedException.php new file mode 100644 index 0000000..8ea820f --- /dev/null +++ b/Core/Exception/LockedException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * LockedException is thrown if the user account is locked. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class LockedException extends AccountStatusException +{ +} diff --git a/Core/Exception/NonceExpiredException.php b/Core/Exception/NonceExpiredException.php new file mode 100644 index 0000000..5e6a0c5 --- /dev/null +++ b/Core/Exception/NonceExpiredException.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Security\EntryPoint; + +use Symfony\Component\Security\Core\Exception\AuthenticationException; +use Symfony\Component\Security\Core\Authentication\EntryPoint\AuthenticationEntryPointInterface; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Log\LoggerInterface; + +/** + * NonceExpiredException is thrown when an authentication is rejected because + * the digest nonce has expired. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class NonceExpiredException extends AuthenticationException +{ +} diff --git a/Core/Exception/ProviderNotFoundException.php b/Core/Exception/ProviderNotFoundException.php new file mode 100644 index 0000000..50112c5 --- /dev/null +++ b/Core/Exception/ProviderNotFoundException.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * ProviderNotFoundException is thrown when no AuthenticationProviderInterface instance + * supports an authentication Token. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class ProviderNotFoundException extends AuthenticationException +{ +} diff --git a/Core/Exception/TokenNotFoundException.php b/Core/Exception/TokenNotFoundException.php new file mode 100644 index 0000000..1c13421 --- /dev/null +++ b/Core/Exception/TokenNotFoundException.php @@ -0,0 +1,20 @@ +<?php +namespace Symfony\Component\Security\Core\Exception; + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * TokenNotFoundException is thrown if a Token cannot be found. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class TokenNotFoundException extends AuthenticationException +{ +} diff --git a/Core/Exception/UnsupportedAccountException.php b/Core/Exception/UnsupportedAccountException.php new file mode 100644 index 0000000..0704b65 --- /dev/null +++ b/Core/Exception/UnsupportedAccountException.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * This exception is thrown when an account is reloaded from a provider which + * doesn't support the passed implementation of AccountInterface. + * + * @author Johannes M. Schmitt <schmittjoh@gmail.com> + */ +class UnsupportedAccountException extends AuthenticationServiceException +{ +}
\ No newline at end of file diff --git a/Core/Exception/UsernameNotFoundException.php b/Core/Exception/UsernameNotFoundException.php new file mode 100644 index 0000000..a1733fe --- /dev/null +++ b/Core/Exception/UsernameNotFoundException.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * UsernameNotFoundException is thrown if a User cannot be found by its username. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class UsernameNotFoundException extends AuthenticationException +{ +} diff --git a/Core/Role/Role.php b/Core/Role/Role.php new file mode 100644 index 0000000..20e4fd5 --- /dev/null +++ b/Core/Role/Role.php @@ -0,0 +1,41 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Role; + +/** + * Role is a simple implementation of a RoleInterface where the role is a + * string. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class Role implements RoleInterface +{ + protected $role; + + /** + * Constructor. + * + * @param string $role The role name + */ + public function __construct($role) + { + $this->role = (string) $role; + } + + /** + * {@inheritdoc} + */ + public function getRole() + { + return $this->role; + } +} diff --git a/Core/Role/RoleHierarchy.php b/Core/Role/RoleHierarchy.php new file mode 100644 index 0000000..9556801 --- /dev/null +++ b/Core/Role/RoleHierarchy.php @@ -0,0 +1,77 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Role; + +/** + * RoleHierarchy defines a role hierarchy. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class RoleHierarchy implements RoleHierarchyInterface +{ + protected $hierarchy; + protected $map; + + /** + * Constructor. + * + * @param array $hierarchy An array defining the hierarchy + */ + public function __construct(array $hierarchy) + { + $this->hierarchy = $hierarchy; + + $this->buildRoleMap(); + } + + /** + * Returns an array of all roles reachable by the given ones. + * + * @param RoleInterface[] $roles An array of RoleInterface instances + * + * @return RoleInterface[] An array of RoleInterface instances + */ + public function getReachableRoles(array $roles) + { + $reachableRoles = $roles; + foreach ($roles as $role) { + if (!isset($this->map[$role->getRole()])) { + continue; + } + + foreach ($this->map[$role->getRole()] as $r) { + $reachableRoles[] = new Role($r); + } + } + + return $reachableRoles; + } + + protected function buildRoleMap() + { + $this->map = array(); + foreach ($this->hierarchy as $main => $roles) { + $this->map[$main] = $roles; + $visited = array(); + $additionalRoles = $roles; + while ($role = array_shift($additionalRoles)) { + if (!isset($this->hierarchy[$role])) { + continue; + } + + $visited[] = $role; + $this->map[$main] = array_unique(array_merge($this->map[$main], $this->hierarchy[$role])); + $additionalRoles = array_merge($additionalRoles, array_diff($this->hierarchy[$role], $visited)); + } + } + } +} diff --git a/Core/Role/RoleHierarchyInterface.php b/Core/Role/RoleHierarchyInterface.php new file mode 100644 index 0000000..9f5cd5d --- /dev/null +++ b/Core/Role/RoleHierarchyInterface.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Role; + +/** + * RoleHierarchyInterface is the interface for a role hierarchy. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface RoleHierarchyInterface +{ + /** + * Returns an array of all reachable roles. + * + * Reachable roles are the roles directly assigned but also all roles that + * are transitively reachable from them in the role hierarchy. + * + * @param array $roles An array of directly assigned roles + * + * @return array An array of all reachable roles + */ + function getReachableRoles(array $roles); +} diff --git a/Core/Role/RoleInterface.php b/Core/Role/RoleInterface.php new file mode 100644 index 0000000..923a933 --- /dev/null +++ b/Core/Role/RoleInterface.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Role; + +/** + * RoleInterface represents a role granted to a user. + * + * A role must either have a string representation or it needs to be explicitly + * supported by an at least one AccessDecisionManager. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface RoleInterface +{ + /** + * Returns the role. + * + * This method returns a string representation whenever possible. + * + * When the role cannot be represented with sufficient precision by a + * string, it should return null. + * + * @return string|null A string representation of the role, or null + */ + function getRole(); +} diff --git a/Core/Role/SwitchUserRole.php b/Core/Role/SwitchUserRole.php new file mode 100644 index 0000000..589129c --- /dev/null +++ b/Core/Role/SwitchUserRole.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\Role; + +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; + +/** + * SwitchUserRole is used when the current user temporarily impersonates + * another one. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class SwitchUserRole extends Role +{ + protected $source; + + /** + * Constructor. + * + * @param string $role The role as a string + * @param TokenInterface $source The original token + */ + public function __construct($role, TokenInterface $source) + { + parent::__construct($role); + + $this->source = $source; + } + + /** + * Returns the original Token. + * + * @return TokenInterface The original TokenInterface instance + */ + public function getSource() + { + return $this->source; + } +} diff --git a/Core/SecurityContext.php b/Core/SecurityContext.php new file mode 100644 index 0000000..405ace9 --- /dev/null +++ b/Core/SecurityContext.php @@ -0,0 +1,94 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; +use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Acl\Voter\FieldVote; + +/** + * SecurityContext is the main entry point of the Security component. + * + * It gives access to the token representing the current user authentication. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class SecurityContext +{ + const ACCESS_DENIED_ERROR = '_security.403_error'; + const AUTHENTICATION_ERROR = '_security.last_error'; + const LAST_USERNAME = '_security.last_username'; + + protected $token; + protected $accessDecisionManager; + protected $authenticationManager; + protected $alwaysAuthenticate; + + /** + * Constructor. + * + * @param AccessDecisionManagerInterface|null $accessDecisionManager An AccessDecisionManager instance + */ + public function __construct(AuthenticationManagerInterface $authenticationManager, AccessDecisionManagerInterface $accessDecisionManager = null, $alwaysAuthenticate = false) + { + $this->authenticationManager = $authenticationManager; + $this->accessDecisionManager = $accessDecisionManager; + $this->alwaysAuthenticate = $alwaysAuthenticate; + } + + public function getUser() + { + return null === $this->token ? null : $this->token->getUser(); + } + + public function vote($attributes, $object = null, $field = null) + { + if (null === $this->token || null === $this->accessDecisionManager) { + return false; + } + + if ($field !== null) { + if (null === $object) { + throw new \InvalidArgumentException('$object cannot be null when field is not null.'); + } + + $object = new FieldVote($object, $field); + } + + if ($this->alwaysAuthenticate || !$this->token->isAuthenticated()) { + $this->token = $this->authenticationManager->authenticate($this->token); + } + + return $this->accessDecisionManager->decide($this->token, (array) $attributes, $object); + } + + /** + * Gets the currently authenticated token. + * + * @return TokenInterface|null A TokenInterface instance or null if no authentication information is available + */ + public function getToken() + { + return $this->token; + } + + /** + * Sets the currently authenticated token. + * + * @param TokenInterface $token A TokenInterface token, or null if no further authentication information should be stored + */ + public function setToken(TokenInterface $token = null) + { + $this->token = $token; + } +} diff --git a/Core/User/AccountChecker.php b/Core/User/AccountChecker.php new file mode 100644 index 0000000..76befa6 --- /dev/null +++ b/Core/User/AccountChecker.php @@ -0,0 +1,61 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\CredentialsExpiredException; +use Symfony\Component\Security\Core\Exception\LockedException; +use Symfony\Component\Security\Core\Exception\DisabledException; +use Symfony\Component\Security\Core\Exception\AccountExpiredException; + +/** + * AccountChecker checks the user account flags. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class AccountChecker implements AccountCheckerInterface +{ + /** + * {@inheritdoc} + */ + public function checkPreAuth(AccountInterface $account) + { + if (!$account instanceof AdvancedAccountInterface) { + return; + } + + if (!$account->isCredentialsNonExpired()) { + throw new CredentialsExpiredException('User credentials have expired.', $account); + } + } + + /** + * {@inheritdoc} + */ + public function checkPostAuth(AccountInterface $account) + { + if (!$account instanceof AdvancedAccountInterface) { + return; + } + + if (!$account->isAccountNonLocked()) { + throw new LockedException('User account is locked.', $account); + } + + if (!$account->isEnabled()) { + throw new DisabledException('User account is disabled.', $account); + } + + if (!$account->isAccountNonExpired()) { + throw new AccountExpiredException('User account has expired.', $account); + } + } +} diff --git a/Core/User/AccountCheckerInterface.php b/Core/User/AccountCheckerInterface.php new file mode 100644 index 0000000..cf0d68b --- /dev/null +++ b/Core/User/AccountCheckerInterface.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AccountCheckerInterface checks user account when authentication occurs. + * + * This should not be used to make authentication decisions. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface AccountCheckerInterface +{ + /** + * Checks the user account before authentication. + * + * @param AccountInterface $account An AccountInterface instance + */ + function checkPreAuth(AccountInterface $account); + + /** + * Checks the user account after authentication. + * + * @param AccountInterface $account An AccountInterface instance + */ + function checkPostAuth(AccountInterface $account); +} diff --git a/Core/User/AccountInterface.php b/Core/User/AccountInterface.php new file mode 100644 index 0000000..1863302 --- /dev/null +++ b/Core/User/AccountInterface.php @@ -0,0 +1,74 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AccountInterface is the interface that user classes must implement. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface AccountInterface +{ + /** + * Returns a string representation of the User. + * + * @return string A string return of the User + */ + function __toString(); + + /** + * Returns the roles granted to the user. + * + * @return Role[] The user roles + */ + function getRoles(); + + /** + * Returns the password used to authenticate the user. + * + * @return string The password + */ + function getPassword(); + + /** + * Returns the salt. + * + * @return string The salt + */ + function getSalt(); + + /** + * Returns the username used to authenticate the user. + * + * @return string The username + */ + function getUsername(); + + /** + * Removes sensitive data from the user. + * + * @return void + */ + function eraseCredentials(); + + /** + * The equality comparison should neither be done by referential equality + * nor by comparing identities (i.e. getId() === getId()). + * + * However, you do not need to compare every attribute, but only those that + * are relevant for assessing whether re-authentication is required. + * + * @param AccountInterface $account + * @return Boolean + */ + function equals(AccountInterface $account); +} diff --git a/Core/User/AdvancedAccountInterface.php b/Core/User/AdvancedAccountInterface.php new file mode 100644 index 0000000..654ccaf --- /dev/null +++ b/Core/User/AdvancedAccountInterface.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * AdvancedAccountInterface adds status flags to a regular account. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface AdvancedAccountInterface extends AccountInterface +{ + /** + * Checks whether the user's account has expired. + * + * @return Boolean true if the user's account is non expired, false otherwise + */ + function isAccountNonExpired(); + + /** + * Checks whether the user is locked. + * + * @return Boolean true if the user is not locked, false otherwise + */ + function isAccountNonLocked(); + + /** + * Checks whether the user's credentials (password) has expired. + * + * @return Boolean true if the user's credentials are non expired, false otherwise + */ + function isCredentialsNonExpired(); + + /** + * Checks whether the user is enabled. + * + * @return Boolean true if the user is enabled, false otherwise + */ + function isEnabled(); +} diff --git a/Core/User/InMemoryUserProvider.php b/Core/User/InMemoryUserProvider.php new file mode 100644 index 0000000..cc15463 --- /dev/null +++ b/Core/User/InMemoryUserProvider.php @@ -0,0 +1,98 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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\UsernameNotFoundException; +use Symfony\Component\Security\Core\Exception\UnsupportedAccountException; + +/** + * InMemoryUserProvider is a simple non persistent user provider. + * + * Useful for testing, demonstration, prototyping, and for simple needs + * (a backend with a unique admin for instance) + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class InMemoryUserProvider implements UserProviderInterface +{ + protected $users; + + /** + * Constructor. + * + * The user array is a hash where the keys are usernames and the values are + * an array of attributes: 'password', 'enabled', and 'roles'. + * + * @param array $users An array of users + * @param string $name + */ + public function __construct(array $users = array()) + { + foreach ($users as $username => $attributes) { + $password = isset($attributes['password']) ? $attributes['password'] : null; + $enabled = isset($attributes['enabled']) ? $attributes['enabled'] : true; + $roles = isset($attributes['roles']) ? $attributes['roles'] : array(); + $user = new User($username, $password, $roles, $enabled, true, true, true); + + $this->createUser($user); + } + } + + /** + * Adds a new User to the provider. + * + * @param AccountInterface $user A AccountInterface instance + */ + public function createUser(AccountInterface $user) + { + if (isset($this->users[strtolower($user->getUsername())])) { + throw new \LogicException('Another user with the same username already exist.'); + } + + $this->users[strtolower($user->getUsername())] = $user; + } + + /** + * {@inheritdoc} + */ + public function loadUserByUsername($username) + { + if (!isset($this->users[strtolower($username)])) { + throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username)); + } + + $user = $this->users[strtolower($username)]; + + return new User($user->getUsername(), $user->getPassword(), $user->getRoles(), $user->isEnabled(), $user->isAccountNonExpired(), + $user->isCredentialsNonExpired(), $user->isAccountNonLocked()); + } + + /** + * {@inheritDoc} + */ + public function loadUserByAccount(AccountInterface $account) + { + if (!$account instanceof User) { + throw new UnsupportedAccountException(sprintf('Instances of "%s" are not supported.', get_class($account))); + } + + return $this->loadUserByUsername((string) $account); + } + + /** + * {@inheritDoc} + */ + public function supportsClass($class) + { + return $class === 'Symfony\Component\Security\Core\User\User'; + } +} diff --git a/Core/User/User.php b/Core/User/User.php new file mode 100644 index 0000000..49f7042 --- /dev/null +++ b/Core/User/User.php @@ -0,0 +1,163 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * User is the user implementation used by the in-memory user provider. + * + * This should not be used for anything else. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +class User implements AdvancedAccountInterface +{ + protected $username; + protected $password; + protected $accountNonExpired; + protected $credentialsNonExpired; + protected $accountNonLocked; + protected $roles; + + public function __construct($username, $password, array $roles = array(), $enabled = true, $accountNonExpired = true, $credentialsNonExpired = true, $accountNonLocked = true) + { + if (empty($username)) { + throw new \InvalidArgumentException('The username cannot be empty.'); + } + + $this->username = $username; + $this->password = $password; + $this->enabled = $enabled; + $this->accountNonExpired = $accountNonExpired; + $this->credentialsNonExpired = $credentialsNonExpired; + $this->accountNonLocked = $accountNonLocked; + $this->roles = $roles; + } + + /** + * {@inheritdoc} + */ + public function __toString() + { + return $this->username; + } + + /** + * {@inheritdoc} + */ + public function getRoles() + { + return $this->roles; + } + + /** + * {@inheritdoc} + */ + public function getPassword() + { + return $this->password; + } + + /** + * {@inheritdoc} + */ + public function getSalt() + { + return null; + } + + /** + * {@inheritdoc} + */ + public function getUsername() + { + return $this->username; + } + + /** + * {@inheritdoc} + */ + public function isAccountNonExpired() + { + return $this->accountNonExpired; + } + + /** + * {@inheritdoc} + */ + public function isAccountNonLocked() + { + return $this->accountNonLocked; + } + + /** + * {@inheritdoc} + */ + public function isCredentialsNonExpired() + { + return $this->credentialsNonExpired; + } + + /** + * {@inheritdoc} + */ + public function isEnabled() + { + return $this->enabled; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + } + + /** + * {@inheritDoc} + */ + public function equals(AccountInterface $account) + { + if (!$account instanceof User) { + return false; + } + + if ($this->password !== $account->getPassword()) { + return false; + } + + if ($this->getSalt() !== $account->getSalt()) { + return false; + } + + if ($this->username !== $account->getUsername()) { + return false; + } + + if ($this->accountNonExpired !== $account->isAccountNonExpired()) { + return false; + } + + if ($this->accountNonLocked !== $account->isAccountNonLocked()) { + return false; + } + + if ($this->credentialsNonExpired !== $account->isCredentialsNonExpired()) { + return false; + } + + if ($this->enabled !== $account->isEnabled()) { + return false; + } + + return true; + } +} diff --git a/Core/User/UserProviderInterface.php b/Core/User/UserProviderInterface.php new file mode 100644 index 0000000..70ba0d0 --- /dev/null +++ b/Core/User/UserProviderInterface.php @@ -0,0 +1,57 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien.potencier@symfony-project.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; + +/** + * UserProviderInterface is the implementation that all user provider must + * implement. + * + * @author Fabien Potencier <fabien.potencier@symfony-project.com> + */ +interface UserProviderInterface +{ + /** + * Loads the user for the given username. + * + * This method must throw UsernameNotFoundException if the user is not + * found. + * + * @throws UsernameNotFoundException if the user is not found + * @param string $username The username + * + * @return AccountInterface + */ + function loadUserByUsername($username); + + /** + * Loads the user for the account interface. + * + * It is up to the implementation if it decides to reload the user data + * from the database, or if it simply merges the passed User into the + * identity map of an entity manager. + * + * @throws UnsupportedAccountException if the account is not supported + * @param AccountInterface $account + * + * @return AccountInterface + */ + function loadUserByAccount(AccountInterface $account); + + /** + * Whether this provider supports the given user class + * + * @param string $class + * + * @return Boolean + */ + function supportsClass($class); +}
\ No newline at end of file |