diff --git a/api/src/Accounts.php b/api/src/Accounts.php index 3c701ced5e..1bc98ea34a 100644 --- a/api/src/Accounts.php +++ b/api/src/Accounts.php @@ -246,6 +246,7 @@ class Accounts $members = array(); 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']))); if ($param['type'] == 'groupmembers+memberships') $members[] = $grp; } @@ -313,6 +314,7 @@ class Accounts * @param array $options * $options['filter']['group'] only return members of that group * $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 */ public static function link_query($pattern, array &$options = array()) @@ -367,8 +369,24 @@ class Accounts 'order' => $order, )) 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']); + + 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; } diff --git a/api/src/Accounts/Ads.php b/api/src/Accounts/Ads.php index 48397c6920..d07ddf5763 100644 --- a/api/src/Accounts/Ads.php +++ b/api/src/Accounts/Ads.php @@ -109,7 +109,14 @@ class Ads */ 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 * @@ -1081,6 +1088,20 @@ class Ads { $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 .= ')'; if ($account_type === 'u') break; $user_filter = $type_filter; @@ -1103,9 +1124,11 @@ class Ads /** * 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" * @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) { @@ -1122,14 +1145,17 @@ class Ads } elseif (($attr = array_search('account_'.$matches[2], $this->attributes2egw))) { - $values[] = [ - 'attr' => $attr, - 'oid' => '2.5.13.3', // caseIgnoreMatch + $value = [ + 'attr' => $mapping[$matches[2]], + 'oid' => '2.5.13.3', // caseIgnoreMatch '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])); - 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; } diff --git a/api/src/Contacts/Ads.php b/api/src/Contacts/Ads.php index 10619ec5fc..69d402e67f 100644 --- a/api/src/Contacts/Ads.php +++ b/api/src/Contacts/Ads.php @@ -233,7 +233,7 @@ class Ads extends Ldap $filter = ''; if ($gid < 0 && ($dn = $GLOBALS['egw']->accounts->id2name($gid, 'account_dn'))) { - $filter .= '(memberOf='.$dn.')'; + $filter .= '(|(memberOf='.$dn.')(primaryGroupID='.abs($gid).'))'; } return $filter; } diff --git a/api/src/Contacts/Ldap.php b/api/src/Contacts/Ldap.php index c72d322277..fb357cc1bb 100644 --- a/api/src/Contacts/Ldap.php +++ b/api/src/Contacts/Ldap.php @@ -1096,9 +1096,11 @@ class Ldap /** * 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" * @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) { @@ -1119,17 +1121,20 @@ class Ldap { if (isset($mapping[$matches[2]])) { - $values[] = [ + $value = [ 'attr' => $mapping[$matches[2]], 'oid' => '2.5.13.3', // caseIgnoreMatch '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; } } } $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)); return $values; diff --git a/api/src/Ldap/ServerInfo.php b/api/src/Ldap/ServerInfo.php index 0d2e13c4d8..9a39319e0d 100644 --- a/api/src/Ldap/ServerInfo.php +++ b/api/src/Ldap/ServerInfo.php @@ -26,9 +26,13 @@ class ServerInfo */ const OPENLDAP = 1; /** - * Samba4 LDAP server + * Samba4 Active Directory server */ const SAMBA4 = 2; + /** + * Windows Active Directory server + */ + const WINDOWS_AD = 4; /** * @var array $namingContext holds the supported namingcontexts @@ -112,6 +116,16 @@ class ServerInfo $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 * @@ -190,7 +204,7 @@ class ServerInfo public static function get($ds, $host, $version=3) { $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(($info = ldap_get_entries($ds, $sr))) @@ -222,9 +236,11 @@ class ServerInfo } $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