From da490bdbbeadfccd06a27d3c8f0cc8a9bc778294 Mon Sep 17 00:00:00 2001 From: Arthur Schiwon Date: Tue, 1 Jul 2014 22:02:41 +0200 Subject: [PATCH] support for AD primary groups MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit support for primary groups actually the problem is only known on AD, it is only needed to take care of their attributes adjust to ADs special behaviour this change was not intended cache the SID value so it is not requested over and over again theres only one, use singular we are access add tests for new Access methods add tests for new Group methods address scrutinizer findings, mostly doc call ldap_explode_dn from ldap wrapper, enables tests without php5-ldap PHP Doc yo dawg, i heard you like backslashes … php doc fix PHPDoc updated and typos fixed while reviewing --- apps/user_ldap/group_ldap.php | 235 +++++++++++++++++++++++----- apps/user_ldap/lib/access.php | 94 ++++++++++- apps/user_ldap/lib/connection.php | 7 + apps/user_ldap/lib/ildapwrapper.php | 9 ++ apps/user_ldap/lib/ldap.php | 11 ++ apps/user_ldap/tests/access.php | 52 +++++- apps/user_ldap/tests/data/sid.dat | Bin 0 -> 24 bytes apps/user_ldap/tests/group_ldap.php | 152 +++++++++++++++++- 8 files changed, 516 insertions(+), 44 deletions(-) create mode 100644 apps/user_ldap/tests/data/sid.dat diff --git a/apps/user_ldap/group_ldap.php b/apps/user_ldap/group_ldap.php index 1a35691be85..0d3a70575ba 100644 --- a/apps/user_ldap/group_ldap.php +++ b/apps/user_ldap/group_ldap.php @@ -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); diff --git a/apps/user_ldap/lib/access.php b/apps/user_ldap/lib/access.php index ca5d1386995..e3b6566bcf0 100644 --- a/apps/user_ldap/lib/access.php +++ b/apps/user_ldap/lib/access.php @@ -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 diff --git a/apps/user_ldap/lib/connection.php b/apps/user_ldap/lib/connection.php index bafb0e0b895..336ea7b3bbc 100644 --- a/apps/user_ldap/lib/connection.php +++ b/apps/user_ldap/lib/connection.php @@ -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; diff --git a/apps/user_ldap/lib/ildapwrapper.php b/apps/user_ldap/lib/ildapwrapper.php index 97ae0810116..590f6d7ac7a 100644 --- a/apps/user_ldap/lib/ildapwrapper.php +++ b/apps/user_ldap/lib/ildapwrapper.php @@ -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 diff --git a/apps/user_ldap/lib/ldap.php b/apps/user_ldap/lib/ldap.php index 2b20b2ab738..967754db7d3 100644 --- a/apps/user_ldap/lib/ldap.php +++ b/apps/user_ldap/lib/ldap.php @@ -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 diff --git a/apps/user_ldap/tests/access.php b/apps/user_ldap/tests/access.php index 8ead5d68482..2ff7540b8ef 100644 --- a/apps/user_ldap/tests/access.php +++ b/apps/user_ldap/tests/access.php @@ -77,4 +77,54 @@ class Test_Access extends \PHPUnit_Framework_TestCase { $expected = 'foo\\\\*bar'; $this->assertTrue($expected === $access->escapeFilterPart($input)); } -} \ No newline at end of file + + 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)); + } +} diff --git a/apps/user_ldap/tests/data/sid.dat b/apps/user_ldap/tests/data/sid.dat new file mode 100644 index 0000000000000000000000000000000000000000..3d500c6a872f3c8904df6fe85af623dbce42b0b2 GIT binary patch literal 24 bcmZQ%VE_SEQ6RCb@hP8gPLB2|Bi48TCDsJ$ literal 0 HcmV?d00001 diff --git a/apps/user_ldap/tests/group_ldap.php b/apps/user_ldap/tests/group_ldap.php index 1184fe1e82e..c4aed25a1cc 100644 --- a/apps/user_ldap/tests/group_ldap.php +++ b/apps/user_ldap/tests/group_ldap.php @@ -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); + } + } -- 2.39.5