608 lines
18 KiB
PHP
608 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 1999-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (LGPL). If you did
|
|
* not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
|
|
* @package Auth
|
|
*/
|
|
|
|
/**
|
|
* The Horde_Auth_Base class provides a common abstracted interface to creating
|
|
* various authentication backends.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Michael Slusarz <slusarz@horde.org>
|
|
* @category Horde
|
|
* @copyright 1999-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
|
|
* @package Auth
|
|
*/
|
|
abstract class Horde_Auth_Base
|
|
{
|
|
/**
|
|
* An array of capabilities, so that the driver can report which
|
|
* operations it supports and which it doesn't.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_capabilities = array(
|
|
'add' => false,
|
|
'authenticate' => true,
|
|
'groups' => false,
|
|
'list' => false,
|
|
'resetpassword' => false,
|
|
'remove' => false,
|
|
'transparent' => false,
|
|
'update' => false,
|
|
'badlogincount' => false,
|
|
'lock' => false,
|
|
);
|
|
|
|
/**
|
|
* Hash containing parameters needed for the drivers.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_params = array();
|
|
|
|
/**
|
|
* The credentials currently being authenticated.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_credentials = array(
|
|
'change' => false,
|
|
'credentials' => array(),
|
|
'expire' => null,
|
|
'userId' => ''
|
|
);
|
|
|
|
/**
|
|
* Logger object.
|
|
*
|
|
* @var Horde_Log_Logger
|
|
*/
|
|
protected $_logger;
|
|
|
|
/**
|
|
* History object.
|
|
*
|
|
* @var Horde_History
|
|
*/
|
|
protected $_history_api;
|
|
|
|
/**
|
|
* Lock object.
|
|
*
|
|
* @var Horde_Lock
|
|
*/
|
|
protected $_lock_api;
|
|
|
|
/**
|
|
* Authentication error information.
|
|
*
|
|
* @var array
|
|
*/
|
|
protected $_error;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param array $params Optional parameters:
|
|
* - default_user: (string) The default user.
|
|
* - logger: (Horde_Log_Logger, optional) A logger object.
|
|
* - lock_api: (Horde_Lock, optional) A locking object.
|
|
* - history_api: (Horde_History, optional) A history object.
|
|
* - login_block_count: (integer, optional) How many failed logins
|
|
* trigger autoblocking? 0 disables the feature.
|
|
* - login_block_time: (integer, options) How many minutes should
|
|
* autoblocking last? 0 means no expiration.
|
|
*/
|
|
public function __construct(array $params = array())
|
|
{
|
|
if (isset($params['logger'])) {
|
|
$this->_logger = $params['logger'];
|
|
unset($params['logger']);
|
|
}
|
|
|
|
if (isset($params['lock_api'])) {
|
|
$this->_lock_api = $params['lock_api'];
|
|
$this->_capabilities['lock'] = true;
|
|
unset($params['lock_api']);
|
|
}
|
|
|
|
if (isset($params['history_api'])) {
|
|
$this->_history_api = $params['history_api'];
|
|
$this->_capabilities['badlogincount'] = true;
|
|
unset($params['history_api']);
|
|
}
|
|
|
|
$params = array_merge(array(
|
|
'default_user' => ''
|
|
), $params);
|
|
|
|
$this->_params = $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 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)
|
|
{
|
|
$userId = trim($userId);
|
|
|
|
try {
|
|
$this->_credentials['userId'] = $userId;
|
|
if (($this->hasCapability('lock')) &&
|
|
$this->isLocked($userId)) {
|
|
$details = $this->isLocked($userId, true);
|
|
if ($details['lock_timeout'] == Horde_Lock::PERMANENT) {
|
|
$message = Horde_Auth_Translation::t("Your account has been permanently locked");
|
|
} else {
|
|
$message = sprintf(Horde_Auth_Translation::t("Your account has been locked for %d minutes"), ceil(($details['lock_timeout'] - time()) / 60));
|
|
}
|
|
throw new Horde_Auth_Exception($message, Horde_Auth::REASON_LOCKED);
|
|
}
|
|
$this->_authenticate($userId, $credentials);
|
|
$this->setCredential('userId', $this->_credentials['userId']);
|
|
$this->setCredential('credentials', $credentials);
|
|
if ($this->hasCapability('badlogincount')) {
|
|
$this->_resetBadLogins($userId);
|
|
}
|
|
return true;
|
|
} catch (Horde_Auth_Exception $e) {
|
|
if (($code = $e->getCode()) &&
|
|
$code != Horde_Auth::REASON_MESSAGE) {
|
|
if (($code == Horde_Auth::REASON_BADLOGIN) &&
|
|
$this->hasCapability('badlogincount')) {
|
|
$this->_badLogin($userId);
|
|
}
|
|
$this->setError($code, $e->getMessage());
|
|
} else {
|
|
$this->setError(Horde_Auth::REASON_MESSAGE, $e->getMessage());
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Basic sort implementation.
|
|
*
|
|
* If the backend has listUsers and doesn't have a native sorting option,
|
|
* fall back to this method.
|
|
*
|
|
* @param array $users An array of usernames.
|
|
* @param boolean $sort Whether to sort or not.
|
|
*
|
|
* @return array the users, sorted or not
|
|
*
|
|
*/
|
|
protected function _sort($users, $sort)
|
|
{
|
|
if ($sort) {
|
|
sort($users);
|
|
}
|
|
return $users;
|
|
}
|
|
|
|
/**
|
|
* Authentication stub.
|
|
*
|
|
* On failure, Horde_Auth_Exception should pass a message string (if any)
|
|
* in the message field, and the Horde_Auth::REASON_* constant in the code
|
|
* field (defaults to Horde_Auth::REASON_MESSAGE).
|
|
*
|
|
* @param string $userId The userID to check.
|
|
* @param array $credentials An array of login credentials.
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
abstract protected function _authenticate($userId, $credentials);
|
|
|
|
/**
|
|
* 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 true;
|
|
}
|
|
|
|
/**
|
|
* Adds a set of authentication credentials.
|
|
*
|
|
* @param string $userId The userId to add.
|
|
* @param array $credentials The credentials to use.
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function addUser($userId, $credentials)
|
|
{
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
/**
|
|
* Locks a user indefinitely or for a specified time.
|
|
*
|
|
* @param string $userId The user to lock.
|
|
* @param integer $time The duration in minutes, 0 = permanent.
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function lockUser($userId, $time = 0)
|
|
{
|
|
if (!$this->_lock_api) {
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
if ($time == 0) {
|
|
$time = Horde_Lock::PERMANENT;
|
|
} else {
|
|
$time *= 60;
|
|
}
|
|
|
|
try {
|
|
if ($this->_lock_api->setLock($userId, 'horde_auth', 'login:' . $userId, $time, Horde_Lock::TYPE_EXCLUSIVE)) {
|
|
return;
|
|
}
|
|
} catch (Horde_Lock_Exception $e) {
|
|
throw new Horde_Auth_Exception($e);
|
|
}
|
|
|
|
throw new Horde_Auth_Exception('User is already locked',
|
|
Horde_Auth::REASON_LOCKED);
|
|
}
|
|
|
|
/**
|
|
* Unlocks a user and optionally resets the bad login count.
|
|
*
|
|
* @param string $userId The user to unlock.
|
|
* @param boolean $resetBadLogins Reset bad login counter?
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function unlockUser($userId, $resetBadLogins = false)
|
|
{
|
|
if (!$this->_lock_api) {
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
try {
|
|
$locks = $this->_lock_api->getLocks(
|
|
'horde_auth', 'login:' . $userId, Horde_Lock::TYPE_EXCLUSIVE);
|
|
$lock_id = key($locks);
|
|
if ($lock_id) {
|
|
$this->_lock_api->clearLock($lock_id);
|
|
}
|
|
if ($resetBadLogins) {
|
|
$this->_resetBadLogins($userId);
|
|
}
|
|
} catch (Horde_Lock_Exception $e) {
|
|
throw new Horde_Auth_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns whether a user is currently locked.
|
|
*
|
|
* @param string $userId The user to check.
|
|
* @param boolean $show_details Return timeout too?
|
|
*
|
|
* @return boolean|array If $show_details is a true, an array with
|
|
* 'locked' and 'lock_timeout' values. Whether the
|
|
* user is locked, otherwise.
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function isLocked($userId, $show_details = false)
|
|
{
|
|
if (!$this->_lock_api) {
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
try {
|
|
$locks = $this->_lock_api->getLocks(
|
|
'horde_auth', 'login:' . $userId, Horde_Lock::TYPE_EXCLUSIVE);
|
|
} catch (Horde_Lock_Exception $e) {
|
|
throw new Horde_Auth_Exception($e);
|
|
}
|
|
|
|
if ($show_details) {
|
|
$lock_id = key($locks);
|
|
return empty($lock_id)
|
|
? array('locked' => false, 'lock_timeout' => 0)
|
|
: array('locked' => true, 'lock_timeout' => $locks[$lock_id]['lock_expiry_timestamp']);
|
|
}
|
|
|
|
return !empty($locks);
|
|
}
|
|
|
|
/**
|
|
* Handles a bad login.
|
|
*
|
|
* @param string $userId The user with a bad login.
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
protected function _badLogin($userId)
|
|
{
|
|
if (!$this->_history_api) {
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
$history_identifier = $userId . '@logins.failed';
|
|
try {
|
|
$this->_history_api->log(
|
|
$history_identifier,
|
|
array('action' => 'login_failed', 'who' => $userId));
|
|
$history_log = $this->_history_api->getHistory($history_identifier);
|
|
if ($this->_params['login_block_count'] > 0 &&
|
|
$this->_params['login_block_count'] <= $history_log->count() &&
|
|
$this->hasCapability('lock')) {
|
|
$this->lockUser($userId, $this->_params['login_block_time']);
|
|
}
|
|
} catch (Horde_History_Exception $e) {
|
|
throw new Horde_Auth_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resets the bad login counter.
|
|
*
|
|
* @param string $userId The user to reset.
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
protected function _resetBadLogins($userId)
|
|
{
|
|
if (!$this->_history_api) {
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
try {
|
|
$this->_history_api->removeByNames(array($userId . '@logins.failed'));
|
|
} catch (Horde_History_Exception $e) {
|
|
throw new Horde_Auth_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates a set of authentication credentials.
|
|
*
|
|
* @param string $oldID The old userId.
|
|
* @param string $newID The new userId.
|
|
* @param array $credentials The new credentials
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function updateUser($oldID, $newID, $credentials)
|
|
{
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
/**
|
|
* Deletes a set of authentication credentials.
|
|
*
|
|
* @param string $userId The userId to delete.
|
|
*
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function removeUser($userId)
|
|
{
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
/**
|
|
* Lists all users in the system.
|
|
*
|
|
* @param boolean $sort Sort the users?
|
|
*
|
|
* @return mixed The array of userIds.
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function listUsers($sort = false)
|
|
{
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
/**
|
|
* Searches the users for a substring.
|
|
*
|
|
* @since Horde_Auth 2.2.0
|
|
*
|
|
* @param string $search The search term.
|
|
*
|
|
* @return array A list of all matching users.
|
|
*/
|
|
public function searchUsers($search)
|
|
{
|
|
try {
|
|
$users = $this->listUsers();
|
|
} catch (Horde_Auth_Exception $e) {
|
|
return array();
|
|
}
|
|
$matches = array();
|
|
foreach ($users as $user) {
|
|
if (Horde_String::ipos($user, $search) !== false) {
|
|
$matches[] = $user;
|
|
}
|
|
}
|
|
return $matches;
|
|
}
|
|
|
|
/**
|
|
* Checks if $userId exists in the system.
|
|
*
|
|
* @param string $userId User ID for which to check
|
|
*
|
|
* @return boolean Whether or not $userId already exists.
|
|
*/
|
|
public function exists($userId)
|
|
{
|
|
try {
|
|
$users = $this->listUsers();
|
|
return in_array($userId, $users);
|
|
} catch (Horde_Auth_Exception $e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Automatic authentication.
|
|
*
|
|
* Transparent authentication should set 'userId', 'credentials', or
|
|
* 'params' in $this->_credentials as needed - these values will be used
|
|
* to set the credentials in the session.
|
|
*
|
|
* Transparent authentication should normally never throw an error - false
|
|
* should be returned.
|
|
*
|
|
* @return boolean Whether transparent login is supported.
|
|
* @throws Horde_Auth_Exception
|
|
*/
|
|
public function transparent()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Reset a user's password. Used for example when the user does not
|
|
* remember the existing 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)
|
|
{
|
|
throw new Horde_Auth_Exception('Unsupported.');
|
|
}
|
|
|
|
/**
|
|
* Queries the current driver 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)
|
|
{
|
|
return !empty($this->_capabilities[$capability]);
|
|
}
|
|
|
|
/**
|
|
* Returns the named parameter for the current auth driver.
|
|
*
|
|
* @param string $param The parameter to fetch.
|
|
*
|
|
* @return string The parameter's value, or null if it doesn't exist.
|
|
*/
|
|
public function getParam($param)
|
|
{
|
|
return isset($this->_params[$param])
|
|
? $this->_params[$param]
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* Returns internal credential value(s).
|
|
*
|
|
* @param mixed $name The credential value to get. If null, will return
|
|
* the entire credential list. Valid names:
|
|
* - '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.
|
|
*
|
|
* @return mixed The credential information, or null if the credential
|
|
* doesn't exist.
|
|
*/
|
|
public function getCredential($name = null)
|
|
{
|
|
if (is_null($name)) {
|
|
return $this->_credentials;
|
|
}
|
|
|
|
return isset($this->_credentials[$name])
|
|
? $this->_credentials[$name]
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* Sets an internal credential value.
|
|
*
|
|
* @param string $type The credential name to set. See getCredential()
|
|
* for the list of valid credentials/types.
|
|
* @param mixed $value The credential value to set.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
}
|