From 385a83f896e2a9d8ef2299af5ef2c25d92ad4533 Mon Sep 17 00:00:00 2001 From: franck Date: Fri, 12 Jul 2013 14:22:08 +0200 Subject: [PATCH] Finalized admin security --- .../Admin/Controller/BaseAdminController.php | 84 +++++++--- .../Admin/Controller/SessionController.php | 65 ++++++-- core/lib/Thelia/Config/Resources/action.xml | 2 + core/lib/Thelia/Config/Resources/config.xml | 21 ++- .../Thelia/Config/Resources/routing/admin.xml | 20 ++- .../Core/HttpFoundation/Session/Session.php | 27 +++- .../AuthenticationProviderInterface.php | 60 ------- .../UsernamePasswordAuthenticator.php | 67 -------- .../Core/Security/AuthenticationProcessor.php | 35 ----- .../Encoder/PasswordEncoderInterface.php | 52 ------ .../Security/Encoder/PasswordHashEncoder.php | 66 -------- .../Encoder/PasswordPhpCompatEncoder.php | 58 ------- .../AuthenticationTokenNotFoundException.php | 28 ---- core/lib/Thelia/Core/Security/Role/Role.php | 2 +- .../Thelia/Core/Security/SecurityContext.php | 121 ++++++++++---- .../Core/Security/Token/AbstractToken.php | 148 ------------------ .../Core/Security/Token/TokenInterface.php | 81 ---------- .../Security/Token/UsernamePasswordToken.php | 75 --------- .../Core/Security/User/UserInterface.php | 9 +- .../UserProvider/AdminUserProvider.php | 7 +- .../UserProvider/CustomerUserProvider.php | 6 +- .../UserProvider/UserProviderInterface.php | 2 +- .../Smarty/Assets/SmartyAssetsManager.php | 3 +- .../Core/Template/Smarty/Plugins/Security.php | 19 ++- .../Template/Smarty/Plugins/Translation.php | 20 +-- core/lib/Thelia/Form/AdminLogin.php | 4 +- core/lib/Thelia/Model/Admin.php | 27 ++++ core/lib/Thelia/Model/AdminLog.php | 32 +++- core/lib/Thelia/Model/Customer.php | 13 +- .../Encoder/PasswordHashEncoderTest.php | 75 --------- .../Encoder/PasswordPhpCompatEncoderTest.php | 31 ---- templates/admin/default/home.html | 2 +- .../admin/default/includes/footer.inc.html | 2 +- templates/admin/default/login.html | 16 +- templates/smarty-sample/includes/header.html | 2 +- 35 files changed, 386 insertions(+), 896 deletions(-) delete mode 100644 core/lib/Thelia/Core/Security/Authentication/AuthenticationProviderInterface.php delete mode 100644 core/lib/Thelia/Core/Security/Authentication/UsernamePasswordAuthenticator.php delete mode 100644 core/lib/Thelia/Core/Security/AuthenticationProcessor.php delete mode 100644 core/lib/Thelia/Core/Security/Encoder/PasswordEncoderInterface.php delete mode 100644 core/lib/Thelia/Core/Security/Encoder/PasswordHashEncoder.php delete mode 100644 core/lib/Thelia/Core/Security/Encoder/PasswordPhpCompatEncoder.php delete mode 100644 core/lib/Thelia/Core/Security/Exception/AuthenticationTokenNotFoundException.php delete mode 100644 core/lib/Thelia/Core/Security/Token/AbstractToken.php delete mode 100644 core/lib/Thelia/Core/Security/Token/TokenInterface.php delete mode 100644 core/lib/Thelia/Core/Security/Token/UsernamePasswordToken.php delete mode 100644 core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php delete mode 100644 core/lib/Thelia/Tests/Core/Security/Encoder/PasswordPhpCompatEncoderTest.php diff --git a/core/lib/Thelia/Admin/Controller/BaseAdminController.php b/core/lib/Thelia/Admin/Controller/BaseAdminController.php index 7dc8f8a15..47258480b 100755 --- a/core/lib/Thelia/Admin/Controller/BaseAdminController.php +++ b/core/lib/Thelia/Admin/Controller/BaseAdminController.php @@ -27,11 +27,13 @@ use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\HttpFoundation\Response; use Thelia\Form\BaseForm; -use Thelia\Model\ConfigQuery; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\Routing\Generator\UrlGeneratorInterface; use Thelia\Core\Security\Exception\AuthenticationTokenNotFoundException; +use Thelia\Model\ConfigQuery; +use Thelia\Core\Security\Exception\AuthenticationException; +use Thelia\Core\Security\SecurityContext; /** * @@ -44,14 +46,14 @@ use Thelia\Core\Security\Exception\AuthenticationTokenNotFoundException; class BaseAdminController extends ContainerAware { - const TEMPLATE_404 = "404.html"; - public function notFoundAction() + protected function undefinedAction() { return new Response($this->renderRaw(self::TEMPLATE_404), 404); } + /** * Render the givent template, and returns the result as an Http Response. * @@ -59,7 +61,7 @@ class BaseAdminController extends ContainerAware * @param array $args the template arguments * @return \Symfony\Component\HttpFoundation\Response */ - public function render($templateName, $args = array()) + protected function render($templateName, $args = array()) { $response = new Response(); @@ -67,46 +69,78 @@ class BaseAdminController extends ContainerAware } /** - * Render the givent template, and returns the result as a string. + * Render the given template, and returns the result as a string. * * @param $templateName the complete template name, with extension * @param array $args the template arguments * @return \Symfony\Component\HttpFoundation\Response */ - public function renderRaw($templateName, $args = array()) + protected function renderRaw($templateName, $args = array()) { - $args = array_merge($args, array('lang' => 'fr')); // FIXME + $session = $this->getSession(); + + $args = array_merge($args, array( + 'locale' => $session->getLocale(), + 'lang' => $session->getLang() + )); try { $data = $this->getParser()->render($templateName, $args); - } - catch (AuthenticationTokenNotFoundException $ex) { - // No auth token -> perform login - return new RedirectResponse($this->generateUrl('admin/login')); + return $data; } + catch (AuthenticationException $ex) { - return $data; + // User is not authenticated, and templates requires authentication -> redirect to login page + // (see Thelia\Core\Template\Smarty\Plugins\Security) + return new RedirectResponse($this->generateUrl('admin/login')); // FIXME shoud be a parameter + } + } + + /** + * Return the security context, by default in admin mode. + * + * @return Thelia\Core\Security\SecurityContext + */ + protected function getSecurityContext($context = false) + { + $securityContext = $this->container->get('thelia.securityContext'); + + $securityContext->setContext($context === false ? SecurityContext::CONTEXT_BACK_OFFICE : $context); + + return $securityContext; } /** * @return \Symfony\Component\HttpFoundation\Request */ - public function getRequest() + protected function getRequest() { return $this->container->get('request'); } + /** + * Returns the session from the current request + * + * @return Ambigous + */ + protected function getSession() { + + $request = $this->getRequest(); + + return $request->getSession(); + } + /** * * @return a ParserInterface instance parser, as configured. */ - public function getParser() + protected function getParser() { $parser = $this->container->get("thelia.parser"); - // FIXME: should be read from config - $parser->setTemplate('admin/default'); + // Define the template thant shoud be used + $parser->setTemplate(ConfigQuery::read('base_admin_template', 'admin/default')); return $parser; } @@ -132,11 +166,19 @@ class BaseAdminController extends ContainerAware * * @see UrlGeneratorInterface */ - public function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH) + protected function generateUrl($path, array $parameters = array()) { - return "thelia2/$route"; //FIXME + $base = ConfigQuery::read("base_url", "/").$path; //FIXME - //return $this->container->get('router')->generate($route, $parameters, $referenceType); + $queryString = ''; + + foreach($parameters as $name => $value) { + $queryString = sprintf("%s=%s&", urlencode($name), urlencode($value)); + } + + if ('' !== $queryString = rtrim($queryString, "&")) $queryString = '?' . $queryString; + + return $base . $queryString; } /** @@ -148,7 +190,7 @@ class BaseAdminController extends ContainerAware * * @return Response A Response instance */ - public function forward($controller, array $path = array(), array $query = array()) + protected function forward($controller, array $path = array(), array $query = array()) { $path['_controller'] = $controller; $subRequest = $this->container->get('request')->duplicate($query, null, $path); @@ -164,7 +206,7 @@ class BaseAdminController extends ContainerAware * * @return RedirectResponse */ - public function redirect($url, $status = 302) + protected function redirect($url, $status = 302) { return new RedirectResponse($url, $status); } diff --git a/core/lib/Thelia/Admin/Controller/SessionController.php b/core/lib/Thelia/Admin/Controller/SessionController.php index cba11a00e..e56af2d1b 100644 --- a/core/lib/Thelia/Admin/Controller/SessionController.php +++ b/core/lib/Thelia/Admin/Controller/SessionController.php @@ -25,33 +25,66 @@ namespace Thelia\Admin\Controller; use Symfony\Component\HttpFoundation\Response; use Thelia\Form\AdminLogin; +use Thelia\Core\Security\Token\UsernamePasswordToken; +use Thelia\Core\Security\Authentication\UsernamePasswordAuthenticator; +use Thelia\Core\Security\Encoder\PasswordPhpCompatEncoder; +use Thelia\Core\Security\Token\TokenInterface; +use Thelia\Core\Security\Authentication\AdminUsernamePasswordFormAuthenticator; +use Thelia\Model\AdminLog; +use Thelia\Core\Security\Exception\AuthenticationException; class SessionController extends BaseAdminController { - public function loginAction() + public function showLoginAction() + { + $form = $this->getLoginForm(); + + return $this->render("login.html", array( + "form" => $form->createView() + )); + } + + public function checkLogoutAction() + { + $this->getSecurityContext()->clear(); + + // Go back to login page. + return $this->redirect($this->generateUrl('admin/login')); + } + + public function checkLoginAction() { - $form = $this->getLoginForm(); + $form = $this->getLoginForm(); - $request = $this->getRequest(); + $request = $this->getRequest(); - if($request->isMethod("POST")) { + $authenticator = new AdminUsernamePasswordFormAuthenticator($request, $form); - $form->bind($request); + try { + $user = $authenticator->getAuthentifiedUser(); - if ($form->isValid()) { + // Success -> store user in security context + $this->getSecurityContext()->setUser($user); - $this->container->get('request')->authenticate( - $form->get('username')->getData(), - $form->get('password')->getData() - ); + // Log authentication success + AdminLog::append("Authentication successufull", $request, $user); - echo "valid"; exit; - } - } + // Redirect to home page - FIXME: requested URL, if we come from another page ? + return $this->redirect($this->generateUrl('admin')); + } + catch (AuthenticationException $ex) { - return $this->render("login.html", array( - "form" => $form->createView() - )); + // Log authentication failure + AdminLog::append(sprintf("Authentication failure for username '%s'", $authenticator->getUsername()), $request); + + $message = "Login failed. Please check your username and password."; + } + + // Display the login form again + return $this->render("login.html", array( + "form" => $authenticator->getLoginForm()->createView(), + "message" => $message + )); } protected function getLoginForm() diff --git a/core/lib/Thelia/Config/Resources/action.xml b/core/lib/Thelia/Config/Resources/action.xml index 2f5583e94..5d4436891 100755 --- a/core/lib/Thelia/Config/Resources/action.xml +++ b/core/lib/Thelia/Config/Resources/action.xml @@ -11,11 +11,13 @@ Thelia\Core\Event\CartEvent + + diff --git a/core/lib/Thelia/Config/Resources/config.xml b/core/lib/Thelia/Config/Resources/config.xml index dee0b57b6..2ae1a668b 100755 --- a/core/lib/Thelia/Config/Resources/config.xml +++ b/core/lib/Thelia/Config/Resources/config.xml @@ -49,15 +49,23 @@ - + - + + + + + + + + + @@ -84,7 +92,7 @@ - + %thelia.parser.loops% @@ -102,15 +110,16 @@ + - + - + diff --git a/core/lib/Thelia/Config/Resources/routing/admin.xml b/core/lib/Thelia/Config/Resources/routing/admin.xml index dc5e38a9b..fe5a0a283 100755 --- a/core/lib/Thelia/Config/Resources/routing/admin.xml +++ b/core/lib/Thelia/Config/Resources/routing/admin.xml @@ -4,14 +4,28 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> + Thelia\Admin\Controller\AdminController::indexAction + + - Thelia\Admin\Controller\SessionController::loginAction + Thelia\Admin\Controller\SessionController::showLoginAction - - Thelia\Admin\Controller\AdminController::notFoundAction + + + + Thelia\Admin\Controller\SessionController::checkLogoutAction + + + + + Thelia\Admin\Controller\SessionController::checkLoginAction + + + + Thelia\Admin\Controller\AdminController::undefinedAction .* \ No newline at end of file diff --git a/core/lib/Thelia/Core/HttpFoundation/Session/Session.php b/core/lib/Thelia/Core/HttpFoundation/Session/Session.php index 4bd079685..63e07b2a7 100644 --- a/core/lib/Thelia/Core/HttpFoundation/Session/Session.php +++ b/core/lib/Thelia/Core/HttpFoundation/Session/Session.php @@ -24,6 +24,7 @@ namespace Thelia\Core\HttpFoundation\Session; use Symfony\Component\HttpFoundation\Session\Session as BaseSession; +use Thelia\Core\Security\User\UserInterface; class Session extends BaseSession { @@ -35,7 +36,31 @@ class Session extends BaseSession { public function getLang() { - return $this->get("lang", "en"); + return substr($this->getLocale(), 0, 2); } + public function setCustomerUser(UserInterface $user) { + $this->set('customer_user', $user); + } + + public function getCustomerUser() { + return $this->get('customer_user'); + } + + public function clearCustomerUser() { + return $this->remove('customer_user'); + } + + + public function setAdminUser(UserInterface $user) { + $this->set('admin_user', $user); + } + + public function getAdminUser() { + return $this->get('admin_user'); + } + + public function clearAdminUser() { + return $this->remove('admin_user'); + } } diff --git a/core/lib/Thelia/Core/Security/Authentication/AuthenticationProviderInterface.php b/core/lib/Thelia/Core/Security/Authentication/AuthenticationProviderInterface.php deleted file mode 100644 index eb60f0482..000000000 --- a/core/lib/Thelia/Core/Security/Authentication/AuthenticationProviderInterface.php +++ /dev/null @@ -1,60 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ - -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/Authentication/UsernamePasswordAuthenticator.php b/core/lib/Thelia/Core/Security/Authentication/UsernamePasswordAuthenticator.php deleted file mode 100644 index f6161f038..000000000 --- a/core/lib/Thelia/Core/Security/Authentication/UsernamePasswordAuthenticator.php +++ /dev/null @@ -1,67 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ - -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/AuthenticationProcessor.php b/core/lib/Thelia/Core/Security/AuthenticationProcessor.php deleted file mode 100644 index 77fd3bd77..000000000 --- a/core/lib/Thelia/Core/Security/AuthenticationProcessor.php +++ /dev/null @@ -1,35 +0,0 @@ -container = $container; - } - - public function createToken(Request $request) { - - $context = $request->get('_context'); - - try { - $securityContext = $this->container->get("security.$context"); - - $token = new UsernamePasswordToken( - $request->get('_username'), - $request->get('_password') - ); - - $securityContext->setToken($token); - } - catch (\Exception $ex) { - // Nothing to do - } - } -} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Encoder/PasswordEncoderInterface.php b/core/lib/Thelia/Core/Security/Encoder/PasswordEncoderInterface.php deleted file mode 100644 index 3353969b8..000000000 --- a/core/lib/Thelia/Core/Security/Encoder/PasswordEncoderInterface.php +++ /dev/null @@ -1,52 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ - -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/Encoder/PasswordHashEncoder.php b/core/lib/Thelia/Core/Security/Encoder/PasswordHashEncoder.php deleted file mode 100644 index 7a66812f5..000000000 --- a/core/lib/Thelia/Core/Security/Encoder/PasswordHashEncoder.php +++ /dev/null @@ -1,66 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ - -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/Encoder/PasswordPhpCompatEncoder.php b/core/lib/Thelia/Core/Security/Encoder/PasswordPhpCompatEncoder.php deleted file mode 100644 index 32b1c7fd0..000000000 --- a/core/lib/Thelia/Core/Security/Encoder/PasswordPhpCompatEncoder.php +++ /dev/null @@ -1,58 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ -namespace Thelia\Core\Security\Encoder; - -/** - * - * use password api include in php 5.5 and available throw the password_compat library. - * - * Class PasswordPhpCompatEncoder - * @package Thelia\Core\Security\Encoder - */ -class PasswordPhpCompatEncoder implements PasswordEncoderInterface { - - /** - * Encode a string. - * - * @param string $password the password to encode - * @param string $algorithm the hash() algorithm - * @return string $salt the salt, the salt is not used here. - */ - public function encode($password, $algorithm, $salt = null) - { - return password_hash($password, $algorithm); - } - - /** - * 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, not used here - * @return string $salt the salt, not used here - */ - public function isEqual($string, $password, $algorithm = null, $salt = null) - { - return password_verify($string, $password); - } -} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Exception/AuthenticationTokenNotFoundException.php b/core/lib/Thelia/Core/Security/Exception/AuthenticationTokenNotFoundException.php deleted file mode 100644 index 061b43257..000000000 --- a/core/lib/Thelia/Core/Security/Exception/AuthenticationTokenNotFoundException.php +++ /dev/null @@ -1,28 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ - -namespace Thelia\Core\Security\Exception; - -class AuthenticationTokenNotFoundException extends \Exception -{ -} diff --git a/core/lib/Thelia/Core/Security/Role/Role.php b/core/lib/Thelia/Core/Security/Role/Role.php index b47e1c089..c363d1c4c 100644 --- a/core/lib/Thelia/Core/Security/Role/Role.php +++ b/core/lib/Thelia/Core/Security/Role/Role.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace use Thelia\Core\Security\Role; +namespace Thelia\Core\Security\Role; /** * Role is a simple implementation of a RoleInterface where the role is a diff --git a/core/lib/Thelia/Core/Security/SecurityContext.php b/core/lib/Thelia/Core/Security/SecurityContext.php index 683431518..645d9a196 100644 --- a/core/lib/Thelia/Core/Security/SecurityContext.php +++ b/core/lib/Thelia/Core/Security/SecurityContext.php @@ -25,62 +25,125 @@ namespace Thelia\Core\Security; use Thelia\Core\Security\Authentication\AuthenticationProviderInterface; use Thelia\Core\Security\Exception\AuthenticationTokenNotFoundException; +use Thelia\Core\Security\Token\TokenInterface; +use Thelia\Core\Security\User\UserInterface; +use Thelia\Core\HttpFoundation\Request; /** - * A simple security manager, in charge of authenticating users using various authentication systems. + * A simple security manager, in charge of checking user * * @author Franck Allimant */ class SecurityContext { -/* - protected $authProvider; - public function __construct(AuthenticationProviderInterface $authProvider) { - $this->authProvider = $authProvider; - } -*/ + const CONTEXT_FRONT_OFFICE = 'front'; + const CONTEXT_BACK_OFFICE = 'admin'; + + private $request; + private $context; + + public function __construct(Request $request) { + + $this->request = $request; + + $this->context = null; + } + + public function setContext($context) { + if ($context !== self::CONTEXT_FRONT_OFFICE && $context !== self::CONTEXT_BACK_OFFICE) { + throw new \InvalidArgumentException(sprintf("Invalid or empty context identifier '%s'", $context)); + } + + $this->context = $context; + } + + public function getContext($exception_if_context_undefined = false) { + if (null === $this->context && $exception_if_context_undefined === true) + throw new \LogicException("No context defined. Please use setContext() first."); + + return $this->context; + } + + private function getSession() { + $session = $this->request->getSession(); + + if ($session === null) + throw new \LogicException("No session found."); + + return $session; + } + /** - * Checks if the current token is authenticated + * Gets the currently authenticated user in the current context, or null if none is defined * - * @throws AuthenticationCredentialsNotFoundException when the security context has no authentication token. + * @return UserInterface|null A UserInterface instance or null if no user is available + */ + public function getUser() { + $context = $this->getContext(true); + + if ($context === self::CONTEXT_FRONT_OFFICE) + $user = $this->getSession()->getCustomerUser(); + else if ($context == self::CONTEXT_BACK_OFFICE) + $user = $this->getSession()->getAdminUser(); + else + $user = null; + + return $user; + } + + final public function isAuthenticated() + { + if (null !== $this->getUser()) { + return true; + } + + return false; + } + + /** + * Checks if the current user is allowed * * @return Boolean - * @throws AuthenticationTokenNotFoundException if no thoken was found in context */ final public function isGranted($roles, $permissions) { - if (null === $this->token) { - throw new AuthenticationTokenNotFoundException('The security context contains no authentication token.'); - } + if ($this->isAuthenticated() === true) { - if (!$this->token->isAuthenticated()) { - $this->token = $this->authProvider->authenticate($this->token); - } + echo "TODO: check roles and permissions !"; - if ($this->token->isAuthenticated()) { - // Check user roles and permissions + // TODO : check roles and permissions + return true; } return false; } /** - * Gets the currently authenticated token. + * Sets the authenticated user. * - * @return TokenInterface|null A TokenInterface instance or null if no authentication information is available + * @param UserInterface $user A UserInterface, or null if no further user should be stored */ - public function getToken() + public function setUser(UserInterface $user) { - return $this->token; + $context = $this->getContext(true); + + $user->eraseCredentials(); + + if ($context === self::CONTEXT_FRONT_OFFICE) + $this->getSession()->setCustomerUser($user); + else if ($context == self::CONTEXT_BACK_OFFICE) + $this->getSession()->setAdminUser($user); } /** - * 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; + * Clear the user from the security context + */ + public function clear() { + $context = $this->getContext(true); + + if ($context === self::CONTEXT_FRONT_OFFICE) + $this->getSession()->clearCustomerUser(); + else if ($context == self::CONTEXT_BACK_OFFICE) + $this->getSession()->clearAdminUser(); } } \ No newline at end of file diff --git a/core/lib/Thelia/Core/Security/Token/AbstractToken.php b/core/lib/Thelia/Core/Security/Token/AbstractToken.php deleted file mode 100644 index be1f8cb8b..000000000 --- a/core/lib/Thelia/Core/Security/Token/AbstractToken.php +++ /dev/null @@ -1,148 +0,0 @@ - -* @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/Token/TokenInterface.php b/core/lib/Thelia/Core/Security/Token/TokenInterface.php deleted file mode 100644 index d70c8eeea..000000000 --- a/core/lib/Thelia/Core/Security/Token/TokenInterface.php +++ /dev/null @@ -1,81 +0,0 @@ -. */ -/* */ -/*************************************************************************************/ - -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/Token/UsernamePasswordToken.php b/core/lib/Thelia/Core/Security/Token/UsernamePasswordToken.php deleted file mode 100644 index 03f73dbdd..000000000 --- a/core/lib/Thelia/Core/Security/Token/UsernamePasswordToken.php +++ /dev/null @@ -1,75 +0,0 @@ - -*/ -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, array $roles = array()) - { - $this->setUser($username); - $this->credentials = $password; - - parent::setAuthenticated(count($roles) > 0); - } - - /** - * {@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/User/UserInterface.php b/core/lib/Thelia/Core/Security/User/UserInterface.php index be158e88c..b747fdffa 100644 --- a/core/lib/Thelia/Core/Security/User/UserInterface.php +++ b/core/lib/Thelia/Core/Security/User/UserInterface.php @@ -21,14 +21,9 @@ interface UserInterface { public function getPassword(); /** - * return the salt used to calculate the user password + * Check a string against a the user password */ - public function getSalt(); - - /** - * return the algorithm used to calculate the user password - */ - public function getAlgo(); + public function checkPassword($password); /** * Returns the roles granted to the user. diff --git a/core/lib/Thelia/Core/Security/UserProvider/AdminUserProvider.php b/core/lib/Thelia/Core/Security/UserProvider/AdminUserProvider.php index c9ae34a96..162edf299 100644 --- a/core/lib/Thelia/Core/Security/UserProvider/AdminUserProvider.php +++ b/core/lib/Thelia/Core/Security/UserProvider/AdminUserProvider.php @@ -1,12 +1,13 @@ filterByLogin($key) ->findOne(); diff --git a/core/lib/Thelia/Core/Security/UserProvider/CustomerUserProvider.php b/core/lib/Thelia/Core/Security/UserProvider/CustomerUserProvider.php index 3472bc013..642056f77 100644 --- a/core/lib/Thelia/Core/Security/UserProvider/CustomerUserProvider.php +++ b/core/lib/Thelia/Core/Security/UserProvider/CustomerUserProvider.php @@ -1,13 +1,13 @@ filterByEmail($key) ->findOne(); diff --git a/core/lib/Thelia/Core/Security/UserProvider/UserProviderInterface.php b/core/lib/Thelia/Core/Security/UserProvider/UserProviderInterface.php index f2a6d139b..931fa6923 100644 --- a/core/lib/Thelia/Core/Security/UserProvider/UserProviderInterface.php +++ b/core/lib/Thelia/Core/Security/UserProvider/UserProviderInterface.php @@ -1,6 +1,6 @@ assetic_manager->asseticize( $asset_dir.'/'.$asset_file, $this->web_root."/".$this->path_relative_to_web_root, - $this->path_relative_to_web_root, + ConfigQuery::read('base_url', '/') . $this->path_relative_to_web_root, $assetType, $filters, $debug diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Security.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Security.php index 93367f341..28fd8e4cf 100644 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Security.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Security.php @@ -27,6 +27,7 @@ use Thelia\Core\Template\Smarty\SmartyPluginDescriptor; use Thelia\Core\Template\Smarty\SmartyPluginInterface; use Thelia\Core\Template\Smarty\Assets\SmartyAssetsManager; use Thelia\Core\Security\SecurityContext; +use Thelia\Core\Security\Exception\AuthenticationException; class Security implements SmartyPluginInterface { @@ -39,7 +40,6 @@ class Security implements SmartyPluginInterface private function _explode($commaSeparatedValues) { - $array = explode(',', $commaSeparatedValues); if (array_walk($array, function(&$item) { @@ -60,10 +60,23 @@ class Security implements SmartyPluginInterface */ public function checkAuthFunction($params, &$smarty) { - $roles = $this->_explode($params['role']); + // Context: 'front' or 'admin' + $context = strtolower(trim($params['context'])); + + $this->securityContext->setContext($context); + + $roles = $this->_explode($params['roles']); $permissions = $this->_explode($params['permissions']); - $this->securityContext->isGranted($roles, $permissions); + if (! $this->securityContext->isGranted($roles, $permissions)) { + throw new AuthenticationException( + sprintf("User not granted for roles '%s', permissions '%s' in context '%s'.", + implode(',', $roles), implode(',', $permissions), $context + ) + ); + } + + return ''; } /** diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php index ec984613e..f03c34a7e 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Translation.php @@ -25,9 +25,16 @@ namespace Thelia\Core\Template\Smarty\Plugins; use Thelia\Core\Template\Smarty\SmartyPluginDescriptor; use Thelia\Core\Template\Smarty\SmartyPluginInterface; +use Symfony\Component\Translation\TranslatorInterface; class Translation implements SmartyPluginInterface { + protected $translator; + + public function __construct(TranslatorInterface $translator) { + $this->translator = $translator; + } + /** * Process translate function * @@ -35,16 +42,9 @@ class Translation implements SmartyPluginInterface * @param unknown $smarty * @return string */ - public function theliaTranslate($params, &$smarty) + public function translate($params, &$smarty) { - if (isset($params['l'])) { - $string = str_replace('\'', '\\\'', $params['l']); - } else { - $string = ''; - } - - // TODO - return "$string"; + return $this->translator->trans($params['l'], isset($params['p']) ? $params['p'] : array()); } /** @@ -55,7 +55,7 @@ class Translation implements SmartyPluginInterface public function getPluginDescriptors() { return array( - new SmartyPluginDescriptor('function', 'intl', $this, 'theliaTranslate'), + new SmartyPluginDescriptor('function', 'intl', $this, 'translate'), ); } } diff --git a/core/lib/Thelia/Form/AdminLogin.php b/core/lib/Thelia/Form/AdminLogin.php index 37efd05e7..6ba27830e 100644 --- a/core/lib/Thelia/Form/AdminLogin.php +++ b/core/lib/Thelia/Form/AdminLogin.php @@ -44,12 +44,12 @@ class AdminLogin extends BaseForm { new NotBlank() ) )) - ->add("remember_me", "checkbox"); + ->add("remember_me", "checkbox") + ; } public function getName() { return "admin_login"; } - } diff --git a/core/lib/Thelia/Model/Admin.php b/core/lib/Thelia/Model/Admin.php index 5dd4b9ed4..42d84ea93 100644 --- a/core/lib/Thelia/Model/Admin.php +++ b/core/lib/Thelia/Model/Admin.php @@ -3,6 +3,8 @@ namespace Thelia\Model; use Thelia\Core\Security\User\UserInterface; +use Thelia\Core\Security\Role\Role; + use Thelia\Model\Base\Admin as BaseAdmin; /** @@ -18,9 +20,34 @@ use Thelia\Model\Base\Admin as BaseAdmin; */ class Admin extends BaseAdmin implements UserInterface { + + public function setPassword($password) + { + \Thelia\Log\Tlog::getInstance()->debug($password); + + if ($this->isNew() && ($password === null || trim($password) == "")) { + throw new InvalidArgumentException("customer password is mandatory on creation"); + } + + if($password !== null && trim($password) != "") { + $this->setAlgo("PASSWORD_BCRYPT"); + return parent::setPassword(password_hash($password, PASSWORD_BCRYPT)); + } + + return $this; + } + /** * {@inheritDoc} */ + public function checkPassword($password) + { + return password_verify($password, $this->password); + } + + /** + * {@inheritDoc} + */ public function getUsername() { return $this->getLogin(); } diff --git a/core/lib/Thelia/Model/AdminLog.php b/core/lib/Thelia/Model/AdminLog.php index efa80209c..e5720fb35 100644 --- a/core/lib/Thelia/Model/AdminLog.php +++ b/core/lib/Thelia/Model/AdminLog.php @@ -3,7 +3,37 @@ namespace Thelia\Model; use Thelia\Model\Base\AdminLog as BaseAdminLog; +use Thelia\Core\HttpFoundation\Request; +use Thelia\Log\Tlog; +use Thelia\Model\Base\Admin as BaseAdminUser; class AdminLog extends BaseAdminLog { -} + /** + * A sdimple helper to insert an entry in the admin log + * + * @param unknown $actionLabel + * @param Request $request + * @param Admin $adminUser + */ + public static function append($actionLabel, Request $request, BaseAdminUser $adminUser = null) { + + $log = new AdminLog(); + + $log + ->setAdminLogin($adminUser !== null ? $adminUser->getLogin() : '') + ->setAdminFirstname($adminUser !== null ? $adminUser->getFirstname() : '') + ->setAdminLastname($adminUser !== null ? $adminUser->getLastname() : '') + ->setAction($actionLabel) + ->setRequest($request->__toString()) + ; + + try { + $log->save(); + } + catch (\Exception $ex) { + Tlog::getInstance()->err("Failed to insert new entry in AdminLog: {ex}", array('ex' => $ex)); + } + + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Model/Customer.php b/core/lib/Thelia/Model/Customer.php index 13024996b..00acf0e83 100644 --- a/core/lib/Thelia/Model/Customer.php +++ b/core/lib/Thelia/Model/Customer.php @@ -113,12 +113,19 @@ class Customer extends BaseCustomer implements UserInterface $this->dispatcher = $dispatcher; } + /** + * {@inheritDoc} + */ + public function getUsername() { + return $this->getEmail(); + } + /** * {@inheritDoc} */ - - public function getUsername() { - return $this->getEmail(); + public function checkPassword($password) + { + return password_verify($password, $this->password); } /** diff --git a/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php b/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php deleted file mode 100644 index 7590b3788..000000000 --- a/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordHashEncoderTest.php +++ /dev/null @@ -1,75 +0,0 @@ -encode('password', 'sha512', 'a simple salt'); - - // echo "PASS=\{$pass\}"; - - $this->assertEquals("L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ==", $pass, "Expected password not found."); - } - - public function testIsEqual() - { - $encoder = new PasswordHashEncoder(); - - $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; - - $this->assertTrue($encoder->isEqual($exp, 'password', 'sha512', 'a simple salt')); - } - - public function testWrongPass() - { - $encoder = new PasswordHashEncoder(); - - $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; - - $this->assertFalse($encoder->isEqual($exp, 'grongron', 'sha512', 'a simple salt')); - } - - public function testWrongSalt() - { - $encoder = new PasswordHashEncoder(); - - $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; - - $this->assertFalse($encoder->isEqual($exp, 'password', 'sha512', 'another salt')); - } - - public function testWrongAlgo() - { - $encoder = new PasswordHashEncoder(); - - $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; - - $this->assertFalse($encoder->isEqual($exp, 'password', 'md5', 'another salt')); - } - - /** - * @expectedException LogicException - */ - public function testUnsupportedAlgo() - { - $encoder = new PasswordHashEncoder(); - - $exp = "L3f/gGy4nBVhi8WSsC1a7E9JM8U+rtk6ZT+NiqX8M1UDJv6mahQEZ1z2cN/y9pixH+hgWbkBitONMiXWscomoQ=="; - - $encoder->isEqual($exp, 'password', 'sbonk', 'another salt'); - } - - /** - * @expectedException LogicException - */ - public function testEncodeWrongAlgorithm() - { - $encoder = new PasswordHashEncoder(); - - $encoder->encode('password', 'pouët', 'a simple salt'); - } -} \ No newline at end of file diff --git a/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordPhpCompatEncoderTest.php b/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordPhpCompatEncoderTest.php deleted file mode 100644 index 988be2360..000000000 --- a/core/lib/Thelia/Tests/Core/Security/Encoder/PasswordPhpCompatEncoderTest.php +++ /dev/null @@ -1,31 +0,0 @@ -encoder = new PasswordPhpCompatEncoder(); - } - - public function testEncode() - { - $hash = $this->encoder->encode("foo", PASSWORD_BCRYPT); - - $this->assertEquals($hash, crypt("foo", $hash)); - } - -} \ No newline at end of file diff --git a/templates/admin/default/home.html b/templates/admin/default/home.html index 01e667a28..53f0a5b6b 100755 --- a/templates/admin/default/home.html +++ b/templates/admin/default/home.html @@ -1,4 +1,4 @@ -{check_auth roles="ADMIN"} +{check_auth context="admin" roles="ADMIN"} {$page_title={intl l='Home'}} {include file='includes/header.inc.html'} diff --git a/templates/admin/default/includes/footer.inc.html b/templates/admin/default/includes/footer.inc.html index 5fd1a6b8f..aca554da2 100755 --- a/templates/admin/default/includes/footer.inc.html +++ b/templates/admin/default/includes/footer.inc.html @@ -1,7 +1,7 @@