From c10086f6c9cfbb27cc47834219784df6f39fdc93 Mon Sep 17 00:00:00 2001 From: franck Date: Mon, 1 Jul 2013 16:50:19 +0200 Subject: [PATCH] Introduction du package Thelia Security --- .../AuthenticationProviderInterface.php | 60 +++++++ .../UsernamePasswordAuthenticator.php | 67 ++++++++ .../Encoder/PasswordEncoderInterface.php | 52 ++++++ .../Security/Encoder/PasswordHashEncoder.php | 66 ++++++++ .../AuthenticationTokenNotFoundException.php | 28 ++++ .../Security/Security/SecurityManager.php | 81 ++++++++++ .../Security/Security/Token/AbstractToken.php | 148 ++++++++++++++++++ .../Security/Token/TokenInterface.php | 81 ++++++++++ .../Security/Token/UsernamePasswordToken.php | 75 +++++++++ .../Security/User/AdminUserProvider.php | 18 +++ .../Security/User/CustomerUserProvider.php | 20 +++ .../Security/Security/User/UserInterface.php | 42 +++++ .../Security/User/UserProviderInterface.php | 14 ++ 13 files changed, 752 insertions(+) create mode 100644 core/lib/Thelia/Core/Security/Security/Authentication/AuthenticationProviderInterface.php create mode 100644 core/lib/Thelia/Core/Security/Security/Authentication/UsernamePasswordAuthenticator.php create mode 100644 core/lib/Thelia/Core/Security/Security/Encoder/PasswordEncoderInterface.php create mode 100644 core/lib/Thelia/Core/Security/Security/Encoder/PasswordHashEncoder.php create mode 100644 core/lib/Thelia/Core/Security/Security/Exception/AuthenticationTokenNotFoundException.php create mode 100644 core/lib/Thelia/Core/Security/Security/SecurityManager.php create mode 100644 core/lib/Thelia/Core/Security/Security/Token/AbstractToken.php create mode 100644 core/lib/Thelia/Core/Security/Security/Token/TokenInterface.php create mode 100644 core/lib/Thelia/Core/Security/Security/Token/UsernamePasswordToken.php create mode 100644 core/lib/Thelia/Core/Security/Security/User/AdminUserProvider.php create mode 100644 core/lib/Thelia/Core/Security/Security/User/CustomerUserProvider.php create mode 100644 core/lib/Thelia/Core/Security/Security/User/UserInterface.php create mode 100644 core/lib/Thelia/Core/Security/Security/User/UserProviderInterface.php diff --git a/core/lib/Thelia/Core/Security/Security/Authentication/AuthenticationProviderInterface.php b/core/lib/Thelia/Core/Security/Security/Authentication/AuthenticationProviderInterface.php new file mode 100644 index 000000000..eb60f0482 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Authentication/AuthenticationProviderInterface.php @@ -0,0 +1,60 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Authentication; + +use Thelia\Core\Security\UserNotFoundException; +use Thelia\Core\Security\IncorrectPasswordException; + +/** + * Aunthentication providers are in charge or retrieving users, and check their + * credentials. + * + * @author Franck + * + */ +interface AuthenticationProviderInterface { + + /** + * Set the authentication token + * + * @param TokenInterface $token the authentication token + */ + public function setToken(TokenInterface $token); + + + /** + * Set the authentication token + * + * @param unknown $key + */ + public function supportsToken(TokenInterface $token); + + /** + * Authenticate the token + * + *@throws Exception if authentication was not successful + */ + public function authenticate(); +} +?> \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Authentication/UsernamePasswordAuthenticator.php b/core/lib/Thelia/Core/Security/Security/Authentication/UsernamePasswordAuthenticator.php new file mode 100644 index 000000000..f6161f038 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Authentication/UsernamePasswordAuthenticator.php @@ -0,0 +1,67 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Authentication; + +use Thelia\Core\Security\Authentication\AuthenticationProviderInterface; +use Thelia\Core\Security\Encoder\PasswordEncoderInterface; +use Thelia\Core\Security\User\UserProviderInterface; +use Thelia\Security\Token\TokenInterface; +use Thelia\Core\Security\Exception\IncorrectPasswordException; +use Thelia\Core\Security\Token\UsernamePasswordToken; + +class UsernamePasswordAuthenticator implements AuthenticationProviderInterface { + + protected $userProvider; + protected $encoder; + + private $token; + + public function __construct(UserProviderInterface $userProvider, PasswordEncoderInterface $encoder) { + $this->userProvider = $userProvider; + $this->encoder = $encoder; + } + + public function supportsToken(TokenInterface $token) { + + return $token instanceof UsernamePasswordToken; + } + + public function authenticate($token) { + + if (!$this->supports($token)) { + return null; + } + + // Retreive user + $user = $this->userProvider->getUser($this->token->getUsername()); + + // Check password + $authOk = $this->encoder->isEqual($password, $user->getPassword(), $user->getAlgo(), $user->getSalt()) === true; + + $authenticatedToken = new UsernamePasswordToken($user, $token->getCredentials(), $authOk); + + return $authenticatedToken; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Encoder/PasswordEncoderInterface.php b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordEncoderInterface.php new file mode 100644 index 000000000..3353969b8 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordEncoderInterface.php @@ -0,0 +1,52 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Encoder; + +/** + * This interface defines a password encoder. + * + * @author Franck Allimant + * + */ +interface PasswordEncoderInterface { + + /** + * Encode a string. + * + * @param string $password the password to encode + * @param string $algorithm the hash() algorithm + * @return string $salt the salt + */ + public function encode($password, $algorithm, $salt); + + /** + * Check a string against an encoded password. + * + * @param string $string the string to compare against password + * @param string $password the encoded password + * @param string $algorithm the hash() algorithm + * @return string $salt the salt + */ + public function isEqual($string, $password, $algorithm, $salt); +} diff --git a/core/lib/Thelia/Core/Security/Security/Encoder/PasswordHashEncoder.php b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordHashEncoder.php new file mode 100644 index 000000000..7a66812f5 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Encoder/PasswordHashEncoder.php @@ -0,0 +1,66 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Encoder; + +/** + * This interface defines a hash based password encoder. + * + * @author Franck Allimant + */ + +class PasswordHashEncoder implements PasswordEncoderInterface { + + /** + * {@inheritdoc} + */ + public function encode($password, $algorithm, $salt) + { + if (!in_array($algorithm, hash_algos(), true)) { + throw new \LogicException(sprintf('The algorithm "%s" is not supported.', $algorithm)); + } + + // Salt the string + $salted = $password.$salt; + + // Create the hash + $digest = hash($algorithm, $salted, true); + + // "stretch" hash + for ($i = 1; $i < 5000; $i++) { + $digest = hash($algorithm, $digest.$salted, true); + } + + return base64_encode($digest); + } + + /** + * {@inheritdoc} + */ + public function isEqual($string, $password, $algorithm, $salt) + { + $encoded = $this->encode($password, $algorithm, $salt); + + return $encoded == $string; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Exception/AuthenticationTokenNotFoundException.php b/core/lib/Thelia/Core/Security/Security/Exception/AuthenticationTokenNotFoundException.php new file mode 100644 index 000000000..061b43257 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Exception/AuthenticationTokenNotFoundException.php @@ -0,0 +1,28 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Exception; + +class AuthenticationTokenNotFoundException extends \Exception +{ +} diff --git a/core/lib/Thelia/Core/Security/Security/SecurityManager.php b/core/lib/Thelia/Core/Security/Security/SecurityManager.php new file mode 100644 index 000000000..82c45a304 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/SecurityManager.php @@ -0,0 +1,81 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security; + +use Thelia\Core\Security\Authentication\AuthenticationProviderInterface; +use Thelia\Core\Security\Exception\AuthenticationTokenNotFoundException; + +/** + * A simple security manager, in charge of authenticating users using various authentication systems. + * + * @author Franck Allimant + */ +class SecurityManager { + + protected $authProvider; + + public function __construct(AuthenticationProviderInterface $authProvider) { + $this->authProvider = $authProvider; + } + + /** + * Checks if the current token is authenticated + * + * @throws AuthenticationCredentialsNotFoundException when the security context has no authentication token. + * + * @return Boolean + */ + final public function isGranted() + { + if (null === $this->token) { + throw new AuthenticationTokenNotFoundException('The security context contains no authentication token.'); + } + + if (!$this->token->isAuthenticated()) { + $this->token = $this->authProvider->authenticate($this->token); + } + + return $this->token->isAuthenticated(); + } + + /** + * 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 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; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Token/AbstractToken.php b/core/lib/Thelia/Core/Security/Security/Token/AbstractToken.php new file mode 100644 index 000000000..be1f8cb8b --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Token/AbstractToken.php @@ -0,0 +1,148 @@ + +* @author Johannes M. Schmitt +*/ +abstract class AbstractToken implements TokenInterface +{ + private $user; + private $authenticated; + + /** + * Constructor. + * + * @param RoleInterface[] $roles An array of roles + * + * @throws \InvalidArgumentException + */ + public function __construct() + { + $this->authenticated = false; + } + + /** + * {@inheritdoc} + */ + public function getUsername() + { + if ($this->user instanceof UserInterface) { + return $this->user->getUsername(); + } + + return (string) $this->user; + } + + public function getUser() + { + return $this->user; + } + + /** + * Sets the user in the token. + * + * The user can be a UserInterface instance, or an object implementing + * a __toString method or the username as a regular string. + * + * @param mixed $user The user + * @throws \InvalidArgumentException + */ + public function setUser($user) + { + if (!($user instanceof UserInterface || is_string($user))) { + throw new \InvalidArgumentException('$user must be an instanceof UserInterface, or a primitive string.'); + } + + if (null === $this->user) { + $changed = false; + } elseif ($this->user instanceof UserInterface) { + if (!$user instanceof UserInterface) { + $changed = true; + } else { + $changed = $this->hasUserChanged($user); + } + } elseif ($user instanceof UserInterface) { + $changed = true; + } else { + $changed = (string) $this->user !== (string) $user; + } + + if ($changed) { + $this->setAuthenticated(false); + } + + $this->user = $user; + } + + /** + * {@inheritdoc} + */ + public function isAuthenticated() + { + return $this->authenticated; + } + + /** + * {@inheritdoc} + */ + public function setAuthenticated($authenticated) + { + $this->authenticated = (Boolean) $authenticated; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + if ($this->getUser() instanceof UserInterface) { + $this->getUser()->eraseCredentials(); + } + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array($this->user, $this->authenticated)); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + list($this->user, $this->authenticated) = unserialize($serialized); + } + + private function hasUserChanged(UserInterface $user) + { + if (!($this->user instanceof UserInterface)) { + throw new \BadMethodCallException('Method "hasUserChanged" should be called when current user class is instance of "UserInterface".'); + } + + if ($this->user instanceof EquatableInterface) { + return ! (Boolean) $this->user->isEqualTo($user); + } + + if ($this->user->getPassword() !== $user->getPassword()) { + return true; + } + + if ($this->user->getSalt() !== $user->getSalt()) { + return true; + } + + if ($this->user->getUsername() !== $user->getUsername()) { + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Token/TokenInterface.php b/core/lib/Thelia/Core/Security/Security/Token/TokenInterface.php new file mode 100644 index 000000000..d70c8eeea --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Token/TokenInterface.php @@ -0,0 +1,81 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Security\Token; + +/** + * TokenInterface is the interface for the user authentication information. + * + * Parts borrowed from Symfony Security Framework (Fabien Potencier / Johannes M. Schmitt ) + */ + +interface TokenInterface extends \Serializable +{ + /** + * Returns the user credentials. + * + * @return mixed The user credentials + */ + public function getCredentials(); + + /** + * Returns a user representation. + * + * @return mixed either returns an object which implements __toString(), or + * a primitive string is returned. + */ + public function getUser(); + + /** + * Sets a user instance + * + * @param mixed $user + */ + public function setUser($user); + + /** + * Returns the username. + * + * @return string + */ + public function getUsername(); + + /** + * Returns whether the user is authenticated or not. + * + * @return Boolean true if the token has been authenticated, false otherwise + */ + public function isAuthenticated(); + + /** + * Sets the authenticated flag. + * + * @param Boolean $isAuthenticated The authenticated flag + */ + public function setAuthenticated($isAuthenticated); + + /** + * Removes sensitive information from the token. + */ + public function eraseCredentials(); +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/Token/UsernamePasswordToken.php b/core/lib/Thelia/Core/Security/Security/Token/UsernamePasswordToken.php new file mode 100644 index 000000000..f605d1767 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/Token/UsernamePasswordToken.php @@ -0,0 +1,75 @@ + +*/ +class UsernamePasswordToken extends AbstractToken +{ + private $credentials; + + /** + * Constructor. + * + * @param string $user The username (like a nickname, email address, etc.), or a UserInterface instance or an object implementing a __toString method. + * @param string $password The password of the user + * + * @throws \InvalidArgumentException + */ + public function __construct($username, $password, $authenticated = false) + { + $this->setUser($username); + $this->credentials = $password; + + parent::setAuthenticated($authenticated); + } + + /** + * {@inheritdoc} + */ + public function setAuthenticated($isAuthenticated) + { + if ($isAuthenticated) { + throw new \LogicException('Cannot set this token to trusted after instantiation.'); + } + + parent::setAuthenticated(false); + } + + public function getCredentials() + { + return $this->credentials; + } + + /** + * {@inheritdoc} + */ + public function eraseCredentials() + { + parent::eraseCredentials(); + + $this->credentials = null; + } + + /** + * {@inheritdoc} + */ + public function serialize() + { + return serialize(array($this->credentials, $this->providerKey, parent::serialize())); + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) + { + list($this->credentials, $this->providerKey, $parentStr) = unserialize($serialized); + parent::unserialize($parentStr); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/AdminUserProvider.php b/core/lib/Thelia/Core/Security/Security/User/AdminUserProvider.php new file mode 100644 index 000000000..9df5ee484 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/AdminUserProvider.php @@ -0,0 +1,18 @@ +filterByLogin($key) + ->findOne(); + + return $admin; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/CustomerUserProvider.php b/core/lib/Thelia/Core/Security/Security/User/CustomerUserProvider.php new file mode 100644 index 000000000..824d9e774 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/CustomerUserProvider.php @@ -0,0 +1,20 @@ +filterByEmail($key) + ->findOne(); + + return $customer; + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/UserInterface.php b/core/lib/Thelia/Core/Security/Security/User/UserInterface.php new file mode 100644 index 000000000..9057504ca --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/UserInterface.php @@ -0,0 +1,42 @@ + + * + */ +interface UserInterface { + + /** + * Return the user unique name + */ + public function getUsername(); + + /** + * Return the user encoded password + */ + public function getPassword(); + + /** + * return the salt used to calculate the user password + */ + public function getSalt(); + + /** + * return the algorithm used to calculate the user password + */ + public function getAlgo(); + + /** + * Removes sensitive data from the user. + * + * This is important if, at any given point, sensitive information like + * the plain-text password is stored on this object. + * + * @return void + */ + public function eraseCredentials(); +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Security/User/UserProviderInterface.php b/core/lib/Thelia/Core/Security/Security/User/UserProviderInterface.php new file mode 100644 index 000000000..217e693c1 --- /dev/null +++ b/core/lib/Thelia/Core/Security/Security/User/UserProviderInterface.php @@ -0,0 +1,14 @@ + \ No newline at end of file