mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-22 07:53:39 +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;
|
var $previous_login = -1;
|
||||||
|
|
||||||
|
protected $config;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ads auth constructor
|
||||||
|
*
|
||||||
|
* @param array|null $config
|
||||||
|
*/
|
||||||
|
function __construct(array $config=null)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* password authentication
|
* 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
|
// 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);
|
$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
|
// 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))
|
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,
|
* @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
|
* or NULL if user never changed his password or timestamp of last change
|
||||||
*/
|
*/
|
||||||
static function getLastPwdChange($username)
|
function getLastPwdChange($username)
|
||||||
{
|
{
|
||||||
$ret = false;
|
$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'))))
|
($data = $adldap->user()->info($username, array('pwdlastset'))))
|
||||||
{
|
{
|
||||||
$ret = !$data[0]['pwdlastset'][0] ? $data[0]['pwdlastset'][0] :
|
$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
|
* @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
|
* @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
|
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)
|
if ($lastpwdchange)
|
||||||
{
|
{
|
||||||
@ -202,7 +214,7 @@ class Ads implements Backend
|
|||||||
*/
|
*/
|
||||||
function change_password($old_passwd, $new_passwd, $account_id=0)
|
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");
|
error_log(__METHOD__."(\$old_passwd, \$new_passwd, $account_id) Api\Accounts\Ads::get_adldap() returned false");
|
||||||
return 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',
|
'mcrypt_algo',
|
||||||
'ldap_search_filter',
|
'ldap_search_filter',
|
||||||
'auth_type',
|
'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
|
* 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>
|
<td><input name="newsettings[cas_cert]" value="{value_cas_cert}" size="40" /></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr class="row_on">
|
<tr class="row_off">
|
||||||
<td colspan="2"> </td>
|
<td colspan="2"> </td>
|
||||||
</tr>
|
</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">
|
<tr class="row_off">
|
||||||
<td colspan="2"> </td>
|
<td colspan="2"> </td>
|
||||||
</tr>
|
</tr>
|
||||||
|
Loading…
Reference in New Issue
Block a user