Merge pull request #9385 from owncloud/fix-7052
support for AD primary groups
This commit is contained in:
commit
51ee4fc5df
|
@ -50,20 +50,29 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
if(!$this->enabled) {
|
||||
return false;
|
||||
}
|
||||
if($this->access->connection->isCached('inGroup'.$uid.':'.$gid)) {
|
||||
return $this->access->connection->getFromCache('inGroup'.$uid.':'.$gid);
|
||||
$cacheKey = 'inGroup'.$uid.':'.$gid;
|
||||
if($this->access->connection->isCached($cacheKey)) {
|
||||
return $this->access->connection->getFromCache($cacheKey);
|
||||
}
|
||||
$dn_user = $this->access->username2dn($uid);
|
||||
$dn_group = $this->access->groupname2dn($gid);
|
||||
|
||||
$userDN = $this->access->username2dn($uid);
|
||||
$groupDN = $this->access->groupname2dn($gid);
|
||||
// just in case
|
||||
if(!$dn_group || !$dn_user) {
|
||||
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
|
||||
if(!$groupDN || !$userDN) {
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
//check primary group first
|
||||
if($gid === $this->getUserPrimaryGroup($userDN)) {
|
||||
$this->access->connection->writeToCache($cacheKey, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
//usually, LDAP attributes are said to be case insensitive. But there are exceptions of course.
|
||||
$members = array_keys($this->_groupMembers($dn_group));
|
||||
$members = array_keys($this->_groupMembers($groupDN));
|
||||
if(!$members) {
|
||||
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, false);
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -82,8 +91,8 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
$members = $dns;
|
||||
}
|
||||
|
||||
$isInGroup = in_array($dn_user, $members);
|
||||
$this->access->connection->writeToCache('inGroup'.$uid.':'.$gid, $isInGroup);
|
||||
$isInGroup = in_array($userDN, $members);
|
||||
$this->access->connection->writeToCache($cacheKey, $isInGroup);
|
||||
|
||||
return $isInGroup;
|
||||
}
|
||||
|
@ -91,6 +100,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
/**
|
||||
* @param string $dnGroup
|
||||
* @param array|null &$seen
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
private function _groupMembers($dnGroup, &$seen = null) {
|
||||
if ($seen === null) {
|
||||
|
@ -125,6 +135,125 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
return $allMembers;
|
||||
}
|
||||
|
||||
/**
|
||||
* translates a primary group ID into an ownCloud internal name
|
||||
* @param string $gid as given by primaryGroupID on AD
|
||||
* @param string $dn a DN that belongs to the same domain as the group
|
||||
* @return string|bool
|
||||
*/
|
||||
public function primaryGroupID2Name($gid, $dn) {
|
||||
$cacheKey = 'primaryGroupIDtoName';
|
||||
if($this->access->connection->isCached($cacheKey)) {
|
||||
$groupNames = $this->access->connection->getFromCache($cacheKey);
|
||||
if(isset($groupNames[$gid])) {
|
||||
return $groupNames[$gid];
|
||||
}
|
||||
}
|
||||
|
||||
$domainObjectSid = $this->access->getSID($dn);
|
||||
if($domainObjectSid === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//we need to get the DN from LDAP
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
$this->access->connection->ldapGroupFilter,
|
||||
'objectsid=' . $domainObjectSid . '-' . $gid
|
||||
));
|
||||
$result = $this->access->searchGroups($filter, array('dn'), 1);
|
||||
if(empty($result)) {
|
||||
return false;
|
||||
}
|
||||
$dn = $result[0];
|
||||
|
||||
//and now the group name
|
||||
//NOTE once we have separate ownCloud group IDs and group names we can
|
||||
//directly read the display name attribute instead of the DN
|
||||
$name = $this->access->dn2groupname($dn);
|
||||
|
||||
$this->access->connection->writeToCache($cacheKey, $name);
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the entry's primary group ID
|
||||
* @param string $dn
|
||||
* @param string $attribute
|
||||
* @return string|bool
|
||||
*/
|
||||
private function getEntryGroupID($dn, $attribute) {
|
||||
$value = $this->access->readAttribute($dn, $attribute);
|
||||
if(is_array($value) && !empty($value)) {
|
||||
return $value[0];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the group's primary ID
|
||||
* @param string $dn
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getGroupPrimaryGroupID($dn) {
|
||||
return $this->getEntryGroupID($dn, 'primaryGroupToken');
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the user's primary group ID
|
||||
* @param string $dn
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getUserPrimaryGroupIDs($dn) {
|
||||
return $this->getEntryGroupID($dn, 'primaryGroupID');
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a list of users that have the given group as primary group
|
||||
*
|
||||
* @param string $groupDN
|
||||
* @param $limit
|
||||
* @param int $offset
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUsersInPrimaryGroup($groupDN, $limit = -1, $offset = 0) {
|
||||
$groupID = $this->getGroupPrimaryGroupID($groupDN);
|
||||
if($groupID === false) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
$this->access->connection->ldapUserFilter,
|
||||
'primaryGroupID=' . $groupID
|
||||
));
|
||||
|
||||
$users = $this->access->fetchListOfUsers(
|
||||
$filter,
|
||||
array($this->access->connection->ldapUserDisplayName, 'dn'),
|
||||
$limit,
|
||||
$offset
|
||||
);
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets the primary group of a user
|
||||
* @param string $dn
|
||||
* @return string
|
||||
*/
|
||||
public function getUserPrimaryGroup($dn) {
|
||||
$groupID = $this->getUserPrimaryGroupIDs($dn);
|
||||
if($groupID !== false) {
|
||||
$groupName = $this->primaryGroupID2Name($groupID, $dn);
|
||||
if($groupName !== false) {
|
||||
return $groupName;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all groups a user belongs to
|
||||
* @param string $uid Name of the user
|
||||
|
@ -161,7 +290,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
}
|
||||
|
||||
$groups = array_values($this->getGroupsByMember($uid));
|
||||
$groups = array_unique($this->access->ownCloudGroupNames($groups), SORT_LOCALE_STRING);
|
||||
$groups = $this->access->ownCloudGroupNames($groups);
|
||||
|
||||
$primaryGroup = $this->getUserPrimaryGroup($userDN);
|
||||
if($primaryGroup !== false) {
|
||||
$groups[] = $primaryGroup;
|
||||
}
|
||||
|
||||
$groups = array_unique($groups, SORT_LOCALE_STRING);
|
||||
$this->access->connection->writeToCache($cacheKey, $groups);
|
||||
|
||||
return $groups;
|
||||
|
@ -170,6 +306,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
/**
|
||||
* @param string $dn
|
||||
* @param array|null &$seen
|
||||
* @return array
|
||||
*/
|
||||
private function getGroupsByMember($dn, &$seen = null) {
|
||||
if ($seen === null) {
|
||||
|
@ -205,6 +342,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
|
||||
/**
|
||||
* get a list of all users in a group
|
||||
*
|
||||
* @param string $gid
|
||||
* @param string $search
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return array with user ids
|
||||
*/
|
||||
public function usersInGroup($gid, $search = '', $limit = -1, $offset = 0) {
|
||||
|
@ -214,9 +356,9 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
if(!$this->groupExists($gid)) {
|
||||
return array();
|
||||
}
|
||||
$cachekey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
|
||||
$cacheKey = 'usersInGroup-'.$gid.'-'.$search.'-'.$limit.'-'.$offset;
|
||||
// check for cache of the exact query
|
||||
$groupUsers = $this->access->connection->getFromCache($cachekey);
|
||||
$groupUsers = $this->access->connection->getFromCache($cacheKey);
|
||||
if(!is_null($groupUsers)) {
|
||||
return $groupUsers;
|
||||
}
|
||||
|
@ -225,7 +367,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
$groupUsers = $this->access->connection->getFromCache('usersInGroup-'.$gid.'-'.$search);
|
||||
if(!is_null($groupUsers)) {
|
||||
$groupUsers = array_slice($groupUsers, $offset, $limit);
|
||||
$this->access->connection->writeToCache($cachekey, $groupUsers);
|
||||
$this->access->connection->writeToCache($cacheKey, $groupUsers);
|
||||
return $groupUsers;
|
||||
}
|
||||
|
||||
|
@ -235,14 +377,14 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
$groupDN = $this->access->groupname2dn($gid);
|
||||
if(!$groupDN) {
|
||||
// group couldn't be found, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, array());
|
||||
$this->access->connection->writeToCache($cacheKey, array());
|
||||
return array();
|
||||
}
|
||||
|
||||
$members = array_keys($this->_groupMembers($groupDN));
|
||||
if(!$members) {
|
||||
//in case users could not be retrieved, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, array());
|
||||
//in case users could not be retrieved, return empty result set
|
||||
$this->access->connection->writeToCache($cacheKey, array());
|
||||
return array();
|
||||
}
|
||||
|
||||
|
@ -250,7 +392,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
$isMemberUid = (strtolower($this->access->connection->ldapGroupMemberAssocAttr) === 'memberuid');
|
||||
foreach($members as $member) {
|
||||
if($isMemberUid) {
|
||||
//we got uids, need to get their DNs to 'tranlsate' them to usernames
|
||||
//we got uids, need to get their DNs to 'translate' them to user names
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
\OCP\Util::mb_str_replace('%uid', $member,
|
||||
$this->access->connection->ldapLoginFilter, 'UTF-8'),
|
||||
|
@ -276,10 +418,16 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
natsort($groupUsers);
|
||||
$this->access->connection->writeToCache('usersInGroup-'.$gid.'-'.$search, $groupUsers);
|
||||
$groupUsers = array_slice($groupUsers, $offset, $limit);
|
||||
$this->access->connection->writeToCache($cachekey, $groupUsers);
|
||||
|
||||
//and get users that have the group as primary
|
||||
$primaryUsers = $this->getUsersInPrimaryGroup($groupDN, $limit, $offset);
|
||||
$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
|
||||
|
||||
$this->access->connection->writeToCache($cacheKey, $groupUsers);
|
||||
|
||||
return $groupUsers;
|
||||
}
|
||||
|
@ -291,32 +439,32 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
* @return int|bool
|
||||
*/
|
||||
public function countUsersInGroup($gid, $search = '') {
|
||||
$cachekey = 'countUsersInGroup-'.$gid.'-'.$search;
|
||||
$cacheKey = 'countUsersInGroup-'.$gid.'-'.$search;
|
||||
if(!$this->enabled || !$this->groupExists($gid)) {
|
||||
return false;
|
||||
}
|
||||
$groupUsers = $this->access->connection->getFromCache($cachekey);
|
||||
$groupUsers = $this->access->connection->getFromCache($cacheKey);
|
||||
if(!is_null($groupUsers)) {
|
||||
return $groupUsers;
|
||||
}
|
||||
|
||||
$groupDN = $this->access->groupname2dn($gid);
|
||||
if(!$groupDN) {
|
||||
// group couldn't be found, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, false);
|
||||
// group couldn't be found, return empty result set
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
$members = array_keys($this->_groupMembers($groupDN));
|
||||
if(!$members) {
|
||||
//in case users could not be retrieved, return empty resultset
|
||||
$this->access->connection->writeToCache($cachekey, false);
|
||||
//in case users could not be retrieved, return empty result set
|
||||
$this->access->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(empty($search)) {
|
||||
$groupUsers = count($members);
|
||||
$this->access->connection->writeToCache($cachekey, $groupUsers);
|
||||
$this->access->connection->writeToCache($cacheKey, $groupUsers);
|
||||
return $groupUsers;
|
||||
}
|
||||
$isMemberUid =
|
||||
|
@ -334,7 +482,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
$groupUsers = array();
|
||||
foreach($members as $member) {
|
||||
if($isMemberUid) {
|
||||
//we got uids, need to get their DNs to 'tranlsate' them to usernames
|
||||
//we got uids, need to get their DNs to 'translate' them to user names
|
||||
$filter = $this->access->combineFilterWithAnd(array(
|
||||
\OCP\Util::mb_str_replace('%uid', $member,
|
||||
$this->access->connection->ldapLoginFilter, 'UTF-8'),
|
||||
|
@ -359,11 +507,19 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
}
|
||||
}
|
||||
|
||||
//and get users that have the group as primary
|
||||
$primaryUsers = $this->getUsersInPrimaryGroup($groupDN);
|
||||
$groupUsers = array_unique(array_merge($groupUsers, $primaryUsers));
|
||||
|
||||
return count($groupUsers);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a list of all groups
|
||||
*
|
||||
* @param string $search
|
||||
* @param $limit
|
||||
* @param int $offset
|
||||
* @return array with group names
|
||||
*
|
||||
* Returns a list with all groups (used by getGroups)
|
||||
|
@ -372,11 +528,11 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
if(!$this->enabled) {
|
||||
return array();
|
||||
}
|
||||
$cachekey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
|
||||
$cacheKey = 'getGroups-'.$search.'-'.$limit.'-'.$offset;
|
||||
|
||||
//Check cache before driving unnecessary searches
|
||||
\OCP\Util::writeLog('user_ldap', 'getGroups '.$cachekey, \OCP\Util::DEBUG);
|
||||
$ldap_groups = $this->access->connection->getFromCache($cachekey);
|
||||
\OCP\Util::writeLog('user_ldap', 'getGroups '.$cacheKey, \OCP\Util::DEBUG);
|
||||
$ldap_groups = $this->access->connection->getFromCache($cacheKey);
|
||||
if(!is_null($ldap_groups)) {
|
||||
return $ldap_groups;
|
||||
}
|
||||
|
@ -397,26 +553,30 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
$offset);
|
||||
$ldap_groups = $this->access->ownCloudGroupNames($ldap_groups);
|
||||
|
||||
$this->access->connection->writeToCache($cachekey, $ldap_groups);
|
||||
$this->access->connection->writeToCache($cacheKey, $ldap_groups);
|
||||
return $ldap_groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a list of all groups using a paged search
|
||||
*
|
||||
* @param string $search
|
||||
* @param int $limit
|
||||
* @param int $offset
|
||||
* @return array with group names
|
||||
*
|
||||
* Returns a list with all groups
|
||||
* Uses a paged search if available to override a
|
||||
* server side search limit.
|
||||
* (active directory has a limit of 1000 by default)
|
||||
* Uses a paged search if available to override a
|
||||
* server side search limit.
|
||||
* (active directory has a limit of 1000 by default)
|
||||
*/
|
||||
public function getGroups($search = '', $limit = -1, $offset = 0) {
|
||||
if(!$this->enabled) {
|
||||
return array();
|
||||
}
|
||||
$pagingsize = $this->access->connection->ldapPagingSize;
|
||||
$pagingSize = $this->access->connection->ldapPagingSize;
|
||||
if ((! $this->access->connection->hasPagedResultSupport)
|
||||
|| empty($pagingsize)) {
|
||||
|| empty($pagingSize)) {
|
||||
return $this->getGroupsChunk($search, $limit, $offset);
|
||||
}
|
||||
$maxGroups = 100000; // limit max results (just for safety reasons)
|
||||
|
@ -428,7 +588,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
$chunkOffset = $offset;
|
||||
$allGroups = array();
|
||||
while ($chunkOffset < $overallLimit) {
|
||||
$chunkLimit = min($pagingsize, $overallLimit - $chunkOffset);
|
||||
$chunkLimit = min($pagingSize, $overallLimit - $chunkOffset);
|
||||
$ldapGroups = $this->getGroupsChunk($search, $chunkLimit, $chunkOffset);
|
||||
$nread = count($ldapGroups);
|
||||
\OCP\Util::writeLog('user_ldap', 'getGroups('.$search.'): read '.$nread.' at offset '.$chunkOffset.' (limit: '.$chunkLimit.')', \OCP\Util::DEBUG);
|
||||
|
@ -445,6 +605,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
|||
|
||||
/**
|
||||
* @param string $group
|
||||
* @return bool
|
||||
*/
|
||||
public function groupMatchesFilter($group) {
|
||||
return (strripos($group, $this->groupSearch) !== false);
|
||||
|
|
|
@ -28,6 +28,9 @@ namespace OCA\user_ldap\lib;
|
|||
* @package OCA\user_ldap\lib
|
||||
*/
|
||||
class Access extends LDAPUtility implements user\IUserTools {
|
||||
/**
|
||||
* @var \OCA\user_ldap\lib\Connection
|
||||
*/
|
||||
public $connection;
|
||||
public $userManager;
|
||||
//never ever check this var directly, always use getPagedSearchResultState
|
||||
|
@ -61,8 +64,8 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
|
||||
/**
|
||||
* reads a given attribute for an LDAP record identified by a DN
|
||||
* @param $dn the record in question
|
||||
* @param $attr the attribute that shall be retrieved
|
||||
* @param string $dn the record in question
|
||||
* @param string $attr the attribute that shall be retrieved
|
||||
* if empty, just check the record's existence
|
||||
* @param string $filter
|
||||
* @return array|false an array of values on success or an empty
|
||||
|
@ -180,6 +183,33 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
return $dn;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a DN-string that is cleaned from not domain parts, e.g.
|
||||
* cn=foo,cn=bar,dc=foobar,dc=server,dc=org
|
||||
* becomes dc=foobar,dc=server,dc=org
|
||||
* @param string $dn
|
||||
* @return string
|
||||
*/
|
||||
public function getDomainDNFromDN($dn) {
|
||||
$allParts = $this->ldap->explodeDN($dn, 0);
|
||||
if($allParts === false) {
|
||||
//not a valid DN
|
||||
return '';
|
||||
}
|
||||
$domainParts = array();
|
||||
$dcFound = false;
|
||||
foreach($allParts as $part) {
|
||||
if(!$dcFound && strpos($part, 'dc=') === 0) {
|
||||
$dcFound = true;
|
||||
}
|
||||
if($dcFound) {
|
||||
$domainParts[] = $part;
|
||||
}
|
||||
}
|
||||
$domainDN = implode(',', $domainParts);
|
||||
return $domainDN;
|
||||
}
|
||||
|
||||
/**
|
||||
* gives back the database table for the query
|
||||
* @param bool $isUser
|
||||
|
@ -534,7 +564,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
if(!\OC_Group::groupExists($altName)) {
|
||||
return $altName;
|
||||
}
|
||||
$altName = $name . '_' . $lastNo + $attempts;
|
||||
$altName = $name . '_' . ($lastNo + $attempts);
|
||||
$attempts++;
|
||||
}
|
||||
return false;
|
||||
|
@ -581,6 +611,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
|
||||
/**
|
||||
* @param boolean $isUsers
|
||||
* @return array
|
||||
*/
|
||||
private function mappedComponents($isUsers) {
|
||||
$table = $this->getMapTable($isUsers);
|
||||
|
@ -834,7 +865,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
private function count($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
|
||||
\OCP\Util::writeLog('user_ldap', 'Count filter: '.print_r($filter, true), \OCP\Util::DEBUG);
|
||||
|
||||
if(is_null($limit)) {
|
||||
if(is_null($limit) || $limit <= 0) {
|
||||
$limit = intval($this->connection->ldapPagingSize);
|
||||
}
|
||||
|
||||
|
@ -894,6 +925,10 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
* @return array with the search result
|
||||
*/
|
||||
private function search($filter, $base, $attr = null, $limit = null, $offset = null, $skipHandling = false) {
|
||||
if($limit <= 0) {
|
||||
//otherwise search will fail
|
||||
$limit = null;
|
||||
}
|
||||
$search = $this->executeSearch($filter, $base, $attr, $limit, $offset);
|
||||
if($search === false) {
|
||||
return array();
|
||||
|
@ -908,7 +943,7 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
$this->processPagedSearchStatus($sr, $filter, $base, 1, $limit,
|
||||
$offset, $pagedSearchOK,
|
||||
$skipHandling);
|
||||
return;
|
||||
return array();
|
||||
}
|
||||
|
||||
// Do the server-side sorting
|
||||
|
@ -1232,6 +1267,55 @@ class Access extends LDAPUtility implements user\IUserTools {
|
|||
return strtoupper($hex_guid_to_guid_str);
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a SID of the domain of the given dn
|
||||
* @param string $dn
|
||||
* @return string|bool
|
||||
*/
|
||||
public function getSID($dn) {
|
||||
$domainDN = $this->getDomainDNFromDN($dn);
|
||||
$cacheKey = 'getSID-'.$domainDN;
|
||||
if($this->connection->isCached($cacheKey)) {
|
||||
return $this->connection->getFromCache($cacheKey);
|
||||
}
|
||||
|
||||
$objectSid = $this->readAttribute($domainDN, 'objectsid');
|
||||
if(!is_array($objectSid) || empty($objectSid)) {
|
||||
$this->connection->writeToCache($cacheKey, false);
|
||||
return false;
|
||||
}
|
||||
$domainObjectSid = $this->convertSID2Str($objectSid[0]);
|
||||
$this->connection->writeToCache($cacheKey, $domainObjectSid);
|
||||
|
||||
return $domainObjectSid;
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a binary SID into a string representation
|
||||
* @param string $sid
|
||||
* @return string
|
||||
* @link http://blogs.freebsdish.org/tmclaugh/2010/07/21/finding-a-users-primary-group-in-ad/#comment-2855
|
||||
*/
|
||||
public function convertSID2Str($sid) {
|
||||
try {
|
||||
$srl = ord($sid[0]);
|
||||
$numberSubID = ord($sid[1]);
|
||||
$x = substr($sid, 2, 6);
|
||||
$h = unpack('N', "\x0\x0" . substr($x,0,2));
|
||||
$l = unpack('N', substr($x,2,6));
|
||||
$iav = bcadd(bcmul($h[1], bcpow(2,32)), $l[1]);
|
||||
$subIDs = array();
|
||||
for ($i=0; $i<$numberSubID; $i++) {
|
||||
$subID = unpack('V', substr($sid, 8+4*$i, 4));
|
||||
$subIDs[] = $subID[1];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return sprintf('S-%d-%d-%s', $srl, $iav, implode('-', $subIDs));
|
||||
}
|
||||
|
||||
/**
|
||||
* converts a stored DN so it can be used as base parameter for LDAP queries, internally we store them for usage in LDAP filters
|
||||
* @param string $dn the DN
|
||||
|
|
|
@ -23,6 +23,13 @@
|
|||
|
||||
namespace OCA\user_ldap\lib;
|
||||
|
||||
//magic properties (incomplete)
|
||||
/**
|
||||
* responsible for LDAP connections in context with the provided configuration
|
||||
* @property string ldapUserFilter
|
||||
* @property string ldapUserDisplayName
|
||||
* @property boolean hasPagedResultSupport
|
||||
*/
|
||||
class Connection extends LDAPUtility {
|
||||
private $ldapConnectionRes = null;
|
||||
private $configPrefix;
|
||||
|
|
|
@ -89,6 +89,15 @@ interface ILDAPWrapper {
|
|||
*/
|
||||
public function error($link);
|
||||
|
||||
/**
|
||||
* Splits DN into its component parts
|
||||
* @param string $dn
|
||||
* @param int @withAttrib
|
||||
* @return array|false
|
||||
* @link http://www.php.net/manual/en/function.ldap-explode-dn.php
|
||||
*/
|
||||
public function explodeDN($dn, $withAttrib);
|
||||
|
||||
/**
|
||||
* Return first result id
|
||||
* @param resource $link LDAP link resource
|
||||
|
|
|
@ -98,6 +98,17 @@ class LDAP implements ILDAPWrapper {
|
|||
return $this->invokeLDAPMethod('error', $link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits DN into its component parts
|
||||
* @param string $dn
|
||||
* @param int @withAttrib
|
||||
* @return array|false
|
||||
* @link http://www.php.net/manual/en/function.ldap-explode-dn.php
|
||||
*/
|
||||
public function explodeDN($dn, $withAttrib) {
|
||||
return $this->invokeLDAPMethod('ldap_explode_dn', $dn, $withAttrib);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LDAP $link
|
||||
* @param LDAP $result
|
||||
|
|
|
@ -77,4 +77,54 @@ class Test_Access extends \PHPUnit_Framework_TestCase {
|
|||
$expected = 'foo\\\\*bar';
|
||||
$this->assertTrue($expected === $access->escapeFilterPart($input));
|
||||
}
|
||||
}
|
||||
|
||||
public function testConvertSID2StrSuccess() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
$sidBinary = file_get_contents(__DIR__ . '/data/sid.dat');
|
||||
$sidExpected = 'S-1-5-21-249921958-728525901-1594176202';
|
||||
|
||||
$this->assertSame($sidExpected, $access->convertSID2Str($sidBinary));
|
||||
}
|
||||
|
||||
public function testConvertSID2StrInputError() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
$sidIllegal = 'foobar';
|
||||
$sidExpected = '';
|
||||
|
||||
$this->assertSame($sidExpected, $access->convertSID2Str($sidIllegal));
|
||||
}
|
||||
|
||||
public function testGetDomainDNFromDNSuccess() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
$inputDN = 'uid=zaphod,cn=foobar,dc=my,dc=server,dc=com';
|
||||
$domainDN = 'dc=my,dc=server,dc=com';
|
||||
|
||||
$lw->expects($this->once())
|
||||
->method('explodeDN')
|
||||
->with($inputDN, 0)
|
||||
->will($this->returnValue(explode(',', $inputDN)));
|
||||
|
||||
$this->assertSame($domainDN, $access->getDomainDNFromDN($inputDN));
|
||||
}
|
||||
|
||||
public function testGetDomainDNFromDNError() {
|
||||
list($lw, $con, $um) = $this->getConnecterAndLdapMock();
|
||||
$access = new Access($con, $lw, $um);
|
||||
|
||||
$inputDN = 'foobar';
|
||||
$expected = '';
|
||||
|
||||
$lw->expects($this->once())
|
||||
->method('explodeDN')
|
||||
->with($inputDN, 0)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$this->assertSame($expected, $access->getDomainDNFromDN($inputDN));
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
|
@ -95,6 +95,10 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
|
|||
->method('groupname2dn')
|
||||
->will($this->returnValue('cn=group,dc=foo,dc=bar'));
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('fetchListOfUsers')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$access->expects($this->any())
|
||||
->method('readAttribute')
|
||||
->will($this->returnCallback(function($name) {
|
||||
|
@ -111,7 +115,9 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
|
|||
|
||||
$access->expects($this->any())
|
||||
->method('dn2username')
|
||||
->will($this->returnValue('foobar'));
|
||||
->will($this->returnCallback(function() {
|
||||
return 'foobar' . \OCP\Util::generateRandomBytes(7);
|
||||
}));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
$users = $groupBackend->countUsersInGroup('group', '3');
|
||||
|
@ -119,4 +125,148 @@ class Test_Group_Ldap extends \PHPUnit_Framework_TestCase {
|
|||
$this->assertSame(2, $users);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameSuccess() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('searchGroups')
|
||||
->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('dn2groupname')
|
||||
->with('cn=foo,dc=barfoo,dc=bar')
|
||||
->will($this->returnValue('MyGroup'));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame('MyGroup', $group);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameNoSID() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('searchGroups');
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('dn2groupname');
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame(false, $group);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameNoGroup() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('searchGroups')
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$access->expects($this->never())
|
||||
->method('dn2groupname');
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame(false, $group);
|
||||
}
|
||||
|
||||
public function testPrimaryGroupID2NameNoName() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('getSID')
|
||||
->with($userDN)
|
||||
->will($this->returnValue('S-1-5-21-249921958-728525901-1594176202'));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('searchGroups')
|
||||
->will($this->returnValue(array('cn=foo,dc=barfoo,dc=bar')));
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('dn2groupname')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$group = $groupBackend->primaryGroupID2Name('3117', $userDN);
|
||||
|
||||
$this->assertSame(false, $group);
|
||||
}
|
||||
|
||||
public function testGetEntryGroupIDValue() {
|
||||
//tests getEntryGroupID via getGroupPrimaryGroupID
|
||||
//which is basically identical to getUserPrimaryGroupIDs
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
|
||||
$attr = 'primaryGroupToken';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('readAttribute')
|
||||
->with($dn, $attr)
|
||||
->will($this->returnValue(array('3117')));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$gid = $groupBackend->getGroupPrimaryGroupID($dn);
|
||||
|
||||
$this->assertSame('3117', $gid);
|
||||
}
|
||||
|
||||
public function testGetEntryGroupIDNoValue() {
|
||||
//tests getEntryGroupID via getGroupPrimaryGroupID
|
||||
//which is basically identical to getUserPrimaryGroupIDs
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
|
||||
$attr = 'primaryGroupToken';
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('readAttribute')
|
||||
->with($dn, $attr)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
|
||||
$gid = $groupBackend->getGroupPrimaryGroupID($dn);
|
||||
|
||||
$this->assertSame(false, $gid);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue