* ActiveDirectory: fix account-selection type groupmembers (applies only to non-admins!) and ignore Domain Users group

not setting caseIgnoreMatch for sorting, as its not supported for Windows ActiveDirectory
This commit is contained in:
Ralf Becker 2020-09-08 15:29:51 +02:00
parent 5afe7ddbca
commit 9f9cce88b2
5 changed files with 80 additions and 15 deletions

View File

@ -246,6 +246,7 @@ class Accounts
$members = array(); $members = array();
foreach((array)$this->memberships($GLOBALS['egw_info']['user']['account_id'],true) as $grp) foreach((array)$this->memberships($GLOBALS['egw_info']['user']['account_id'],true) as $grp)
{ {
if (isset($this->backend->ignore_membership) && in_array($grp, $this->backend->ignore_membership)) continue;
$members = array_unique(array_merge($members, (array)$this->members($grp,true,$param['active']))); $members = array_unique(array_merge($members, (array)$this->members($grp,true,$param['active'])));
if ($param['type'] == 'groupmembers+memberships') $members[] = $grp; if ($param['type'] == 'groupmembers+memberships') $members[] = $grp;
} }
@ -313,6 +314,7 @@ class Accounts
* @param array $options * @param array $options
* $options['filter']['group'] only return members of that group * $options['filter']['group'] only return members of that group
* $options['account_type'] "accounts", "groups", "both" or "groupmembers" * $options['account_type'] "accounts", "groups", "both" or "groupmembers"
* $options['tag_list'] true: return array of values for keys "value", "label" and "icon"
* @return array with id - title pairs of the matching entries * @return array with id - title pairs of the matching entries
*/ */
public static function link_query($pattern, array &$options = array()) public static function link_query($pattern, array &$options = array())
@ -367,8 +369,24 @@ class Accounts
'order' => $order, 'order' => $order,
)) as $account) )) as $account)
{ {
$accounts[$account['account_id']] = self::format_username($account['account_lid'], $displayName = self::format_username($account['account_lid'],
$account['account_firstname'],$account['account_lastname'],$account['account_id']); $account['account_firstname'],$account['account_lastname'],$account['account_id']);
if (!empty($options['tag_list']))
{
$accounts[] = [
'value' => $account['account_id'],
'label' => $displayName,
'icon' => Framework::link('/api/avatar.php', [
'account_id' => $account['account_id'],
'modified' => $account['account_modified'],
]),
];
}
else
{
$accounts[$account['account_id']] = $displayName;
}
} }
return $accounts; return $accounts;
} }

View File

