Files
server/usr/share/psa-pear/pear/php/Horde/Auth/Base.php
2026-01-07 20:52:11 +01:00

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;
}
}