Group_LDAPTest.php 39 KB

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