diff options
author | diosmosis <benaka@piwik.pro> | 2014-09-28 15:19:30 +0400 |
---|---|---|
committer | diosmosis <benaka@piwik.pro> | 2014-09-28 15:19:30 +0400 |
commit | c7b287c012f34f81f4fb19115b80d6bffcb9f122 (patch) | |
tree | a74b7da8f355014a0f62de60193e261ebf627df1 | |
parent | 3a6ca2319d4ee570f2258dc7d4b9ad229bc7b443 (diff) |
Refactor Auth::initSession method into its own class (SessionInitializer) since the logic will be common to all authentication implementations. Includes tweak to docs of UsersManager API method and additional methods to Auth interface.
-rw-r--r-- | core/Auth.php | 34 | ||||
-rw-r--r-- | plugins/Login/Auth.php | 170 | ||||
-rw-r--r-- | plugins/Login/Controller.php | 25 | ||||
-rw-r--r-- | plugins/Login/SessionInitializer.php | 228 | ||||
-rw-r--r-- | plugins/UsersManager/API.php | 8 |
5 files changed, 294 insertions, 171 deletions
diff --git a/core/Auth.php b/core/Auth.php index 5243290a81..5c858010c4 100644 --- a/core/Auth.php +++ b/core/Auth.php @@ -45,6 +45,25 @@ interface Auth public function setTokenAuth($token_auth); /** + * Returns the login of the user being authenticated. + * + * @return string + */ + public function getLogin(); + + /** + * Returns the secret used to calculate a user's token auth. + * + * A users token auth is generated using the user's login and this secret. The secret + * should be specific to the user and not easily guessed. Piwik's default Auth implementation + * uses an MD5 hash of a user's password. + * + * @return string + * @throws Exception if the token auth cannot be calculated at the current time. + */ + public function getTokenAuthSecret(); + + /** * Sets the login name to authenticate with. * * @param string $login The username. @@ -70,19 +89,14 @@ interface Auth * Authenticates a user using the login and password set using the setters. Can also authenticate * via token auth if one is set and no password is set. * + * Note: this method must successfully authenticate if the token auth supplied is a special hash + * of the user's real token auth. This is because the SessionInitializer class stores a + * hash of the token auth in the session cookie. You can calculate the token auth hash using the + * {@link Piwik\Plugins\Login\SessionInitializer::getHashTokenAuth()} method. + * * @return AuthResult */ public function authenticate(); - - /** - * Authenticates the user using login and password and initializes an authenticated session. - * - * @param bool $rememberMe Whether the user should be remembered by setting a client side cookie - * or not. - * - * TODO: maybe this logic should be handled by Login\Controller? - */ - public function initSession($rememberMe); } /** diff --git a/plugins/Login/Auth.php b/plugins/Login/Auth.php index d21a256aa9..719533f247 100644 --- a/plugins/Login/Auth.php +++ b/plugins/Login/Auth.php @@ -10,14 +10,10 @@ namespace Piwik\Plugins\Login; use Exception; use Piwik\AuthResult; -use Piwik\Config; -use Piwik\Cookie; use Piwik\Db; -use Piwik\Piwik; -use Piwik\Plugins\UsersManager\API; use Piwik\Plugins\UsersManager\Model; -use Piwik\ProxyHttp; use Piwik\Session; +use Piwik\Plugins\UsersManager\API as UsersManagerAPI; /** * @@ -45,6 +41,10 @@ class Auth implements \Piwik\Auth */ public function authenticate() { + if (!empty($this->md5Password)) { // favor authenticating by password + $this->token_auth = UsersManagerAPI::getInstance()->getTokenAuth($this->login, $this->getTokenAuthSecret()); + } + if (is_null($this->login)) { $model = new Model(); $user = $model->getUserByTokenAuth($this->token_auth); @@ -59,7 +59,7 @@ class Auth implements \Piwik\Auth $user = $model->getUser($this->login); if (!empty($user['token_auth']) - && (($this->getHashTokenAuth($this->login, $user['token_auth']) === $this->token_auth) + && ((SessionInitializer::getHashTokenAuth($this->login, $user['token_auth']) === $this->token_auth) || $user['token_auth'] === $this->token_auth) ) { $this->setTokenAuth($user['token_auth']); @@ -73,31 +73,13 @@ class Auth implements \Piwik\Auth } /** - * Authenticates the user and initializes the session. + * Returns the login of the user being authenticated. + * + * @return string */ - public function initSession($rememberMe) + public function getLogin() { - $this->regenerateSessionId(); - - $authResult = $this->doAuthenticateSession($this->login, $this->md5Password); - - if (!$authResult->wasAuthenticationSuccessful()) { - $this->processFailedSession($rememberMe); - } else { - $this->processSuccessfulSession($this->login, $authResult->getTokenAuth(), $rememberMe); - } - - /** - * Triggered after session initialize. - * This event notify about end of init session process. - * - * **Example** - * - * Piwik::addAction('Login.initSession.end', function () { - * // session has been initialized - * }); - */ - Piwik::postEvent('Login.initSession.end'); + return $this->login; } /** @@ -111,25 +93,23 @@ class Auth implements \Piwik\Auth } /** - * Accessor to set authentication token + * Returns the secret used to calculate a user's token auth. * - * @param string $token_auth authentication token + * @return string */ - public function setTokenAuth($token_auth) + public function getTokenAuthSecret() { - $this->token_auth = $token_auth; + return $this->md5Password; } /** - * Accessor to compute the hashed authentication token + * Accessor to set authentication token * - * @param string $login user login * @param string $token_auth authentication token - * @return string hashed authentication token */ - public function getHashTokenAuth($login, $token_auth) + public function setTokenAuth($token_auth) { - return md5($login . $token_auth); + $this->token_auth = $token_auth; } /** @@ -156,118 +136,4 @@ class Auth implements \Piwik\Auth $this->md5Password = $passwordHash; } - - /** - * @param $login - * @param $md5Password - * @return AuthResult - * @throws \Exception - */ - protected function doAuthenticateSession($login, $md5Password) - { - $tokenAuth = API::getInstance()->getTokenAuth($login, $md5Password); - - $this->setLogin($login); - $this->setTokenAuth($tokenAuth); - - /** - * Triggered before authenticate function. - * This event propagate login and token_auth which will be using in authenticate process. - * - * This event exists to enable possibility for user authentication prevention. - * For example when user is locked or inactive. - * - * **Example** - * - * Piwik::addAction('Login.authenticate', function ($login, $tokenAuth) { - * if (!UserActivityManager::isActive ($login, $tokenAuth) { - * throw new Exception('Your account is inactive.'); - * } - * }); - * - * @param string $login User login. - * @param string $tokenAuth User token auth. - */ - Piwik::postEvent( - 'Login.authenticate', - array( - $login, - $tokenAuth - ) - ); - - $authResult = $this->authenticate(); - return $authResult; - } - - /** - * @param $rememberMe - * @return Cookie - */ - protected function getAuthCookie($rememberMe) - { - $authCookieName = Config::getInstance()->General['login_cookie_name']; - $authCookieExpiry = $rememberMe ? time() + Config::getInstance()->General['login_cookie_expire'] : 0; - $authCookiePath = Config::getInstance()->General['login_cookie_path']; - $cookie = new Cookie($authCookieName, $authCookieExpiry, $authCookiePath); - return $cookie; - } - - /** - * Executed when the session could not authenticate - * @param $rememberMe - * @throws \Exception - */ - protected function processFailedSession($rememberMe) - { - $cookie = $this->getAuthCookie($rememberMe); - $cookie->delete(); - throw new Exception(Piwik::translate('Login_LoginPasswordNotCorrect')); - } - - /** - * Executed when the session was successfully authenticated - * @param $login - * @param $tokenAuth - * @param $rememberMe - */ - protected function processSuccessfulSession($login, $tokenAuth, $rememberMe) - { - /** - * Triggered after successful authenticate, but before cookie creation. - * This event propagate login and token_auth which was used in authenticate process. - * - * This event exists to enable the ability to custom action before the cookie will be created, - * but after a successful authentication. - * For example when user have to fill survey or change password. - * - * **Example** - * - * Piwik::addAction('Login.authenticate.successful', function ($login, $tokenAuth) { - * // redirect to change password action - * }); - * - * @param string $login User login. - * @param string $tokenAuth User token auth. - */ - Piwik::postEvent( - 'Login.authenticate.successful', - array( - $login, - $tokenAuth - ) - ); - - $cookie = $this->getAuthCookie($rememberMe); - $cookie->set('login', $login); - $cookie->set('token_auth', $this->getHashTokenAuth($login, $tokenAuth)); - $cookie->setSecure(ProxyHttp::isHttps()); - $cookie->setHttpOnly(true); - $cookie->save(); - } - - protected function regenerateSessionId() - { - Session::regenerateId(); - } }
\ No newline at end of file diff --git a/plugins/Login/Controller.php b/plugins/Login/Controller.php index 525e98a555..82ae9ccab2 100644 --- a/plugins/Login/Controller.php +++ b/plugins/Login/Controller.php @@ -13,6 +13,7 @@ use Piwik\Auth as AuthInterface; use Piwik\Common; use Piwik\Config; use Piwik\Cookie; +use Piwik\Log; use Piwik\Nonce; use Piwik\Piwik; use Piwik\QuickForm2; @@ -20,8 +21,6 @@ use Piwik\Session; use Piwik\Url; use Piwik\View; -require_once PIWIK_INCLUDE_PATH . '/core/Config.php'; - /** * Login controller * @@ -39,12 +38,18 @@ class Controller extends \Piwik\Plugin\Controller private $auth; /** + * @var SessionInitializer + */ + private $sessionInitializer; + + /** * Constructor. * * @param PasswordResetter $passwordResetter * @param AuthInterface $auth + * @param SessionInitializer $authenticatedSessionFactory \ */ - public function __construct($passwordResetter = null, $auth = null) + public function __construct($passwordResetter = null, $auth = null, $sessionInitializer = null) { parent::__construct(); @@ -57,6 +62,11 @@ class Controller extends \Piwik\Plugin\Controller $auth = \Piwik\Registry::get('auth'); } $this->auth = $auth; + + if (empty($sessionInitializer)) { + $sessionInitializer = new SessionInitializer(); + } + $this->sessionInitializer = $sessionInitializer; } /** @@ -168,7 +178,8 @@ class Controller extends \Piwik\Plugin\Controller } else { $this->auth->setPasswordHash($password); } - $this->auth->initSession($rememberMe); + + $this->sessionInitializer->initSession($this->auth, $rememberMe); // remove password reset entry if it exists $this->passwordResetter->removePasswordResetInfo($login); @@ -236,6 +247,8 @@ class Controller extends \Piwik\Plugin\Controller try { $this->passwordResetter->initiatePasswordResetProcess($loginMail, $password); } catch (Exception $ex) { + Log::debug($ex); + return array($ex->getMessage()); } @@ -256,6 +269,8 @@ class Controller extends \Piwik\Plugin\Controller try { $this->passwordResetter->confirmNewPassword($login, $resetToken); } catch (Exception $ex) { + Log::debug($ex); + $errorMessage = $ex->getMessage(); } @@ -315,4 +330,4 @@ class Controller extends \Piwik\Plugin\Controller Url::redirectToUrl($logoutUrl); } } -} +}
\ No newline at end of file diff --git a/plugins/Login/SessionInitializer.php b/plugins/Login/SessionInitializer.php new file mode 100644 index 0000000000..1ede89c639 --- /dev/null +++ b/plugins/Login/SessionInitializer.php @@ -0,0 +1,228 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + * + */ +namespace Piwik\Plugins\Login; + +use Exception; +use Piwik\Auth as AuthInterface; +use Piwik\AuthResult; +use Piwik\Config; +use Piwik\Cookie; +use Piwik\Db; +use Piwik\Piwik; +use Piwik\Plugins\UsersManager\API as UsersManagerAPI; +use Piwik\ProxyHttp; +use Piwik\Session; + +/** + * Initializes authenticated sessions using an Auth implementation. + * + * If a user is authenticated, a browser cookie is created so the user will be remembered + * until the cookie is destroyed. + * + * Plugins can override SessionInitializer behavior by extending this class and + * overriding methods. In order for these changes to have effect, however, an instance of + * the derived class must be used by the Login/Controller. + * + * @api + */ +class SessionInitializer +{ + /** + * The UsersManager API instance. + * + * @var UsersManagerAPI + */ + private $usersManagerAPI; + + /** + * The authenticated session cookie's name. Defaults to the value of the `[General] login_cookie_name` + * INI config option. + * + * @var string + */ + private $authCookieName; + + /** + * The time in seconds before the authenticated session cookie expires. Only used if `$rememberMe` + * is true in the {@link initSession()} call. + * + * Defaults to the value of the `[General] login_cookie_expire` INI config option. + * + * @var string + */ + private $authCookieValidTime; + + /** + * The path for the authenticated session cookie. Defaults to the value of the `[General] login_cookie_path` + * INI config option. + * + * @var string + */ + private $authCookiePath; + + /** + * Constructor. + * + * @param UsersManagerAPI|null $usersManagerAPI + * @param string|null $authCookieName + * @param int|null $authCookieValidTime + * @param string|null $authCookiePath + */ + public function __construct($usersManagerAPI = null, $authCookieName = null, $authCookieValidTime = null, + $authCookiePath = null) + { + if (empty($usersManagerAPI)) { + $usersManagerAPI = UsersManagerAPI::getInstance(); + } + $this->usersManagerAPI = $usersManagerAPI; + + if (empty($authCookieName)) { + $authCookieName = Config::getInstance()->General['login_cookie_name']; + } + $this->authCookieName = $authCookieName; + + if (empty($authCookieValidTime)) { + $authCookieValidTime = Config::getInstance()->General['login_cookie_expire']; + } + $this->authCookieValidTime = $authCookieValidTime; + + if (empty($authCookiePath)) { + $authCookiePath = Config::getInstance()->General['login_cookie_path']; + } + $this->authCookiePath = $authCookiePath; + } + + /** + * Authenticates the user and, if successful, initializes an authenticated session. + * + * @param \Piwik\Auth $auth The Auth implementation to use. + * @param bool $rememberMe Whether the authenticated session should be remembered after + * the browser is closed or not. + * @throws Exception If authentication fails or the user is not allowed to login for some reason. + */ + public function initSession(AuthInterface $auth, $rememberMe) + { + $this->regenerateSessionId(); + + $authResult = $this->doAuthenticateSession($auth); + + if (!$authResult->wasAuthenticationSuccessful()) { + $this->processFailedSession($rememberMe); + } else { + $this->processSuccessfulSession($authResult, $rememberMe); + } + + /** + * @deprecated Create a custom SessionInitializer instead. + */ + Piwik::postEvent('Login.initSession.end'); + } + + /** + * Authenticates the user. + * + * Derived classes can override this method to customize authentication logic or impose + * extra requirements on the user trying to login. + * + * @param AuthInterface $auth The Auth implementation to use when authenticating. + * @return AuthResult + */ + protected function doAuthenticateSession(AuthInterface $auth) + { + $tokenAuth = $this->usersManagerAPI->getTokenAuth($auth->getLogin(), $auth->getTokenAuthSecret()); + + /** + * @deprecated Create a custom SessionInitializer instead. + */ + Piwik::postEvent( + 'Login.authenticate', + array( + $auth->getLogin(), + $tokenAuth + ) + ); + + return $auth->authenticate(); + } + + /** + * Returns a Cookie instance that manages the browser cookie used to store session + * information. + * + * @param bool $rememberMe Whether the authenticated session should be remembered after + * the browser is closed or not. + * @return Cookie + */ + protected function getAuthCookie($rememberMe) + { + $authCookieExpiry = $rememberMe ? time() + $this->authCookieValidTime : 0; + $cookie = new Cookie($this->authCookieName, $authCookieExpiry, $this->authCookiePath); + return $cookie; + } + + /** + * Executed when the session could not authenticate. + * + * @param bool $rememberMe Whether the authenticated session should be remembered after + * the browser is closed or not. + * @throws Exception always. + */ + protected function processFailedSession($rememberMe) + { + $cookie = $this->getAuthCookie($rememberMe); + $cookie->delete(); + + throw new Exception(Piwik::translate('Login_LoginPasswordNotCorrect')); + } + + /** + * Executed when the session was successfully authenticated. + * + * @param AuthResult $authResult The successful authentication result. + * @param bool $rememberMe Whether the authenticated session should be remembered after + * the browser is closed or not. + */ + protected function processSuccessfulSession(AuthResult $authResult, $rememberMe) + { + /** + * @deprecated Create a custom SessionInitializer instead. + */ + Piwik::postEvent( + 'Login.authenticate.successful', + array( + $authResult->getIdentity(), + $authResult->getTokenAuth() + ) + ); + + $cookie = $this->getAuthCookie($rememberMe); + $cookie->set('login', $authResult->getIdentity()); + $cookie->set('token_auth', $this->getHashTokenAuth($authResult->getIdentity(), $authResult->getTokenAuth())); + $cookie->setSecure(ProxyHttp::isHttps()); + $cookie->setHttpOnly(true); + $cookie->save(); + } + + protected function regenerateSessionId() + { + Session::regenerateId(); + } + + /** + * Accessor to compute the hashed authentication token. + * + * @param string $login user login + * @param string $token_auth authentication token + * @return string hashed authentication token + */ + public static function getHashTokenAuth($login, $token_auth) + { + return md5($login . $token_auth); + } +}
\ No newline at end of file diff --git a/plugins/UsersManager/API.php b/plugins/UsersManager/API.php index 61b26323da..fec439a581 100644 --- a/plugins/UsersManager/API.php +++ b/plugins/UsersManager/API.php @@ -643,15 +643,15 @@ class API extends \Piwik\Plugin\API * Generates a unique MD5 for the given login & password * * @param string $userLogin Login - * @param string $md5Password MD5ied string of the password + * @param string $passwordHash MD5ied string of the password * @throws Exception * @return string */ - public function getTokenAuth($userLogin, $md5Password) + public function getTokenAuth($userLogin, $passwordHash) { - if (strlen($md5Password) != 32) { + if (strlen($passwordHash) != 32) { throw new Exception(Piwik::translate('UsersManager_ExceptionPasswordMD5HashExpected')); } - return md5($userLogin . $md5Password); + return md5($userLogin . $passwordHash); } } |