From 48e426b589029b8e616be2785afc8c0b4b4aecaf Mon Sep 17 00:00:00 2001 From: root Date: Fri, 6 Dec 2013 16:46:52 +0100 Subject: [PATCH] add support for nested groups --- apps/user_ldap/group_ldap.php | 79 +++++++++++++++++++++++---- apps/user_ldap/lib/configuration.php | 3 + apps/user_ldap/templates/settings.php | 1 + 3 files changed, 73 insertions(+), 10 deletions(-) diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index 32e2cec596..0a5ab19276 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -61,8 +61,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return false; } //usually, LDAP attributes are said to be case insensitive. But there are exceptions of course. - $members = $this->access->readAttribute($dn_group, - $this->access->connection->ldapGroupMemberAssocAttr); + $members = array_keys($this->_groupMembers($dn_group)); if(!$members) { $this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false); return false; @@ -89,6 +88,39 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return $isInGroup; } + private function _groupMembers($dn_group, &$groups_seen = null) { + if ($groups_seen == null) { + $groups_seen = array(); + } + $all_members = array(); + if (array_key_exists($dn_group, $groups_seen)) { + // avoid loops + return array(); + } + // used extensively in cron job, caching makes sense for nested groups + $cache_key = '_groupMembers'.$dn_group; + if($this->access->connection->isCached($cache_key)) { + \OCP\Util::writeLog('user_ldap', 'LEO _groupMembers('.$dn_group.') using cached value', \OCP\Util::DEBUG); + return $this->access->connection->getFromCache($cache_key); + } + $groups_seen[$dn_group] = 1; + $members = $this->access->readAttribute($dn_group, $this->access->connection->ldapGroupMemberAssocAttr, + $this->access->connection->ldapGroupFilter); + if ($members) { + foreach ($members as $member_dn) { + $all_members[$member_dn] = 1; + if ($this->access->connection->ldapNestedGroups) { + $submembers = $this->_groupMembers($member_dn, $groups_seen); + if ($submembers) { + $all_members = array_merge($all_members, $submembers); + } + } + } + } + $this->access->connection->writeToCache($cache_key, $all_members); + return $all_members; + } + /** * @brief Get all groups a user belongs to * @param $uid Name of the user @@ -124,18 +156,45 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { $uid = $userDN; } - $filter = $this->access->combineFilterWithAnd(array( - $this->access->connection->ldapGroupFilter, - $this->access->connection->ldapGroupMemberAssocAttr.'='.$uid - )); - $groups = $this->access->fetchListOfGroups($filter, - array($this->access->connection->ldapGroupDisplayName, 'dn')); + $groups = array_values($this->_getGroupsByMember($uid)); $groups = array_unique($this->access->ownCloudGroupNames($groups), SORT_LOCALE_STRING); + \OCP\Util::writeLog('user_ldap', 'LEO _getGroupsByMember('.$uid.'): '.implode(", ", $groups), \OCP\Util::DEBUG); $this->access->connection->writeToCache($cacheKey, $groups); return $groups; } + /* private */ public function _getGroupsByMember($dn, &$seen = null) { + if ($seen == null) { + $seen = array(); + } + $all_groups = array(); + if (array_key_exists($dn, $seen)) { + // avoid loops + return array(); + } + $seen[$dn] = 1; + $filter = $this->combineFilterWithAnd(array( + $this->access->connection->ldapGroupFilter, + $this->access->connection->ldapGroupMemberAssocAttr.'='.$dn + )); + $groups = $this->fetchListOfGroups($filter, + array($this->access->connection->ldapGroupDisplayName, 'dn')); + if ($groups) { + foreach ($groups as $groupobj) { + $group_dn = $groupobj['dn']; + $all_groups[$group_dn] = $groupobj; + if ($this->access->connection->ldapNestedGroups) { + $supergroups = $this->_getGroupsByMember($group_dn, $seen); + if ($supergroups) { + $all_groups = array_merge($all_groups, $supergroups); + } + } + } + } + return $all_groups; + } + /** * @brief get a list of all users in a group * @returns array with user ids @@ -172,8 +231,8 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface { return array(); } - $members = $this->access->readAttribute($groupDN, - $this->access->connection->ldapGroupMemberAssocAttr); + $members = array_keys($this->_groupMembers($groupDN)); + \OCP\Util::writeLog('user_ldap', 'LEO _groupMembers('.$groupDN.'): '.implode(", ", $members), \OCP\Util::DEBUG); if(!$members) { //in case users could not be retrieved, return empty resultset $this->access->connection->writeToCache($cachekey, array()); diff --git a/apps/user_ldap/lib/configuration.php b/apps/user_ldap/lib/configuration.php index 874082f78f..bceacdfdf8 100644 --- a/apps/user_ldap/lib/configuration.php +++ b/apps/user_ldap/lib/configuration.php @@ -76,6 +76,7 @@ class Configuration { 'ldapExpertUUIDUserAttr' => null, 'ldapExpertUUIDGroupAttr' => null, 'lastJpegPhotoLookup' => null, + 'ldapNestedGroups' => false, ); public function __construct($configPrefix, $autoread = true) { @@ -338,6 +339,7 @@ class Configuration { 'ldap_expert_uuid_group_attr' => '', 'has_memberof_filter_support' => 0, 'last_jpegPhoto_lookup' => 0, + 'ldap_nested_groups' => 0, ); } @@ -389,6 +391,7 @@ class Configuration { 'ldap_expert_uuid_group_attr' => 'ldapExpertUUIDGroupAttr', 'has_memberof_filter_support' => 'hasMemberOfFilterSupport', 'last_jpegPhoto_lookup' => 'lastJpegPhotoLookup', + 'ldap_nested_groups' => 'ldapNestedGroups', ); return $array; } diff --git a/apps/user_ldap/templates/settings.php b/apps/user_ldap/templates/settings.php index 3ccc7a860f..ad527c0634 100644 --- a/apps/user_ldap/templates/settings.php +++ b/apps/user_ldap/templates/settings.php @@ -58,6 +58,7 @@

t('Username-LDAP User Mapping'));?>

t('Usernames are used to store and assign (meta) data. In order to precisely identify and recognize users, each LDAP user will have a internal username. This requires a mapping from username to LDAP user. The created username is mapped to the UUID of the LDAP user. Additionally the DN is cached as well to reduce LDAP interaction, but it is not used for identification. If the DN changes, the changes will be found. The internal username is used all over. Clearing the mappings will have leftovers everywhere. Clearing the mappings is not configuration sensitive, it affects all LDAP configurations! Never clear the mappings in a production environment, only in a testing or experimental stage.'));?>


+