@ -109,7 +109,14 @@ class Ads
*/ */
const MIN_ACCOUNT_ID = 1000; const MIN_ACCOUNT_ID = 1000;
/**
* Ignore group-membership of following groups, when compiling group-members
*
* We ignore "Domain Users" group with RID 513, as it contains all users!
*
* @var int[]
*/
public $ignore_membership = [ -513 ];
/** /**
* Timestamps ldap => egw used in several places * Timestamps ldap => egw used in several places
* *
@ -1081,6 +1088,20 @@ class Ads
{ {
$type_filter .= $this->frontend->config['ads_user_filter']; $type_filter .= $this->frontend->config['ads_user_filter'];
} }
// for non-admins and account_selection "groupmembers" we have to filter by memberOf attribute
if ($GLOBALS['egw_info']['user']['preferences']['common']['account_selection'] === 'groupmembers' &&
(!isset($GLOBALS['egw_info']['user']['apps']['admin'])))
{
$type_filter .= '(|';
foreach($GLOBALS['egw']->accounts->memberships($GLOBALS['egw_info']['user']['account_id'],true) as $group_id)
{
if (!in_array($group_id, $this->ignore_membership) && ($dn = Api\Accounts::id2name($group_id, 'account_dn')))
{
$type_filter .= '(memberOf='.$dn.')(primaryGroupID='.abs($group_id).')';
}
}
$type_filter .= ')';
}
$type_filter .= ')'; $type_filter .= ')';
if ($account_type === 'u') break; if ($account_type === 'u') break;
$user_filter = $type_filter; $user_filter = $type_filter;
@ -1103,9 +1124,11 @@ class Ads
/** /**
* Get value(s) for LDAP_CONTROL_SORTREQUEST * Get value(s) for LDAP_CONTROL_SORTREQUEST
* *
* Sorting by multiple criteria is supported in LDAP RFC 2891, but - at least with Univention Samba - gives wired results,
* Windows AD does NOT support it and gives an error if the oid is specified!
*
* @param ?string $order_by sql order string eg. "contact_email ASC" * @param ?string $order_by sql order string eg. "contact_email ASC"
* @return array of arrays with values for keys 'attr', 'oid' (caseIgnoreMatch='2.5.13.3') and 'reverse' * @return array of arrays with values for keys 'attr', 'oid' (caseIgnoreMatch='2.5.13.3') and 'reverse'
* @todo sorting by multiple criteria is supported in LDAP RFC 2891, but - at least with Univention - gives wired results
*/ */
protected function sort_values($order_by) protected function sort_values($order_by)
{ {
@ -1122,14 +1145,17 @@ class Ads
} }
elseif (($attr = array_search('account_'.$matches[2], $this->attributes2egw))) elseif (($attr = array_search('account_'.$matches[2], $this->attributes2egw)))
{ {
$values[] = [ $value = [
'attr' => $attr, 'attr' => $mapping[$matches[2]],
'oid' => '2.5.13.3', // caseIgnoreMatch 'oid' => '2.5.13.3', // caseIgnoreMatch
'reverse' => strtoupper($matches[3]) === ' DESC', 'reverse' => strtoupper($matches[3]) === ' DESC',
]; ];
// Windows AD does NOT support caseIgnoreMatch sorting, only it's default sorting
if ($this->serverinfo->activeDirectory(true)) unset($value['oid']);
$values[] = $value;
} }
$order_by = substr($order_by, strlen($matches[0])); $order_by = substr($order_by, strlen($matches[0]));
if ($values) break; // sorting by multiple criteria gives wired results if ($values) break; // sorting by multiple criteria gives no result for Windows AD and wired result for Samba4
} }
return $values; return $values;
} }

View File

@ -233,7 +233,7 @@ class Ads extends Ldap
$filter = ''; $filter = '';
if ($gid < 0 && ($dn = $GLOBALS['egw']->accounts->id2name($gid, 'account_dn'))) if ($gid < 0 && ($dn = $GLOBALS['egw']->accounts->id2name($gid, 'account_dn')))
{ {
$filter .= '(memberOf='.$dn.')'; $filter .= '(|(memberOf='.$dn.')(primaryGroupID='.abs($gid).'))';
} }
return $filter; return $filter;
} }

View File

