Final parts of the Horde_Auth rewrite.
authorMichael M Slusarz <slusarz@curecanti.org>
Tue, 8 Jun 2010 04:53:02 +0000 (22:53 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 9 Jun 2010 23:17:18 +0000 (17:17 -0600)
All session related code has now been moved to horde/Core.
Horde_Auth no longer depends on horde/Core.
Authentication for each application is handled entirely by the
Horde_Core_Auth_Application driver.

Removed registry functions:
getProvider()
requireAuth()

isAuthenticated() now has a new option - 'notransparent'

Removed the recently added clearAuth() call to isAuthenticated() -
isAuthenticated() is meant solely for determining whether the given app
is authenticated.  Code to invalidate a session (for example, if a
checkExistingAuth() call fails) MUST go elsewhere.

Moved last login display to a Horde LoginTask.

30 files changed:
folks/edit/password.php
folks/lib/Application.php
framework/Auth/lib/Horde/Auth.php
framework/Auth/lib/Horde/Auth/Auto.php
framework/Auth/lib/Horde/Auth/Base.php
framework/Auth/lib/Horde/Auth/Composite.php
framework/Auth/lib/Horde/Auth/Cyrsql.php
framework/Auth/lib/Horde/Auth/Cyrus.php
framework/Auth/lib/Horde/Auth/Http.php
framework/Auth/lib/Horde/Auth/Ipbasic.php
framework/Auth/lib/Horde/Auth/Kolab.php
framework/Auth/lib/Horde/Auth/Ldap.php
framework/Auth/lib/Horde/Auth/Msad.php
framework/Auth/lib/Horde/Auth/Shibboleth.php
framework/Auth/lib/Horde/Auth/Sql.php
framework/Auth/package.xml
framework/Auth/test/Horde/Auth/credentials.php
framework/Auth/test/Horde/Auth/passwd.phpt
framework/Core/lib/Horde/Core/Auth/Application.php
framework/Core/lib/Horde/Core/Auth/Ldap.php [new file with mode: 0644]
framework/Core/lib/Horde/Core/Auth/Msad.php [new file with mode: 0644]
framework/Core/lib/Horde/Core/Auth/Shibboleth.php [new file with mode: 0644]
framework/Core/lib/Horde/Core/Autoloader/Callback/Auth.php [deleted file]
framework/Core/lib/Horde/Core/Factory/Auth.php
framework/Core/lib/Horde/Registry.php
framework/Core/package.xml
gollem/lib/Auth.php
horde/lib/LoginTasks/Task/LastLogin.php [new file with mode: 0644]
horde/login.php
passwd/lib/Passwd.php

index 97f7714..06a2a9b 100644 (file)
@@ -161,13 +161,7 @@ do {
     // reset credentials so user is not forced to relogin
     if ($registry->getAuthCredential('password') == $info['old']) {
         $registry->setAuthCredential('password', $info['new']);
-        $secret = $injector->getInstance('Horde_Secret');
-        if ($registry->getProvider() == 'imp' ||
-            !empty($_SESSION['imp']['pass'])) {
-            $_SESSION['imp']['pass'] = $secret->write($secret->getKey('imp'), $info['new']);
-        }
     }
-
 } while (false);
 
 // update password reminder prefs
index 9a7f7ea..d9bca58 100644 (file)
@@ -57,7 +57,7 @@ class Folks_Application extends Horde_Registry_Application
         require_once dirname(__FILE__) . '/base.php';
         $GLOBALS['folks_driver'] = Folks_Driver::factory();
         if ($_COOKIE['folks_login_code'] == $GLOBALS['folks_driver']->getCookie($_COOKIE['folks_login_user'])) {
-            Horde_Auth::setAuth($_COOKIE['folks_login_user'], array(), array('transparent' => true, 'nologin' => true));
+            $GLOBALS['registry']->setAuth($_COOKIE['folks_login_user']);
             $auth_ob->setCredential('userId', $_COOKIE['folks_login_user']);
             $GLOBALS['folks_driver']->resetOnlineUsers();
             return true;
index c182650..900b2cd 100644 (file)
@@ -1,24 +1,8 @@
 <?php
 /**
- * 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:
- * <pre>
- * 'app' - (array) Application-specific authentication. Keys are the
- *         app names, values are an array containing credentials. If true,
- *         application does not require any specific credentials.
- * 'authId' - (string) The username used during the original authentication.
- * 'browser' - (string) The remote browser string.
- * 'change' - (boolean) Is a password change requested?
- * 'credentials' - (string) The 'app' entry that contains the Horde
- *                 credentials.
- * 'driver' - (string) The driver used for base horde auth.
- * 'remoteAddr' - (string) The remote IP address of the user.
- * 'timestamp' - (integer) The login time.
- * 'userId' - (string) The unique Horde username.
- * </pre>
+ * The Horde_Auth:: class provides a common abstracted interface for various
+ * authentication backends.  It also provides some useful
+ * authentication-related utilities.
  *
  * Copyright 1999-2010 The Horde Project (http://www.horde.org/)
  *
 class Horde_Auth
 {
     /**
-     * The parameter name for the logout reason.
-     */
-    const REASON_PARAM = 'logout_reason';
-
-    /**
-     * The parameter name for the logout message used with type
-     * REASON_MESSAGE.
-    */
-    const REASON_MSG_PARAM = 'logout_msg';
-
-    /**
-     * The 'badlogin' reason.
+     * Authentication failure reasons.
      *
-     * The following 'reasons' for the logout screen are recognized:
      * <pre>
      * REASON_BADLOGIN - Bad username and/or password
-     * REASON_BROWSER - A browser change was detected
      * REASON_FAILED - Login failed
      * REASON_EXPIRED - Password has expired
      * REASON_LOGOUT - Logout due to user request
-     * REASON_MESSAGE - Logout with custom message in REASON_MSG_PARAM
+     * REASON_MESSAGE - Logout with custom message
      * REASON_SESSION - Logout due to session expiration
-     * REASON_SESSIONIP - Logout due to change of IP address during session
      * </pre>
      */
     const REASON_BADLOGIN = 1;
-    const REASON_BROWSER = 2;
-    const REASON_FAILED = 3;
-    const REASON_EXPIRED = 4;
-    const REASON_LOGOUT = 5;
-    const REASON_MESSAGE = 6;
-    const REASON_SESSION = 7;
-    const REASON_SESSIONIP = 8;
+    const REASON_FAILED = 2;
+    const REASON_EXPIRED = 3;
+    const REASON_LOGOUT = 4;
+    const REASON_MESSAGE = 5;
+    const REASON_SESSION = 6;
 
     /**
      * 64 characters that are valid for APRMD5 passwords.
@@ -81,20 +49,6 @@ class Horde_Auth
     const NUMBERS = '0123456789';
 
     /**
-     * A Net_DNS_Resolver object to use to determine hostnames.
-     *
-     * @var Net_DNS_Resolver
-     */
-    static public $dnsResolver;
-
-    /**
-     * The logout reason information.
-     *
-     * @var array
-     */
-    static protected $_reason = array();
-
-    /**
      * Attempts to return a concrete Horde_Auth_Base instance based on
      * $driver.
      *
@@ -286,278 +240,6 @@ class Horde_Auth
     }
 
     /**
-     * Generates a random, hopefully pronounceable, password. This can be used
-     * when resetting automatically a user's password.
-     *
-     * @return string A random password
-     */
-    static public function genRandomPassword()
-    {
-        /* Alternate consonant and vowel random chars with two random numbers
-         * at the end. This should produce a fairly pronounceable password. */
-        return substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
-            substr(self::VOWELS, mt_rand(0, strlen(self::VOWELS) - 1), 1) .
-            substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
-            substr(self::VOWELS, mt_rand(0, strlen(self::VOWELS) - 1), 1) .
-            substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
-            substr(self::NUMBERS, mt_rand(0, strlen(self::NUMBERS) - 1), 1) .
-            substr(self::NUMBERS, mt_rand(0, strlen(self::NUMBERS) - 1), 1);
-    }
-
-    /**
-     * Calls all applications' removeUser API methods.
-     *
-     * @param string $userId  The userId to delete.
-     *
-     * @throws Horde_Auth_Exception
-     */
-    static public function removeUserData($userId)
-    {
-        $errApps = array();
-
-        foreach ($GLOBALS['registry']->listApps(array('notoolbar', 'hidden', 'active', 'admin')) as $app) {
-            try {
-                $GLOBALS['registry']->callByPackage($app, 'removeUserData', array($userId));
-            } catch (Horde_Auth_Exception $e) {
-                Horde::logMessage($e, 'ERR');
-                $errApps[] = $app;
-            }
-        }
-
-        if (count($errApps)) {
-            throw new Horde_Auth_Exception(sprintf(_("The following applications encountered errors removing user data: %s"), implode(', ', $errApps)));
-        }
-    }
-
-    /**
-     * 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'])) {
-            if ($_SESSION['horde_auth']['browser'] != $GLOBALS['injector']->getInstance('Horde_Browser')->getAgentString()) {
-                self::setAuthError(self::REASON_BROWSER);
-                return false;
-            }
-        }
-
-        return $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->checkExistingAuth();
-    }
-
-    /**
-     * Sets a variable in the session saying that authorization has succeeded,
-     * note which userId was authorized, and note when the login took place.
-     *
-     * If a user name hook was defined in the configuration, it gets applied
-     * to the $userId at this point.
-     *
-     * @param string $authId      The userId that has been authorized.
-     * @param array $credentials  The credentials of the user.
-     * @param array $options      Additional options:
-     * <pre>
-     * 'app' - (string) The app to set authentication credentials for.
-     *         DEFAULT: Set horde authentication
-     * 'change' - (boolean) Whether to request that the user change their
-     *            password.
-     *            DEFAULT: No
-     * 'nologin' - (boolean) Don't do login tasks?
-     *             DEFAULT: Perform login tasks
-     * </pre>
-     *
-     * @return boolean  Whether authentication was successful.
-     */
-    static public function setAuth($authId, $credentials, $options = array())
-    {
-        $app = empty($options['app']) ? 'horde' : $options['app'];
-        $authId = $userId = trim($authId);
-        $is_auth = $GLOBALS['registry']->getAuth();
-
-        try {
-            if (!$is_auth) {
-                $userId = $GLOBALS['registry']->convertUserName($userId, true);
-            }
-            list(,$credentials) = self::runHook($userId, $credentials, $app, 'postauthenticate');
-        } catch (Horde_Auth_Exception $e) {
-            return false;
-        }
-
-        $app_array = $is_auth
-            ? $_SESSION['horde_auth']['app']
-            : array();
-
-        $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
-        $app_array[$app] = $secret->write($secret->getKey('auth'), serialize($credentials));
-
-        if ($is_auth) {
-            /* Store app credentials. */
-            $_SESSION['horde_auth']['app'] = $app_array;
-            return true;
-        }
-
-        /* Clear any existing info. */
-        $GLOBALS['registry']->clearAuth(false);
-
-        $_SESSION['horde_auth'] = array(
-            'app' => $app_array,
-            'authId' => $authId,
-            'browser' => $GLOBALS['injector']->getInstance('Horde_Browser')->getAgentString(),
-            'change' => !empty($options['change']),
-            'credentials' => $app,
-            'driver' => $GLOBALS['conf']['auth']['driver'],
-            'remoteAddr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
-            'timestamp' => time(),
-            'userId' => $userId
-        );
-
-        /* Reload preferences for the new user. */
-        $GLOBALS['registry']->loadPrefs();
-        Horde_Nls::setLanguageEnvironment($GLOBALS['prefs']->getValue('language'), $app);
-
-        if (!empty($options['nologin'])) {
-            return true;
-        }
-
-        /* Fetch the user's last login time. */
-        $old_login = @unserialize($GLOBALS['prefs']->getValue('last_login'));
-
-        /* Display it, if we have a notification object and the
-         * show_last_login preference is active. */
-        if (isset($GLOBALS['notification']) &&
-            $GLOBALS['prefs']->getValue('show_last_login')) {
-            if (empty($old_login['time'])) {
-                $GLOBALS['notification']->push(_("Last login: Never"), 'horde.message');
-            } else {
-                if (empty($old_login['host'])) {
-                    $GLOBALS['notification']->push(sprintf(_("Last login: %s"), strftime('%c', $old_login['time'])), 'horde.message');
-                } else {
-                    $GLOBALS['notification']->push(sprintf(_("Last login: %s from %s"), strftime('%c', $old_login['time']), $old_login['host']), 'horde.message');
-                }
-            }
-        }
-
-        /* Set the user's last_login information. */
-        $host = empty($_SERVER['HTTP_X_FORWARDED_FOR'])
-            ? $_SERVER['REMOTE_ADDR']
-            : $_SERVER['HTTP_X_FORWARDED_FOR'];
-
-        if (!empty(self::$dnsResolver)) {
-            $ptrdname = $host;
-            if ($response = self::$dnsResolver->query($host, 'PTR')) {
-                foreach ($response->answer as $val) {
-                    if (isset($val->ptrdname)) {
-                        $ptrdname = $val->ptrdname;
-                        break;
-                    }
-                }
-            }
-        } else {
-            $ptrdname = @gethostbyaddr($host);
-        }
-
-        $last_login = array('time' => time(), 'host' => $ptrdname);
-        $GLOBALS['prefs']->setValue('last_login', serialize($last_login));
-
-        return true;
-    }
-
-    /**
-     * Runs the pre/post-authenticate hook and parses the result.
-     *
-     * @param string $userId      The userId who has been authorized.
-     * @param array $credentials  The credentials of the user.
-     * @param string $app         The app currently being authenticated.
-     * @param string $type        Either 'preauthenticate' or
-     *                            'postauthenticate'.
-     * @param string $method      The triggering method (preauthenticate only).
-     *                            Either 'authenticate', 'transparent', or
-     *                            'admin'
-     *
-     * @return array  Two element array, $userId and $credentials.
-     * @throws Horde_Auth_Exception
-     */
-    static public function runHook($userId, $credentials, $app, $type,
-                                   $method = null)
-    {
-        $ret_array = array($userId, $credentials);
-
-        if ($type == 'preauthenticate') {
-            $credentials['authMethod'] = $method;
-        }
-
-        try {
-            $result = Horde::callHook($type, array($userId, $credentials), $app);
-        } catch (Horde_Exception $e) {
-            throw new Horde_Auth_Exception($e->getMessage());
-        } catch (Horde_Exception_HookNotSet $e) {
-            return $ret_array;
-        }
-
-        unset($credentials['authMethod']);
-
-        if ($result === false) {
-            if (self::getAuthError() != self::REASON_MESSAGE) {
-                self::setAuthError(self::REASON_FAILED);
-            }
-            throw new Horde_Auth_Exception($type . ' hook failed');
-        }
-
-        if (is_array($result)) {
-            if ($type == 'postauthenticate') {
-                $ret_array[1] = $result;
-            } else {
-                if (isset($result['userId'])) {
-                    $ret_array[0] = $result['userId'];
-                }
-
-                if (isset($result['credentials'])) {
-                    $ret_array[1] = $result['credentials'];
-                }
-            }
-        }
-
-        return $ret_array;
-    }
-
-    /**
-     * Sets the error message for an invalid authentication.
-     *
-     * @param string $type  The type of error (self::REASON_* constant).
-     * @param string $msg   The error message/reason for invalid
-     *                      authentication.
-     */
-    static public function setAuthError($type, $msg = null)
-    {
-        self::$_reason = array(
-            'msg' => $msg,
-            'type' => $type
-        );
-    }
-
-    /**
-     * Returns the error type or message for an invalid authentication.
-     *
-     * @param boolean $msg  If true, returns the message string (if set).
-     *
-     * @return mixed  Error type, error message (if $msg is true) or false
-     *                if entry doesn't exist.
-     */
-    static public function getAuthError($msg = false)
-    {
-        return isset(self::$_reason['type'])
-            ? ($msg ? self::$_reason['msg'] : self::$_reason['type'])
-            : false;
-    }
-
-    /**
      * Converts to allowed 64 characters for APRMD5 passwords.
      *
      * @param string $value   TODO
@@ -579,4 +261,23 @@ class Horde_Auth
         return $aprmd5;
     }
 
+    /**
+     * Generates a random, hopefully pronounceable, password. This can be used
+     * when resetting automatically a user's password.
+     *
+     * @return string A random password
+     */
+    static public function genRandomPassword()
+    {
+        /* Alternate consonant and vowel random chars with two random numbers
+         * at the end. This should produce a fairly pronounceable password. */
+        return substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
+            substr(self::VOWELS, mt_rand(0, strlen(self::VOWELS) - 1), 1) .
+            substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
+            substr(self::VOWELS, mt_rand(0, strlen(self::VOWELS) - 1), 1) .
+            substr(self::CONSONANTS, mt_rand(0, strlen(self::CONSONANTS) - 1), 1) .
+            substr(self::NUMBERS, mt_rand(0, strlen(self::NUMBERS) - 1), 1) .
+            substr(self::NUMBERS, mt_rand(0, strlen(self::NUMBERS) - 1), 1);
+    }
+
 }
index 0018664..9aff8be 100644 (file)
@@ -72,7 +72,7 @@ class Horde_Auth_Auto extends Horde_Auth_Base
      *
      * @return boolean  Whether or not the client is allowed.
      */
-    protected function _transparent()
+    public function transparent()
     {
         $this->_credentials['userId'] = (!empty($this->_params['requestuser']) && isset($_REQUEST['username']))
             ? $_REQUEST['username']
index eece8f6..a52d0bf 100644 (file)
@@ -46,24 +46,25 @@ abstract class Horde_Auth_Base
      * @var array
      */
     protected $_credentials = array(
+        'change' => false,
         'credentials' => array(),
-        'params' => array('change' => false),
+        'expire' => null,
         'userId' => ''
     );
 
     /**
-     * Current application for authentication.
+     * Logger object.
      *
-     * @var string
+     * @var Horde_Log_Logger
      */
-    protected $_app = 'horde';
+    protected $_logger;
 
     /**
-     * Logger object.
+     * Authentication error information.
      *
-     * @var Horde_Log_Logger
+     * @var array
      */
-    protected $_logger;
+    protected $_error;
 
     /**
      * Constructor.
@@ -72,9 +73,6 @@ abstract class Horde_Auth_Base
      * <pre>
      * 'default_user' - (string) The default user.
      * 'logger' - (Horde_Log_Logger) A logger object.
-     * 'notify_expire' - (callback) Callback function to output notification
-     *                   when password is about to expire. Passed one
-     *                   argument: UNIX timestamp of when password expires.
      * </pre>
      */
     public function __construct(array $params = array())
@@ -105,42 +103,21 @@ abstract class Horde_Auth_Base
      */
     public function authenticate($userId, $credentials, $login = true)
     {
-        $auth = false;
         $userId = trim($userId);
 
         try {
-            list($userId, $credentials) = Horde_Auth::runHook($userId, $credentials, $this->_app, 'preauthenticate', 'authenticate');
-         } catch (Horde_Auth_Exception $e) {
-            return false;
-        }
-
-        /* Store the credentials being checked so that subclasses can modify
-         * them if necessary. */
-        $this->_credentials['credentials'] = $credentials;
-        $this->_credentials['userId'] = $userId;
-        $this->_credentials['params']['app'] = $this->_app;
-
-        try {
             $this->_authenticate($userId, $credentials);
-
-            if ($login) {
-                $auth = Horde_Auth::setAuth(
-                    $this->_credentials['userId'],
-                    $this->_credentials['credentials'],
-                    $this->_credentials['params']
-                );
-            } else {
-                $auth = Horde_Auth::checkExistingAuth();
-            }
+            $this->setCredential('userId', $userId);
+            $this->setCredential('credentials', $credentials);
+            return true;
         } catch (Horde_Auth_Exception $e) {
             if ($e->getCode()) {
-                Horde_Auth::setAuthError($e->getCode());
+                $this->setError($e->getCode());
             } else {
-                Horde_Auth::setAuthError(Horde_Auth::REASON_MESSAGE, $e->getMessage());
+                $this->setError(Horde_Auth::REASON_MESSAGE, $e->getMessage());
             }
+            return false;
         }
-
-        return $auth;
     }
 
     /**
@@ -158,11 +135,12 @@ abstract class Horde_Auth_Base
     abstract protected function _authenticate($userId, $credentials);
 
     /**
-     * Check existing auth for triggers that might invalidate it.
+     * Checks for triggers that may invalidate the current auth.
+     * These triggers are independent of the credentials.
      *
-     * @return boolean  Is existing auth valid?
+     * @return boolean  True if the results of authenticate() are still valid.
      */
-    public function checkExistingAuth()
+    public function validateAuth()
     {
         return true;
     }
@@ -237,36 +215,6 @@ abstract class Horde_Auth_Base
     /**
      * Automatic authentication.
      *
-     * @return boolean  Whether or not the user is authenticated
-     *                  automatically.
-     * @throws Horde_Auth_Exception
-     */
-    public function transparent()
-    {
-        $userId = empty($this->_credentials['userId'])
-            ? $this->_params['default_user']
-            : $this->_credentials['userId'];
-        $credentials = empty($this->_credentials['credentials'])
-            ? $GLOBALS['registry']->getAuthCredential()
-            : $this->_credentials['credentials'];
-
-        list($this->_credentials['userId'], $this->_credentials['credentials']) = Horde_Auth::runHook($userId, $credentials, $this->_app, 'preauthenticate', 'transparent');
-        $this->_credentials['params']['app'] = $this->_app;
-
-        if ($this->_transparent()) {
-            return Horde_Auth::setAuth(
-                $this->_credentials['userId'],
-                $this->_credentials['credentials'],
-                $this->_credentials['params']
-            );
-        }
-
-        return false;
-    }
-
-    /**
-     * Transparent authentication stub.
-     *
      * 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.
@@ -277,7 +225,7 @@ abstract class Horde_Auth_Base
      * @return boolean  Whether transparent login is supported.
      * @throws Horde_Auth_Exception
      */
-    protected function _transparent()
+    public function transparent()
     {
         return false;
     }
@@ -324,26 +272,87 @@ abstract class Horde_Auth_Base
     }
 
     /**
-     * Returns information on what login parameters to display on the login
-     * screen. If not defined, will display the default (username, password).
+     * Retrieve internal credential value(s).
      *
-     * @return array  An array with the following elements:
+     * @param mixed $name  The credential value to get. If null, will return
+     *                     the entire credential list. Valid names:
      * <pre>
-     * '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
+     * 'change' - (boolean) Do credentials need to be changed?
+     * 'credentials' - (array) The credentials needed to authenticate.
+     * 'expire' - (integer) UNIX timestamp of the credential expiration date.
+     * 'userId' - (string) The user ID.
      * </pre>
-     * @throws Horde_Exception
+     *
+     * @return mixed  Return the credential information, or null if the
+     *                credential doesn't exist.
      */
-    public function getLoginParams()
+    public function getCredential($name = null)
     {
-        return array(
-            'js_code' => array(),
-            'js_files' => array(),
-            'params' => array()
+        if (is_null($name)) {
+            return $this->_credentials;
+        }
+
+        return isset($this->_credentials[$name])
+            ? $this->_credentials[$name]
+            : null;
+    }
+
+    /**
+     * Set internal credential value.
+     *
+     * @param string $name  The credential name to set.
+     * @param mixed $value  The credential value to set. See getCredential()
+     *                      for the list of valid credentials/types.
+     */
+    public function setCredential($type, $value)
+    {
+        switch ($type) {
+        case 'change':
+            $this->_credentials['change'] = (bool)$value;
+            break;
+
+        case 'credentials':
+            $this->_credentials['credentials'] = array_filter(array_merge($this->_credentials['credentials'], $value));
+            break;
+
+        case 'expire':
+            $this->_credentials['expire'] = intval($value);
+            break;
+
+        case 'userId':
+            $this->_credentials['userId'] = strval($value);
+            break;
+        }
+    }
+
+    /**
+     * Sets the error message for an invalid authentication.
+     *
+     * @param string $type  The type of error (Horde_Auth::REASON_* constant).
+     * @param string $msg   The error message/reason for invalid
+     *                      authentication.
+     */
+    public function setError($type, $msg = null)
+    {
+        $this->_error = array(
+            'msg' => $msg,
+            'type' => $type
         );
     }
 
+    /**
+     * Returns the error type or message for an invalid authentication.
+     *
+     * @param boolean $msg  If true, returns the message string (if set).
+     *
+     * @return mixed  Error type, error message (if $msg is true) or false
+     *                if entry doesn't exist.
+     */
+    public function getError($msg = false)
+    {
+        return isset($this->_error['type'])
+            ? ($msg ? $this->_error['msg'] : $this->_error['type'])
+            : false;
+    }
+
 }
index f597c76..979279f 100644 (file)
@@ -47,7 +47,7 @@ class Horde_Auth_Composite extends Horde_Auth_Base
      */
     protected function _authenticate($userId, $credentials)
     {
-        return $this->_params['auth_driver']->authenticate($userId, $credentials, false);
+        return $this->_params['auth_driver']->authenticate($userId, $credentials);
     }
 
     /**
@@ -73,7 +73,7 @@ class Horde_Auth_Composite extends Horde_Auth_Base
      *
      * @return boolean  Whether or not the client is allowed.
      */
-    protected function _transparent()
+    public function transparent()
     {
         try {
             return $this->_params['auth_driver']->transparent();
index 772202d..a78ba1c 100644 (file)
@@ -199,7 +199,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->_credentials['params']['change'] = true;
+            $this->setCredential('change', true);
         }
     }
 
index f824305..4e002b4 100644 (file)
@@ -230,7 +230,7 @@ class Horde_Auth_Cyrus extends Horde_Auth_Base
      *
      * @return boolean  Whether or not the client is allowed.
      */
-    protected function _transparent()
+    public function transparent()
     {
         return $this->_backend->transparent();
     }
index 128e03c..277bd6a 100644 (file)
@@ -110,7 +110,7 @@ class Horde_Auth_Http extends Horde_Auth_Base
      *
      * @return boolean  Whether or not the client is allowed.
      */
-    protected function _transparent()
+    public function transparent()
     {
         if (empty($_SERVER['PHP_AUTH_USER']) ||
             empty($_SERVER['PHP_AUTH_PW'])) {
index 959e78b..330d329 100644 (file)
@@ -52,7 +52,7 @@ class Horde_Auth_Ipbasic extends Horde_Auth_Base
      *
      * @return boolean  Whether or not the client is allowed.
      */
-    protected function _transparent()
+    public function transparent()
     {
         if (isset($_SERVER['REMOTE_ADDR'])) {
             foreach ($this->_params['blocks'] as $cidr) {
index bdaae04..0d0abd5 100644 (file)
@@ -40,7 +40,7 @@ class Horde_Auth_Kolab extends Horde_Auth_Base
     public function __construct(array $params = array())
     {
         if (!isset($params['kolab'])) {
-            throw new InvalidArgumentException('Missing ' . $params . ' parameter.');
+            throw new InvalidArgumentException('Missing kolab parameter.');
         }
 
         parent::__construct($params);
index 6fae771..a2ceed4 100644 (file)
@@ -236,16 +236,13 @@ class Horde_Auth_Ldap extends Horde_Auth_Base
                 $toexpire = $shadow['shadowlastchange'] +
                             $shadow['shadowmax'] - $today;
 
-                if ($this->_params['notify_expire']) {
-                    $warnday = $shadow['shadowlastchange'] +
-                               $shadow['shadowmax'] - $shadow['shadowwarning'];
-                    if ($today >= $warnday) {
-                        call_user_func($this->_params['notify_expire'], $toexpire);
-                    }
+                $warnday = $shadow['shadowlastchange'] + $shadow['shadowmax'] - $shadow['shadowwarning'];
+                if ($today >= $warnday) {
+                    $this->setCredential('expire', $toexpire);
                 }
 
                 if ($toexpire == 0) {
-                    $this->_credentials['params']['change'] = true;
+                    $this->setCredential('change', true);
                 } elseif ($toexpire < 0) {
                     throw new Horde_Auth_Exception('', Horde_Auth::REASON_EXPIRED);
                 }
@@ -267,7 +264,6 @@ class Horde_Auth_Ldap extends Horde_Auth_Base
             throw new Horde_Auth_Exception(__CLASS__ . ': Adding users is not supported for Active Directory.');
         }
 
-        list($userId, $credentials) = Horde_Auth::runHook($userId, $credentials, $this->_app, 'preauthenticate', 'admin');
         if (isset($credentials['ldap'])) {
             $entry = $credentials['ldap'];
             $dn = $entry['dn'];
@@ -308,19 +304,17 @@ class Horde_Auth_Ldap extends Horde_Auth_Base
      * Remove a set of authentication credentials.
      *
      * @param string $userId  The userId to add.
+     * @param string $dn      TODO
      *
      * @throws Horde_Auth_Exception
      */
-    public function removeUser($userId)
+    public function removeUser($userId, $dn = null)
     {
         if ($this->_params['ad']) {
            throw new Horde_Auth_Exception(__CLASS__ . ': Removing users is not supported for Active Directory');
         }
 
-        list($userId, $credentials) = Horde_Auth::runHook($userId, array(), $this->_app, 'preauthenticate', 'admin');
-        if (isset($credentials['ldap'])) {
-            $dn = $credentials['ldap']['dn'];
-        } else {
+        if (is_null($dn)) {
             /* Search for the user's full DN. */
             $dn = $this->_findDN($userId);
         }
@@ -340,22 +334,19 @@ class Horde_Auth_Ldap extends Horde_Auth_Base
      * @param string $oldID       The old userId.
      * @param string $newID       The new userId.
      * @param array $credentials  The new credentials
+     * @param string $olddn       TODO
+     * @param string $newdn       TODO
      *
      * @throws Horde_Auth_Exception
      */
-    public function updateUser($oldID, $newID, $credentials)
+    public function updateUser($oldID, $newID, $credentials, $olddn = null,
+                               $newdn = null)
     {
         if ($this->_params['ad']) {
            throw new Horde_Auth_Exception(__CLASS__ . ': Updating users is not supported for Active Directory.');
         }
 
-        list($oldID, $old_credentials) = Horde_Auth::runHook($oldID, $credentials, $this->_app, 'preauthenticate', 'admin');
-        if (isset($old_credentials['ldap'])) {
-            $olddn = $old_credentials['ldap']['dn'];
-            list($newID, $new_credentials) = Horde_Auth::runHook($newID, $credentials, $this->_app, 'preauthenticate', 'admin');
-            $newdn = $new_credentials['ldap']['dn'];
-            unset($new_credentials['ldap']['dn']);
-        } else {
+        if (is_null($olddn)) {
             /* Search for the user's full DN. */
             $dn = $this->_findDN($oldID);
 
index 6ba696a..87a5492 100644 (file)
@@ -69,7 +69,6 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap
         /* Connect to the MSAD server. */
         $this->_connect();
 
-        list($accountName, $credentials) = Horde_Auth::runHook($accountName, $credentials, $this->_app, 'preauthenticate', 'admin');
         if (isset($credentials['ldap'])) {
             $dn = $credentials['ldap']['dn'];
         } else {
@@ -117,18 +116,16 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap
      * Remove a set of authentication credentials.
      *
      * @param string $accountName  The user sAMAccountName to remove.
+     * @param string $dn           TODO
      *
      * @throws Horde_Auth_Exception
      */
-    public function removeUser($accountName)
+    public function removeUser($accountName, $dn = null)
     {
         /* Connect to the MSAD server. */
         $this->_connect();
 
-        list($accountName, $credentials) = Horde_Auth::runHook($accountName, $credentials, $this->_app, 'preauthenticate', 'admin');
-        if (isset($credentials['ldap'])) {
-            $dn = $credentials['ldap']['dn'];
-        } else {
+        if (is_null($dn)) {
             /* Search for the user's full DN. */
             $dn = $this->_findDN($accountName);
         }
@@ -156,7 +153,6 @@ class Horde_Auth_Msad extends Horde_Auth_Ldap
         /* Connect to the MSAD server. */
         $this->_connect();
 
-        list($oldId, $credentials) = Horde_Auth::runHook($oldId, $credentials, $this->_app, 'preauthenticate', 'admin');
         if (isset($credentials['ldap'])) {
             $olddn = $credentials['ldap']['dn'];
         } else {
index 7b610d9..15f1096 100644 (file)
@@ -76,23 +76,12 @@ class Horde_Auth_Shibboleth extends Horde_Auth_Base
     }
 
     /**
-     * Check existing auth for triggers that might invalidate it.
-     *
-     * @return boolean  Is existing auth valid?
-     */
-    public function checkExistingAuth()
-    {
-        return !empty($_SERVER[$this->_params['username_header']]) &&
-               ($this->_removeScope($_SERVER[$this->_params['username_header']]) == $this->_params['default_user']);
-    }
-
-    /**
      * Automatic authentication: check if the username is set in the
      * configured header.
      *
      * @return boolean  Whether or not the client is allowed.
      */
-    protected function _transparent()
+    public function transparent()
     {
         if (empty($_SERVER[$this->_params['username_header']])) {
             return false;
@@ -101,20 +90,21 @@ class Horde_Auth_Shibboleth extends Horde_Auth_Base
         $username = $_SERVER[$this->_params['username_header']];
 
         // Remove scope from username, if present.
-        $this->_credentials['userId'] = $this->_removeScope($username);
+        $this->setCredential('userId', $this->_removeScope($username));
 
         // Set password for hordeauth login.
         switch ($this->_params['password_holder']) {
         case 'header':
-            $this->_credentials['credentials'] = array(
+            $this->setCredential('credentials', array(
                 'password' => $_SERVER[$this->_params['password_header']]
-            );
+            ));
             break;
 
         case 'preferences':
-            $this->_credentials['credentials'] = array(
+            $this->setCredential('credentials', array(
                 'password' => $_SERVER[$this->_params['password_preference']]
-            );
+            ));
+            break;
         }
 
         return true;
@@ -130,7 +120,6 @@ class Horde_Auth_Shibboleth extends Horde_Auth_Base
     protected function _removeScope($username)
     {
         $pos = strrpos($username, '@');
-
         return ($pos !== false)
             ? substr($username, 0, $pos)
             : $username;
index 0dde20e..a5ec6fd 100644 (file)
@@ -133,7 +133,8 @@ 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->_credentials['params']['change'] = true;
+            $this->setCredential('change', true);
+            $this->setCredential('expire', $date);
         }
     }
 
@@ -201,12 +202,6 @@ class Horde_Auth_Sql extends Horde_Auth_Base
             $query .= ', ' . $this->_params['soft_expiration_field'] . ' = ?';
             $values[] = $date;
 
-            if ($this->_params['notify_expire']) {
-                call_user_func($this->_params['notify_expire'], $date);
-            }
-
-            $query .= ', ' . $this->_params['soft_expiration_field'] . ' = ?';
-
             if (empty($this->_params['hard_expiration_window'])) {
                 $values[] = null;
             } else {
index b407e7e..1a32b3c 100644 (file)
@@ -30,7 +30,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <api>beta</api>
  </stability>
  <license uri="http://opensource.org/licenses/lgpl-2.1.php">LGPL</license>
- <notes>* Removed Krb5 driver.
+ <notes>* Remove dependency on horde/Core.
+ * Remove all non-authentication backend related code.
+ * Removed Krb5 driver.
  * Moved signup code to horde/Core.
  * Split Horde_Auth:: into Horde_Auth:: and Horde_Auth_Base:: components.
  * Initial Horde 4 package.
@@ -103,18 +105,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <min>1.5.4</min>
    </pearinstaller>
    <package>
-    <name>Core</name>
-    <channel>pear.horde.org</channel>
-   </package>
-   <package>
     <name>Exception</name>
     <channel>pear.horde.org</channel>
    </package>
    <package>
-    <name>Secret</name>
-    <channel>pear.horde.org</channel>
-   </package>
-   <package>
     <name>Util</name>
     <channel>pear.horde.org</channel>
    </package>
@@ -125,14 +119,6 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <channel>pear.horde.org</channel>
    </package>
    <package>
-    <name>Form</name>
-    <channel>pear.horde.org</channel>
-   </package>
-   <package>
-    <name>History</name>
-    <channel>pear.horde.org</channel>
-   </package>
-   <package>
     <name>Imap_Client</name>
     <channel>pear.horde.org</channel>
    </package>
@@ -150,9 +136,6 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <channel>pear.horde.org</channel>
    </package>
    <extension>
-    <name>gettext</name>
-   </extension>
-   <extension>
     <name>pam_auth</name>
    </extension>
    <extension>
index a044190..da51702 100644 (file)
Binary files a/framework/Auth/test/Horde/Auth/credentials.php and b/framework/Auth/test/Horde/Auth/credentials.php differ
index 92c882b..ddffc27 100644 (file)
@@ -4,7 +4,7 @@ Horde_Auth_Passwd:: test
 <?php
 
 require_once dirname(__FILE__) . '/../../../lib/Horde/Auth.php';
-require_once dirname(__FILE__) . '/../../../lib/Horde/Auth/Driver.php';
+require_once dirname(__FILE__) . '/../../../lib/Horde/Auth/Base.php';
 require_once dirname(__FILE__) . '/../../../lib/Horde/Auth/Passwd.php';
 
 $auth = Horde_Auth::factory('passwd', array('filename' => dirname(__FILE__) . '/test.passwd'));
@@ -13,7 +13,7 @@ $auth = Horde_Auth::factory('passwd', array('filename' => dirname(__FILE__) . '/
 var_dump($auth->listUsers());
 
 // Authenticate
-var_dump($auth->authenticate('user', array('password' => 'password'), false));
+var_dump($auth->authenticate('user', array('password' => 'password')));
 
 ?>
 --EXPECT--
index 3b94a7d..652ca28 100644 (file)
@@ -1,8 +1,7 @@
 <?php
 /**
- * The Horde_Core_Auth_Application class provides a wrapper around
- * application-provided Horde authentication which fits inside the
- * Horde_Auth:: API.
+ * The Horde_Core_Auth_Application class provides application-specific
+ * authentication built on top of the Horde_Auth:: API.
  *
  * Copyright 2002-2010 The Horde Project (http://www.horde.org/)
  *
@@ -10,6 +9,7 @@
  * not receive this file, see http://opensource.org/licenses/lgpl-2.1.php
  *
  * @author   Chuck Hagenbuch <chuck@horde.org>
+ * @author   Michael Slusarz <slusarz@horde.org>
  * @category Horde
  * @license  http://opensource.org/licenses/lgpl-2.1.php LGPL
  * @package  Core
 class Horde_Core_Auth_Application extends Horde_Auth_Base
 {
     /**
+     * Authentication failure reasons (additions to Horde_Auth:: reasons).
+     *
+     * <pre>
+     * REASON_BROWSER - A browser change was detected
+     * REASON_SESSIONIP - Logout due to change of IP address during session
+     * </pre>
+     */
+    const REASON_BROWSER = 100;
+    const REASON_SESSIONIP = 101;
+
+    /**
+     * The base auth driver, used for horde authentication.
+     *
+     * @var Horde_Auth_Base
+     */
+    protected $_base;
+
+    /**
+     * Application for authentication.
+     *
+     * @var string
+     */
+    protected $_app = 'horde';
+
+    /**
      * Cache for hasCapability().
      *
      * @var array
@@ -47,6 +72,8 @@ class Horde_Core_Auth_Application extends Horde_Auth_Base
      * @param array $params  Required parameters:
      * <pre>
      * 'app' - (string) The application which is providing authentication.
+     * 'base' - (Horde_Auth_Base) The base Horde_Auth driver. Only needed if
+                'app' is 'horde'.
      * </pre>
      *
      * @throws InvalidArgumentException
@@ -56,60 +83,53 @@ class Horde_Core_Auth_Application extends Horde_Auth_Base
         if (!isset($params['app'])) {
             throw new InvalidArgumentException('Missing app parameter.');
         }
-
         $this->_app = $params['app'];
+        unset($params['app']);
 
-        parent::__construct($params);
-    }
-
-    /**
-     * Queries the current Auth object to find out if it supports the given
-     * capability.
-     *
-     * @param string $capability  The capability to test for.
-     *
-     * @return boolean  Whether or not the capability is supported.
-     */
-    public function hasCapability($capability)
-    {
-        $capability = strtolower($capability);
+        if ($this->_app == 'horde') {
+            if (!isset($params['base'])) {
+                throw new InvalidArgumentException('Missing base parameter.');
+            }
 
-        if (!in_array($capability, $this->_loaded) &&
-            isset($this->_apiMethods[$capability])) {
-            $this->_capabilities[$capability] = $GLOBALS['registry']->hasAppMethod($this->_app, $this->_apiMethods[$capability]);
-            $this->_loaded[] = $capability;
+            $this->_base = $params['base'];
+            unset($params['base']);
         }
 
-        return parent::hasCapability($capability);
+        parent::__construct($params);
     }
 
     /**
      * Finds out if a set of login credentials are valid, and if requested,
      * mark the user as logged in in the current session.
      *
-     * @param string $userId      The userId to check.
+     * @param string $userId      The user ID to check.
      * @param array $credentials  The credentials to check.
-     * @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.
      *
      * @return boolean  Whether or not the credentials are valid.
      */
-    public function authenticate($userId, $credentials, $login = true)
+    public function authenticate($userId, $credentials)
     {
-        if (!parent::authenticate($userId, $credentials, $login)) {
+        try {
+            list($userId, $credentials) = $this->runHook(trim($userId), $credentials, 'preauthenticate', 'authenticate');
+         } catch (Horde_Auth_Exception $e) {
             return false;
         }
 
-        $this->_authCallback();
+        if ($this->_base) {
+            if (!$this->_base->authenticate($userId, $credentials)) {
+                return false;
+            }
+        } elseif (!parent::authenticate($userId, $credentials)) {
+            return false;
+        }
 
-        return true;
+        return $this->_setAuth();
     }
 
     /**
      * Find out if a set of login credentials are valid.
      *
-     * @param string $userId      The userId to check.
+     * @param string $userId      The user ID to check.
      * @param array $credentials  The credentials to use. This object will
      *                            always be available in the 'auth_ob' key.
      *
@@ -127,42 +147,33 @@ class Horde_Core_Auth_Application extends Horde_Auth_Base
     }
 
     /**
-     * List all users in the system.
+     * Checks for triggers that may invalidate the current auth.
+     * These triggers are independent of the credentials.
      *
-     * @return array  The array of userIds.
-     * @throws Horde_Auth_Exception
+     * @return boolean  True if the results of authenticate() are still valid.
      */
-    public function listUsers()
+    public function validateAuth()
     {
-        return $this->hasCapability('list')
-            ? $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['list'])
-            : parent::listUsers();
-    }
-
-    /**
-     * Checks if $userId exists in the system.
-     *
-     * @param string $userId  User ID to check.
-     *
-     * @return boolean  Whether or not $userId already exists.
-     */
-    public function exists($userId)
-    {
-        return $this->hasCapability('exists')
-            ? $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['exists'], array('args' => array($userId)))
-            : parent::exists($userId);
+        return $this->_base
+            ? $this->_base->validateAuth()
+            : parent::validateAuth();
     }
 
     /**
      * Add a set of authentication credentials.
      *
-     * @param string $userId      The userId to add.
+     * @param string $userId      The user ID to add.
      * @param array $credentials  The credentials to use.
      *
      * @throws Horde_Auth_Exception
      */
     public function addUser($userId, $credentials)
     {
+        if ($this->_base) {
+            $this->_base->addUser($userId, $credentials);
+            return;
+        }
+
         if ($this->hasCapability('add')) {
             $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['add'], array('args' => array($userId, $credentials)));
         } else {
@@ -173,14 +184,19 @@ class Horde_Core_Auth_Application extends Horde_Auth_Base
     /**
      * Update a set of authentication credentials.
      *
-     * @param string $oldID       The old userId.
-     * @param string $newID       The new userId.
+     * @param string $oldID       The old user ID.
+     * @param string $newID       The new user ID.
      * @param array $credentials  The new credentials
      *
      * @throws Horde_Auth_Exception
      */
     public function updateUser($oldID, $newID, $credentials)
     {
+        if ($this->_base) {
+            $this->_base->updateUser($oldID, $newID, $credentials);
+            return;
+        }
+
         if ($this->hasCapability('update')) {
             $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['update'], array('args' => array($oldID, $newID, $credentials)));
         } else {
@@ -189,72 +205,234 @@ class Horde_Core_Auth_Application extends Horde_Auth_Base
     }
 
     /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The user ID to delete.
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function removeUser($userId)
+    {
+        if ($this->_base) {
+            $this->_base->removeUser($userId);
+
+            try {
+                $GLOBALS['registry']->callAppMethod('horde', 'removeUserDataFromAllApplications', array('args' => array($userId)));
+            } catch (Horde_Exception $e) {
+                throw new Horde_Auth_Exception($e);
+            }
+        } else {
+            if ($this->hasCapability('remove')) {
+                $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['remove'], array('args' => array($userId)));
+            } else {
+                parent::removeUser($userId);
+            }
+
+            try {
+                $GLOBALS['registry']->callAppMethod($this->_app, 'removeUserData', array('args' => array($userId)));
+            } catch (Horde_Exception $e) {
+                throw new Horde_Auth_Exception($e);
+            }
+        }
+    }
+
+    /**
+     * List all users in the system.
+     *
+     * @return array  The array of user IDs.
+     * @throws Horde_Auth_Exception
+     */
+    public function listUsers()
+    {
+        if ($this->_base) {
+            return $this->_base->listUsers();
+        }
+
+        return $this->hasCapability('list')
+            ? $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['list'])
+            : parent::listUsers();
+    }
+
+    /**
+     * Checks if a user ID exists in the system.
+     *
+     * @param string $userId  User ID to check.
+     *
+     * @return boolean  Whether or not the user ID already exists.
+     */
+    public function exists($userId)
+    {
+        if ($this->_base) {
+            return $this->_base->exists($userId);
+        }
+
+        return $this->hasCapability('exists')
+            ? $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['exists'], array('args' => array($userId)))
+            : parent::exists($userId);
+    }
+
+    /**
+     * Automatic authentication.
+     *
+     * @return boolean  Whether or not the client is allowed.
+     * @throws Horde_Auth_Exception
+     */
+    public function transparent()
+    {
+        global $registry;
+
+        if (!($userId = $this->getCredential('userId'))) {
+            $userId = $registry->getAuth();
+        }
+        if (!($credentials = $this->getCredential('credentials'))) {
+            $credentials = $registry->getAuthCredential();
+        }
+
+        list($userId, $credentials) = $this->runHook($userId, $credentials, 'preauthenticate', 'transparent');
+
+        $this->setCredential('userId', $userId);
+        $this->setCredential('credentials', $credentials);
+
+        if ($this->_base) {
+            $result = $this->_base->transparent();
+        } else {
+            $result = $this->hasCapability('transparent')
+                ? $registry->callAppMethod($this->_app, $this->_apiMethods['transparent'], array('args' => array($this), 'noperms' => true))
+                /* If this application contains neither transparent nor
+                 * authenticate capabilities, it does not require any
+                 * authentication if already authenticated to Horde. */
+                : ($registry->getAuth() && !$this->hasCapability('authenticate'));
+        }
+
+        return $result && $this->_setAuth();
+    }
+
+    /**
      * Reset a user's password. Used for example when the user does not
      * remember the existing password.
      *
-     * @param string $userId  The userId for which to reset the password.
+     * @param string $userId  The user ID for which to reset the password.
      *
      * @return string  The new password on success.
      * @throws Horde_Auth_Exception
      */
     public function resetPassword($userId)
     {
+        if ($this->_base) {
+            return $this->_base->resetPassword($userId);
+        }
+
         return $this->hasCapability('resetpassword')
             ? $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['resetpassword'], array('args' => array($userId)))
             : parent::resetPassword();
     }
 
     /**
-     * Delete a set of authentication credentials.
+     * Queries the current driver to find out if it supports the given
+     * capability.
      *
-     * @param string $userId  The userId to delete.
+     * @param string $capability  The capability to test for.
      *
-     * @throws Horde_Auth_Exception
+     * @return boolean  Whether or not the capability is supported.
      */
-    public function removeUser($userId)
+    public function hasCapability($capability)
     {
-        if ($this->hasCapability('remove')) {
-            $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['remove'], array('args' => array($userId)));
-            Horde_Auth::removeUserData($userId);
-        } else {
-            parent::removeUser($userId);
+        if ($this->_base) {
+            return $this->_base->hasCapability($capability);
         }
+
+        $capability = strtolower($capability);
+
+        if (!in_array($capability, $this->_loaded) &&
+            isset($this->_apiMethods[$capability])) {
+            $this->_capabilities[$capability] = $GLOBALS['registry']->hasAppMethod($this->_app, $this->_apiMethods[$capability]);
+            $this->_loaded[] = $capability;
+        }
+
+        return parent::hasCapability($capability);
     }
 
     /**
-     * Automatic authentication.
+     * Returns the named parameter for the current auth driver.
      *
-     * @return boolean  Whether or not the client is allowed.
-     * @throws Horde_Auth_Exception
+     * @param string $param  The parameter to fetch.
+     *
+     * @return string  The parameter's value, or null if it doesn't exist.
      */
-    public function transparent()
+    public function getParam($param)
     {
-        if (!parent::transparent()) {
-            return false;
-        }
+        return $this->_base
+            ? $this->_base->getParam($param)
+            : parent::getParam($param);
+    }
 
-        $this->_authCallback();
+    /**
+     * Retrieve internal credential value(s).
+     *
+     * @param mixed $name  The credential value to get. If null, will return
+     *                     the entire credential list. Valid names:
+     * <pre>
+     * 'change' - (boolean) Do credentials need to be changed?
+     * 'credentials' - (array) The credentials needed to authenticate.
+     * 'expire' - (integer) UNIX timestamp of the credential expiration date.
+     * 'userId' - (string) The user ID.
+     * </pre>
+     *
+     * @return mixed  Return the credential information, or null if the
+     *                credential doesn't exist.
+     */
+    public function getCredential($name = null)
+    {
+        return $this->_base
+            ? $this->_base->getCredential($name)
+            : parent::getCredential($name);
+    }
 
-        return true;
+    /**
+     * Set internal credential value.
+     *
+     * @param string $name  The credential name to set.
+     * @param mixed $value  The credential value to set. See getCredential()
+     *                      for the list of valid credentials/types.
+     */
+    public function setCredential($type, $value)
+    {
+        if ($this->_base) {
+            $this->_base->setCredential($type, $value);
+        } else {
+            parent::setCredential($type, $value);
+        }
     }
 
     /**
-     * Attempt transparent authentication. The application method is passed a
-     * single parameter: the current class instance.
+     * Sets the error message for an invalid authentication.
      *
-     * @return boolean  Whether transparent login is supported.
+     * @param string $type  The type of error (Horde_Auth::REASON_* constant).
+     * @param string $msg   The error message/reason for invalid
+     *                      authentication.
      */
-    protected function _transparent()
+    public function setError($type, $msg = null)
     {
-        if (!$this->hasCapability('transparent')) {
-            /* If this application contains neither transparent nor
-             * authenticate capabilities, it does not require any
-             * authentication if already authenticated to Horde. */
-            return ($GLOBALS['registry']->getAuth() &&
-                    !$this->hasCapability('authenticate'));
+        if ($this->_base) {
+            $this->_base->setError($type, $msg);
+        } else {
+            parent::setError($type, $msg);
         }
+    }
 
-        return $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['transparent'], array('args' => array($this), 'noperms' => true));
+    /**
+     * Returns the error type or message for an invalid authentication.
+     *
+     * @param boolean $msg  If true, returns the message string (if set).
+     *
+     * @return mixed  Error type, error message (if $msg is true) or false
+     *                if entry doesn't exist.
+     */
+    public function getError($msg = false)
+    {
+        return $this->_base
+            ? $this->_base->getError($msg)
+            : parent::getError($msg);
     }
 
     /**
@@ -288,81 +466,127 @@ class Horde_Core_Auth_Application extends Horde_Auth_Base
      */
     public function getLoginParams()
     {
-        if (!$this->hasCapability('loginparams')) {
-            return parent::getLoginParams();
+        if ($this->hasCapability('loginparams')) {
+            return $this->_base
+                ? $this->_base->getLoginParams()
+                : $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['loginparams'], array('noperms' => true));
         }
 
-        return $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['loginparams'], array('noperms' => true));
+        return array(
+            'js_code' => array(),
+            'js_files' => array(),
+            'params' => array()
+        );
     }
 
     /**
-     * Provide method to get internal credential values. Necessary as the
-     * application API does not have direct access to the protected member
-     * variables of this class.
-     *
-     * @param mixed $name  The credential name to get. If null, will return
-     *                     the entire credential list.
+     * Indicate whether the application requires authentication.
      *
-     * @return mixed  Return the credential information, or null if the.
-     *                credential doesn't exist.
+     * @return boolean  True if application requires authentication.
      */
-    public function getCredential($name = null)
+    public function requireAuth()
     {
-        if (is_null($name)) {
-            return $this->_credentials;
-        }
-
-        return isset($this->_credentials[$name])
-            ? $this->_credentials[$name]
-            : null;
+        return !$this->_base &&
+               ($this->hasCapability('authenticate') ||
+                $this->hasCapability('transparent'));
     }
 
     /**
-     * Provide method to set internal credential values. Necessary as the
-     * application API does not have direct access to the protected member
-     * variables of this class.
+     * Runs the pre/post-authenticate hook and parses the result.
      *
-     * @param string $name  The credential name to set.
-     * @param mixed $value  The credential value to set. If $name is 'userId',
-     *                      this must be a text value. If $name is
-     *                      'credentials' or 'params', this is an array of
-     *                      values to be merged in.
+     * @param string $userId      The userId who has been authorized.
+     * @param array $credentials  The credentials of the user.
+     * @param string $type        Either 'preauthenticate' or
+     *                            'postauthenticate'.
+     * @param string $method      The triggering method (preauthenticate only).
+     *                            Either 'authenticate' or 'transparent'.
+     *
+     * @return array  Two element array, $userId and $credentials.
+     * @throws Horde_Auth_Exception
      */
-    public function setCredential($type, $value)
+    public function runHook($userId, $credentials, $type, $method = null)
     {
-        switch ($type) {
-        case 'userId':
-            $this->_credentials['userId'] = $value;
-            break;
-
-        case 'credentials':
-        case 'params':
-            $this->_credentials[$type] = array_merge($this->_credentials[$type], $value);
-            break;
+        if (!is_array($credentials)) {
+            $credentials = empty($credentials)
+                ? array()
+                : array($credentials);
+        }
+
+        $ret_array = array($userId, $credentials);
+
+        if ($type == 'preauthenticate') {
+            $credentials['authMethod'] = $method;
+        }
+
+        try {
+            $result = Horde::callHook($type, array($userId, $credentials), $this->_app);
+        } catch (Horde_Exception $e) {
+            throw new Horde_Auth_Exception($e);
+        } catch (Horde_Exception_HookNotSet $e) {
+            return $ret_array;
+        }
+
+        unset($credentials['authMethod']);
+
+        if ($result === false) {
+            if ($this->getError() != Horde_Auth::REASON_MESSAGE) {
+                $this->setError(Horde_Auth::REASON_FAILED);
+            }
+            throw new Horde_Auth_Exception($type . ' hook failed');
+        }
+
+        if (is_array($result)) {
+            if ($type == 'postauthenticate') {
+                $ret_array[1] = $result;
+            } else {
+                if (isset($result['userId'])) {
+                    $ret_array[0] = $result['userId'];
+                }
+
+                if (isset($result['credentials'])) {
+                    $ret_array[1] = $result['credentials'];
+                }
+            }
         }
     }
 
     /**
-     * Provide way to finish authentication tasks in an application and ensure
-     * that the full application environment is loaded.
+     * Set authentication credentials in the Horde session.
      *
-     * @throws Horde_Auth_Exception
+     * @return boolean  True on success, false on failure.
      */
-    protected function _authCallback()
+    protected function _setAuth()
     {
+        if ($GLOBALS['registry']->isAuthenticated(array('app' => $this->_app, 'notransparent' => true))) {
+            return true;
+        }
+
+        $userId = $this->getCredential('userId');
+        $credentials = $this->getCredential('credentials');
+
+        try {
+            list(,$credentials) = $this->runHook($userId, $credentials, 'postauthenticate');
+        } catch (Horde_Auth_Exception $e) {
+            return false;
+        }
+
+        $GLOBALS['registry']->setAuth($userId, $credentials, array(
+            'app' => $this->_app,
+            'change' => $this->getCredential('change')
+        ));
+
+        if ($this->_base &&
+            isset($GLOBALS['notification']) &&
+            ($expire = $this->_base->getCredential('expire'))) {
+            $toexpire = ($expire - time()) / 86400;
+            $GLOBALS['notification']->push(sprintf(ngettext("%d day until your password expires.", "%d days until your password expires.", $toexpire), $toexpire), 'horde.warning');
+        }
+
         if ($this->hasCapability('authenticatecallback')) {
             $GLOBALS['registry']->callAppMethod($this->_app, $this->_apiMethods['authenticatecallback'], array('noperms' => true));
         }
-    }
 
-    /**
-     * Indicate whether the application requires authentication.
-     *
-     * @return boolean  True if application requires authentication.
-     */
-    public function requireAuth()
-    {
-        return $this->hasCapability('authenticate') || $this->hasCapability('transparent');
+        return true;
     }
 
 }
diff --git a/framework/Core/lib/Horde/Core/Auth/Ldap.php b/framework/Core/lib/Horde/Core/Auth/Ldap.php
new file mode 100644 (file)
index 0000000..a2fef38
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * The Horde_Core_Auth_Ldap class provides Horde-specific code that
+ * extends the base LDAP driver.
+ *
+ * Copyright 2010 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   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://opensource.org/licenses/lgpl-2.1.php LGPL
+ * @package  Core
+ */
+class Horde_Core_Auth_Ldap extends Horde_Auth_Ldap
+{
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The user ID to add.
+     * @param array $credentials  The credentials to use.
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        list($userId, $credentials) = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->runHook($userId, $credentials, 'preauthenticate', 'admin');
+
+        parent::addUser($userId, $credentials);
+    }
+
+    /**
+     * Update a set of authentication credentials.
+     *
+     * @param string $oldID       The old user ID.
+     * @param string $newID       The new user ID.
+     * @param array $credentials  The new credentials
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        $auth = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth();
+
+        list($oldID, $old_credentials) = $auth->runHook($oldID, $credentials, 'preauthenticate', 'admin');
+        if (isset($old_credentials['ldap'])) {
+            list($newID, $new_credentials) = $auth->runHook($newID, $credentials, 'preauthenticate', 'admin');
+            $olddn = $old_credentials['ldap']['dn'];
+            $newdn = $new_credentials['ldap']['dn'];
+        } else {
+            $olddn = $newdn = null;
+        }
+
+        parent::updateUser($oldID, $newID, $credentials, $olddn, $newdn);
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The user ID to delete.
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function removeUser($userId)
+    {
+        list($userId, $credentials) = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->runHook($userId, array(), 'preauthenticate', 'admin');
+
+        parent::removeUser($userId, isset($credentials['ldap']) ? $credentials['ldap']['dn'] : null);
+    }
+
+}
diff --git a/framework/Core/lib/Horde/Core/Auth/Msad.php b/framework/Core/lib/Horde/Core/Auth/Msad.php
new file mode 100644 (file)
index 0000000..34adb51
--- /dev/null
@@ -0,0 +1,63 @@
+<?php
+/**
+ * The Horde_Core_Auth_Msad class provides Horde-specific code that
+ * extends the base LDAP driver.
+ *
+ * Copyright 2010 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   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://opensource.org/licenses/lgpl-2.1.php LGPL
+ * @package  Core
+ */
+class Horde_Core_Auth_Msad extends Horde_Auth_Msad
+{
+    /**
+     * Add a set of authentication credentials.
+     *
+     * @param string $userId      The user ID to add.
+     * @param array $credentials  The credentials to use.
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function addUser($userId, $credentials)
+    {
+        list($userId, $credentials) = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->runHook($userId, $credentials, 'preauthenticate', 'admin');
+
+        parent::addUser($userId, $credentials);
+    }
+
+    /**
+     * Update a set of authentication credentials.
+     *
+     * @param string $oldID       The old user ID.
+     * @param string $newID       The new user ID.
+     * @param array $credentials  The new credentials
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function updateUser($oldID, $newID, $credentials)
+    {
+        list($oldId, $credentials) = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->runHook($oldId, $credentials, 'preauthenticate', 'admin');
+
+        parent::updateUser($oldID, $newID, $credentials);
+    }
+
+    /**
+     * Delete a set of authentication credentials.
+     *
+     * @param string $userId  The user ID to delete.
+     *
+     * @throws Horde_Auth_Exception
+     */
+    public function removeUser($userId)
+    {
+        list($userId, $credentials) = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->runHook($userId, array(), 'preauthenticate', 'admin');
+
+        parent::removeUser($userId, isset($credentials['ldap']) ? $credentials['ldap']['dn'] : null);
+    }
+
+}
diff --git a/framework/Core/lib/Horde/Core/Auth/Shibboleth.php b/framework/Core/lib/Horde/Core/Auth/Shibboleth.php
new file mode 100644 (file)
index 0000000..35bf9eb
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+/**
+ * The Horde_Core_Auth_Shibboleth class provides Horde-specific code that
+ * extends the base Shibboleth driver.
+ *
+ * Copyright 2010 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   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://opensource.org/licenses/lgpl-2.1.php LGPL
+ * @package  Core
+ */
+class Horde_Core_Auth_Shibboleth extends Horde_Auth_Shibboleth
+{
+    /**
+     * Checks for triggers that may invalidate the current auth.
+     * These triggers are independent of the credentials.
+     *
+     * @return boolean  True if the results of authenticate() are still valid.
+     */
+    public function validateAuth()
+    {
+        return !empty($_SERVER[$this->getParam('username_header')]) &&
+               ($this->_removeScope($_SERVER[$this->getParam('username_header')]) == $GLOBALS['registry']->getAuth());
+    }
+
+}
diff --git a/framework/Core/lib/Horde/Core/Autoloader/Callback/Auth.php b/framework/Core/lib/Horde/Core/Autoloader/Callback/Auth.php
deleted file mode 100644 (file)
index 8897b1e..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-<?php
-/**
- * @category Horde
- * @package  Core
- */
-class Horde_Core_Autoloader_Callback_Auth
-{
-    /**
-     * TODO
-     */
-    static public function callback()
-    {
-        Horde_Auth::$dnsResolver = $GLOBALS['injector']->getInstance('Net_DNS_Resolver');
-    }
-
-}
index ac859c3..3844100 100644 (file)
@@ -54,36 +54,47 @@ class Horde_Core_Factory_Auth
     /**
      * Return the Horde_Auth:: instance.
      *
-     * @param string $driver  The driver.
-     * @param array $params   Additional parameters to pass to the driver
-     *                        (will override Horde defaults).
+     * @param string $app  The application to authenticate to.
      *
      * @return Horde_Auth_Base  The singleton instance.
      * @throws Horde_Auth_Exception
      */
-    public function getAuth($driver = null, array $params = array())
+    public function getAuth($app = null)
     {
-        if (is_null($driver)) {
-            $driver = $GLOBALS['conf']['auth']['driver'];
+        if (is_null($app)) {
+            $app = 'horde';
         }
 
-        $params = array_merge(Horde::getDriverConfig('auth', $driver), $params);
-        ksort($params);
-
-        /* Get proper driver name now that we have grabbed the
-         * configuration. */
-        if (strcasecmp($driver, 'httpremote') === 0) {
-            /* BC */
-            $driver = 'Http_Remote';
-        } elseif (strcasecmp($driver, 'application') === 0) {
-            $driver = 'Horde_Core_Auth_Application';
-        } else {
-            $driver = Horde_String::ucfirst(Horde_String::lower(basename($driver)));
+        if (isset($this->_instances[$app])) {
+            return $this->_instances[$app];
         }
 
-        $sig = hash('md5', serialize(array($driver, $params)));
+        $base_params = array(
+            'app' => $app,
+            'logger' => $this->_injector->getInstance('Horde_Log_Logger')
+        );
+
+        if ($app == 'horde') {
+            $driver = $GLOBALS['conf']['auth']['driver'];
+            $params = Horde::getDriverConfig('auth', $driver);
+
+            /* Get proper driver name now that we have grabbed the
+             * configuration. */
+            if (strcasecmp($driver, 'application') === 0) {
+                $driver = 'Horde_Core_Auth_Application';
+            } elseif (strcasecmp($driver, 'httpremote') === 0) {
+                /* BC */
+                $driver = 'Http_Remote';
+            } elseif (strcasecmp($driver, 'ldap') === 0) {
+                $driver = 'Horde_Core_Auth_Ldap';
+            } elseif (strcasecmp($driver, 'msad') === 0) {
+                $driver = 'Horde_Core_Auth_Msad';
+            } elseif (strcasecmp($driver, 'shibboleth') === 0) {
+                $driver = 'Horde_Core_Auth_Shibboleth';
+            } else {
+                $driver = Horde_String::ucfirst(Horde_String::lower(basename($driver)));
+            }
 
-        if (!isset($this->_instances[$sig])) {
             $lc_driver = Horde_String::lower($driver);
             switch ($lc_driver) {
             case 'composite':
@@ -126,7 +137,8 @@ class Horde_Core_Factory_Auth
                 $params['kolab'] = $this->_injector->getInstance('Horde_Kolab_Session');
                 break;
 
-            case 'ldap':
+            case 'horde_core_auth_ldap':
+            case 'horde_core_auth_msad':
                 $params['ldap'] = $this->_injector->getInstance('Horde_Ldap')->getLdap('horde', 'auth');
                 break;
 
@@ -137,25 +149,16 @@ class Horde_Core_Factory_Auth
 
             $params['default_user'] = $GLOBALS['registry']->getAuth();
             $params['logger'] = $this->_injector->getInstance('Horde_Log_Logger');
-            $params['notify_expire'] = array($this, 'notifyExpire');
 
-            $this->_instances[$sig] = Horde_Auth::factory($driver, $params);
+            $base_params['base'] = Horde_Auth::factory($driver, $params);
+            if ($driver == 'Horde_Core_Auth_Application') {
+                $this->_instances[$params['app']] = $base_params['base'];
+            }
         }
 
-        return $this->_instances[$sig];
-    }
+        $this->_instances[$app] = Horde_Auth::factory('Horde_Core_Auth_Application', $base_params);
 
-    /**
-     * Expire notification callback.
-     *
-     * @param integer $date  UNIX timestamp of password expiration.
-     */
-    public function notifyExpire($date)
-    {
-        if (isset($GLOBALS['notification'])) {
-            $toexpire = ($date - time()) / 86400;
-            $GLOBALS['notification']->push(sprintf(ngettext("%d day until your password expires.", "%d days until your password expires.", $toexpire), $toexpire), 'horde.warning');
-        }
+        return $this->_instances[$app];
     }
 
 }
index 9c7974e..c321a23 100644 (file)
@@ -224,7 +224,6 @@ class Horde_Registry
     {
         /* Define autoloader callbacks. */
         $callbacks = array(
-            'Horde_Auth' => 'Horde_Core_Autoloader_Callback_Auth',
             'Horde_Mime' => 'Horde_Core_Autoloader_Callback_Mime'
         );
 
@@ -1085,9 +1084,10 @@ class Horde_Registry
          *  - To all authenticated users if no permission is set on $app.
          *  - To anyone who is allowed by an explicit ACL on $app. */
         if ($checkPerms) {
-            if ($this->getAuth() && !Horde_Auth::checkExistingAuth()) {
+            if ($this->getAuth() && !$this->checkExistingAuth($app)) {
                 throw new Horde_Exception('User is not authorized', self::AUTH_FAILURE);
             }
+
             if (!$this->hasPermission($app, Horde_Perms::READ)) {
                 if (!$this->isAuthenticated(array('app' => $app))) {
                     throw new Horde_Exception('User is not authorized', self::AUTH_FAILURE);
@@ -1218,7 +1218,7 @@ class Horde_Registry
          * application auth != Horde admin auth. And there can *never* be
          * non-SHOW access to an application that requires authentication. */
         if (!$this->isAuthenticated(array('app' => $app)) &&
-            $this->requireAuth($app) &&
+            $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth($app)->requireAuth() &&
             ($perms != Horde_Perms::SHOW)) {
             return false;
         }
@@ -1590,19 +1590,6 @@ class Horde_Registry
     }
 
     /**
-     * Returns the name of the authentication provider.
-     *
-     * @return string  The name of the driver currently providing
-     *                 authentication, or false if not set.
-     */
-    public function getProvider()
-    {
-        return empty($_SESSION['horde_auth']['driver'])
-            ? false
-            : $_SESSION['horde_auth']['driver'];
-    }
-
-    /**
      * Clears any authentication tokens in the current session.
      *
      * @param boolean $destroy  Destroy the session?
@@ -1652,23 +1639,6 @@ class Horde_Registry
     }
 
     /**
-     * Checks if an application requires additional authentication above and
-     * beyond Horde authentication.
-     *
-     * @params string $app  The application to check.
-     *
-     * @return boolean  Whether or not the application required additional
-     *                  authentication.
-     * @throws Horde_Exception
-     */
-    public function requireAuth($app)
-    {
-        return ($app == 'horde')
-            ? false
-            : $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth('application', array('app' => $app))->requireAuth();
-    }
-
-    /**
      * Checks if there is a session with valid auth information. If there
      * isn't, but the configured Auth driver supports transparent
      * authentication, then we try that.
@@ -1677,34 +1647,33 @@ class Horde_Registry
      * <pre>
      * 'app' - (string) Check authentication for this app.
      *         DEFAULT: Checks horde-wide authentication.
+     * 'notransparent' - (boolean) Do not attempt transparent authentication.
+     *                   DEFAULT: false
      * </pre>
      *
      * @return boolean  Whether or not the user is authenticated.
      */
-    public function isAuthenticated($options = array())
+    public function isAuthenticated(array $options = array())
     {
+        $app = empty($options['app'])
+            ? 'horde'
+            : $options['app'];
+
         /* Check for cached authentication results. */
-        if ($GLOBALS['registry']->getAuth()) {
-            $driver = (empty($options['app']) || ($options['app'] == 'horde'))
-                ? $GLOBALS['conf']['auth']['driver']
-                : $options['app'];
-
-            if (($_SESSION['horde_auth']['driver'] == $driver) ||
-                isset($_SESSION['horde_auth']['app'][$driver])) {
-                if (Horde_Auth::checkExistingAuth()) {
-                    return true;
-                }
-                $this->clearAuth();
-                return false;
+        if ($this->getAuth() &&
+            (($app == 'horde') ||
+             isset($_SESSION['horde_auth']['app'][$app]))) {
+            if ($this->checkExistingAuth($app)) {
+                return true;
             }
+
+            return false;
         }
 
         /* Try transparent authentication. */
-        $auth = (empty($options['app']) || ($options['app'] == 'horde'))
-            ? $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()
-            : $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth('application', array('app' => $options['app']));
-
-        return $auth->transparent();
+        return empty($options['notransparent'])
+            ? $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth($app)->transparent()
+            : false;
     }
 
     /**
@@ -1770,7 +1739,9 @@ class Horde_Registry
     public function getLogoutUrl(array $options = array())
     {
         if (!isset($options['reason'])) {
-            $options['reason'] = Horde_Auth::getAuthError();
+            // TODO: This only returns the error for Horde-wide
+            // authentication, not for application auth.
+            $options['reason'] = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->getError();
         }
 
         if (empty($options['app']) ||
@@ -1791,10 +1762,10 @@ class Horde_Registry
         }
 
         if ($options['reason']) {
-            $params[Horde_Auth::REASON_PARAM] = $options['reason'];
+            $params['logout_reason'] = $options['reason'];
             if ($options['reason'] == Horde_Auth::REASON_MESSAGE) {
-                $params[Horde_Auth::REASON_MSG_PARAM] = empty($options['msg'])
-                    ? Horde_Auth::getAuthError(true)
+                $params['logout_msg'] = empty($options['msg'])
+                    ? $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth()->getError(true)
                     : $options['msg'];
             }
         }
@@ -1904,28 +1875,35 @@ class Horde_Registry
     /**
      * Sets the requested credential for the currently logged in user.
      *
-     * @param string $credential  The credential to set.
-     * @param string $value       The value to set the credential to.
-     * @param string $app         The app to update. Defaults to Horde.
+     * @param mixed $credential  The credential to set.  If an array,
+     *                           overwrites the current credentials array.
+     * @param string $value      The value to set the credential to. If
+     *                           $credential is an array, this value is
+     *                           ignored.
+     * @param string $app        The app to update. Defaults to Horde.
      */
-    public function setAuthCredential($credential, $value, $app = null)
+    public function setAuthCredential($credential, $value = null, $app = null)
     {
         if (!$this->getAuth()) {
             return;
         }
 
-        $credentials = $this->_getAuthCredentials($app);
+        if (is_array($credential)) {
+            $credentials = $credential;
+        } else {
+            if (($credentials = $this->_getAuthCredentials($app)) === false) {
+                return;
+            }
 
-        if ($credentials !== false) {
-            if (is_array($credentials)) {
-                $credentials[$credential] = $value;
-            } else {
-                $credentials = array($credential => $value);
+            if (!is_array($credentials)) {
+                $credentials = array();
             }
 
-            $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
-            $_SESSION['horde_auth']['app'][$app] = $secret->write($secret->getKey('auth'), serialize($credentials));
+            $credentials[$credential] = $value;
         }
+
+        $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
+        $_SESSION['horde_auth']['app'][$app] = $secret->write($secret->getKey('auth'), serialize($credentials));
     }
 
     /**
@@ -1937,16 +1915,113 @@ class Horde_Registry
      */
     protected function _getAuthCredentials($app)
     {
+        if (!isset($_SESSION['horde_auth']['app'])) {
+            return false;
+        }
+
         if (is_null($app)) {
             $app = $_SESSION['horde_auth']['credentials'];
         }
 
-        if (!isset($_SESSION['horde_auth']['app'])) {
+        $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
+        return @unserialize($secret->read($secret->getKey('auth'), $_SESSION['horde_auth']['app'][$app]));
+    }
+
+    /**
+     * Sets a variable in the session saying that authorization has succeeded,
+     * note which userId was authorized, and note when the login took place.
+     *
+     * If a user name hook was defined in the configuration, it gets applied
+     * to the $userId at this point.
+     *
+     * Horde authentication data is stored in the session in the 'horde_auth'
+     * array key.  That key has the following structure:
+     * <pre>
+     * 'app' - (array) Application-specific authentication. Keys are the
+     *         app names, values are an array containing credentials. If true,
+     *         application does not require any specific credentials.
+     * 'authId' - (string) The username used during the original
+                  authentication.
+     * 'browser' - (string) The remote browser string.
+     * 'change' - (boolean) Is a password change requested?
+     * 'credentials' - (string) The 'app' entry that contains the Horde
+     *                 credentials.
+     * 'remoteAddr' - (string) The remote IP address of the user.
+     * 'timestamp' - (integer) The login time.
+     * 'userId' - (string) The unique Horde username.
+     * </pre>
+     *
+     * @param string $authId      The userId that has been authorized.
+     * @param array $credentials  The credentials of the user.
+     * @param array $options      Additional options:
+     * <pre>
+     * 'app' - (string) The app to set authentication credentials for.
+     *         DEFAULT: 'horde'
+     * 'change' - (boolean) Whether to request that the user change their
+     *            password.
+     *            DEFAULT: No
+     * </pre>
+     */
+    public function setAuth($authId, $credentials, array $options = array())
+    {
+        $app = empty($options['app']) ? 'horde' : $options['app'];
+
+        if ($this->getAuth()) {
+            /* Store app credentials. */
+            $this->setAuthCredential($credentials, null, $app);
+            return;
+        }
+
+        /* Clear any existing info. */
+        $this->clearAuth(false);
+
+        $_SESSION['horde_auth'] = array(
+            'app' => array(),
+            'authId' => $authId,
+            'browser' => $GLOBALS['injector']->getInstance('Horde_Browser')->getAgentString(),
+            'change' => !empty($options['change']),
+            'credentials' => $app,
+            'remoteAddr' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
+            'timestamp' => time(),
+            'userId' => $this->convertUsername(trim($authId), true)
+        );
+
+        $this->setAuthCredential($credentials, null, $app);
+
+        /* Reload preferences for the new user. */
+        $this->loadPrefs();
+        Horde_Nls::setLanguageEnvironment($GLOBALS['prefs']->getValue('language'), $app);
+    }
+
+    /**
+     * Check existing auth for triggers that might invalidate it.
+     *
+     * @param string $app  Check authentication for this app.
+     *
+     * @return boolean  Is existing auth valid?
+     */
+    public function checkExistingAuth($app)
+    {
+        if ($app != 'horde') {
+            return true;
+        }
+
+        $auth = $GLOBALS['injector']->getInstance('Horde_Auth')->getAuth();
+
+        if (!empty($GLOBALS['conf']['auth']['checkip']) &&
+            !empty($_SESSION['horde_auth']['remoteAddr']) &&
+            ($_SESSION['horde_auth']['remoteAddr'] != $_SERVER['REMOTE_ADDR'])) {
+            $auth->setError(Horde_Core_Auth_Application::REASON_SESSIONIP);
             return false;
         }
 
-        $secret = $GLOBALS['injector']->getInstance('Horde_Secret');
-        return @unserialize($secret->read($secret->getKey('auth'), $_SESSION['horde_auth']['app'][$app]));
+        if (!empty($GLOBALS['conf']['auth']['checkbrowser']) &&
+            ($_SESSION['horde_auth']['browser'] != $GLOBALS['injector']->getInstance('Horde_Browser')->getAgentString())) {
+            $auth->setError(Horde_Core_Auth_Application::REASON_BROWSER);
+            return false;
+        }
+
+        return $auth->validateAuth();
     }
 
 }
index 714b299..e171c25 100644 (file)
@@ -64,6 +64,9 @@ Application Framework.
      <dir name="Core">
       <dir name="Auth">
        <file name="Application.php" role="php" />
+       <file name="Ldap.php" role="php" />
+       <file name="Msad.php" role="php" />
+       <file name="Shibboleth.php" role="php" />
        <dir name="Signup">
         <file name="Base.php" role="php" />
         <file name="Form.php" role="php" />
@@ -76,7 +79,6 @@ Application Framework.
       <file name="Autoloader.php" role="php" />
       <dir name="Autoloader">
        <dir name="Callback">
-        <file name="Auth.php" role="php" />
         <file name="Mime.php" role="php" />
        </dir> <!-- /lib/Horde/Core/Autoloader/Callback -->
       </dir> <!-- /lib/Horde/Core/Autoloader -->
@@ -267,6 +269,9 @@ Application Framework.
    <install name="lib/Horde/Config.php" as="Horde/Config.php" />
    <install name="lib/Horde/Config/Form.php" as="Horde/Config/Form.php" />
    <install name="lib/Horde/Core/Auth/Application.php" as="Horde/Core/Auth/Application.php" />
+   <install name="lib/Horde/Core/Auth/Ldap.php" as="Horde/Core/Auth/Ldap.php" />
+   <install name="lib/Horde/Core/Auth/Msad.php" as="Horde/Core/Auth/Msad.php" />
+   <install name="lib/Horde/Core/Auth/Shibboleth.php" as="Horde/Core/Auth/Shibboleth.php" />
    <install name="lib/Horde/Core/Auth/Signup.php" as="Horde/Core/Auth/Signup.php" />
    <install name="lib/Horde/Core/Auth/Signup/Base.php" as="Horde/Core/Auth/Signup/Base.php" />
    <install name="lib/Horde/Core/Auth/Signup/Form.php" as="Horde/Core/Auth/Signup/Form.php" />
@@ -274,7 +279,6 @@ Application Framework.
    <install name="lib/Horde/Core/Auth/Signup/Sql.php" as="Horde/Core/Auth/Signup/Sql.php" />
    <install name="lib/Horde/Core/Auth/Signup/SqlObject.php" as="Horde/Core/Auth/Signup/SqlObject.php" />
    <install name="lib/Horde/Core/Autoloader.php" as="Horde/Core/Autoloader.php" />
-   <install name="lib/Horde/Core/Autoloader/Callback/Auth.php" as="Horde/Core/Autoloader/Callback/Auth.php" />
    <install name="lib/Horde/Core/Autoloader/Callback/Mime.php" as="Horde/Core/Autoloader/Callback/Mime.php" />
    <install name="lib/Horde/Core/Binder/Alarm.php" as="Horde/Core/Binder/Alarm.php" />
    <install name="lib/Horde/Core/Binder/Auth.php" as="Horde/Core/Binder/Auth.php" />
index 3934e62..d918169 100644 (file)
@@ -63,7 +63,7 @@ class Gollem_Auth
             $credentials = array('password' => $secret->read($secret->getKey('gollem'), $GLOBALS['gollem_be']['params']['password']));
         }
 
-        $login = ($login && ($GLOBALS['registry']->getProvider() == 'gollem'));
+        //$login = ($login && ($GLOBALS['registry']->getProvider() == 'gollem'));
 
         return parent::authenticate($userID, $credentials, $login);
     }
diff --git a/horde/lib/LoginTasks/Task/LastLogin.php b/horde/lib/LoginTasks/Task/LastLogin.php
new file mode 100644 (file)
index 0000000..64e4d21
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Login task to output last login information.
+ *
+ * Copyright 2010 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://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @package  Horde
+ */
+class Horde_LoginTasks_Task_LastLogin extends Horde_LoginTasks_Task
+{
+    /**
+     * The interval at which to run the task.
+     *
+     * @var integer
+     */
+    public $interval = Horde_LoginTasks::EVERY;
+
+    /**
+     * Display type.
+     *
+     * @var integer
+     */
+    public $display = Horde_LoginTasks::DISPLAY_NONE;
+
+    /**
+     * Perform all functions for this task.
+     */
+    public function execute()
+    {
+        /* Fetch the user's last login time. */
+        $old_login = @unserialize($GLOBALS['prefs']->getValue('last_login'));
+
+        /* Display it, if we have a notification object and the
+         * show_last_login preference is active. */
+        if (isset($GLOBALS['notification']) &&
+            $GLOBALS['prefs']->getValue('show_last_login')) {
+            if (empty($old_login['time'])) {
+                $GLOBALS['notification']->push(_("Last login: Never"), 'horde.message');
+            } elseif (empty($old_login['host'])) {
+                $GLOBALS['notification']->push(sprintf(_("Last login: %s"), strftime('%c', $old_login['time'])), 'horde.message');
+            } else {
+                $GLOBALS['notification']->push(sprintf(_("Last login: %s from %s"), strftime('%c', $old_login['time']), $old_login['host']), 'horde.message');
+            }
+        }
+
+        /* Set the user's last_login information. */
+        $host = empty($_SERVER['HTTP_X_FORWARDED_FOR'])
+            ? $_SERVER['REMOTE_ADDR']
+            : $_SERVER['HTTP_X_FORWARDED_FOR'];
+
+        if ($dns = $GLOBALS['injector']->getInstance('Net_DNS_Resolver')) {
+            $ptrdname = $host;
+            if ($response = $dns->query($host, 'PTR')) {
+                foreach ($response->answer as $val) {
+                    if (isset($val->ptrdname)) {
+                        $ptrdname = $val->ptrdname;
+                        break;
+                    }
+                }
+            }
+        } else {
+            $ptrdname = @gethostbyaddr($host);
+        }
+
+        $GLOBALS['prefs']->setValue('last_login', serialize(array(
+            'host' => $ptrdname,
+            'time' => time()
+        )));
+    }
+
+}
index 49c102a..d355d5d 100644 (file)
@@ -44,10 +44,10 @@ function _getLogoutReasonString($code)
     case Horde_Auth::REASON_SESSION:
         return _("Your session has expired. Please login again.");
 
-    case Horde_Auth::REASON_SESSIONIP:
+    case Horde_Core_Auth_Application::REASON_SESSIONIP:
         return _("Your Internet Address has changed since the beginning of your session. To protect your security, you must login again.");
 
-    case Horde_Auth::REASON_BROWSER:
+    case Horde_Core_Auth_Application::REASON_BROWSER:
         return _("Your browser appears to have changed since the beginning of your session. To protect your security, you must login again.");
 
     case Horde_Auth::REASON_LOGOUT:
@@ -97,9 +97,7 @@ if (!$is_auth) {
 }
 
 /* Get an Auth object. */
-$auth = ($app && $is_auth)
-    ? $injector->getInstance('Horde_Auth')->getAuth('application', array('app' => $app))
-    : $injector->getInstance('Horde_Auth')->getAuth();
+$auth = $injector->getInstance('Horde_Auth')->getAuth(($is_auth && $vars->app) ? $vars->app : null);
 
 /* Build the list of necessary login parameters. */
 $loginparams = array(
index 456a2d2..3b5a64a 100644 (file)
@@ -60,10 +60,6 @@ class Passwd {
     {
         if ($GLOBALS['registry']->getAuthCredential('password') == $old_password) {
             $GLOBALS['registry']->setAuthCredential('password', $new_password);
-            if ($GLOBALS['registry']->getProvider() == 'imp') {
-                $_SESSION['imp']['pass'] = Secret::write(Secret::getKey('imp'),
-                                                         $new_password);
-            }
         }
     }