mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-21 15:33:23 +01:00
* Authentication: allow using multiple backends, even same backend multiple times with different configuration
This commit is contained in:
parent
ca9526988f
commit
3ee7574294
@ -30,6 +30,18 @@ class Ads implements Backend
|
||||
{
|
||||
var $previous_login = -1;
|
||||
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* Ads auth constructor
|
||||
*
|
||||
* @param array|null $config
|
||||
*/
|
||||
function __construct(array $config=null)
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* password authentication
|
||||
*
|
||||
@ -48,7 +60,7 @@ class Ads implements Backend
|
||||
// harden ldap auth, by removing \000 bytes, causing passwords to be not empty by php, but empty to c libaries
|
||||
$passwd = str_replace("\000", '', $_passwd);
|
||||
|
||||
$adldap = Api\Accounts\Ads::get_adldap();
|
||||
$adldap = Api\Accounts\Ads::get_adldap($this->config);
|
||||
// bind with username@ads_domain, only if a non-empty password given, in case anonymous search is enabled
|
||||
if(empty($passwd) || !$adldap->authenticate($username, $passwd))
|
||||
{
|
||||
@ -131,10 +143,10 @@ class Ads implements Backend
|
||||
* @return mixed false on error, 0 if user must change on next login,
|
||||
* or NULL if user never changed his password or timestamp of last change
|
||||
*/
|
||||
static function getLastPwdChange($username)
|
||||
function getLastPwdChange($username)
|
||||
{
|
||||
$ret = false;
|
||||
if (($adldap = Api\Accounts\Ads::get_adldap()) &&
|
||||
if (($adldap = Api\Accounts\Ads::get_adldap($this->config)) &&
|
||||
($data = $adldap->user()->info($username, array('pwdlastset'))))
|
||||
{
|
||||
$ret = !$data[0]['pwdlastset'][0] ? $data[0]['pwdlastset'][0] :
|
||||
@ -155,10 +167,10 @@ class Ads implements Backend
|
||||
* @param boolean $return_mod =false true return ldap modification instead of executing it
|
||||
* @return boolean|array true if account_lastpwd_change successful changed, false otherwise or array if $return_mod
|
||||
*/
|
||||
static function setLastPwdChange($account_id=0, $passwd=NULL, $lastpwdchange=NULL, $return_mod=false)
|
||||
function setLastPwdChange($account_id=0, $passwd=NULL, $lastpwdchange=NULL, $return_mod=false)
|
||||
{
|
||||
unset($passwd); // not used but required by function signature
|
||||
if (!($adldap = Api\Accounts\Ads::get_adldap())) return false;
|
||||
if (!($adldap = Api\Accounts\Ads::get_adldap($this->config))) return false;
|
||||
|
||||
if ($lastpwdchange)
|
||||
{
|
||||
@ -202,7 +214,7 @@ class Ads implements Backend
|
||||
*/
|
||||
function change_password($old_passwd, $new_passwd, $account_id=0)
|
||||
{
|
||||
if (!($adldap = Api\Accounts\Ads::get_adldap()))
|
||||
if (!($adldap = Api\Accounts\Ads::get_adldap($this->config)))
|
||||
{
|
||||
error_log(__METHOD__."(\$old_passwd, \$new_passwd, $account_id) Api\Accounts\Ads::get_adldap() returned false");
|
||||
return false;
|
||||
|
272
api/src/Auth/Multiple.php
Normal file
272
api/src/Auth/Multiple.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
/**
|
||||
* EGroupware API - Authentication against multiple backends
|
||||
*
|
||||
* The first backend against which authentication succeeds is used, so you either need *somehow* to make sure usernames are unique,
|
||||
* or the backends in the *right* order. The name of the succeeding backend is stored in the instance cache.
|
||||
*
|
||||
* Specified via auth_multiple config variable with either
|
||||
* - a comma-separated string, eg. "Ldap,Sql" to first try LDAP then SQL authentication configured directly in setup
|
||||
* - a JSON encoded object eg.
|
||||
* {
|
||||
* "Ads": null, <-- uses default Ads config from Setup
|
||||
* "Ads2": { <-- 2nd Ads using given config (append a number to use backends multiple times)
|
||||
* "ads_host":"...",
|
||||
* "ads_domain":"...",
|
||||
* "ads_admin_user":"...",
|
||||
* "ads_admin_passwd":"...",
|
||||
* optional attributes like: "ads_connection":"tls"|"ssl", "ads_context", "ads_user_filter", "ads_group_filter"
|
||||
* },
|
||||
* optional further backend objects
|
||||
* }
|
||||
*
|
||||
* @link https://www.egroupware.org
|
||||
* @author Ralf Becker <rb@egroupware.org>
|
||||
* @license http://opensource.org/licenses/lgpl-license.php LGPL - GNU Lesser General Public License
|
||||
* @package api
|
||||
* @subpackage auth
|
||||
*/
|
||||
|
||||
namespace EGroupware\Api\Auth;
|
||||
|
||||
use EGroupware\Api;
|
||||
|
||||
/**
|
||||
* Authentication agains a LDAP Server with fallback to SQL
|
||||
*
|
||||
* For other fallback types, simply change auth backends in constructor call
|
||||
*/
|
||||
class Multiple implements Backend
|
||||
{
|
||||
/**
|
||||
* @var ?array[] with name as key
|
||||
*/
|
||||
private $config;
|
||||
/**
|
||||
* @var Backend[] with name as key
|
||||
*/
|
||||
private $backends = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $config auth_multiple config variable
|
||||
* @throws \Exception on invalid configuration
|
||||
*/
|
||||
function __construct($config=null)
|
||||
{
|
||||
if (!isset($config)) $config = $GLOBALS['egw_info']['server']['auth_multiple'];
|
||||
|
||||
$this->config = self::parseConfig($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse configuration
|
||||
*
|
||||
* @param string $config
|
||||
* @param boolean $checks true: run some extra checks, used in setup to check config is sane
|
||||
* @return array
|
||||
* @throws \Exception on invalid configuration
|
||||
*/
|
||||
static public function parseConfig($config, $checks=false)
|
||||
{
|
||||
try
|
||||
{
|
||||
$config = $config[0] === '{' ? json_decode($config, true, 512, JSON_THROW_ON_ERROR) :
|
||||
array_combine($csv = preg_split('/,\s*/', $config), array_fill(0, count($csv), null));
|
||||
}
|
||||
catch(\JsonException $e) {
|
||||
throw new \Exception('Invalid JSON: '.$e->getMessage());
|
||||
}
|
||||
if ($checks)
|
||||
{
|
||||
foreach($config as $name => $data)
|
||||
{
|
||||
if (!class_exists($class = __NAMESPACE__.'\\'.ucfirst(preg_replace('/\d+$/', '', $name))))
|
||||
{
|
||||
throw new \Exception("Invalid Backend name: '$name', no class $class found!");
|
||||
}
|
||||
if ($data !== null && !is_array($data))
|
||||
{
|
||||
throw new \Exception("Invalid Backend config: must by either null or an object!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all backends
|
||||
*
|
||||
* @return \Generator $name => Backend
|
||||
*/
|
||||
protected function backends()
|
||||
{
|
||||
foreach($this->config as $name => $config)
|
||||
{
|
||||
yield $name => $this->backend($name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a given backend
|
||||
*
|
||||
* @param $name
|
||||
* @return Backend
|
||||
*/
|
||||
protected function backend($name)
|
||||
{
|
||||
if (!isset($this->backends[$name]))
|
||||
{
|
||||
$class = __NAMESPACE__.'\\'.ucfirst(preg_replace('/\d+$/', '', $name));
|
||||
$this->backends[$name] = new $class($this->config[$name]);
|
||||
}
|
||||
return $this->backends[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate
|
||||
*
|
||||
* @param string $username username of account to authenticate
|
||||
* @param string $passwd corresponding password
|
||||
* @return boolean true if successful authenticated, false otherwise
|
||||
*/
|
||||
function authenticate($username, $passwd, $passwd_type='text')
|
||||
{
|
||||
$ret = false;
|
||||
if (($name = Api\Cache::getInstance(__CLASS__,'backend_used-'.$username)))
|
||||
{
|
||||
$ret = $this->backend($name)->authenticate($username, $passwd, $passwd_type);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ($this->backends() as $name => $backend)
|
||||
{
|
||||
if (($ret = $backend->authenticate($username, $passwd, $passwd_type)))
|
||||
{
|
||||
Api\Cache::setInstance(__CLASS__, 'backend_used-' . $username, $name);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__."('$username', \$passwd, '$passwd_type') backend=$name" returning ".array2string($ret));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes password in authentication backend
|
||||
*
|
||||
* If $old_passwd is given, the password change is done binded as user and NOT with the
|
||||
* "root" dn given in the configurations.
|
||||
*
|
||||
* @param string $old_passwd must be cleartext or empty to not to be checked
|
||||
* @param string $new_passwd must be cleartext
|
||||
* @param int $account_id account id of user whose passwd should be changed
|
||||
* @return boolean true if password successful changed, false otherwise
|
||||
*/
|
||||
function change_password($old_passwd, $new_passwd, $account_id=0)
|
||||
{
|
||||
if (!$account_id)
|
||||
{
|
||||
$account_id = $GLOBALS['egw_info']['user']['account_id'];
|
||||
$username = $GLOBALS['egw_info']['user']['account_lid'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$username = $GLOBALS['egw']->accounts->id2name($account_id);
|
||||
}
|
||||
$ret = false;
|
||||
if (($name = Api\Cache::getInstance(__CLASS__,'backend_used-'.$username)))
|
||||
{
|
||||
$ret = $this->backend($name)->change_password($old_passwd, $new_passwd, $account_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($this->backends as $name => $backend)
|
||||
{
|
||||
if (($ret = $backend->change_password($old_passwd, $new_passwd, $account_id)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__."('$old_passwd', '$new_passwd', $account_id) username='$username', backend=$name" returning ".array2string($ret));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the last pwd change for the user
|
||||
*
|
||||
* @param string $username username of account to authenticate
|
||||
* @return mixed false or account_lastpwd_change
|
||||
*/
|
||||
function getLastPwdChange($username)
|
||||
{
|
||||
$ret = false;
|
||||
if (($name = Api\Cache::getInstance(__CLASS__,'backend_used-'.$username)))
|
||||
{
|
||||
$backend = $this->backend($name);
|
||||
if (method_exists($backend, 'getLastPwdChange'))
|
||||
{
|
||||
$ret = $backend->getLastPwdChange($username);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($this->backends as $name => $backend)
|
||||
{
|
||||
if (method_exists($backend, 'getLastPwdChange') &&
|
||||
($ret = $backend->getLastPwdChange($username)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__."('$username'), backend=$name" returning ".array2string($ret));
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes account_lastpwd_change in auth backend
|
||||
*
|
||||
* @param int $account_id account id of user whose passwd should be changed
|
||||
* @param string $passwd must be cleartext, usually not used, but may be used to authenticate as user to do the change -> ldap
|
||||
* @param int $lastpwdchange must be a unixtimestamp
|
||||
* @return boolean true if account_lastpwd_change successful changed, false otherwise
|
||||
*/
|
||||
function setLastPwdChange($account_id=0, $passwd=NULL, $lastpwdchange=NULL, $return_mod=false)
|
||||
{
|
||||
if(!$account_id || $GLOBALS['egw_info']['flags']['currentapp'] == 'login')
|
||||
{
|
||||
$account_id = $GLOBALS['egw_info']['user']['account_id'];
|
||||
$username = $GLOBALS['egw_info']['user']['account_lid'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$username = $GLOBALS['egw']->accounts->id2name($account_id);
|
||||
}
|
||||
$ret = false;
|
||||
if (($name = Api\Cache::getInstance(__CLASS__,'backend_used-'.$username)))
|
||||
{
|
||||
$backend = $this->backend($name);
|
||||
if (method_exists($backend, 'setLastPwdChange'))
|
||||
{
|
||||
$ret = $backend->setLastPwdChange($account_id, $passwd, $lastpwdchange, $return_mod);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($this->backends as $name => $backend)
|
||||
{
|
||||
if (method_exists($backend, 'setLastPwdChange') &&
|
||||
($ret = $backend->setLastPwdChange($account_id, $passwd, $lastpwdchange, $return_mod)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__."('$username'), backend=$name" returning ".array2string($ret));
|
||||
return $ret;
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ $GLOBALS['egw_info']['server']['found_validation_hook'] = array(
|
||||
'mcrypt_algo',
|
||||
'ldap_search_filter',
|
||||
'auth_type',
|
||||
'auth_multiple',
|
||||
);
|
||||
|
||||
/**
|
||||
@ -50,6 +51,24 @@ function auth_type($settings)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate auth_multiple config
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
function auth_multiple(array $settings)
|
||||
{
|
||||
try {
|
||||
if ($settings['auth_multiple'] !== '')
|
||||
{
|
||||
Api\Auth\Multiple::parseConfig($settings['auth_multiple'], true);
|
||||
}
|
||||
}
|
||||
catch (Exception $ex) {
|
||||
$GLOBALS['config_error'] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set vfs_fstab depending from what the user selected for vfs_storage_mode
|
||||
*
|
||||
|
@ -623,10 +623,19 @@
|
||||
<td><input name="newsettings[cas_cert]" value="{value_cas_cert}" size="40" /></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_on">
|
||||
<tr class="row_off">
|
||||
<td colspan="2"> </td>
|
||||
</tr>
|
||||
|
||||
<tr class="th">
|
||||
<td colspan="2"><b>{lang_If_using_Multiple_authentication_providers:}</b></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_on">
|
||||
<td>{lang_Comma-separated_provider_names_or_JSON}: <a href="https://github.com/EGroupware/egroupware/blob/master/api/src/Auth/Multiple.php" target="_blank">Auth/Multiple.php</a></td>
|
||||
<td><textarea name="newsettings[auth_multiple]" cols="64" rows="1" onfocus="this.style='height: '+this.scrollHeight+'px'" onblur="this.style='height: auto'">{value_auth_multiple}</textarea></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<td colspan="2"> </td>
|
||||
</tr>
|
||||
|
Loading…
Reference in New Issue
Block a user