mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-25 01:13:25 +01:00
* LDAP: implement optional group-filter
also some code cleanups and fixes
This commit is contained in:
parent
f42a10deb7
commit
ab427562b7
@ -32,14 +32,15 @@ use setup_cmd_ldap;
|
||||
*
|
||||
* A user is recognised by eGW, if he's in the user_context tree AND has the posixAccount object class AND
|
||||
* matches the LDAP search filter specified in setup >> configuration.
|
||||
* A group is recogniced by eGW, if it's in the group_context tree AND has the posixGroup object class.
|
||||
* A group is recognised by eGW, if it's in the group_context tree AND has the posixGroup object class AND
|
||||
* - if specified - matches the LDAP group filter.
|
||||
* The group members are stored as memberuid's.
|
||||
*
|
||||
* The (positive) group-id's (gidnumber) of LDAP groups are mapped in this class to negative numeric
|
||||
* account_id's to not conflict with the user-id's, as both share in eGW internaly the same numberspace!
|
||||
* account_id's to not conflict with the user-id's, as both share in eGW internally the same numberspace!
|
||||
*
|
||||
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
||||
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @author Ralf Becker <rb@egroupware.org>
|
||||
* @license https://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||
* @access internal only use the interface provided by the accounts class
|
||||
*/
|
||||
class Ldap
|
||||
@ -51,7 +52,7 @@ class Ldap
|
||||
/**
|
||||
* resource with connection to the ldap server
|
||||
*
|
||||
* @var resource
|
||||
* @var resource|object
|
||||
*/
|
||||
var $ds;
|
||||
/**
|
||||
@ -72,6 +73,12 @@ class Ldap
|
||||
* @var string
|
||||
*/
|
||||
var $group_context;
|
||||
/**
|
||||
* Additional LDAP search filter for groups
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
var $group_filter;
|
||||
/**
|
||||
* total number of found entries from get_list method
|
||||
*
|
||||
@ -79,10 +86,8 @@ class Ldap
|
||||
*/
|
||||
var $total;
|
||||
|
||||
var $ldapServerInfo;
|
||||
|
||||
/**
|
||||
* required classe for user and groups
|
||||
* required object-classes for user and groups
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
@ -140,7 +145,7 @@ class Ldap
|
||||
const CHANGE_ACCOUNT_LID = true;
|
||||
|
||||
/**
|
||||
* does backend requires password to be set, before allowing to enable an account
|
||||
* does backend require password to be set, before allowing to enable an account
|
||||
*/
|
||||
const REQUIRE_PASSWORD_FOR_ENABLE = false;
|
||||
|
||||
@ -153,13 +158,34 @@ class Ldap
|
||||
{
|
||||
$this->frontend = $frontend;
|
||||
|
||||
$this->ldap = Api\Ldap::factory(false, $this->frontend->config['ldap_host'],
|
||||
$this->frontend->config['ldap_root_dn'],$this->frontend->config['ldap_root_pw']);
|
||||
$this->ds = $this->ldap->ds;
|
||||
$this->ds = $this->ldap_connection();
|
||||
|
||||
$this->user_context = $this->frontend->config['ldap_context'];
|
||||
$this->account_filter = $this->frontend->config['ldap_search_filter'];
|
||||
$this->group_context = $this->frontend->config['ldap_group_context'] ?: $this->frontend->config['ldap_context'];
|
||||
$this->group_filter = $this->frontend->config['ldap_group_filter'];
|
||||
if (!empty($this->group_filter) && !($this->group_filter[0] === '(' && substr($this->group_filter, -1) === ')'))
|
||||
{
|
||||
$this->group_filter = '('.$this->group_filter.')';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection to ldap server and optionally reconnect
|
||||
*
|
||||
* @param boolean $reconnect =false true: reconnect even if already connected
|
||||
* @return resource|object
|
||||
* @throws Api\Exception\AssertionFailed
|
||||
* @throws Api\Exception\NoPermission
|
||||
*/
|
||||
function ldap_connection(bool $reconnect = false)
|
||||
{
|
||||
$this->ldap = Api\Ldap::factory(false, $this->frontend->config['ldap_host'],
|
||||
$this->frontend->config['ldap_root_dn'],$this->frontend->config['ldap_root_pw'], $reconnect);
|
||||
|
||||
$this->serverinfo = $this->ldap->getLDAPServerInfo();
|
||||
|
||||
return $this->ldap->ds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,9 +220,9 @@ class Ldap
|
||||
$data_utf8 = Api\Translation::convert($data,Api\Translation::charset(),'utf-8');
|
||||
$members = $data['account_members'];
|
||||
|
||||
if (!is_object($this->ldapServerInfo))
|
||||
if (!is_object($this->serverinfo))
|
||||
{
|
||||
$this->ldapServerInfo = $this->ldap->getLDAPServerInfo($this->frontend->config['ldap_host']);
|
||||
$this->serverinfo = $this->ldap->getLDAPServerInfo();
|
||||
}
|
||||
// common code for users and groups
|
||||
// checks if account_lid (dn) has been changed or required objectclass'es are missing
|
||||
@ -264,7 +290,7 @@ class Ldap
|
||||
$add = $additional;
|
||||
$additional = array_shift($add);
|
||||
}
|
||||
if ($this->ldapServerInfo->supportsObjectClass($additional))
|
||||
if ($this->serverinfo->supportsObjectClass($additional))
|
||||
{
|
||||
$to_write['objectclass'][] = $additional;
|
||||
if ($add) $to_write += $add;
|
||||
@ -299,7 +325,7 @@ class Ldap
|
||||
$keep_objectclass = false;
|
||||
if (is_array($forward)) list($forward,$extra_attr,$keep_objectclass) = $forward;
|
||||
|
||||
if ($this->ldapServerInfo->supportsObjectClass($objectclass) &&
|
||||
if ($this->serverinfo->supportsObjectClass($objectclass) &&
|
||||
($old && in_array($objectclass,$old['objectclass']) || $data_utf8['account_email'] || $old[static::MAIL_ATTR]))
|
||||
{
|
||||
if ($data_utf8['account_email']) // setting an email
|
||||
@ -431,13 +457,13 @@ class Ldap
|
||||
protected function _read_group($account_id)
|
||||
{
|
||||
$group = array();
|
||||
if (!is_object($this->ldapServerInfo))
|
||||
if (!is_object($this->serverinfo))
|
||||
{
|
||||
$this->ldapServerInfo = $this->ldap->getLDAPServerInfo($this->frontend->config['ldap_host']);
|
||||
$this->serverinfo = $this->ldap->getLDAPServerInfo($this->frontend->config['ldap_host']);
|
||||
}
|
||||
foreach(array_keys($this->group_mail_classes) as $objectclass)
|
||||
{
|
||||
if ($this->ldapServerInfo->supportsObjectClass($objectclass))
|
||||
if ($this->serverinfo->supportsObjectClass($objectclass))
|
||||
{
|
||||
$group['mailAllowed'] = $objectclass;
|
||||
break;
|
||||
@ -755,14 +781,15 @@ class Ldap
|
||||
$order = isset($propertyMap[$orders[0]]) ? $propertyMap[$orders[0]] : 'uid';
|
||||
$sri = ldap_search($this->ds, $this->user_context, $filter,array('uid', $order));
|
||||
$fullSet = array();
|
||||
foreach ((array)ldap_get_entries($this->ds, $sri) as $key => $entry)
|
||||
foreach (ldap_get_entries($this->ds, $sri) ?: [] as $key => $entry)
|
||||
{
|
||||
if ($key !== 'count') $fullSet[$entry['uid'][0]] = $entry[$order][0];
|
||||
}
|
||||
|
||||
if (is_numeric($param['type'])) // return only group-members
|
||||
{
|
||||
$sri = ldap_search($this->ds,$this->group_context,"(&(objectClass=posixGroup)(gidnumber=" . abs($param['type']) . "))",array('memberuid'));
|
||||
$sri = ldap_search($this->ds,$this->group_context,"(&(objectClass=posixGroup)(gidnumber=" .
|
||||
abs($param['type']) . "))",array('memberuid'));
|
||||
$group = ldap_get_entries($this->ds, $sri);
|
||||
$fullSet = $group[0]['memberuid'] ? array_intersect_key($fullSet, array_flip($group[0]['memberuid'])) : array();
|
||||
}
|
||||
@ -774,12 +801,12 @@ class Ldap
|
||||
$filter = '(&(objectclass=posixaccount)(|(uid='.implode(')(uid=',$relevantAccounts).'))' . $this->account_filter.')';
|
||||
$filter = str_replace(array('%user','%domain'),array('*',$GLOBALS['egw_info']['user']['domain']),$filter);
|
||||
}
|
||||
/** @noinspection SuspiciousAssignmentsInspection */
|
||||
$sri = ldap_search($this->ds, $this->user_context, $filter,array('uid','uidNumber','givenname','sn',static::MAIL_ATTR,'shadowExpire','createtimestamp','modifytimestamp','objectclass','gidNumber'));
|
||||
|
||||
$utc_diff = date('Z');
|
||||
foreach(ldap_get_entries($this->ds, $sri) as $allVals)
|
||||
foreach(ldap_get_entries($this->ds, $sri) ?: [] as $allVals)
|
||||
{
|
||||
settype($allVals,'array');
|
||||
$test = @$allVals['uid'][0];
|
||||
if (!$this->frontend->config['global_denied_users'][$test] && $allVals['uid'][0])
|
||||
{
|
||||
@ -816,9 +843,9 @@ class Ldap
|
||||
}
|
||||
if ($param['type'] == 'groups' || $param['type'] == 'both')
|
||||
{
|
||||
if(empty($query) || $query == '*')
|
||||
if(empty($query) || $query === '*')
|
||||
{
|
||||
$filter = '(objectclass=posixgroup)';
|
||||
$filter = "(&(objectclass=posixgroup)$this->group_filter)";
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -834,12 +861,11 @@ class Ldap
|
||||
case 'exact':
|
||||
break;
|
||||
}
|
||||
$filter = "(&(objectclass=posixgroup)(cn=$query))";
|
||||
$filter = "(&(objectclass=posixgroup)(cn=$query)$this->group_filter)";
|
||||
}
|
||||
$sri = ldap_search($this->ds, $this->group_context, $filter,array('cn','gidNumber'));
|
||||
foreach((array)ldap_get_entries($this->ds, $sri) as $allVals)
|
||||
foreach(ldap_get_entries($this->ds, $sri) ?: [] as $allVals)
|
||||
{
|
||||
settype($allVals,'array');
|
||||
$test = $allVals['cn'][0];
|
||||
if (!$this->frontend->config['global_denied_groups'][$test] && $allVals['cn'][0])
|
||||
{
|
||||
@ -960,10 +986,10 @@ class Ldap
|
||||
if (in_array($which, array('account_lid','account_email')) && $account_type !== 'u') // groups only support account_(lid|email)
|
||||
{
|
||||
$attr = $which == 'account_lid' ? 'cn' : static::MAIL_ATTR;
|
||||
$sri = ldap_search($this->ds, $this->group_context, '(&('.$attr.'=' . $name . ')(objectclass=posixgroup))', array('gidNumber'));
|
||||
$allValues = ldap_get_entries($this->ds, $sri);
|
||||
|
||||
if (@$allValues[0]['gidnumber'][0])
|
||||
if (($sri = ldap_search($this->ds, $this->group_context, '(&('.$attr.'=' . $name . ")(objectclass=posixgroup)$this->group_filter)", array('gidNumber'))) &&
|
||||
($allValues = ldap_get_entries($this->ds, $sri)) &&
|
||||
!empty($allValues[0]['gidnumber'][0]))
|
||||
{
|
||||
return -$allValues[0]['gidnumber'][0];
|
||||
}
|
||||
@ -978,11 +1004,9 @@ class Ldap
|
||||
return False;
|
||||
}
|
||||
|
||||
$sri = ldap_search($this->ds, $this->user_context, '(&('.$to_ldap[$which].'=' . $name . ')(objectclass=posixaccount))', array('uidNumber'));
|
||||
|
||||
$allValues = ldap_get_entries($this->ds, $sri);
|
||||
|
||||
if (@$allValues[0]['uidnumber'][0])
|
||||
if (($sri = ldap_search($this->ds, $this->user_context, '(&('.$to_ldap[$which].'=' . $name . ')(objectclass=posixaccount))', array('uidNumber'))) &&
|
||||
($allValues = ldap_get_entries($this->ds, $sri)) &&
|
||||
!empty($allValues[0]['uidnumber'][0]))
|
||||
{
|
||||
return (int)$allValues[0]['uidnumber'][0];
|
||||
}
|
||||
@ -1042,7 +1066,7 @@ class Ldap
|
||||
*
|
||||
* @param int $_gid
|
||||
* @return array|boolean array with uidnumber => uid pairs,
|
||||
* false if $_git is not nummeric and can't be resolved to a nummeric gid
|
||||
* false if $_gid is not numeric and can't be resolved to a numeric gid
|
||||
*/
|
||||
function members($_gid)
|
||||
{
|
||||
@ -1121,7 +1145,7 @@ class Ldap
|
||||
if (!isset($objectclass))
|
||||
{
|
||||
$objectclass = $this->id2name($gid, 'objectclass');
|
||||
// if we cant find objectclass, we might ge in the middle of a migration
|
||||
// if we can't find objectclass, we might ge in the middle of a migration
|
||||
if (!isset($objectclass))
|
||||
{
|
||||
Api\Accounts::cache_invalidate($gid);
|
||||
@ -1141,7 +1165,7 @@ class Ldap
|
||||
$member_dn = $this->id2name($member, 'account_dn');
|
||||
if (is_numeric($member)) $member = $this->id2name($member);
|
||||
|
||||
// only add a member, if we have the neccessary info / he already exists in migration
|
||||
// only add a member, if we have the necessary info / he already exists in migration
|
||||
if ($member && ($member_dn || !array_intersect(array('groupofnames','groupofuniquenames','univentiongroup'), $objectclass)))
|
||||
{
|
||||
$to_write['memberuid'][] = $member;
|
||||
@ -1281,7 +1305,7 @@ class Ldap
|
||||
return -1;
|
||||
}
|
||||
|
||||
$id = (int)$GLOBALS['egw_info']['server'][$key='last_id_'.$location];
|
||||
$id = (int)$GLOBALS['egw_info']['server']['last_id_'.$location];
|
||||
|
||||
if (!$id || $id < $min)
|
||||
{
|
||||
@ -1306,7 +1330,6 @@ class Ldap
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['ds']);
|
||||
unset($this->ds);
|
||||
return array_keys($vars);
|
||||
}
|
||||
|
||||
@ -1315,7 +1338,6 @@ class Ldap
|
||||
*/
|
||||
function __wakeup()
|
||||
{
|
||||
$this->ds = Api\Ldap::factory(true, $this->frontend->config['ldap_host'],
|
||||
$this->frontend->config['ldap_root_dn'],$this->frontend->config['ldap_root_pw']);
|
||||
$this->ds = $this->ldap_connection();
|
||||
}
|
||||
}
|
@ -26,7 +26,6 @@ use EGroupware\Api\Ldap\ServerInfo;
|
||||
*/
|
||||
class Ldap
|
||||
{
|
||||
|
||||
const ALL = 0;
|
||||
const ACCOUNTS = 1;
|
||||
const PERSONAL = 2;
|
||||
@ -372,7 +371,6 @@ class Ldap
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['ds']);
|
||||
unset($this->ds);
|
||||
return array_keys($vars);
|
||||
}
|
||||
|
||||
@ -472,7 +470,7 @@ class Ldap
|
||||
* reads contact data
|
||||
*
|
||||
* @param string|array $contact_id contact_id or array with values for id or account_id
|
||||
* @return array/boolean data if row could be retrived else False
|
||||
* @return array|false data if row could be retrived else False
|
||||
*/
|
||||
function read($contact_id)
|
||||
{
|
||||
@ -512,6 +510,7 @@ class Ldap
|
||||
*
|
||||
* @param array $keys if given $keys are copied to data before saveing => allows a save as
|
||||
* @return int 0 on success and errno != 0 else
|
||||
* @noinspection UnsupportedStringOffsetOperationsInspection
|
||||
*/
|
||||
function save($keys=null)
|
||||
{
|
||||
@ -785,7 +784,7 @@ class Ldap
|
||||
* @param string $join ='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or
|
||||
* "LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
|
||||
* @param boolean $need_full_no_count =false If true an unlimited query is run to determine the total number of rows, default false
|
||||
* @return array of matching rows (the row is an array of the cols) or False
|
||||
* @return array|false of matching rows (the row is an array of the cols) or False
|
||||
*/
|
||||
function &search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join='',$need_full_no_count=false)
|
||||
{
|
||||
@ -1024,9 +1023,9 @@ class Ldap
|
||||
elseif ($value)
|
||||
{
|
||||
if (is_array($value)) $filters .= '(|';
|
||||
foreach((array)$value as $value)
|
||||
foreach((array)$value as $val)
|
||||
{
|
||||
$filters .= '(uidNumber='.(int)$value.')';
|
||||
$filters .= '(uidNumber='.(int)$val.')';
|
||||
}
|
||||
if (is_array($value)) $filters .= ')';
|
||||
}
|
||||
@ -1192,7 +1191,7 @@ class Ldap
|
||||
* @param int $_addressbooktype
|
||||
* @param array $_skipPlugins =null schema-plugins to skip
|
||||
* @param string $order_by sql order string eg. "contact_email ASC"
|
||||
* @param null|int|array $start [$start,$offset], on return null, if result sorted and limited by server
|
||||
* @param null|int|array $start [$start, $num_rows], on return null, if result sorted and limited by server
|
||||
* @return array/boolean with eGW contacts or false on error
|
||||
*/
|
||||
function _searchLDAP($_ldapContext, $_filter, $_attributes, $_addressbooktype, array $_skipPlugins=null, $order_by=null, &$start=null)
|
||||
|
@ -20,7 +20,7 @@ namespace EGroupware\Api;
|
||||
* Please note for SSL or TLS connections hostname has to be:
|
||||
* - SSL: "ldaps://host[:port]/"
|
||||
* - TLS: "tls://host[:port]/"
|
||||
* Both require certificats installed on the webserver, otherwise the connection will fail!
|
||||
* Both require certificates installed on the webserver, otherwise the connection will fail!
|
||||
*
|
||||
* If multiple (space-separated) ldap hosts or urls are given, try them in order and
|
||||
* move first successful one to first place in session, to try not working ones
|
||||
@ -79,15 +79,16 @@ class Ldap
|
||||
* @param string $host ='' ldap host, default $GLOBALS['egw_info']['server']['ldap_host']
|
||||
* @param string $dn ='' ldap dn, default $GLOBALS['egw_info']['server']['ldap_root_dn']
|
||||
* @param string $passwd ='' ldap pw, default $GLOBALS['egw_info']['server']['ldap_root_pw']
|
||||
* @param bool $reconnect default false, true: reconnect, even if we have an existing connection
|
||||
* @return object|resource|self|false resource/object from ldap_connect(), self or false on error
|
||||
* @throws Exception\AssertionFailed 'LDAP support unavailable!' (no ldap extension)
|
||||
* @throws Exception\NoPermission if bind fails
|
||||
*/
|
||||
public static function factory($ressource=true, $host='', $dn='', $passwd='')
|
||||
public static function factory($ressource=true, $host='', $dn='', $passwd='', bool $reconnect=false)
|
||||
{
|
||||
$key = md5($host.':'.$dn.':'.$passwd);
|
||||
|
||||
if (!isset(self::$connections[$key]))
|
||||
if (!isset(self::$connections[$key]) || $reconnect)
|
||||
{
|
||||
self::$connections[$key] = new Ldap(true);
|
||||
|
||||
@ -302,4 +303,27 @@ class Ldap
|
||||
Cache::setSession(__CLASS__, 'ldapServerInfo', $this->ldapserverinfo);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic method called when object gets serialized
|
||||
*
|
||||
* We do NOT store ldapConnection, as we need to reconnect anyway.
|
||||
* PHP 8.1 gives an error when trying to serialize LDAP\Connection object!
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function __sleep()
|
||||
{
|
||||
$vars = get_object_vars($this);
|
||||
unset($vars['ds']);
|
||||
return array_keys($vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* __wakeup function gets called by php while unserializing the object to reconnect with the ldap server
|
||||
*/
|
||||
function __wakeup()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -308,16 +308,21 @@
|
||||
</tr>
|
||||
|
||||
<tr class="row_on">
|
||||
<td>{lang_Additional_group_filter_(optional)}:</td>
|
||||
<td><input name="newsettings[ldap_group_filter]" value="{value_ldap_group_filter}" size="40" /></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<td>{lang_LDAP_rootdn} {lang_(searching_accounts_and_changing_passwords)}:</td>
|
||||
<td><input name="newsettings[ldap_root_dn]" value="{value_ldap_root_dn}" size="40" /></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<tr class="row_on">
|
||||
<td>{lang_LDAP_root_password}:</td>
|
||||
<td><input name="newsettings[ldap_root_pw]" type="password" value="{value_ldap_root_pw}" /></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_on">
|
||||
<tr class="row_off">
|
||||
<td>{lang_LDAP_encryption_type}:</td>
|
||||
<td>
|
||||
<select name="newsettings[ldap_encryption_type]">
|
||||
@ -326,7 +331,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<tr class="row_on">
|
||||
<td>{lang_Do_you_want_to_manage_homedirectory_and_loginshell_attributes?}:</td>
|
||||
<td>
|
||||
<select name="newsettings[ldap_extra_attributes]">
|
||||
@ -336,17 +341,17 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_on">
|
||||
<tr class="row_off">
|
||||
<td>{lang_LDAP_Default_homedirectory_prefix_(e.g._/home_for_/home/username)}:</td>
|
||||
<td><input name="newsettings[ldap_account_home]" value="{value_ldap_account_home}" /></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<tr class="row_on">
|
||||
<td>{lang_LDAP_Default_shell_(e.g._/bin/bash)}:</td>
|
||||
<td><input name="newsettings[ldap_account_shell]" value="{value_ldap_account_shell}" /></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_on">
|
||||
<tr class="row_off">
|
||||
<td>{lang_Allow_usernames_identical_to_system_users?}:</td>
|
||||
<td>
|
||||
<select name="newsettings[ldap_allow_systemusernames]">
|
||||
@ -356,7 +361,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off" valign="top">
|
||||
<tr class="row_on" valign="top">
|
||||
<td colspan="2">
|
||||
<a href="account_migration.php"><b>{lang_Migration_between_eGroupWare_account_repositories}:</b></a>
|
||||
</td>
|
||||
@ -710,5 +715,3 @@
|
||||
</table>
|
||||
</form>
|
||||
<!-- END footer -->
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user