@ -1096,9 +1096,11 @@ class Ldap
/** /**
* Get value(s) for LDAP_CONTROL_SORTREQUEST * Get value(s) for LDAP_CONTROL_SORTREQUEST
* *
* Sorting by multiple criteria is supported in LDAP RFC 2891, but - at least with Univention Samba - gives wired results,
* Windows AD does NOT support it and gives an error if the oid is specified!
*
* @param ?string $order_by sql order string eg. "contact_email ASC" * @param ?string $order_by sql order string eg. "contact_email ASC"
* @return array of arrays with values for keys 'attr', 'oid' (caseIgnoreMatch='2.5.13.3') and 'reverse' * @return array of arrays with values for keys 'attr', 'oid' (caseIgnoreMatch='2.5.13.3') and 'reverse'
* @todo sorting by multiple criteria is supported in LDAP RFC 2891, but - at least with Univention - gives wired results
*/ */
protected function sort_values($order_by) protected function sort_values($order_by)
{ {
@ -1119,17 +1121,20 @@ class Ldap
{ {
if (isset($mapping[$matches[2]])) if (isset($mapping[$matches[2]]))
{ {
$values[] = [ $value = [
'attr' => $mapping[$matches[2]], 'attr' => $mapping[$matches[2]],
'oid' => '2.5.13.3', // caseIgnoreMatch 'oid' => '2.5.13.3', // caseIgnoreMatch
'reverse' => strtoupper($matches[3]) === ' DESC', 'reverse' => strtoupper($matches[3]) === ' DESC',
]; ];
// Windows AD does NOT support caseIgnoreMatch sorting, only it's default sorting
if ($this->ldapServerInfo->activeDirectory(true)) unset($value['oid']);
$values[] = $value;
break; break;
} }
} }
} }
$order_by = substr($order_by, strlen($matches[0])); $order_by = substr($order_by, strlen($matches[0]));
if ($values) break; // sorting by multiple criteria gives wired results if ($values) break; // sorting by multiple criteria gives no result for Windows AD and wired result for Samba4
} }
//error_log(__METHOD__."('$order_by') returning ".json_encode($values)); //error_log(__METHOD__."('$order_by') returning ".json_encode($values));
return $values; return $values;

View File

@ -26,9 +26,13 @@ class ServerInfo
*/ */
const OPENLDAP = 1; const OPENLDAP = 1;
/** /**
* Samba4 LDAP server * Samba4 Active Directory server
*/ */
const SAMBA4 = 2; const SAMBA4 = 2;
/**
* Windows Active Directory server
*/
const WINDOWS_AD = 4;
/** /**
* @var array $namingContext holds the supported namingcontexts * @var array $namingContext holds the supported namingcontexts
@ -112,6 +116,16 @@ class ServerInfo
$this->serverType = $_serverType; $this->serverType = $_serverType;
} }
/**
* @param ?bool $windows_ad true: check for windows AD, false: check for Samba4, null: check of any AD
* @return bool
*/
function activeDirectory($windows_ad=null)
{
return !isset($windows_ad) ? in_array($this->serverType, [self::WINDOWS_AD, self::SAMBA4], true) :
$this->serverType === ($windows_ad ? self::WINDOWS_AD : self::SAMBA4);
}
/** /**
* sets the DN for the subschema entry * sets the DN for the subschema entry
* *
@ -190,7 +204,7 @@ class ServerInfo
public static function get($ds, $host, $version=3) public static function get($ds, $host, $version=3)
{ {
$filter='(objectclass=*)'; $filter='(objectclass=*)';
$justthese = array('structuralObjectClass','namingContexts','supportedLDAPVersion','subschemaSubentry','vendorname','supportedControl'); $justthese = array('structuralObjectClass','namingContexts','supportedLDAPVersion','subschemaSubentry','vendorname','supportedControl','forestFunctionality');
if(($sr = @ldap_read($ds, '', $filter, $justthese))) if(($sr = @ldap_read($ds, '', $filter, $justthese)))
{ {
if(($info = ldap_get_entries($ds, $sr))) if(($info = ldap_get_entries($ds, $sr)))
@ -222,9 +236,11 @@ class ServerInfo
} }
$ldapServerInfo->setServerType($ldapServerType); $ldapServerInfo->setServerType($ldapServerType);
} }
if ($info[0]['vendorname'] && stripos($info[0]['vendorname'][0], 'samba') !== false) // Check for ActiveDirectory by forestFunctionality and set Samba4 if vendorName includes samba
if(!empty($info[0]['forestfunctionality'][0]))
{ {
$ldapServerInfo->setServerType(self::SAMBA4); $ldapServerInfo->setServerType(!empty($info[0]['vendorname']) && stripos($info[0]['vendorname'][0], 'samba') !== false ?
self::SAMBA4 : self::WINDOWS_AD);
} }
// check for subschema entry dn // check for subschema entry dn