diff options
author | Johannes Schmitt <schmittjoh@gmail.com> | 2011-01-25 20:28:26 +0100 |
---|---|---|
committer | Fabien Potencier <fabien.potencier@gmail.com> | 2011-01-26 16:38:54 +0100 |
commit | 521c9f65e9d70618f63ac6ed803a495651b9fd35 (patch) | |
tree | 4e64bf3f877a4050eb3eb95c0b55630a4105053c | |
parent | bff922f5c7ab61fb144e124b584da067842cb955 (diff) | |
download | symfony-security-521c9f65e9d70618f63ac6ed803a495651b9fd35.zip symfony-security-521c9f65e9d70618f63ac6ed803a495651b9fd35.tar.gz symfony-security-521c9f65e9d70618f63ac6ed803a495651b9fd35.tar.bz2 |
[Security] many improvements, and fixes
19 files changed, 456 insertions, 29 deletions
diff --git a/Acl/Domain/SecurityIdentityRetrievalStrategy.php b/Acl/Domain/SecurityIdentityRetrievalStrategy.php index eaf48de..d347b97 100644 --- a/Acl/Domain/SecurityIdentityRetrievalStrategy.php +++ b/Acl/Domain/SecurityIdentityRetrievalStrategy.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Security\Acl\Domain; +use Symfony\Component\Security\Authentication\Token\AnonymousToken; + use Symfony\Component\Security\User\AccountInterface; use Symfony\Component\Security\Authentication\Token\TokenInterface; use Symfony\Component\Security\Acl\Model\SecurityIdentityRetrievalStrategyInterface; @@ -49,9 +51,12 @@ class SecurityIdentityRetrievalStrategy implements SecurityIdentityRetrievalStra $sids = array(); // add user security identity - $user = $token->getUser(); - if ($user instanceof AccountInterface) { - $sids[] = UserSecurityIdentity::fromAccount($user); + if (!$token instanceof AnonymousToken) { + try { + $sids[] = UserSecurityIdentity::fromToken($token); + } catch (\InvalidArgumentException $invalid) { + // ignore, user has no user security identity + } } // add all reachable roles diff --git a/Acl/Domain/UserSecurityIdentity.php b/Acl/Domain/UserSecurityIdentity.php index 3b03191..3c30a96 100644 --- a/Acl/Domain/UserSecurityIdentity.php +++ b/Acl/Domain/UserSecurityIdentity.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Acl\Domain; +use Symfony\Component\Security\Authentication\Token\TokenInterface; use Symfony\Component\Security\User\AccountInterface; use Symfony\Component\Security\Acl\Model\SecurityIdentityInterface; @@ -51,7 +52,24 @@ class UserSecurityIdentity implements SecurityIdentityInterface */ public static function fromAccount(AccountInterface $user) { - return new self((string) $user, get_class($user)); + return new self($user->getUsername(), get_class($user)); + } + + /** + * Creates a user security identity from a TokenInterface + * + * @param TokenInterface $token + * @return UserSecurityIdentity + */ + public static function fromToken(TokenInterface $token) + { + $user = $token->getUser(); + + if ($user instanceof AccountInterface) { + return self::fromAccount($user); + } + + return new self((string) $user, is_object($user)? get_class($user) : get_class($token)); } /** diff --git a/Authentication/Provider/DaoAuthenticationProvider.php b/Authentication/Provider/DaoAuthenticationProvider.php index d83a125..69ef9a3 100644 --- a/Authentication/Provider/DaoAuthenticationProvider.php +++ b/Authentication/Provider/DaoAuthenticationProvider.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Authentication\Provider; +use Symfony\Component\Security\Authentication\Token\TokenInterface; use Symfony\Component\Security\Encoder\EncoderFactoryInterface; use Symfony\Component\Security\User\UserProviderInterface; use Symfony\Component\Security\User\AccountCheckerInterface; @@ -38,9 +39,9 @@ class DaoAuthenticationProvider extends UserAuthenticationProvider * @param AccountCheckerInterface $accountChecker An AccountCheckerInterface instance * @param EncoderFactoryInterface $encoderFactory A EncoderFactoryInterface instance */ - public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true) + public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker, $providerKey, EncoderFactoryInterface $encoderFactory, $hideUserNotFoundExceptions = true) { - parent::__construct($accountChecker, $hideUserNotFoundExceptions); + parent::__construct($accountChecker, $providerKey, $hideUserNotFoundExceptions); $this->encoderFactory = $encoderFactory; $this->userProvider = $userProvider; diff --git a/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php b/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php index aab823a..850b1ec 100644 --- a/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php +++ b/Authentication/Provider/PreAuthenticatedAuthenticationProvider.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Security\Authentication\Provider; +use Symfony\Component\Security\User\AccountInterface; use Symfony\Component\Security\User\UserProviderInterface; use Symfony\Component\Security\User\AccountCheckerInterface; use Symfony\Component\Security\Exception\BadCredentialsException; @@ -31,6 +32,7 @@ class PreAuthenticatedAuthenticationProvider implements AuthenticationProviderIn { protected $userProvider; protected $accountChecker; + protected $providerKey; /** * Constructor. @@ -38,10 +40,11 @@ class PreAuthenticatedAuthenticationProvider implements AuthenticationProviderIn * @param UserProviderInterface $userProvider A UserProviderInterface instance * @param AccountCheckerInterface $accountChecker An AccountCheckerInterface instance */ - public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker) + public function __construct(UserProviderInterface $userProvider, AccountCheckerInterface $accountChecker, $providerKey) { $this->userProvider = $userProvider; $this->accountChecker = $accountChecker; + $this->providerKey = $providerKey; } /** @@ -73,6 +76,6 @@ class PreAuthenticatedAuthenticationProvider implements AuthenticationProviderIn */ public function supports(TokenInterface $token) { - return $token instanceof PreAuthenticatedToken; + return $token instanceof PreAuthenticatedToken && $this->providerKey === $token->getProviderKey(); } } diff --git a/Authentication/Provider/RememberMeAuthenticationProvider.php b/Authentication/Provider/RememberMeAuthenticationProvider.php new file mode 100644 index 0000000..d2d0268 --- /dev/null +++ b/Authentication/Provider/RememberMeAuthenticationProvider.php @@ -0,0 +1,45 @@ +<?php +namespace Symfony\Component\Security\Authentication\Provider; + +use Symfony\Component\Security\User\AccountCheckerInterface; +use Symfony\Component\Security\User\AccountInterface; +use Symfony\Component\Security\Authentication\Token\TokenInterface; +use Symfony\Component\Security\Authentication\Token\RememberMeToken; +use Symfony\Component\Security\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/Authentication/Provider/UserAuthenticationProvider.php b/Authentication/Provider/UserAuthenticationProvider.php index 9ee4d61..fa678b7 100644 --- a/Authentication/Provider/UserAuthenticationProvider.php +++ b/Authentication/Provider/UserAuthenticationProvider.php @@ -29,6 +29,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter { protected $hideUserNotFoundExceptions; protected $accountChecker; + protected $providerKey; /** * Constructor. @@ -36,9 +37,14 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter * @param AccountCheckerInterface $accountChecker An AccountCheckerInterface interface * @param Boolean $hideUserNotFoundExceptions Whether to hide user not found exception or not */ - public function __construct(AccountCheckerInterface $accountChecker, $hideUserNotFoundExceptions = true) + 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; } @@ -64,7 +70,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter $this->checkAuthentication($user, $token); $this->accountChecker->checkPostAuth($user); - return new UsernamePasswordToken($user, $token->getCredentials(), $user->getRoles()); + return new UsernamePasswordToken($user, $token->getCredentials(), $this->providerKey, $user->getRoles()); } catch (UsernameNotFoundException $notFound) { if ($this->hideUserNotFoundExceptions) { throw new BadCredentialsException('Bad credentials', 0, $notFound); @@ -79,7 +85,7 @@ abstract class UserAuthenticationProvider implements AuthenticationProviderInter */ public function supports(TokenInterface $token) { - return $token instanceof UsernamePasswordToken; + return $token instanceof UsernamePasswordToken && $this->providerKey === $token->getProviderKey(); } /** diff --git a/Authentication/RememberMe/InMemoryTokenProvider.php b/Authentication/RememberMe/InMemoryTokenProvider.php new file mode 100644 index 0000000..71c1bf2 --- /dev/null +++ b/Authentication/RememberMe/InMemoryTokenProvider.php @@ -0,0 +1,50 @@ +<?php + +namespace Symfony\Component\Security\Authentication\RememberMe; + +use Symfony\Component\Security\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/Authentication/RememberMe/PersistentToken.php b/Authentication/RememberMe/PersistentToken.php new file mode 100644 index 0000000..cdbc296 --- /dev/null +++ b/Authentication/RememberMe/PersistentToken.php @@ -0,0 +1,107 @@ +<?php + +namespace Symfony\Component\Security\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/Authentication/RememberMe/PersistentTokenInterface.php b/Authentication/RememberMe/PersistentTokenInterface.php new file mode 100644 index 0000000..5525a34 --- /dev/null +++ b/Authentication/RememberMe/PersistentTokenInterface.php @@ -0,0 +1,45 @@ +<?php + +namespace Symfony\Component\Security\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/Authentication/RememberMe/TokenProviderInterface.php b/Authentication/RememberMe/TokenProviderInterface.php new file mode 100644 index 0000000..0ed3f50 --- /dev/null +++ b/Authentication/RememberMe/TokenProviderInterface.php @@ -0,0 +1,51 @@ +<?php + +namespace Symfony\Component\Security\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/Authentication/Token/PreAuthenticatedToken.php b/Authentication/Token/PreAuthenticatedToken.php index 8c6fc5f..0980ae6 100644 --- a/Authentication/Token/PreAuthenticatedToken.php +++ b/Authentication/Token/PreAuthenticatedToken.php @@ -18,10 +18,12 @@ namespace Symfony\Component\Security\Authentication\Token; */ class PreAuthenticatedToken extends Token { + protected $providerKey; + /** * Constructor. */ - public function __construct($user, $credentials, array $roles = null) + public function __construct($user, $credentials, $providerKey, array $roles = null) { parent::__construct(null === $roles ? array() : $roles); if (null !== $roles) { @@ -30,6 +32,12 @@ class PreAuthenticatedToken extends Token $this->user = $user; $this->credentials = $credentials; + $this->providerKey = $providerKey; + } + + public function getProviderKey() + { + return $this->providerKey; } /** diff --git a/Authentication/Token/RememberMeToken.php b/Authentication/Token/RememberMeToken.php index 50284aa..f006a3a 100644 --- a/Authentication/Token/RememberMeToken.php +++ b/Authentication/Token/RememberMeToken.php @@ -22,6 +22,7 @@ use Symfony\Component\Security\User\AccountInterface; class RememberMeToken extends Token { protected $key; + protected $providerKey; /** * The persistent token which resulted in this authentication token. @@ -36,30 +37,39 @@ class RememberMeToken extends Token * @param string $username * @param string $key */ - public function __construct(AccountInterface $user, $key) { + public function __construct(AccountInterface $user, $providerKey, $key) { parent::__construct($user->getRoles()); - if (0 === strlen($key)) { - throw new \InvalidArgumentException('$key cannot be empty.'); + if (empty($key)) { + throw new \InvalidArgumentException('$key must not be empty.'); + } + if (empty($providerKey)) { + throw new \InvalidArgumentException('$providerKey must not be empty.'); } - $this->user = $user; + $this->setUser($user); + $this->providerKey = $providerKey; $this->key = $key; $this->setAuthenticated(true); } - public function getKey() + public function getProviderKey() { - return $this->key; + return $this->providerKey; } - public function setPersistentToken(PersistentTokenInterface $persistentToken) + public function getKey() { - $this->persistentToken = $persistentToken; + 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/Authentication/Token/UsernamePasswordToken.php b/Authentication/Token/UsernamePasswordToken.php index ed82ec8..1794481 100644 --- a/Authentication/Token/UsernamePasswordToken.php +++ b/Authentication/Token/UsernamePasswordToken.php @@ -18,19 +18,30 @@ namespace Symfony\Component\Security\Authentication\Token; */ 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, array $roles = array()) + 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} */ diff --git a/Exception/CookieTheftException.php b/Exception/CookieTheftException.php new file mode 100644 index 0000000..a69ccdf --- /dev/null +++ b/Exception/CookieTheftException.php @@ -0,0 +1,22 @@ +<?php + +namespace Symfony\Component\Security\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/Exception/TokenNotFoundException.php b/Exception/TokenNotFoundException.php new file mode 100644 index 0000000..65d2d2a --- /dev/null +++ b/Exception/TokenNotFoundException.php @@ -0,0 +1,20 @@ +<?php +namespace Symfony\Component\Security\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/SecurityContext.php b/SecurityContext.php index e694d82..bf6324a 100644 --- a/SecurityContext.php +++ b/SecurityContext.php @@ -11,8 +11,9 @@ namespace Symfony\Component\Security; +use Symfony\Component\Security\Authorization\AccessDecisionManagerInterface; +use Symfony\Component\Security\Authentication\AuthenticationManagerInterface; use Symfony\Component\Security\Authentication\Token\TokenInterface; -use Symfony\Component\Security\Authorization\AccessDecisionManager; use Symfony\Component\Security\Acl\Voter\FieldVote; /** @@ -30,15 +31,19 @@ class SecurityContext protected $token; protected $accessDecisionManager; + protected $authenticationManager; + protected $alwaysAuthenticate; /** * Constructor. * - * @param AccessDecisionManager|null $accessDecisionManager An AccessDecisionManager instance + * @param AccessDecisionManagerInterface|null $accessDecisionManager An AccessDecisionManager instance */ - public function __construct(AccessDecisionManager $accessDecisionManager = null) + public function __construct(AuthenticationManagerInterface $authenticationManager, AccessDecisionManagerInterface $accessDecisionManager = null, $alwaysAuthenticate = false) { + $this->authenticationManager = $authenticationManager; $this->accessDecisionManager = $accessDecisionManager; + $this->alwaysAuthenticate = $alwaysAuthenticate; } public function getUser() @@ -60,6 +65,10 @@ class SecurityContext $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); } diff --git a/User/InMemoryUserProvider.php b/User/InMemoryUserProvider.php index e83f7fa..082e9c9 100644 --- a/User/InMemoryUserProvider.php +++ b/User/InMemoryUserProvider.php @@ -87,4 +87,12 @@ class InMemoryUserProvider implements UserProviderInterface return $this->loadUserByUsername((string) $account); } + + /** + * {@inheritDoc} + */ + public function supportsClass($class) + { + return $class === 'Symfony\Component\Security\User\User'; + } } diff --git a/User/User.php b/User/User.php index 4885ca0..338d43c 100644 --- a/User/User.php +++ b/User/User.php @@ -119,7 +119,6 @@ class User implements AdvancedAccountInterface */ public function eraseCredentials() { - $this->password = null; } /** diff --git a/User/UserProviderInterface.php b/User/UserProviderInterface.php index 4e182ee..926f575 100644 --- a/User/UserProviderInterface.php +++ b/User/UserProviderInterface.php @@ -36,13 +36,22 @@ interface UserProviderInterface * 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 + * 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 $user + * @param AccountInterface $account * * @return AccountInterface */ - function loadUserByAccount(AccountInterface $user); + 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 |