Group_LDAPTest.php 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\User_LDAP\Tests;
  8. use OCA\User_LDAP\Access;
  9. use OCA\User_LDAP\Connection;
  10. use OCA\User_LDAP\Group_LDAP as GroupLDAP;
  11. use OCA\User_LDAP\GroupPluginManager;
  12. use OCA\User_LDAP\ILDAPWrapper;
  13. use OCA\User_LDAP\Mapping\GroupMapping;
  14. use OCA\User_LDAP\User\Manager;
  15. use OCA\User_LDAP\User\OfflineUser;
  16. use OCA\User_LDAP\User\User;
  17. use OCA\User_LDAP\User_Proxy;
  18. use OCP\GroupInterface;
  19. use OCP\IConfig;
  20. use OCP\IUser;
  21. use OCP\IUserManager;
  22. use PHPUnit\Framework\MockObject\MockObject;
  23. use Test\TestCase;
  24. /**
  25. * Class GroupLDAPTest
  26. *
  27. * @group DB
  28. *
  29. * @package OCA\User_LDAP\Tests
  30. */
  31. class Group_LDAPTest extends TestCase {
  32. private MockObject|Access $access;
  33. private MockObject|GroupPluginManager $pluginManager;
  34. private MockObject|IConfig $config;
  35. private MockObject|IUserManager $ncUserManager;
  36. private GroupLDAP $groupBackend;
  37. public function setUp(): void {
  38. parent::setUp();
  39. $this->access = $this->getAccessMock();
  40. $this->pluginManager = $this->createMock(GroupPluginManager::class);
  41. $this->config = $this->createMock(IConfig::class);
  42. $this->ncUserManager = $this->createMock(IUserManager::class);
  43. }
  44. public function initBackend(): void {
  45. $this->groupBackend = new GroupLDAP($this->access, $this->pluginManager, $this->config, $this->ncUserManager);
  46. }
  47. public function testCountEmptySearchString(): void {
  48. $groupDN = 'cn=group,dc=foo,dc=bar';
  49. $this->enableGroups();
  50. $this->access->expects($this->any())
  51. ->method('groupname2dn')
  52. ->willReturn($groupDN);
  53. $this->access->expects($this->any())
  54. ->method('readAttribute')
  55. ->willReturnCallback(function ($dn) use ($groupDN) {
  56. if ($dn === $groupDN) {
  57. return [
  58. 'uid=u11,ou=users,dc=foo,dc=bar',
  59. 'uid=u22,ou=users,dc=foo,dc=bar',
  60. 'uid=u33,ou=users,dc=foo,dc=bar',
  61. 'uid=u34,ou=users,dc=foo,dc=bar'
  62. ];
  63. }
  64. return [];
  65. });
  66. $this->access->expects($this->any())
  67. ->method('isDNPartOfBase')
  68. ->willReturn(true);
  69. // for primary groups
  70. $this->access->expects($this->once())
  71. ->method('countUsers')
  72. ->willReturn(2);
  73. $this->access->userManager->expects($this->any())
  74. ->method('getAttributes')
  75. ->willReturn(['displayName', 'mail']);
  76. $this->initBackend();
  77. $users = $this->groupBackend->countUsersInGroup('group');
  78. $this->assertSame(6, $users);
  79. }
  80. /**
  81. * @return MockObject|Access
  82. */
  83. private function getAccessMock() {
  84. static $conMethods;
  85. static $accMethods;
  86. if (is_null($conMethods) || is_null($accMethods)) {
  87. $conMethods = get_class_methods(Connection::class);
  88. $accMethods = get_class_methods(Access::class);
  89. }
  90. $lw = $this->createMock(ILDAPWrapper::class);
  91. $connector = $this->getMockBuilder(Connection::class)
  92. ->setMethods($conMethods)
  93. ->setConstructorArgs([$lw, '', null])
  94. ->getMock();
  95. $this->access = $this->createMock(Access::class);
  96. $this->access->connection = $connector;
  97. $this->access->userManager = $this->createMock(Manager::class);
  98. return $this->access;
  99. }
  100. private function enableGroups() {
  101. $this->access->connection->expects($this->any())
  102. ->method('__get')
  103. ->willReturnCallback(function ($name) {
  104. if ($name === 'ldapDynamicGroupMemberURL') {
  105. return '';
  106. } elseif ($name === 'ldapBaseGroups') {
  107. return [];
  108. }
  109. return 1;
  110. });
  111. }
  112. public function testCountWithSearchString(): void {
  113. $this->enableGroups();
  114. $this->access->expects($this->any())
  115. ->method('groupname2dn')
  116. ->willReturn('cn=group,dc=foo,dc=bar');
  117. $this->access->expects($this->any())
  118. ->method('fetchListOfUsers')
  119. ->willReturn([]);
  120. $this->access->expects($this->any())
  121. ->method('readAttribute')
  122. ->willReturnCallback(function ($name) {
  123. //the search operation will call readAttribute, thus we need
  124. //to analyze the "dn". All other times we just need to return
  125. //something that is neither null or false, but once an array
  126. //with the users in the group – so we do so all other times for
  127. //simplicity.
  128. if (str_starts_with($name, 'u')) {
  129. return strpos($name, '3');
  130. }
  131. return ['u11', 'u22', 'u33', 'u34'];
  132. });
  133. $this->access->expects($this->any())
  134. ->method('dn2username')
  135. ->willReturnCallback(function () {
  136. return 'foobar' . \OC::$server->getSecureRandom()->generate(7);
  137. });
  138. $this->access->expects($this->any())
  139. ->method('isDNPartOfBase')
  140. ->willReturn(true);
  141. $this->access->expects($this->any())
  142. ->method('escapeFilterPart')
  143. ->willReturnArgument(0);
  144. $this->access->userManager->expects($this->any())
  145. ->method('getAttributes')
  146. ->willReturn(['displayName', 'mail']);
  147. $this->initBackend();
  148. $users = $this->groupBackend->countUsersInGroup('group', '3');
  149. $this->assertSame(2, $users);
  150. }
  151. public function testCountUsersWithPlugin(): void {
  152. /** @var GroupPluginManager|MockObject $pluginManager */
  153. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  154. ->setMethods(['implementsActions', 'countUsersInGroup'])
  155. ->getMock();
  156. $this->pluginManager->expects($this->once())
  157. ->method('implementsActions')
  158. ->with(GroupInterface::COUNT_USERS)
  159. ->willReturn(true);
  160. $this->pluginManager->expects($this->once())
  161. ->method('countUsersInGroup')
  162. ->with('gid', 'search')
  163. ->willReturn(42);
  164. $this->initBackend();
  165. $this->assertEquals($this->groupBackend->countUsersInGroup('gid', 'search'), 42);
  166. }
  167. public function testGidNumber2NameSuccess(): void {
  168. $this->enableGroups();
  169. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  170. $this->access->expects($this->once())
  171. ->method('searchGroups')
  172. ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]);
  173. $this->access->expects($this->once())
  174. ->method('dn2groupname')
  175. ->with('cn=foo,dc=barfoo,dc=bar')
  176. ->willReturn('MyGroup');
  177. $this->initBackend();
  178. $group = $this->groupBackend->gidNumber2Name('3117', $userDN);
  179. $this->assertSame('MyGroup', $group);
  180. }
  181. public function testGidNumberID2NameNoGroup(): void {
  182. $this->enableGroups();
  183. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  184. $this->access->expects($this->once())
  185. ->method('searchGroups')
  186. ->willReturn([]);
  187. $this->access->expects($this->never())
  188. ->method('dn2groupname');
  189. $this->initBackend();
  190. $group = $this->groupBackend->gidNumber2Name('3117', $userDN);
  191. $this->assertSame(false, $group);
  192. }
  193. public function testGidNumberID2NameNoName(): void {
  194. $this->enableGroups();
  195. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  196. $this->access->expects($this->once())
  197. ->method('searchGroups')
  198. ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]);
  199. $this->access->expects($this->once())
  200. ->method('dn2groupname')
  201. ->willReturn(false);
  202. $this->initBackend();
  203. $group = $this->groupBackend->gidNumber2Name('3117', $userDN);
  204. $this->assertSame(false, $group);
  205. }
  206. public function testGetEntryGidNumberValue(): void {
  207. $this->enableGroups();
  208. $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
  209. $attr = 'gidNumber';
  210. $this->access->expects($this->once())
  211. ->method('readAttribute')
  212. ->with($dn, $attr)
  213. ->willReturn(['3117']);
  214. $this->initBackend();
  215. $gid = $this->groupBackend->getGroupGidNumber($dn);
  216. $this->assertSame('3117', $gid);
  217. }
  218. public function testGetEntryGidNumberNoValue(): void {
  219. $this->enableGroups();
  220. $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
  221. $attr = 'gidNumber';
  222. $this->access->expects($this->once())
  223. ->method('readAttribute')
  224. ->with($dn, $attr)
  225. ->willReturn(false);
  226. $this->initBackend();
  227. $gid = $this->groupBackend->getGroupGidNumber($dn);
  228. $this->assertSame(false, $gid);
  229. }
  230. public function testPrimaryGroupID2NameSuccessCache(): void {
  231. $this->enableGroups();
  232. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  233. $gid = '3117';
  234. /** @var MockObject $connection */
  235. $connection = $this->access->connection;
  236. $connection->expects($this->once())
  237. ->method('getFromCache')
  238. ->with('primaryGroupIDtoName_' . $gid)
  239. ->willReturn('MyGroup');
  240. $this->access->expects($this->never())
  241. ->method('getSID');
  242. $this->access->expects($this->never())
  243. ->method('searchGroups');
  244. $this->access->expects($this->never())
  245. ->method('dn2groupname');
  246. $this->initBackend();
  247. $group = $this->groupBackend->primaryGroupID2Name($gid, $userDN);
  248. $this->assertSame('MyGroup', $group);
  249. }
  250. public function testPrimaryGroupID2NameSuccess(): void {
  251. $this->enableGroups();
  252. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  253. $this->access->expects($this->once())
  254. ->method('getSID')
  255. ->with($userDN)
  256. ->willReturn('S-1-5-21-249921958-728525901-1594176202');
  257. $this->access->expects($this->once())
  258. ->method('searchGroups')
  259. ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]);
  260. $this->access->expects($this->once())
  261. ->method('dn2groupname')
  262. ->with('cn=foo,dc=barfoo,dc=bar')
  263. ->willReturn('MyGroup');
  264. $this->initBackend();
  265. $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN);
  266. $this->assertSame('MyGroup', $group);
  267. }
  268. public function testPrimaryGroupID2NameNoSID(): void {
  269. $this->enableGroups();
  270. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  271. $this->access->expects($this->once())
  272. ->method('getSID')
  273. ->with($userDN)
  274. ->willReturn(false);
  275. $this->access->expects($this->never())
  276. ->method('searchGroups');
  277. $this->access->expects($this->never())
  278. ->method('dn2groupname');
  279. $this->initBackend();
  280. $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN);
  281. $this->assertSame(false, $group);
  282. }
  283. public function testPrimaryGroupID2NameNoGroup(): void {
  284. $this->enableGroups();
  285. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  286. $this->access->expects($this->once())
  287. ->method('getSID')
  288. ->with($userDN)
  289. ->willReturn('S-1-5-21-249921958-728525901-1594176202');
  290. $this->access->expects($this->once())
  291. ->method('searchGroups')
  292. ->willReturn([]);
  293. $this->access->expects($this->never())
  294. ->method('dn2groupname');
  295. $this->initBackend();
  296. $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN);
  297. $this->assertSame(false, $group);
  298. }
  299. public function testPrimaryGroupID2NameNoName(): void {
  300. $this->enableGroups();
  301. $userDN = 'cn=alice,cn=foo,dc=barfoo,dc=bar';
  302. $this->access->expects($this->once())
  303. ->method('getSID')
  304. ->with($userDN)
  305. ->willReturn('S-1-5-21-249921958-728525901-1594176202');
  306. $this->access->expects($this->once())
  307. ->method('searchGroups')
  308. ->willReturn([['dn' => ['cn=foo,dc=barfoo,dc=bar']]]);
  309. $this->access->expects($this->once())
  310. ->method('dn2groupname')
  311. ->willReturn(false);
  312. $this->initBackend();
  313. $group = $this->groupBackend->primaryGroupID2Name('3117', $userDN);
  314. $this->assertSame(false, $group);
  315. }
  316. public function testGetEntryGroupIDValue(): void {
  317. //tests getEntryGroupID via getGroupPrimaryGroupID
  318. //which is basically identical to getUserPrimaryGroupIDs
  319. $this->enableGroups();
  320. $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
  321. $attr = 'primaryGroupToken';
  322. $this->access->expects($this->once())
  323. ->method('readAttribute')
  324. ->with($dn, $attr)
  325. ->willReturn(['3117']);
  326. $this->initBackend();
  327. $gid = $this->groupBackend->getGroupPrimaryGroupID($dn);
  328. $this->assertSame('3117', $gid);
  329. }
  330. public function testGetEntryGroupIDNoValue(): void {
  331. //tests getEntryGroupID via getGroupPrimaryGroupID
  332. //which is basically identical to getUserPrimaryGroupIDs
  333. $this->enableGroups();
  334. $dn = 'cn=foobar,cn=foo,dc=barfoo,dc=bar';
  335. $attr = 'primaryGroupToken';
  336. $this->access->expects($this->once())
  337. ->method('readAttribute')
  338. ->with($dn, $attr)
  339. ->willReturn(false);
  340. $this->initBackend();
  341. $gid = $this->groupBackend->getGroupPrimaryGroupID($dn);
  342. $this->assertSame(false, $gid);
  343. }
  344. /**
  345. * tests whether Group Backend behaves correctly when cache with uid and gid
  346. * is hit
  347. */
  348. public function testInGroupHitsUidGidCache(): void {
  349. $this->enableGroups();
  350. $uid = 'someUser';
  351. $gid = 'someGroup';
  352. $cacheKey = 'inGroup' . $uid . ':' . $gid;
  353. $this->access->connection->expects($this->once())
  354. ->method('getFromCache')
  355. ->with($cacheKey)
  356. ->willReturn(true);
  357. $this->access->expects($this->never())
  358. ->method('username2dn');
  359. $this->initBackend();
  360. $this->groupBackend->inGroup($uid, $gid);
  361. }
  362. public function groupWithMembersProvider() {
  363. return [
  364. [
  365. 'someGroup',
  366. 'cn=someGroup,ou=allTheGroups,ou=someDepartment,dc=someDomain,dc=someTld',
  367. [
  368. 'uid=oneUser,ou=someTeam,ou=someDepartment,dc=someDomain,dc=someTld',
  369. 'uid=someUser,ou=someTeam,ou=someDepartment,dc=someDomain,dc=someTld',
  370. 'uid=anotherUser,ou=someTeam,ou=someDepartment,dc=someDomain,dc=someTld',
  371. 'uid=differentUser,ou=someTeam,ou=someDepartment,dc=someDomain,dc=someTld',
  372. ],
  373. ],
  374. ];
  375. }
  376. /**
  377. * @dataProvider groupWithMembersProvider
  378. */
  379. public function testInGroupMember(string $gid, string $groupDn, array $memberDNs): void {
  380. $uid = 'someUser';
  381. $userDn = $memberDNs[0];
  382. $this->access->connection->expects($this->any())
  383. ->method('__get')
  384. ->willReturnCallback(function ($name) {
  385. switch ($name) {
  386. case 'ldapGroupMemberAssocAttr':
  387. return 'member';
  388. case 'ldapDynamicGroupMemberURL':
  389. return '';
  390. case 'hasPrimaryGroups':
  391. case 'ldapNestedGroups':
  392. return 0;
  393. default:
  394. return 1;
  395. }
  396. });
  397. $this->access->connection->expects($this->any())
  398. ->method('getFromCache')
  399. ->willReturn(null);
  400. $this->access->expects($this->once())
  401. ->method('username2dn')
  402. ->with($uid)
  403. ->willReturn($userDn);
  404. $this->access->expects($this->once())
  405. ->method('groupname2dn')
  406. ->willReturn($groupDn);
  407. $this->access->expects($this->any())
  408. ->method('readAttribute')
  409. ->willReturn($memberDNs);
  410. $this->initBackend();
  411. $this->assertTrue($this->groupBackend->inGroup($uid, $gid));
  412. }
  413. /**
  414. * @dataProvider groupWithMembersProvider
  415. */
  416. public function testInGroupMemberNot(string $gid, string $groupDn, array $memberDNs): void {
  417. $uid = 'unelatedUser';
  418. $userDn = 'uid=unrelatedUser,ou=unrelatedTeam,ou=unrelatedDepartment,dc=someDomain,dc=someTld';
  419. $this->access->connection->expects($this->any())
  420. ->method('__get')
  421. ->willReturnCallback(function ($name) {
  422. switch ($name) {
  423. case 'ldapGroupMemberAssocAttr':
  424. return 'member';
  425. case 'ldapDynamicGroupMemberURL':
  426. return '';
  427. case 'hasPrimaryGroups':
  428. case 'ldapNestedGroups':
  429. return 0;
  430. default:
  431. return 1;
  432. }
  433. });
  434. $this->access->connection->expects($this->any())
  435. ->method('getFromCache')
  436. ->willReturn(null);
  437. $this->access->expects($this->once())
  438. ->method('username2dn')
  439. ->with($uid)
  440. ->willReturn($userDn);
  441. $this->access->expects($this->once())
  442. ->method('groupname2dn')
  443. ->willReturn($groupDn);
  444. $this->access->expects($this->any())
  445. ->method('readAttribute')
  446. ->willReturn($memberDNs);
  447. $this->initBackend();
  448. $this->assertFalse($this->groupBackend->inGroup($uid, $gid));
  449. }
  450. /**
  451. * @dataProvider groupWithMembersProvider
  452. */
  453. public function testInGroupMemberUid(string $gid, string $groupDn, array $memberDNs): void {
  454. $memberUids = [];
  455. $userRecords = [];
  456. foreach ($memberDNs as $dn) {
  457. $memberUids[] = ldap_explode_dn($dn, false)[0];
  458. $userRecords[] = ['dn' => [$dn]];
  459. }
  460. $uid = 'someUser';
  461. $userDn = $memberDNs[0];
  462. $this->access->connection->expects($this->any())
  463. ->method('__get')
  464. ->willReturnCallback(function ($name) {
  465. switch ($name) {
  466. case 'ldapGroupMemberAssocAttr':
  467. return 'memberUid';
  468. case 'ldapDynamicGroupMemberURL':
  469. return '';
  470. case 'ldapLoginFilter':
  471. return 'uid=%uid';
  472. case 'hasPrimaryGroups':
  473. case 'ldapNestedGroups':
  474. return 0;
  475. default:
  476. return 1;
  477. }
  478. });
  479. $this->access->connection->expects($this->any())
  480. ->method('getFromCache')
  481. ->willReturn(null);
  482. $this->access->userManager->expects($this->any())
  483. ->method('getAttributes')
  484. ->willReturn(['uid', 'mail', 'displayname']);
  485. $this->access->expects($this->once())
  486. ->method('username2dn')
  487. ->with($uid)
  488. ->willReturn($userDn);
  489. $this->access->expects($this->once())
  490. ->method('groupname2dn')
  491. ->willReturn($groupDn);
  492. $this->access->expects($this->any())
  493. ->method('readAttribute')
  494. ->willReturn($memberUids);
  495. $this->access->expects($this->any())
  496. ->method('fetchListOfUsers')
  497. ->willReturn($userRecords);
  498. $this->access->expects($this->any())
  499. ->method('combineFilterWithOr')
  500. ->willReturn('(|(pseudo=filter)(filter=pseudo))');
  501. $this->initBackend();
  502. $this->assertTrue($this->groupBackend->inGroup($uid, $gid));
  503. }
  504. public function testGetGroupsWithOffset(): void {
  505. $this->enableGroups();
  506. $this->access->expects($this->once())
  507. ->method('nextcloudGroupNames')
  508. ->willReturn(['group1', 'group2']);
  509. $this->initBackend();
  510. $groups = $this->groupBackend->getGroups('', 2, 2);
  511. $this->assertSame(2, count($groups));
  512. }
  513. /**
  514. * tests that a user listing is complete, if all its members have the group
  515. * as their primary.
  516. */
  517. public function testUsersInGroupPrimaryMembersOnly(): void {
  518. $this->enableGroups();
  519. $this->access->connection->expects($this->any())
  520. ->method('getFromCache')
  521. ->willReturn(null);
  522. $this->access->expects($this->any())
  523. ->method('readAttribute')
  524. ->willReturnCallback(function ($dn, $attr) {
  525. if ($attr === 'primaryGroupToken') {
  526. return [1337];
  527. } elseif ($attr === 'gidNumber') {
  528. return [4211];
  529. }
  530. return [];
  531. });
  532. $this->access->expects($this->any())
  533. ->method('groupname2dn')
  534. ->willReturn('cn=foobar,dc=foo,dc=bar');
  535. $this->access->expects($this->exactly(2))
  536. ->method('nextcloudUserNames')
  537. ->willReturnOnConsecutiveCalls(['lisa', 'bart', 'kira', 'brad'], ['walle', 'dino', 'xenia']);
  538. $this->access->expects($this->any())
  539. ->method('isDNPartOfBase')
  540. ->willReturn(true);
  541. $this->access->expects($this->any())
  542. ->method('combineFilterWithAnd')
  543. ->willReturn('pseudo=filter');
  544. $this->access->userManager->expects($this->any())
  545. ->method('getAttributes')
  546. ->willReturn(['displayName', 'mail']);
  547. $this->initBackend();
  548. $users = $this->groupBackend->usersInGroup('foobar');
  549. $this->assertSame(7, count($users));
  550. }
  551. /**
  552. * tests that a user listing is complete, if all its members have the group
  553. * as their primary.
  554. */
  555. public function testUsersInGroupPrimaryAndUnixMembers(): void {
  556. $this->enableGroups();
  557. $this->access->connection->expects($this->any())
  558. ->method('getFromCache')
  559. ->willReturn(null);
  560. $this->access->expects($this->any())
  561. ->method('readAttribute')
  562. ->willReturnCallback(function ($dn, $attr) {
  563. if ($attr === 'primaryGroupToken') {
  564. return [1337];
  565. }
  566. return [];
  567. });
  568. $this->access->expects($this->any())
  569. ->method('groupname2dn')
  570. ->willReturn('cn=foobar,dc=foo,dc=bar');
  571. $this->access->expects($this->once())
  572. ->method('nextcloudUserNames')
  573. ->willReturn(['lisa', 'bart', 'kira', 'brad']);
  574. $this->access->expects($this->any())
  575. ->method('isDNPartOfBase')
  576. ->willReturn(true);
  577. $this->access->expects($this->any())
  578. ->method('combineFilterWithAnd')
  579. ->willReturn('pseudo=filter');
  580. $this->access->userManager->expects($this->any())
  581. ->method('getAttributes')
  582. ->willReturn(['displayName', 'mail']);
  583. $this->initBackend();
  584. $users = $this->groupBackend->usersInGroup('foobar');
  585. $this->assertSame(4, count($users));
  586. }
  587. /**
  588. * tests that a user counting is complete, if all its members have the group
  589. * as their primary.
  590. */
  591. public function testCountUsersInGroupPrimaryMembersOnly(): void {
  592. $this->enableGroups();
  593. $this->access->connection->expects($this->any())
  594. ->method('getFromCache')
  595. ->willReturn(null);
  596. $this->access->expects($this->any())
  597. ->method('readAttribute')
  598. ->willReturnCallback(function ($dn, $attr) {
  599. if ($attr === 'primaryGroupToken') {
  600. return [1337];
  601. }
  602. return [];
  603. });
  604. $this->access->expects($this->any())
  605. ->method('groupname2dn')
  606. ->willReturn('cn=foobar,dc=foo,dc=bar');
  607. $this->access->expects($this->once())
  608. ->method('countUsers')
  609. ->willReturn(4);
  610. $this->access->expects($this->any())
  611. ->method('isDNPartOfBase')
  612. ->willReturn(true);
  613. $this->access->userManager->expects($this->any())
  614. ->method('getAttributes')
  615. ->willReturn(['displayName', 'mail']);
  616. $this->initBackend();
  617. $users = $this->groupBackend->countUsersInGroup('foobar');
  618. $this->assertSame(4, $users);
  619. }
  620. public function testGetUserGroupsMemberOf(): void {
  621. $this->enableGroups();
  622. $dn = 'cn=userX,dc=foobar';
  623. $this->access->connection->hasPrimaryGroups = false;
  624. $this->access->connection->hasGidNumber = false;
  625. $expectedGroups = ['cn=groupA,dc=foobar', 'cn=groupB,dc=foobar'];
  626. $this->access->expects($this->any())
  627. ->method('username2dn')
  628. ->willReturn($dn);
  629. $this->access->expects($this->exactly(5))
  630. ->method('readAttribute')
  631. ->will($this->onConsecutiveCalls($expectedGroups, [], [], [], []));
  632. $this->access->expects($this->any())
  633. ->method('dn2groupname')
  634. ->willReturnArgument(0);
  635. $this->access->expects($this->any())
  636. ->method('groupname2dn')
  637. ->willReturnArgument(0);
  638. $this->access->expects($this->any())
  639. ->method('isDNPartOfBase')
  640. ->willReturn(true);
  641. $this->config->expects($this->once())
  642. ->method('setUserValue')
  643. ->with('userX', 'user_ldap', 'cached-group-memberships-', \json_encode($expectedGroups));
  644. $this->initBackend();
  645. $groups = $this->groupBackend->getUserGroups('userX');
  646. $this->assertSame(2, count($groups));
  647. }
  648. public function testGetUserGroupsMemberOfDisabled(): void {
  649. $this->access->connection->expects($this->any())
  650. ->method('__get')
  651. ->willReturnCallback(function ($name) {
  652. if ($name === 'useMemberOfToDetectMembership') {
  653. return 0;
  654. } elseif ($name === 'ldapDynamicGroupMemberURL') {
  655. return '';
  656. }
  657. return 1;
  658. });
  659. $dn = 'cn=userX,dc=foobar';
  660. $this->access->connection->hasPrimaryGroups = false;
  661. $this->access->connection->hasGidNumber = false;
  662. $this->access->expects($this->once())
  663. ->method('username2dn')
  664. ->willReturn($dn);
  665. $this->access->expects($this->never())
  666. ->method('readAttribute')
  667. ->with($dn, 'memberOf');
  668. $this->access->expects($this->once())
  669. ->method('nextcloudGroupNames')
  670. ->willReturn([]);
  671. // empty group result should not be oer
  672. $this->config->expects($this->once())
  673. ->method('setUserValue')
  674. ->with('userX', 'user_ldap', 'cached-group-memberships-', '[]');
  675. $ldapUser = $this->createMock(User::class);
  676. $this->access->userManager->expects($this->any())
  677. ->method('get')
  678. ->with('userX')
  679. ->willReturn($ldapUser);
  680. $userBackend = $this->createMock(User_Proxy::class);
  681. $userBackend->expects($this->once())
  682. ->method('userExistsOnLDAP')
  683. ->with('userX', true)
  684. ->willReturn(true);
  685. $ncUser = $this->createMock(IUser::class);
  686. $ncUser->expects($this->any())
  687. ->method('getBackend')
  688. ->willReturn($userBackend);
  689. $this->ncUserManager->expects($this->once())
  690. ->method('get')
  691. ->with('userX')
  692. ->willReturn($ncUser);
  693. $this->initBackend();
  694. $this->groupBackend->getUserGroups('userX');
  695. }
  696. public function testGetUserGroupsOfflineUser(): void {
  697. $this->enableGroups();
  698. $offlineUser = $this->createMock(OfflineUser::class);
  699. $this->config->expects($this->any())
  700. ->method('getUserValue')
  701. ->with('userX', 'user_ldap', 'cached-group-memberships-', $this->anything())
  702. ->willReturn(\json_encode(['groupB', 'groupF']));
  703. $this->access->userManager->expects($this->any())
  704. ->method('get')
  705. ->with('userX')
  706. ->willReturn($offlineUser);
  707. $this->initBackend();
  708. $returnedGroups = $this->groupBackend->getUserGroups('userX');
  709. $this->assertCount(2, $returnedGroups);
  710. $this->assertTrue(in_array('groupB', $returnedGroups));
  711. $this->assertTrue(in_array('groupF', $returnedGroups));
  712. }
  713. /**
  714. * regression tests against a case where a json object was stored instead of expected list
  715. * @see https://github.com/nextcloud/server/issues/42374
  716. */
  717. public function testGetUserGroupsOfflineUserUnexpectedJson(): void {
  718. $this->enableGroups();
  719. $offlineUser = $this->createMock(OfflineUser::class);
  720. $this->config->expects($this->any())
  721. ->method('getUserValue')
  722. ->with('userX', 'user_ldap', 'cached-group-memberships-', $this->anything())
  723. // results in a json object: {"0":"groupB","2":"groupF"}
  724. ->willReturn(\json_encode([0 => 'groupB', 2 => 'groupF']));
  725. $this->access->userManager->expects($this->any())
  726. ->method('get')
  727. ->with('userX')
  728. ->willReturn($offlineUser);
  729. $this->initBackend();
  730. $returnedGroups = $this->groupBackend->getUserGroups('userX');
  731. $this->assertCount(2, $returnedGroups);
  732. $this->assertTrue(in_array('groupB', $returnedGroups));
  733. $this->assertTrue(in_array('groupF', $returnedGroups));
  734. }
  735. public function testGetUserGroupsUnrecognizedOfflineUser(): void {
  736. $this->enableGroups();
  737. $dn = 'cn=userX,dc=foobar';
  738. $ldapUser = $this->createMock(User::class);
  739. $userBackend = $this->createMock(User_Proxy::class);
  740. $userBackend->expects($this->once())
  741. ->method('userExistsOnLDAP')
  742. ->with('userX', true)
  743. ->willReturn(false);
  744. $ncUser = $this->createMock(IUser::class);
  745. $ncUser->expects($this->any())
  746. ->method('getBackend')
  747. ->willReturn($userBackend);
  748. $this->config->expects($this->atLeastOnce())
  749. ->method('getUserValue')
  750. ->with('userX', 'user_ldap', 'cached-group-memberships-', $this->anything())
  751. ->willReturn(\json_encode(['groupB', 'groupF']));
  752. $this->access->expects($this->any())
  753. ->method('username2dn')
  754. ->willReturn($dn);
  755. $this->access->userManager->expects($this->any())
  756. ->method('get')
  757. ->with('userX')
  758. ->willReturn($ldapUser);
  759. $this->ncUserManager->expects($this->once())
  760. ->method('get')
  761. ->with('userX')
  762. ->willReturn($ncUser);
  763. $this->initBackend();
  764. $returnedGroups = $this->groupBackend->getUserGroups('userX');
  765. $this->assertCount(2, $returnedGroups);
  766. $this->assertTrue(in_array('groupB', $returnedGroups));
  767. $this->assertTrue(in_array('groupF', $returnedGroups));
  768. }
  769. public function nestedGroupsProvider(): array {
  770. return [
  771. [true],
  772. [false],
  773. ];
  774. }
  775. /**
  776. * @dataProvider nestedGroupsProvider
  777. */
  778. public function testGetGroupsByMember(bool $nestedGroups): void {
  779. $groupFilter = '(&(objectclass=nextcloudGroup)(nextcloudEnabled=TRUE))';
  780. $this->access->connection->expects($this->any())
  781. ->method('__get')
  782. ->willReturnCallback(function (string $name) use ($nestedGroups, $groupFilter) {
  783. switch ($name) {
  784. case 'useMemberOfToDetectMembership':
  785. return 0;
  786. case 'ldapDynamicGroupMemberURL':
  787. return '';
  788. case 'ldapNestedGroups':
  789. return (int)$nestedGroups;
  790. case 'ldapGroupMemberAssocAttr':
  791. return 'member';
  792. case 'ldapGroupFilter':
  793. return $groupFilter;
  794. case 'ldapBaseGroups':
  795. return [];
  796. case 'ldapGroupDisplayName':
  797. return 'cn';
  798. }
  799. return 1;
  800. });
  801. $dn = 'cn=userX,dc=foobar';
  802. $this->access->connection->hasPrimaryGroups = false;
  803. $this->access->connection->hasGidNumber = false;
  804. $this->access->expects($this->exactly(2))
  805. ->method('username2dn')
  806. ->willReturn($dn);
  807. $this->access->expects($this->any())
  808. ->method('readAttribute')
  809. ->willReturn([]);
  810. $this->access->expects($this->any())
  811. ->method('combineFilterWithAnd')
  812. ->willReturnCallback(function (array $filterParts) {
  813. // ⚠ returns a pseudo-filter only, not real LDAP Filter syntax
  814. return implode('&', $filterParts);
  815. });
  816. $group1 = [
  817. 'cn' => 'group1',
  818. 'dn' => ['cn=group1,ou=groups,dc=domain,dc=com'],
  819. 'member' => [$dn],
  820. ];
  821. $group2 = [
  822. 'cn' => 'group2',
  823. 'dn' => ['cn=group2,ou=groups,dc=domain,dc=com'],
  824. 'member' => [$dn],
  825. ];
  826. $group3 = [
  827. 'cn' => 'group3',
  828. 'dn' => ['cn=group3,ou=groups,dc=domain,dc=com'],
  829. 'member' => [$group2['dn'][0]],
  830. ];
  831. $expectedGroups = ($nestedGroups ? [$group1, $group2, $group3] : [$group1, $group2]);
  832. $expectedGroupsNames = ($nestedGroups ? ['group1', 'group2', 'group3'] : ['group1', 'group2']);
  833. $this->access->expects($this->any())
  834. ->method('nextcloudGroupNames')
  835. ->with($expectedGroups)
  836. ->willReturn($expectedGroupsNames);
  837. $this->access->expects($nestedGroups ? $this->atLeastOnce() : $this->once())
  838. ->method('fetchListOfGroups')
  839. ->willReturnCallback(function ($filter, $attr, $limit, $offset) use ($nestedGroups, $groupFilter, $group1, $group2, $group3, $dn) {
  840. static $firstRun = true;
  841. if (!$nestedGroups) {
  842. // When nested groups are enabled, groups cannot be filtered early as it would
  843. // exclude intermediate groups. But we can, and should, when working with flat groups.
  844. $this->assertTrue(str_contains($filter, $groupFilter));
  845. }
  846. [$memberFilter] = explode('&', $filter);
  847. if ($memberFilter === 'member='.$dn) {
  848. return [$group1, $group2];
  849. return [];
  850. } elseif ($memberFilter === 'member='.$group2['dn'][0]) {
  851. return [$group3];
  852. } else {
  853. return [];
  854. }
  855. });
  856. $this->access->expects($this->any())
  857. ->method('dn2groupname')
  858. ->willReturnCallback(function (string $dn) {
  859. return ldap_explode_dn($dn, 1)[0];
  860. });
  861. $this->access->expects($this->any())
  862. ->method('groupname2dn')
  863. ->willReturnCallback(function (string $gid) use ($group1, $group2, $group3) {
  864. if ($gid === $group1['cn']) {
  865. return $group1['dn'][0];
  866. }
  867. if ($gid === $group2['cn']) {
  868. return $group2['dn'][0];
  869. }
  870. if ($gid === $group3['cn']) {
  871. return $group3['dn'][0];
  872. }
  873. });
  874. $this->access->expects($this->any())
  875. ->method('isDNPartOfBase')
  876. ->willReturn(true);
  877. $this->initBackend();
  878. $groups = $this->groupBackend->getUserGroups('userX');
  879. $this->assertEquals($expectedGroupsNames, $groups);
  880. $groupsAgain = $this->groupBackend->getUserGroups('userX');
  881. $this->assertEquals($expectedGroupsNames, $groupsAgain);
  882. }
  883. public function testCreateGroupWithPlugin(): void {
  884. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  885. ->setMethods(['implementsActions', 'createGroup'])
  886. ->getMock();
  887. $this->pluginManager->expects($this->once())
  888. ->method('implementsActions')
  889. ->with(GroupInterface::CREATE_GROUP)
  890. ->willReturn(true);
  891. $this->pluginManager->expects($this->once())
  892. ->method('createGroup')
  893. ->with('gid')
  894. ->willReturn('result');
  895. $this->initBackend();
  896. $this->assertEquals($this->groupBackend->createGroup('gid'), true);
  897. }
  898. public function testCreateGroupFailing(): void {
  899. $this->expectException(\Exception::class);
  900. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  901. ->setMethods(['implementsActions', 'createGroup'])
  902. ->getMock();
  903. $this->pluginManager->expects($this->once())
  904. ->method('implementsActions')
  905. ->with(GroupInterface::CREATE_GROUP)
  906. ->willReturn(false);
  907. $this->initBackend();
  908. $this->groupBackend->createGroup('gid');
  909. }
  910. public function testDeleteGroupWithPlugin(): void {
  911. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  912. ->setMethods(['implementsActions', 'deleteGroup'])
  913. ->getMock();
  914. $this->pluginManager->expects($this->once())
  915. ->method('implementsActions')
  916. ->with(GroupInterface::DELETE_GROUP)
  917. ->willReturn(true);
  918. $this->pluginManager->expects($this->once())
  919. ->method('deleteGroup')
  920. ->with('gid')
  921. ->willReturn(true);
  922. $mapper = $this->getMockBuilder(GroupMapping::class)
  923. ->setMethods(['unmap'])
  924. ->disableOriginalConstructor()
  925. ->getMock();
  926. $this->access->expects($this->any())
  927. ->method('getGroupMapper')
  928. ->willReturn($mapper);
  929. $this->initBackend();
  930. $this->assertTrue($this->groupBackend->deleteGroup('gid'));
  931. }
  932. public function testDeleteGroupFailing(): void {
  933. $this->expectException(\Exception::class);
  934. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  935. ->setMethods(['implementsActions', 'deleteGroup'])
  936. ->getMock();
  937. $this->pluginManager->expects($this->once())
  938. ->method('implementsActions')
  939. ->with(GroupInterface::DELETE_GROUP)
  940. ->willReturn(false);
  941. $this->initBackend();
  942. $this->groupBackend->deleteGroup('gid');
  943. }
  944. public function testAddToGroupWithPlugin(): void {
  945. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  946. ->setMethods(['implementsActions', 'addToGroup'])
  947. ->getMock();
  948. $this->pluginManager->expects($this->once())
  949. ->method('implementsActions')
  950. ->with(GroupInterface::ADD_TO_GROUP)
  951. ->willReturn(true);
  952. $this->pluginManager->expects($this->once())
  953. ->method('addToGroup')
  954. ->with('uid', 'gid')
  955. ->willReturn('result');
  956. $this->initBackend();
  957. $this->assertEquals($this->groupBackend->addToGroup('uid', 'gid'), 'result');
  958. }
  959. public function testAddToGroupFailing(): void {
  960. $this->expectException(\Exception::class);
  961. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  962. ->setMethods(['implementsActions', 'addToGroup'])
  963. ->getMock();
  964. $this->pluginManager->expects($this->once())
  965. ->method('implementsActions')
  966. ->with(GroupInterface::ADD_TO_GROUP)
  967. ->willReturn(false);
  968. $this->initBackend();
  969. $this->groupBackend->addToGroup('uid', 'gid');
  970. }
  971. public function testRemoveFromGroupWithPlugin(): void {
  972. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  973. ->setMethods(['implementsActions', 'removeFromGroup'])
  974. ->getMock();
  975. $this->pluginManager->expects($this->once())
  976. ->method('implementsActions')
  977. ->with(GroupInterface::REMOVE_FROM_GROUP)
  978. ->willReturn(true);
  979. $this->pluginManager->expects($this->once())
  980. ->method('removeFromGroup')
  981. ->with('uid', 'gid')
  982. ->willReturn('result');
  983. $this->initBackend();
  984. $this->assertEquals($this->groupBackend->removeFromGroup('uid', 'gid'), 'result');
  985. }
  986. public function testRemoveFromGroupFailing(): void {
  987. $this->expectException(\Exception::class);
  988. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  989. ->setMethods(['implementsActions', 'removeFromGroup'])
  990. ->getMock();
  991. $this->pluginManager->expects($this->once())
  992. ->method('implementsActions')
  993. ->with(GroupInterface::REMOVE_FROM_GROUP)
  994. ->willReturn(false);
  995. $this->initBackend();
  996. $this->groupBackend->removeFromGroup('uid', 'gid');
  997. }
  998. public function testGetGroupDetailsWithPlugin(): void {
  999. /** @var GroupPluginManager|MockObject $pluginManager */
  1000. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  1001. ->setMethods(['implementsActions', 'getGroupDetails'])
  1002. ->getMock();
  1003. $this->pluginManager->expects($this->once())
  1004. ->method('implementsActions')
  1005. ->with(GroupInterface::GROUP_DETAILS)
  1006. ->willReturn(true);
  1007. $this->pluginManager->expects($this->once())
  1008. ->method('getGroupDetails')
  1009. ->with('gid')
  1010. ->willReturn('result');
  1011. $this->initBackend();
  1012. $this->assertEquals($this->groupBackend->getGroupDetails('gid'), 'result');
  1013. }
  1014. public function testGetGroupDetailsFailing(): void {
  1015. $this->expectException(\Exception::class);
  1016. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  1017. ->setMethods(['implementsActions', 'getGroupDetails'])
  1018. ->getMock();
  1019. $this->pluginManager->expects($this->once())
  1020. ->method('implementsActions')
  1021. ->with(GroupInterface::GROUP_DETAILS)
  1022. ->willReturn(false);
  1023. $this->initBackend();
  1024. $this->groupBackend->getGroupDetails('gid');
  1025. }
  1026. public function groupMemberProvider() {
  1027. $base = 'dc=species,dc=earth';
  1028. $birdsDn = [
  1029. 'uid=3723,' . $base,
  1030. 'uid=8372,' . $base,
  1031. 'uid=8427,' . $base,
  1032. 'uid=2333,' . $base,
  1033. 'uid=4754,' . $base,
  1034. ];
  1035. $birdsUid = [
  1036. '3723',
  1037. '8372',
  1038. '8427',
  1039. '2333',
  1040. '4754',
  1041. ];
  1042. $animalsDn = [
  1043. 'uid=lion,' . $base,
  1044. 'uid=tiger,' . $base,
  1045. ];
  1046. $plantsDn = [
  1047. 'uid=flower,' . $base,
  1048. 'uid=tree,' . $base,
  1049. ];
  1050. $thingsDn = [
  1051. 'uid=thing1,' . $base,
  1052. 'uid=thing2,' . $base,
  1053. ];
  1054. return [
  1055. [ #0 – test DNs
  1056. ['cn=Birds,' . $base => $birdsDn],
  1057. ['cn=Birds,' . $base => $birdsDn]
  1058. ],
  1059. [ #1 – test uids
  1060. ['cn=Birds,' . $base => $birdsUid],
  1061. ['cn=Birds,' . $base => $birdsUid]
  1062. ],
  1063. [ #2 – test simple nested group
  1064. ['cn=Animals,' . $base => array_merge($birdsDn, $animalsDn)],
  1065. [
  1066. 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base], $animalsDn),
  1067. 'cn=Birds,' . $base => $birdsDn,
  1068. ]
  1069. ],
  1070. [ #3 – test recursive nested group
  1071. [
  1072. 'cn=Animals,' . $base => array_merge($birdsDn, $animalsDn),
  1073. 'cn=Birds,' . $base => array_merge($birdsDn, $animalsDn),
  1074. ],
  1075. [
  1076. 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base,'cn=Birds,' . $base,'cn=Animals,' . $base], $animalsDn),
  1077. 'cn=Birds,' . $base => array_merge(['cn=Animals,' . $base,'cn=Birds,' . $base], $birdsDn),
  1078. ]
  1079. ],
  1080. [ #4 – Complicated nested group
  1081. ['cn=Things,' . $base => array_merge($birdsDn, $animalsDn, $thingsDn, $plantsDn)],
  1082. [
  1083. 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base], $animalsDn),
  1084. 'cn=Birds,' . $base => $birdsDn,
  1085. 'cn=Plants,' . $base => $plantsDn,
  1086. 'cn=Things,' . $base => array_merge(['cn=Animals,' . $base,'cn=Plants,' . $base], $thingsDn),
  1087. ]
  1088. ],
  1089. ];
  1090. }
  1091. /**
  1092. * @param string[] $expectedMembers
  1093. * @dataProvider groupMemberProvider
  1094. */
  1095. public function testGroupMembers(array $expectedResult, ?array $groupsInfo = null): void {
  1096. $this->access->expects($this->any())
  1097. ->method('readAttribute')
  1098. ->willReturnCallback(function ($group) use ($groupsInfo) {
  1099. if (isset($groupsInfo[$group])) {
  1100. return $groupsInfo[$group];
  1101. }
  1102. return [];
  1103. });
  1104. $this->access->connection->expects($this->any())
  1105. ->method('__get')
  1106. ->willReturnCallback(function (string $name) {
  1107. if ($name === 'ldapNestedGroups') {
  1108. return 1;
  1109. } elseif ($name === 'ldapGroupMemberAssocAttr') {
  1110. return 'attr';
  1111. }
  1112. return null;
  1113. });
  1114. $this->initBackend();
  1115. foreach ($expectedResult as $groupDN => $expectedMembers) {
  1116. $resultingMembers = $this->invokePrivate($this->groupBackend, '_groupMembers', [$groupDN]);
  1117. $this->assertEqualsCanonicalizing($expectedMembers, $resultingMembers);
  1118. }
  1119. }
  1120. public function displayNameProvider() {
  1121. return [
  1122. ['Graphic Novelists', ['Graphic Novelists']],
  1123. ['', false],
  1124. ];
  1125. }
  1126. /**
  1127. * @dataProvider displayNameProvider
  1128. */
  1129. public function testGetDisplayName(string $expected, $ldapResult): void {
  1130. $gid = 'graphic_novelists';
  1131. $this->access->expects($this->atLeastOnce())
  1132. ->method('readAttribute')
  1133. ->willReturn($ldapResult);
  1134. $this->access->connection->expects($this->any())
  1135. ->method('__get')
  1136. ->willReturnCallback(function ($name) {
  1137. if ($name === 'ldapGroupMemberAssocAttr') {
  1138. return 'member';
  1139. } elseif ($name === 'ldapGroupFilter') {
  1140. return 'objectclass=nextcloudGroup';
  1141. } elseif ($name === 'ldapGroupDisplayName') {
  1142. return 'cn';
  1143. }
  1144. return null;
  1145. });
  1146. $this->access->expects($this->any())
  1147. ->method('groupname2dn')
  1148. ->willReturn('fakedn');
  1149. $this->initBackend();
  1150. $this->assertSame($expected, $this->groupBackend->getDisplayName($gid));
  1151. }
  1152. }