From 3ae3920253e12c1528e601cf96a612a7add8d499 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Tue, 14 Jul 2009 23:09:31 -0600 Subject: [PATCH] Horde_Auth rewrite. Store data in static class variable instead of globally Add getLoginParams() Move logout reason string and changepassword code to login page Rework session storage of auth information. All application-specific auth tasks should now be handled properly by both Horde_Auth and Horde_Auth_Application Horde_Registry::pushApp() should now do all necessary authentication and permissions checking in a single call. --- framework/Auth/lib/Horde/Auth.php | 567 ++++++++++++-------------- framework/Auth/lib/Horde/Auth/Application.php | 57 ++- framework/Auth/lib/Horde/Auth/Base.php | 99 +++-- framework/Auth/lib/Horde/Auth/Composite.php | 143 ++----- framework/Auth/lib/Horde/Auth/Cyrsql.php | 2 +- framework/Auth/lib/Horde/Auth/Imap.php | 2 +- framework/Auth/lib/Horde/Auth/Kolab.php | 7 +- framework/Auth/lib/Horde/Auth/Ldap.php | 2 +- framework/Auth/lib/Horde/Auth/Msad.php | 22 +- framework/Auth/lib/Horde/Auth/Sql.php | 2 +- framework/Core/lib/Horde.php | 23 +- framework/Core/lib/Horde/Registry.php | 41 +- 12 files changed, 443 insertions(+), 524 deletions(-) diff --git a/framework/Auth/lib/Horde/Auth.php b/framework/Auth/lib/Horde/Auth.php index 924e4eafa..71468f135 100644 --- a/framework/Auth/lib/Horde/Auth.php +++ b/framework/Auth/lib/Horde/Auth.php @@ -3,13 +3,29 @@ * The Horde_Auth:: class provides a common abstracted interface into the * various backends for the Horde authentication system. * + * Horde authentication data is stored in the session in the 'horde_auth' + * array key. That key has the following structure: + *
+ * 'app' - (array) Application-specific authentication. Keys are the
+ *         app names, values are an array of credentials.
+ * 'browser' - (string) The remote browser string.
+ * 'change' - (boolean) Is a password change requested?
+ * 'credentials' - (array) The credentials needed for this driver.
+ * 'driver' - (string) The driver used for base horde auth.
+ * 'remoteAddr' - (string) The remote IP address of the user.
+ * 'timestamp' - (integer) The login time.
+ * 'userId' - (username) The horde username.
+ * 
+ * * Copyright 1999-2009 The Horde Project (http://www.horde.org/) * * See the enclosed file COPYING for license information (LGPL). If you did * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php * - * @author Chuck Hagenbuch - * @package Horde_Auth + * @author Chuck Hagenbuch + * @author Michael Slusarz + * @category Horde + * @package Horde_Auth */ class Horde_Auth { @@ -68,6 +84,13 @@ class Horde_Auth static protected $_instances = array(); /** + * The logout reason information. + * + * @var array + */ + static protected $_reason = array(); + + /** * Attempts to return a concrete Horde_Auth_Base instance based on * $driver. * @@ -312,10 +335,11 @@ class Horde_Auth static public function removeUserData($userId) { $errApps = array(); + $registry = Horde_Registry::singleton(); - foreach ($GLOBALS['registry']->listApps(array('notoolbar', 'hidden', 'active', 'admin')) as $app) { + foreach ($registry->listApps(array('notoolbar', 'hidden', 'active', 'admin')) as $app) { try { - $GLOBALS['registry']->callByPackage($app, 'removeUserData', array($userId)); + $registry->callByPackage($app, 'removeUserData', array($userId)); } catch (Horde_Auth_Exception $e) { Horde::logMessage($e, __FILE__, __LINE__, PEAR_LOG_ERR); $errApps[] = $app; @@ -332,33 +356,66 @@ class Horde_Auth * specified user. If there isn't, but the configured Auth driver supports * transparent authentication, then we try that. * - * @param string $realm The authentication realm to check. + * @params array $options Additional options: + *
+     * 'app' - (string) Check authentication for this app.
+     *         DEFAULT: Checks horde-wide authentication.
+     * 
* * @return boolean Whether or not the user is authenticated. */ - static public function isAuthenticated($realm = null) + static public function isAuthenticated($options = array()) { - if (isset($_SESSION['horde_auth']) && - !empty($_SESSION['horde_auth']['authenticated']) && - !empty($_SESSION['horde_auth']['userId']) && - ($_SESSION['horde_auth']['realm'] == $realm)) { - if (!self::checkSessionIP()) { - self::setAuthError(self::REASON_SESSIONIP); - return false; - } elseif (!self::checkBrowserString()) { - self::setAuthError(self::REASON_BROWSER); - return false; + $driver = empty($options['app']) + ? $GLOBALS['conf']['auth']['driver'] + : $options['app']; + + /* Check for cached authentication results. */ + if (self::getAuth() && + (($_SESSION['horde_auth']['driver'] == $driver) || + isset($_SESSION['horde_auth']['app'][$driver]))) { + return self::checkExistingAuth(); + } + + if (empty($options['app'])) { + $auth = self::singleton($driver); + } else { + $auth = self::singleton('application', array('app' => $driver)); + + /* If we have a horde session, and the app doesn't need additional + * auth, mark that in the session and return. */ + if (self::getAuth() && !$auth->hasCapability('authenticate')) { + $_SESSION['horde_auth']['app'][$driver] = true; + return true; } - return true; } - // Try transparent authentication now. - $auth = self::singleton($GLOBALS['conf']['auth']['driver']); - if ($auth->hasCapability('transparent') && $auth->transparent()) { - return self::isAuthenticated($realm); + return $auth->transparent(); + } + + /** + * Check existing auth for triggers that might invalidate it. + * + * @return boolean Is existing auth valid? + */ + static public function checkExistingAuth() + { + if (!empty($GLOBALS['conf']['auth']['checkip']) && + !empty($_SESSION['horde_auth']['remoteAddr']) && + ($_SESSION['horde_auth']['remoteAddr'] != $_SERVER['REMOTE_ADDR'])) { + self::setAuthError(self::REASON_SESSIONIP); + return false; + } + + if (!empty($GLOBALS['conf']['auth']['checkbrowser'])) { + $browser = Horde_Browser::singleton(); + if ($_SESSION['horde_auth']['browser'] != $browser->getAgentString()) { + self::setAuthError(self::REASON_BROWSER); + return false; + } } - return false; + return true; } /** @@ -369,14 +426,105 @@ class Horde_Auth */ static public function getAuth() { - if (isset($_SESSION['horde_auth'])) { - if (!empty($_SESSION['horde_auth']['authenticated']) && - !empty($_SESSION['horde_auth']['userId'])) { - return $_SESSION['horde_auth']['userId']; + return (!empty($_SESSION['horde_auth']['userId'])) + ? $_SESSION['horde_auth']['userId'] + : false; + } + + /** + * Redirects to the Horde login page on authentication failure. + * + * @param string $app The app which failed authentication. + * @param Horde_Exception $e An exception thrown by + * Horde_Registry::pushApp(). + * + * @throws Horde_Exception + */ + static public function authenticationFailureRedirect($app = 'horde', + $e = null) + { + if (Horde_Cli::runningFromCLI()) { + $cli = Horde_Cli::singleton(); + $cli->fatal(_("You are not authenticated.")); + } + + if (is_null($e)) { + $params = array(); + } else { + switch ($e->getCode()) { + case Horde_Registry::PERMISSION_DENIED: + $params = array('app' => $app, 'reason' => Horde_Auth::REASON_MESSAGE, 'msg' => $e->getMessage()); + break; + + case Horde_Registry::AUTH_FAILURE: + $params = array('app' => $app); + break; + + default: + throw $e; + } + } + + header('Location: ' . self::getLogoutUrl($params)); + exit; + } + + /** + * Return a URL to the login screen, adding the necessary logout + * parameters. + * If no reason/msg is passed in, use the current global authentication + * error message. + * + * @param array $options Additional options: + *
+     * 'app' - (string) Authenticate to this application
+     *         DEFAULT: Horde
+     * 'msg' - (string) If reason is self::REASON_MESSAGE, the message
+     *         to display to the user.
+     *         DEFAULT: None
+     * 'params' - (array) Additional params to add to the URL (not allowed:
+     *            'app', 'horde_logout_token', 'msg', 'nosidebar', 'reason',
+     *            'url').
+     *            DEFAULT: None
+     * 'reason' - (integer) The reason for logout
+     *            DEFAULT: None
+     * 
+ * + * @return string The formatted URL + */ + static public function getLogoutUrl($options = array()) + { + $registry = Horde_Registry::singleton(); + + if (!isset($options['reason'])) { + $options['reason'] = self::getAuthError(); + } + + if ($options['reason'] == self::REASON_LOGOUT) { + $params = array( + 'horde_logout_token' => Horde::getRequestToken('horde.logout'), + 'nosidebar' => 1 + ); + } else { + $params = array( + 'url' => Horde::selfUrl(true) + ); + } + + if (isset($options['app'])) { + $params['app'] = $options['app']; + } + + if ($options['reason']) { + $params[self::REASON_PARAM] = $options['reason']; + if ($options['reason'] == self::REASON_MESSAGE) { + $params[self::REASON_MSG_PARAM] = empty($options['msg']) + ? '' + : $options['msg']; } } - return false; + return Horde_Util::addParameter($registry->get('webroot', 'horde') . '/login.php', $params, null, false); } /** @@ -384,11 +532,9 @@ class Horde_Auth * * @return boolean Whether the backend requested a password change. */ - static public function isPasswordChangeRequested() + static public function passwordChangeRequested() { - return (isset($_SESSION['horde_auth']) && - !empty($_SESSION['horde_auth']['authenticated']) && - !empty($_SESSION['horde_auth']['changeRequested'])); + return !empty($_SESSION['horde_auth']['change']); } /** @@ -401,14 +547,9 @@ class Horde_Auth static public function getBareAuth() { $user = self::getAuth(); - if ($user) { - $pos = strpos($user, '@'); - if ($pos !== false) { - $user = substr($user, 0, $pos); - } - } - - return $user; + return ($user && (($pos = strpos($user, '@')) !== false)) + ? substr($user, 0, $pos) + : $user; } /** @@ -419,14 +560,10 @@ class Horde_Auth */ static public function getAuthDomain() { - if ($user = self::getAuth()) { - $pos = strpos($user, '@'); - if ($pos !== false) { - return substr($user, $pos + 1); - } - } - - return false; + $user = self::getAuth(); + return ($user && (($pos = strpos($user, '@')) !== false)) + ? substr($user, $pos + 1) + : false; } /** @@ -435,25 +572,23 @@ class Horde_Auth * * @param string $credential The credential to retrieve. * - * @return mixed The requested credential, or false if no user is - * logged in. + * @return mixed The requested credential, all credentials if $credential + * is null, or false if no user is logged in. */ - static public function getCredential($credential) + static public function getCredential($credential = null) { - if (empty($_SESSION['horde_auth']) || - empty($_SESSION['horde_auth']['authenticated'])) { + if (!self::getAuth()) { return false; } $credentials = Horde_Secret::read(Horde_Secret::getKey('auth'), $_SESSION['horde_auth']['credentials']); $credentials = @unserialize($credentials); - if (is_array($credentials) && - isset($credentials[$credential])) { - return $credentials[$credential]; - } - - return false; + return is_null($credential) + ? $credentials + : ((is_array($credentials) && isset($credentials[$credential])) + ? $credentials[$credential] + : false); } /** @@ -464,8 +599,7 @@ class Horde_Auth */ static public function setCredential($credential, $value) { - if (!empty($_SESSION['horde_auth']) && - !empty($_SESSION['horde_auth']['authenticated'])) { + if (self::getAuth()) { $credentials = @unserialize(Horde_Secret::read(Horde_Secret::getKey('auth'), $_SESSION['horde_auth']['credentials'])); if (is_array($credentials)) { $credentials[$credential] = $value; @@ -485,17 +619,23 @@ class Horde_Auth * * @param string $userId The userId who has been authorized. * @param array $credentials The credentials of the user. - * @param string $realm The authentication realm to use. - * @param boolean $change Whether to request that the user change - * their password. + * @param array $options Additional options: + *
+     * 'app' - (string) The app to set authentication credentials for.
+     * 'change' - (boolean) Whether to request that the user change their
+     *            password.
+     * 'login' - (boolean) Do login tasks?
+     *           DEFAULT: TODO
+     * 
+ * + * @return boolean Whether authentication was successful. */ - static public function setAuth($userId, $credentials, $realm = null, - $change = false) + static public function setAuth($userId, $credentials, $options = array()) { $userId = self::addHook(trim($userId)); if (!empty($GLOBALS['conf']['hooks']['postauthenticate'])) { - if (Horde::callHook('_horde_hook_postauthenticate', array($userId, $credentials, $realm), 'horde') === false) { + if (Horde::callHook('_horde_hook_postauthenticate', array($userId, $credentials, empty($options['app']) ? 'horde' : $options['app']), 'horde') === false) { if (self::getAuthError() != self::REASON_MESSAGE) { self::setAuthError(self::REASON_FAILED); } @@ -503,36 +643,43 @@ class Horde_Auth } } + if (!empty($options['app'])) { + if (!empty($_SESSION['horde_auth'])) { + $_SESSION['horde_auth']['app'][$options['app']] = true; + return true; + } + $app_array = array($options['app'] => true); + } else { + $app_array = array(); + } + /* If we're already set with this userId, don't continue. */ - if (isset($_SESSION['horde_auth']['userId']) && + if (self::getAuth() && ($_SESSION['horde_auth']['userId'] == $userId)) { return true; } /* Clear any existing info. */ - self::clearAuth($realm); + self::clearAuth(); $credentials = Horde_Secret::write(Horde_Secret::getKey('auth'), serialize($credentials)); - if (!empty($realm)) { - $userId .= '@' . $realm; - } - $browser = Horde_Browser::singleton(); $_SESSION['horde_auth'] = array( - 'authenticated' => true, + 'app' => $app_array, 'browser' => $browser->getAgentString(), - 'changeRequested' => $change, + 'change' => !empty($options['change']), 'credentials' => $credentials, - 'realm' => $realm, - 'remote_addr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null, + 'driver' => empty($options['app']) ? $GLOBALS['conf']['auth']['driver'] : $options['app'], + 'remoteAddr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null, 'timestamp' => time(), 'userId' => $userId ); /* Reload preferences for the new user. */ - $GLOBALS['registry']->loadPrefs(); + $registry = Horde_Registry::singleton(); + $registry->loadPrefs(); Horde_Nls::setLang($GLOBALS['prefs']->getValue('language')); /* Fetch the user's last login time. */ @@ -571,54 +718,19 @@ class Horde_Auth $last_login = array('time' => time(), 'host' => $ptrdname); $GLOBALS['prefs']->setValue('last_login', serialize($last_login)); - if ($change) { - $GLOBALS['notification']->push(_("Your password has expired."), - 'horde.message'); - - $auth = self::singleton($GLOBALS['conf']['auth']['driver']); - if ($auth->hasCapability('update')) { - /* A bit of a kludge. URL is set from the login screen, but - * we aren't completely certain we got here from the login - * screen. So any screen which calls setAuth() which has a - * url will end up going there. Should be OK. */ - $url_param = Horde_Util::getFormData('url'); - - if ($url_param) { - $url = Horde::url(Horde_Util::removeParameter($url_param, session_name()), true); - $return_to = $GLOBALS['registry']->get('webroot', 'horde') . '/index.php'; - $return_to = Horde_Util::addParameter($return_to, 'url', $url); - } else { - $return_to = Horde::url($GLOBALS['registry']->get('webroot', 'horde') . '/index.php'); - } - - $url = Horde::applicationUrl('services/changepassword.php'); - $url = Horde_Util::addParameter($url, array('return_to' => $return_to), null, false); - - header('Location: ' . $url); - exit; - } - } - return true; } /** * Clears any authentication tokens in the current session. - * - * @param string $realm The authentication realm to clear. */ - static public function clearAuth($realm = null) + static public function clearAuth() { - if (!empty($realm) && isset($_SESSION['horde_auth'][$realm])) { - $_SESSION['horde_auth'][$realm] = array('authenticated' => false); - } elseif (isset($_SESSION['horde_auth'])) { - $_SESSION['horde_auth'] = array('authenticated' => false); - } + unset($_SESSION['horde_auth']); /* Remove the user's cached preferences if they are present. */ - if (isset($GLOBALS['registry'])) { - $GLOBALS['registry']->unloadPrefs(); - } + $registry = Horde_Registry::singleton(); + $registry->unloadPrefs(); } /** @@ -669,6 +781,7 @@ class Horde_Auth * @param string $userId The authentication backend's user name. * * @return string The internal Horde user name. + * @throws Horde_Exception */ static public function addHook($userId) { @@ -689,6 +802,7 @@ class Horde_Auth * @param string $userId The internal Horde user name. * * @return string The authentication backend's user name. + * @throws Horde_Exception */ static public function removeHook($userId) { @@ -700,131 +814,14 @@ class Horde_Auth /** * Returns the name of the authentication provider. * - * @param string $driver Used by recursive calls when untangling composite - * auth. - * @param array $params Used by recursive calls when untangling composite - * auth. - * * @return string The name of the driver currently providing - * authentication. - */ - static public function getProvider($driver = null, $params = null) - { - if (is_null($driver)) { - $driver = $GLOBALS['conf']['auth']['driver']; - } - - if (is_null($params)) { - $params = Horde::getDriverConfig('auth', is_array($driver) ? $driver[1] : $driver); - } - - if ($driver == 'application') { - return isset($params['app']) ? $params['app'] : 'application'; - } elseif ($driver == 'composite') { - if (($login_driver = self::getDriverByParam('loginscreen_switch', $params)) && - !empty($params['drivers'][$login_driver])) { - return self::getProvider($params['drivers'][$login_driver]['driver'], - isset($params['drivers'][$login_driver]['params']) ? $params['drivers'][$login_driver]['params'] : null); - } - return 'composite'; - } else { - return $driver; - } - } - - /** - * Returns the logout reason. - * - * @return string One of the logout reasons (see the AUTH_LOGOUT_* - * constants for the valid reasons). Returns null if there - * is no logout reason present. - */ - static public function getLogoutReason() - { - return isset($GLOBALS['horde_auth']['logout']['type']) - ? $GLOBALS['horde_auth']['logout']['type'] - : Horde_Util::getFormData(self::REASON_PARAM); - } - - /** - * Returns the status string to use for logout messages. - * - * @return string The logout reason string. - */ - static public function getLogoutReasonString() - { - switch (self::getLogoutReason()) { - case self::REASON_SESSION: - return sprintf(_("Your %s session has expired. Please login again."), $GLOBALS['registry']->get('name')); - - case self::REASON_SESSIONIP: - return sprintf(_("Your Internet Address has changed since the beginning of your %s session. To protect your security, you must login again."), $GLOBALS['registry']->get('name')); - - case self::REASON_BROWSER: - return sprintf(_("Your browser appears to have changed since the beginning of your %s session. To protect your security, you must login again."), $GLOBALS['registry']->get('name')); - - case self::REASON_LOGOUT: - return _("You have been logged out."); - - case self::REASON_FAILED: - return _("Login failed."); - break; - - case self::REASON_BADLOGIN: - return _("Login failed because your username or password was entered incorrectly."); - break; - - case self::REASON_EXPIRED: - return _("Your login has expired."); - break; - - case self::REASON_MESSAGE: - return isset($GLOBALS['horde_auth']['logout']['msg']) - ? $GLOBALS['horde_auth']['logout']['msg'] - : Horde_Util::getFormData(self::REASON_MSG_PARAM); - - default: - return ''; - } - } - - /** - * Generates the correct parameters to pass to the given logout URL. - * - * If no reason/msg is passed in, use the current global authentication - * error message. - * - * @param string $url The URL to redirect to. - * @param string $reason The reason for logout. - * @param string $msg If reason is self::REASON_MESSAGE, the message to - * display to the user. - * - * @return string The formatted URL + * authentication, or false if not set. */ - static public function addLogoutParameters($url, $reason = null, - $msg = null) + static public function getProvider() { - $params = array('horde_logout_token' => Horde::getRequestToken('horde.logout')); - - if (isset($GLOBALS['registry'])) { - $params['app'] = $GLOBALS['registry']->getApp(); - } - - if (is_null($reason)) { - $reason = self::getLogoutReason(); - } - - if ($reason) { - $params[self::REASON_PARAM] = $reason; - if ($reason == self::REASON_MESSAGE) { - if (is_null($msg)) { - $msg = self::getLogoutReasonString(); - } - $params[self::REASON_MSG_PARAM] = $msg; - } - } - - return Horde_Util::addParameter($url, $params, null, false); + return empty($_SESSION['horde_auth']['driver']) + ? false + : $_SESSION['horde_auth']['driver']; } /** @@ -833,42 +830,34 @@ class Horde_Auth * * @param string $session_data The session data. * @param boolean $info Return session information. The following - * information is returned: userid, realm, - * timestamp, remote_addr, browser. + * information is returned: userid, + * timestamp, remoteAddr, browser. * * @return array An array of the user's sesion information if * authenticated or false. The following information is - * returned: userid, realm, timestamp, remote_addr, browser. + * returned: userid, timestamp, remoteAddr, browser. */ static public function readSessionData($session_data) { - if (empty($session_data)) { - return false; - } - - $pos = strpos($session_data, 'horde_auth|'); - if ($pos === false) { + if (empty($session_data) || + (($pos = strpos($session_data, 'horde_auth|')) === false)) { return false; } $endpos = $pos + 7; - $old_error = error_reporting(0); while ($endpos !== false) { $endpos = strpos($session_data, '|', $endpos); - $data = unserialize(substr($session_data, $pos + 7, $endpos)); + $data = @unserialize(substr($session_data, $pos + 7, $endpos)); if (is_array($data)) { - error_reporting($old_error); - if (empty($data['authenticated'])) { - return false; - } - return array( - 'browser' => $data['browser'], - 'realm' => $data['realm'], - 'remote_addr' => $data['remote_addr'], - 'timestamp' => $data['timestamp'], - 'userid' => $data['userId'] - ); + return empty($data) + ? false + : array( + 'browser' => $data['browser'], + 'remoteAddr' => $data['remoteAddr'], + 'timestamp' => $data['timestamp'], + 'userid' => $data['userId'] + ); } ++$endpos; } @@ -883,9 +872,9 @@ class Horde_Auth * @param string $msg The error message/reason for invalid * authentication. */ - public function setAuthError($type, $msg = null) + static public function setAuthError($type, $msg = null) { - $GLOBALS['horde_auth']['logout'] = array( + self::$_reason = array( 'msg' => $msg, 'type' => $type ); @@ -896,62 +885,14 @@ class Horde_Auth * * @return mixed Error type or false on error. */ - public function getAuthError() + static public function getAuthError() { - return isset($GLOBALS['horde_auth']['logout']['type']) - ? $GLOBALS['horde_auth']['logout']['type'] + return isset(self::$_reason['type']) + ? self::$_reason['type'] : false; } /** - * Returns the appropriate authentication driver, if any, selecting by the - * specified parameter. - * - * @param string $name The parameter name. - * @param array $params The parameter list. - * @param string $driverparams A list of parameters to pass to the driver. - * - * @return mixed Return value or called user func or null if unavailable - */ - public function getDriverByParam($name, $params, - $driverparams = array()) - { - if (isset($params[$name]) && - function_exists($params[$name])) { - return call_user_func_array($params[$name], $driverparams); - } - - return null; - } - - /** - * Performs check on session to see if IP Address has changed since the - * last access. - * - * @return boolean True if IP Address is the same (or the check is - * disabled), false if the address has changed. - */ - static public function checkSessionIP() - { - return (empty($GLOBALS['conf']['auth']['checkip']) || - (isset($_SESSION['horde_auth']['remote_addr']) && - ($_SESSION['horde_auth']['remote_addr'] == $_SERVER['REMOTE_ADDR']))); - } - - /** - * Performs check on session to see if browser string has changed since - * the last access. - * - * @return boolean True if browser string is the same, false if the - * string has changed. - */ - static public function checkBrowserString() - { - return (empty($GLOBALS['conf']['auth']['checkbrowser']) || - ($_SESSION['horde_auth']['browser'] == $GLOBALS['browser']->getAgentString())); - } - - /** * Converts to allowed 64 characters for APRMD5 passwords. * * @param string $value TODO diff --git a/framework/Auth/lib/Horde/Auth/Application.php b/framework/Auth/lib/Horde/Auth/Application.php index f3e853435..a6c544f38 100644 --- a/framework/Auth/lib/Horde/Auth/Application.php +++ b/framework/Auth/lib/Horde/Auth/Application.php @@ -42,7 +42,9 @@ class Horde_Auth_Application extends Horde_Auth_Base 'authenticate' => 'authAuthenticate', 'exists' => 'authUserExists', 'list' => 'authUserList', + 'loginparams' => 'authLoginParams', 'remove' => 'authRemoveUser', + 'transparent' => 'authTransparent', 'update' => 'authUpdateUser' ); @@ -98,23 +100,10 @@ class Horde_Auth_Application extends Horde_Auth_Base try { $result = $registry->callByPackage($this->_params['app'], $this->_apiMethods['authenticate'], array($userId, $credentials)); + $this->_credentials['params']['app'] = $this->_params['app']; } catch (Horde_Auth_Exception $e) { throw new Horde_Auth_Exception('', Horde_Auth::REASON_BADLOGIN); } - - // Horrific hack. Avert your eyes. Since an application may already - // set the authentication information necessary, we don't want to - // overwrite that info. Coming into this function, we know that - // the authentication has not yet been set in this session. So after - // calling the app-specific auth handler, if authentication - // information has suddenly appeared, it follows that the information - // has been stored already in the session and we shouldn't overwrite. - // So grab the authentication ID set and stick it in $_authCredentials - // this will eventually cause setAuth() in authenticate() to exit - // before re-setting the auth info values. - if ($ret && ($authid = Horde_Auth::getAuth())) { - $this->_authCredentials['userId'] = $authid; - } } /** @@ -205,4 +194,44 @@ class Horde_Auth_Application extends Horde_Auth_Base } } + /** + * Attempt transparent authentication. + * + * @return boolean Whether transparent login is supported. + * @throws Horde_Auth_Exception + */ + protected function _transparent() + { + if (!$this->hasCapability('transparent')) { + return false; + } + + $registry = Horde_Registry::singleton(); + if (!$registry->callByPackage($this->_params['app'], $this->_apiMethods['transparent'])) { + return false; + } + + $this->_credentials['params']['app'] = $this->_params['app']; + return true; + } + + /** + * Returns information on what login parameters to display on the login + * screen. + * + * Is defined in an application's API in the function name identified by + * self::_apiMethods['loginparams']. + * + * @throws Horde_Exception + */ + public function getLoginParams() + { + if (!$this->hasCapability('loginparams')) { + return parent::getLoginParams(); + } + + $registry = Horde_Registry::singleton(); + return $registry->callByPackage($this->_params['app'], $this->_apiMethods['loginparams']); + } + } diff --git a/framework/Auth/lib/Horde/Auth/Base.php b/framework/Auth/lib/Horde/Auth/Base.php index 603db2eb1..e1b6f3a2d 100644 --- a/framework/Auth/lib/Horde/Auth/Base.php +++ b/framework/Auth/lib/Horde/Auth/Base.php @@ -22,6 +22,7 @@ abstract class Horde_Auth_Base */ protected $_capabilities = array( 'add' => false, + 'authenticate' => true, 'groups' => false, 'list' => false, 'resetpassword' => false, @@ -42,7 +43,7 @@ abstract class Horde_Auth_Base * * @var array */ - protected $_authCredentials = array(); + protected $_credentials = array(); /** * Constructor. @@ -63,18 +64,16 @@ abstract class Horde_Auth_Base * @param boolean $login Whether to log the user in. If false, we'll * only test the credentials and won't modify * the current session. Defaults to true. - * @param string $realm The authentication realm to check. * * @return boolean Whether or not the credentials are valid. */ - public function authenticate($userId, $credentials, $login = true, - $realm = null) + public function authenticate($userId, $credentials, $login = true) { $auth = false; $userId = trim($userId); if (!empty($GLOBALS['conf']['hooks']['preauthenticate'])) { - if (!Horde::callHook('_horde_hook_preauthenticate', array($userId, $credentials, $realm), 'horde')) { + if (!Horde::callHook('_horde_hook_preauthenticate', array($userId, $credentials), 'horde')) { if (Horde_Auth::getAuthError() != Horde_Auth::REASON_MESSAGE) { Horde_Auth::setAuthError(Horde_Auth::REASON_FAILED); } @@ -83,11 +82,10 @@ abstract class Horde_Auth_Base } /* Store the credentials being checked so that subclasses can modify - * them if necessary (like transparent auth does). */ - $this->_authCredentials = array( - 'changeRequested' => false, + * them if necessary. */ + $this->_credentials = array( 'credentials' => $credentials, - 'realm' => $realm, + 'params' => array('change' => false), 'userId' => $userId ); @@ -96,19 +94,12 @@ abstract class Horde_Auth_Base if ($login) { $auth = Horde_Auth::setAuth( - $this->_authCredentials['userId'], - $this->_authCredentials['credentials'], - $this->_authCredentials['realm'], - $this->_authCredentials['changeRequested'] + $this->_credentials['userId'], + $this->_credentials['credentials'], + $this->_credentials['params'] ); } else { - if (!Horde_Auth::checkSessionIP()) { - Horde_Auth::setAuthError(self::REASON_SESSIONIP); - } elseif (!Horde_Auth::checkBrowserString()) { - Horde_Auth::setAuthError(self::REASON_BROWSER); - } else { - $auth = true; - } + $auth = Horde_Auth::checkExistingAuth(); } } catch (Horde_Auth_Exception $e) { if ($e->getCode()) { @@ -124,9 +115,9 @@ abstract class Horde_Auth_Base /** * Authentication stub. * - * Horde_Auth_Exception should pass a message string (if any) in the message - * field, and the REASON_* constant in the code field (defaults to - * REASON_MESSAGE). + * On failure, Horde_Auth_Exception should pass a message string (if any) + * in the message field, and the Horde_Auth::REASON_* constant in the code + * field (defaults to Horde_Auth::REASON_MESSAGE). * * @param string $userID The userID to check. * @param array $credentials An array of login credentials. @@ -203,29 +194,44 @@ abstract class Horde_Auth_Base } /** - * Automatic authentication: Finds out if the client matches an allowed IP - * block. + * Automatic authentication. * * @return boolean Whether or not the client is allowed. + * @throws Horde_Auth_Exception */ public function transparent() { - try { - return $this->_transparent(); - } catch (Horde_Auth_Exception $e) { - Horde_Auth::setAuthError($e->getCode() || Horde_Auth::REASON_MESSAGE, $e->getMessage()); - return false; + /* Reset the credentials being checked so that subclasses can modify + * them if necessary. */ + $this->_credentials = array( + 'credentials' => array(), + 'params' => array('change' => false), + 'userId' => '' + ); + + if ($this->_transparent()) { + return Horde_Auth::setAuth( + $this->_credentials['userId'], + $this->_credentials['credentials'], + $this->_credentials['params'] + ); } + + return false; } /** * Transparent authentication stub. * - * If the auth error message is desired to be set, Horde_Auth_Exception should - * thrown instead of returning false. - * The Horde_Auth_Exception object should have a message string (if any) in the - * message field, and the REASON_* constant in the code field (defaults to - * REASON_MESSAGE). + * Transparent authentication should set 'userId', 'credentials', or + * 'params' in $this->_credentials as needed - these values will be used + * to set the credentials in the session. + * + * Transparent authentication should normally never throw an error - false + * should normally be returned. However, it is also possible that a + * transparent authentication is the only available auth method; if so, + * attempting to login via a login page may cause an endless loop. In this + * case, an Exception should be thrown which will act as a fatal error. * * @return boolean Whether transparent login is supported. * @throws Horde_Auth_Exception @@ -277,15 +283,26 @@ abstract class Horde_Auth_Base } /** - * Driver-level admin check stub. + * Returns information on what login parameters to display on the login + * screen. If not defined, will display the default (username, password). * - * @todo - * - * @return boolean False. + * @return array An array with the following elements: + *
+     * 'js_code' - (array) A list of javascript code to output to the login
+     *              page.
+     * 'js_files' - (array) A list of javascript files to include in the login
+     *              page.
+     * 'params' - (array) TODO
+     * 
+ * @throws Horde_Exception */ - public function isAdmin($permission = null, $permlevel = null, $user = null) + public function getLoginParams() { - return false; + return array( + 'js_code' => array(), + 'js_files' => array(), + 'params' => array() + ); } } diff --git a/framework/Auth/lib/Horde/Auth/Composite.php b/framework/Auth/lib/Horde/Auth/Composite.php index ddb011342..a417d8ca7 100644 --- a/framework/Auth/lib/Horde/Auth/Composite.php +++ b/framework/Auth/lib/Horde/Auth/Composite.php @@ -1,8 +1,15 @@ + * 'admin_driver' - (string) TODO + * 'admin_driver_config' - (array) TODO + * 'auth_driver' - (string) TODO + * 'auth_driver_config' - (string) TODO + * * * Copyright 2002-2009 The Horde Project (http://www.horde.org/) * @@ -22,23 +29,6 @@ class Horde_Auth_Composite extends Horde_Auth_Base protected $_drivers = array(); /** - * Return the named parameter for the current auth driver. - * - * @param string $param The parameter to fetch. - * - * @return string The parameter's value. - */ - public function getParam($param) - { - if (($login_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) && - $this->_loadDriver($login_driver)) { - return $this->_drivers[$login_driver]->getParam($param); - } - - return null; - } - - /** * Find out if a set of login credentials are valid. * * @param string $userId The userId to check. @@ -48,19 +38,8 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ protected function _authenticate($userId, $credentials) { - if (($auth_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) && - $this->_loadDriver($auth_driver)) { - $this->_drivers[$auth_driver]->authenticate($userId, $credentials); - return; - } - - if (($auth_driver = Horde_Auth::getDriverByParam('username_switch', $this->_params, array($userId))) && - $this->_loadDriver($auth_driver)) { - $this->_drivers[$auth_driver]->hasCapability('transparent'); - return; - } - - throw new Horde_Auth_Exception('', Horde_Auth::REASON_FAILED); + $driver = $this->_loadDriver('auth'); + return $driver->authenticate($userId, $credentials, false); } /** @@ -73,28 +52,10 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ public function hasCapability($capability) { - switch ($capability) { - case 'add': - case 'update': - case 'remove': - case 'list': - if (!empty($this->_params['admin_driver']) && - $this->_loadDriver($this->_params['admin_driver'])) { - return $this->_drivers[$this->_params['admin_driver']]->hasCapability($capability); - } else { - return false; - } - break; - - case 'transparent': - if (($login_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) && - $this->_loadDriver($login_driver)) { - return $this->_drivers[$login_driver]->hasCapability('transparent'); - } - return false; - break; - - default: + try { + $driver = $this->_loadDriver('admin'); + return $driver->hasCapability($capability); + } catch (Horde_Auth_Exception $e) { return false; } } @@ -107,12 +68,12 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ protected function _transparent() { - if (($login_driver = Horde_Auth::getDriverByParam('loginscreen_switch', $this->_params)) && - $this->_loadDriver($login_driver)) { - return $this->_drivers[$login_driver]->transparent(); + try { + $driver = $this->_loadDriver('auth'); + return $driver->transparent(); + } catch (Horde_Auth_Exception $e) { + return false; } - - return false; } /** @@ -125,12 +86,8 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ public function addUser($userId, $credentials) { - if (!empty($this->_params['admin_driver']) && - $this->_loadDriver($this->_params['admin_driver'])) { - $this->_drivers[$this->_params['admin_driver']]->addUser($userId, $credentials); - } else { - parent::addUser($userId, $credentials); - } + $driver = $this->_loadDriver('admin'); + $driver->addUser($userId, $credentials); } /** @@ -144,12 +101,8 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ public function updateUser($oldID, $newID, $credentials) { - if (!empty($this->_params['admin_driver']) && - $this->_loadDriver($this->_params['admin_driver'])) { - $this->_drivers[$this->_params['admin_driver']]->updateUser($oldID, $newID, $credentials); - } else { - parent::updateUser($oldID, $newID, $credentials); - } + $driver = $this->_loadDriver('admin'); + $driver->updateUser($oldID, $newID, $credentials); } /** @@ -161,12 +114,8 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ public function removeUser($userId) { - if (!empty($this->_params['admin_driver']) && - $this->_loadDriver($this->_params['admin_driver'])) { - $this->_drivers[$this->_params['admin_driver']]->removeUser($userId); - } else { - parent::removeUser($userId); - } + $driver = $this->_loadDriver('admin'); + $driver->removeUser($userId); } /** @@ -177,12 +126,8 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ public function listUsers() { - if (!empty($this->_params['admin_driver']) && - $this->_loadDriver($this->_params['admin_driver'])) { - return $this->_drivers[$this->_params['admin_driver']]->listUsers(); - } - - return parent::listUsers(); + $driver = $this->_loadDriver('admin'); + return $driver->listUsers(); } /** @@ -194,12 +139,12 @@ class Horde_Auth_Composite extends Horde_Auth_Base */ public function exists($userId) { - if (!empty($this->_params['admin_driver']) && - $this->_loadDriver($this->_params['admin_driver'])) { - return $this->_drivers[$this->_params['admin_driver']]->exists($userId); + try { + $driver = $this->_loadDriver('admin'); + return $driver->exists($userId); + } catch (Horde_Auth_Exception $e) { + return false; } - - return parent::exists($userId); } /** @@ -208,29 +153,15 @@ class Horde_Auth_Composite extends Horde_Auth_Base * * @param string $driver The name of the driver to load. * - * @return boolean True if driver successfully initializes. + * @throws Horde_Auth_Exception */ protected function _loadDriver($driver) { if (empty($this->_drivers[$driver])) { - // This is a bit specialized for Horde::getDriverConfig(), - // so localize it here: - global $conf; - if (!empty($this->_params['drivers'][$driver]['params'])) { - $params = $this->_params['drivers'][$driver]['params']; - if (isset($conf[$this->_params['drivers'][$driver]['driver']])) { - $params = array_merge($conf[$this->_params['drivers'][$driver]['driver']], $params); - } - } elseif (!empty($conf[$driver])) { - $params = $conf[$driver]; - } else { - $params = null; - } - - $this->_drivers[$driver] = Horde_Auth::singleton($this->_params['drivers'][$driver]['driver'], $params); + $this->_drivers[$driver] = Horde_Auth::singleton($this->_params[$driver . '_driver'], $this->_params[$driver . '_driver_config']); } - return isset($this->_drivers[$driver]); + return $this->_drivers[$driver]; } } diff --git a/framework/Auth/lib/Horde/Auth/Cyrsql.php b/framework/Auth/lib/Horde/Auth/Cyrsql.php index 4d30aff47..39f8ccce5 100644 --- a/framework/Auth/lib/Horde/Auth/Cyrsql.php +++ b/framework/Auth/lib/Horde/Auth/Cyrsql.php @@ -217,7 +217,7 @@ class Horde_Auth_Cyrsql extends Horde_Auth_Sql if (!empty($this->_params['soft_expiration_field']) && !empty($row[$this->_params['soft_expiration_field']]) && ($now > $row[$this->_params['soft_expiration_field']])) { - $this->_authCredentials['changeRequested'] = true; + $this->_credentials['params']['change'] = true; } } diff --git a/framework/Auth/lib/Horde/Auth/Imap.php b/framework/Auth/lib/Horde/Auth/Imap.php index 6a04500b1..294cf0f0b 100644 --- a/framework/Auth/lib/Horde/Auth/Imap.php +++ b/framework/Auth/lib/Horde/Auth/Imap.php @@ -62,7 +62,7 @@ class Horde_Auth_Imap extends Horde_Auth_Base 'userhierarchy' => 'user.' ), $params); - parent::__construct(array_merge($default_params, $params)); + parent::__construct($params); if (!empty($this->_params['admin_user'])) { $this->_capabilities['add'] = true; diff --git a/framework/Auth/lib/Horde/Auth/Kolab.php b/framework/Auth/lib/Horde/Auth/Kolab.php index 34ee5df80..fc910596f 100644 --- a/framework/Auth/lib/Horde/Auth/Kolab.php +++ b/framework/Auth/lib/Horde/Auth/Kolab.php @@ -132,11 +132,10 @@ class Horde_Auth_Kolab extends Horde_Auth_Base * * @param string $userId The userId who has been authorized. * @param array $credentials The credentials of the user. - * @param string $realm The authentication realm to use. - * @param boolean $changeRequested Whether to request that the user change + * @param boolean $change Whether to request that the user change * their password. */ - function setAuth($userId, $credentials, $realm = null, $changeRequested = false) + function setAuth($userId, $credentials, $change = false) { // TODO - setAuth doesn't exist in Horde_Auth_Base // This should probably use _username_hook_frombackend. @@ -146,7 +145,7 @@ class Horde_Auth_Kolab extends Horde_Auth_Base $userId = $session->user_mail; } - return parent::setAuth($userId, $credentials, $realm, $changeRequested); + return parent::setAuth($userId, $credentials, $change); } /** diff --git a/framework/Auth/lib/Horde/Auth/Ldap.php b/framework/Auth/lib/Horde/Auth/Ldap.php index c29fcdc55..666a43da4 100644 --- a/framework/Auth/lib/Horde/Auth/Ldap.php +++ b/framework/Auth/lib/Horde/Auth/Ldap.php @@ -295,7 +295,7 @@ class Horde_Auth_Ldap extends Horde_Auth_Base } if ($toexpire == 0) { - $this->_authCredentials['changeRequested'] = true; + $this->_credentials['params']['change'] = true; } elseif ($toexpire < 0) { throw new Horde_Auth_Exception('', Horde_Auth::REASON_EXPIRED); } diff --git a/framework/Auth/lib/Horde/Auth/Msad.php b/framework/Auth/lib/Horde/Auth/Msad.php index 327af681d..6903d1158 100644 --- a/framework/Auth/lib/Horde/Auth/Msad.php +++ b/framework/Auth/lib/Horde/Auth/Msad.php @@ -220,7 +220,7 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap $ssl = ($this->_params['ssl']) ? 'ldaps://' : ''; $this->_ds = ldap_connect($ssl . $this->_params['hostspec'], $this->_params['port']); if (!$this->_ds) { - return PEAR::raiseError(_("Failed to connect to MSAD server.")); + throw new Horde_Auth_Exception(_("Failed to connect to MSAD server.")); } if (!ldap_set_option($this->_ds, LDAP_OPT_PROTOCOL_VERSION, 3)) { @@ -249,7 +249,7 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap } if (!$bind) { - return PEAR::raiseError(_("Could not bind to MSAD server.")); + throw new Horde_Auth_Exception(_("Could not bind to MSAD server.")); } return true; @@ -258,13 +258,11 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap /** * Find the user dn * - * @access private - * * @param string $userId The user UID to find. * * @return string The user's full DN */ - function _findDN($userId) + protected function _findDN($userId) { /* Search for the user's full DN. */ foreach ($this->_params['uid'] as $uid) { @@ -278,7 +276,7 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap ); /* Searching the tree is not successful */ if (!$search) { - return PEAR::raiseError(_("Could not search the MSAD server.")); + throw new Horde_Auth_Exception(_("Could not search the MSAD server.")); } /* Fetch the search result */ @@ -289,14 +287,14 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap } } - if (is_array($result) && (count($result) > 1)) { - $dn = $result[0]['dn']; - } else { - return PEAR::raiseError(_("Empty result.")); + if (!is_array($result) || (count($result) <= 1)) { + throw new Horde_Auth_Exception(_("Empty result.")); } + /* Be sure the horde userId is the configured one */ - $this->_authCredentials['userId'] = $result[0][$this->_params['authId']][0]; - return $dn; + $this->_credentials['userId'] = $result[0][$this->_params['authId']][0]; + + return $result[0]['dn']; } } diff --git a/framework/Auth/lib/Horde/Auth/Sql.php b/framework/Auth/lib/Horde/Auth/Sql.php index 32cc5bbd5..7b573f2f3 100644 --- a/framework/Auth/lib/Horde/Auth/Sql.php +++ b/framework/Auth/lib/Horde/Auth/Sql.php @@ -158,7 +158,7 @@ class Horde_Auth_Sql extends Horde_Auth_Base if (!empty($this->_params['soft_expiration_field']) && !empty($row[$this->_params['soft_expiration_field']]) && ($now > $row[$this->_params['soft_expiration_field']])) { - $this->_authCredentials['changeRequested'] = true; + $this->_credentials['params']['change'] = true; } } diff --git a/framework/Core/lib/Horde.php b/framework/Core/lib/Horde.php index 5d58e5953..e176172dd 100644 --- a/framework/Core/lib/Horde.php +++ b/framework/Core/lib/Horde.php @@ -433,12 +433,10 @@ HTML; return self::url($GLOBALS['registry']->get('webroot', 'horde') . '/services/problem.php?return_url=' . urlencode(self::selfUrl(true, true, true))); case 'logout': - return self::url(Horde_Auth::addLogoutParameters($GLOBALS['registry']->get('webroot', 'horde') . '/login.php', Horde_Auth::REASON_LOGOUT)); + return Horde_Auth::getLogoutUrl(array('reason' => Horde_Auth::REASON_LOGOUT)); case 'login': - /* Get an Auth object. */ - $auth = Horde_Auth::singleton($GLOBALS['conf']['auth']['driver']); - return $auth->getLoginScreen('', $referrer ? self::selfUrl(true) : null); + return self::url($GLOBALS['registry']->get('webroot', 'horde') . '/login.php'); case 'options': global $conf; @@ -1780,23 +1778,6 @@ HTML; } /** - * Redirects to the main Horde login page on authentication failure. - */ - static public function authenticationFailureRedirect() - { - if (Horde_Cli::runningFromCLI()) { - $cli = Horde_Cli::singleton(); - $cli->fatal(_("You are not authenticated.")); - } - - $url = $GLOBALS['registry']->get('webroot', 'horde') . '/login.php'; - $url = Horde_Util::addParameter($url, array('url' => self::selfUrl(true), 'nosidebar' => 1), null, false); - $url = Horde_Auth::addLogoutParameters($url); - header('Location: ' . self::url($url, true)); - exit; - } - - /** * Provides a standardised function to call a Horde hook, checking whether * a hook config file exists and whether the function itself exists. If * these two conditions are not satisfied it will return the specified diff --git a/framework/Core/lib/Horde/Registry.php b/framework/Core/lib/Horde/Registry.php index 7d4389fe2..f74c0503b 100644 --- a/framework/Core/lib/Horde/Registry.php +++ b/framework/Core/lib/Horde/Registry.php @@ -21,6 +21,11 @@ class Horde_Registry const SESSION_NONE = 1; const SESSION_READONLY = 2; + /* Error codes for pushApp(). */ + const AUTH_FAILURE = 1; + const NOT_ACTIVE = 2; + const PERMISSION_DENIED = 3; + /** * Singleton value. * @@ -775,6 +780,10 @@ class Horde_Registry * * @return boolean Whether or not the _appStack was modified. * @throws Horde_Exception + * Code can be one of the following: + * Horde_Registry::AUTH_FAILURE + * Horde_Registry::NOT_ACTIVE + * Horde_Registry::PERMISSION_DENIED */ public function pushApp($app, $checkPerms = true) { @@ -786,19 +795,24 @@ class Horde_Registry if (!isset($this->applications[$app]) || $this->applications[$app]['status'] == 'inactive' || ($this->applications[$app]['status'] == 'admin' && !Horde_Auth::isAdmin())) { - throw new Horde_Exception($app . ' is not activated.'); + throw new Horde_Exception($app . ' is not activated.', 'not_active'); } /* If permissions checking is requested, return an error if the * current user does not have read perms to the application being * loaded. We allow access: - * * - To all admins. * - To all authenticated users if no permission is set on $app. * - To anyone who is allowed by an explicit ACL on $app. */ - if ($checkPerms && !$this->hasPermission($app)) { - Horde::logMessage(sprintf('%s does not have READ permission for %s', Horde_Auth::getAuth() ? 'User ' . Horde_Auth::getAuth() : 'Guest user', $app), __FILE__, __LINE__, PEAR_LOG_DEBUG); - throw new Horde_Exception(sprintf(_('%s is not authorised for %s.'), Horde_Auth::getAuth() ? 'User ' . Horde_Auth::getAuth() : 'Guest user', $this->applications[$app]['name']), 'permission_denied'); + if ($checkPerms) { + if (!Horde_Auth::isAuthenticated(array('app' => $app))) { + throw new Horde_Exception('User is not authorized', self::AUTH_FAILURE); + } + + if (!$this->hasPermission($app, PERMS_READ)) { + Horde::logMessage(sprintf('%s does not have READ permission for %s', Horde_Auth::getAuth() ? 'User ' . Horde_Auth::getAuth() : 'Guest user', $app), __FILE__, __LINE__, PEAR_LOG_DEBUG); + throw new Horde_Exception(sprintf(_('%s is not authorized for %s.'), Horde_Auth::getAuth() ? 'User ' . Horde_Auth::getAuth() : 'Guest user', $this->applications[$app]['name']), 'permission_denied'); + } } /* Set up autoload paths for the current application. This needs to @@ -889,10 +903,19 @@ class Horde_Registry */ public function hasPermission($app, $perms = PERMS_READ) { - return Horde_Auth::isAdmin() || - ($GLOBALS['perms']->exists($app) - ? $GLOBALS['perms']->hasPermission($app, Horde_Auth::getAuth(), $perms) - : (bool)Horde_Auth::getAuth()); + $this->_lastPerms = null; + + if (Horde_Auth::isAdmin()) { + return true; + } + + if ($GLOBALS['perms']->exists($app) && + !$GLOBALS['perms']->hasPermission($app, Horde_Auth::getAuth(), $perms)) { + $this->_lastPerms = 'perms'; + return false; + } + + return true; } /** -- 2.11.0