Group_LDAPTest.php 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398
  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. public function testGetUserGroupsUnrecognizedOfflineUser() {
  738. $this->enableGroups();
  739. $dn = 'cn=userX,dc=foobar';
  740. $ldapUser = $this->createMock(User::class);
  741. $userBackend = $this->createMock(User_Proxy::class);
  742. $userBackend->expects($this->once())
  743. ->method('userExistsOnLDAP')
  744. ->with('userX', true)
  745. ->willReturn(false);
  746. $ncUser = $this->createMock(IUser::class);
  747. $ncUser->expects($this->any())
  748. ->method('getBackend')
  749. ->willReturn($userBackend);
  750. $this->config->expects($this->atLeastOnce())
  751. ->method('getUserValue')
  752. ->with('userX', 'user_ldap', 'cached-group-memberships-', $this->anything())
  753. ->willReturn(\json_encode(['groupB', 'groupF']));
  754. $this->access->expects($this->any())
  755. ->method('username2dn')
  756. ->willReturn($dn);
  757. $this->access->userManager->expects($this->any())
  758. ->method('get')
  759. ->with('userX')
  760. ->willReturn($ldapUser);
  761. $this->ncUserManager->expects($this->once())
  762. ->method('get')
  763. ->with('userX')
  764. ->willReturn($ncUser);
  765. $this->initBackend();
  766. $returnedGroups = $this->groupBackend->getUserGroups('userX');
  767. $this->assertCount(2, $returnedGroups);
  768. $this->assertTrue(in_array('groupB', $returnedGroups));
  769. $this->assertTrue(in_array('groupF', $returnedGroups));
  770. }
  771. public function nestedGroupsProvider(): array {
  772. return [
  773. [true],
  774. [false],
  775. ];
  776. }
  777. /**
  778. * @dataProvider nestedGroupsProvider
  779. */
  780. public function testGetGroupsByMember(bool $nestedGroups) {
  781. $groupFilter = '(&(objectclass=nextcloudGroup)(nextcloudEnabled=TRUE))';
  782. $this->access->connection->expects($this->any())
  783. ->method('__get')
  784. ->willReturnCallback(function (string $name) use ($nestedGroups, $groupFilter) {
  785. switch ($name) {
  786. case 'useMemberOfToDetectMembership':
  787. return 0;
  788. case 'ldapDynamicGroupMemberURL':
  789. return '';
  790. case 'ldapNestedGroups':
  791. return (int)$nestedGroups;
  792. case 'ldapGroupMemberAssocAttr':
  793. return 'member';
  794. case 'ldapGroupFilter':
  795. return $groupFilter;
  796. case 'ldapBaseGroups':
  797. return [];
  798. case 'ldapGroupDisplayName':
  799. return 'cn';
  800. }
  801. return 1;
  802. });
  803. $dn = 'cn=userX,dc=foobar';
  804. $this->access->connection->hasPrimaryGroups = false;
  805. $this->access->connection->hasGidNumber = false;
  806. $this->access->expects($this->exactly(2))
  807. ->method('username2dn')
  808. ->willReturn($dn);
  809. $this->access->expects($this->any())
  810. ->method('readAttribute')
  811. ->willReturn([]);
  812. $this->access->expects($this->any())
  813. ->method('combineFilterWithAnd')
  814. ->willReturnCallback(function (array $filterParts) {
  815. // ⚠ returns a pseudo-filter only, not real LDAP Filter syntax
  816. return implode('&', $filterParts);
  817. });
  818. $group1 = [
  819. 'cn' => 'group1',
  820. 'dn' => ['cn=group1,ou=groups,dc=domain,dc=com'],
  821. 'member' => [$dn],
  822. ];
  823. $group2 = [
  824. 'cn' => 'group2',
  825. 'dn' => ['cn=group2,ou=groups,dc=domain,dc=com'],
  826. 'member' => [$dn],
  827. ];
  828. $group3 = [
  829. 'cn' => 'group3',
  830. 'dn' => ['cn=group3,ou=groups,dc=domain,dc=com'],
  831. 'member' => [$group2['dn'][0]],
  832. ];
  833. $expectedGroups = ($nestedGroups ? [$group1, $group2, $group3] : [$group1, $group2]);
  834. $expectedGroupsNames = ($nestedGroups ? ['group1', 'group2', 'group3'] : ['group1', 'group2']);
  835. $this->access->expects($this->any())
  836. ->method('nextcloudGroupNames')
  837. ->with($expectedGroups)
  838. ->willReturn($expectedGroupsNames);
  839. $this->access->expects($nestedGroups ? $this->atLeastOnce() : $this->once())
  840. ->method('fetchListOfGroups')
  841. ->willReturnCallback(function ($filter, $attr, $limit, $offset) use ($nestedGroups, $groupFilter, $group1, $group2, $group3, $dn) {
  842. static $firstRun = true;
  843. if (!$nestedGroups) {
  844. // When nested groups are enabled, groups cannot be filtered early as it would
  845. // exclude intermediate groups. But we can, and should, when working with flat groups.
  846. $this->assertTrue(str_contains($filter, $groupFilter));
  847. }
  848. [$memberFilter] = explode('&', $filter);
  849. if ($memberFilter === 'member='.$dn) {
  850. return [$group1, $group2];
  851. return [];
  852. } elseif ($memberFilter === 'member='.$group2['dn'][0]) {
  853. return [$group3];
  854. } else {
  855. return [];
  856. }
  857. });
  858. $this->access->expects($this->any())
  859. ->method('dn2groupname')
  860. ->willReturnCallback(function (string $dn) {
  861. return ldap_explode_dn($dn, 1)[0];
  862. });
  863. $this->access->expects($this->any())
  864. ->method('groupname2dn')
  865. ->willReturnCallback(function (string $gid) use ($group1, $group2, $group3) {
  866. if ($gid === $group1['cn']) {
  867. return $group1['dn'][0];
  868. }
  869. if ($gid === $group2['cn']) {
  870. return $group2['dn'][0];
  871. }
  872. if ($gid === $group3['cn']) {
  873. return $group3['dn'][0];
  874. }
  875. });
  876. $this->access->expects($this->any())
  877. ->method('isDNPartOfBase')
  878. ->willReturn(true);
  879. $this->initBackend();
  880. $groups = $this->groupBackend->getUserGroups('userX');
  881. $this->assertEquals($expectedGroupsNames, $groups);
  882. $groupsAgain = $this->groupBackend->getUserGroups('userX');
  883. $this->assertEquals($expectedGroupsNames, $groupsAgain);
  884. }
  885. public function testCreateGroupWithPlugin() {
  886. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  887. ->setMethods(['implementsActions', 'createGroup'])
  888. ->getMock();
  889. $this->pluginManager->expects($this->once())
  890. ->method('implementsActions')
  891. ->with(GroupInterface::CREATE_GROUP)
  892. ->willReturn(true);
  893. $this->pluginManager->expects($this->once())
  894. ->method('createGroup')
  895. ->with('gid')
  896. ->willReturn('result');
  897. $this->initBackend();
  898. $this->assertEquals($this->groupBackend->createGroup('gid'), true);
  899. }
  900. public function testCreateGroupFailing() {
  901. $this->expectException(\Exception::class);
  902. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  903. ->setMethods(['implementsActions', 'createGroup'])
  904. ->getMock();
  905. $this->pluginManager->expects($this->once())
  906. ->method('implementsActions')
  907. ->with(GroupInterface::CREATE_GROUP)
  908. ->willReturn(false);
  909. $this->initBackend();
  910. $this->groupBackend->createGroup('gid');
  911. }
  912. public function testDeleteGroupWithPlugin() {
  913. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  914. ->setMethods(['implementsActions', 'deleteGroup'])
  915. ->getMock();
  916. $this->pluginManager->expects($this->once())
  917. ->method('implementsActions')
  918. ->with(GroupInterface::DELETE_GROUP)
  919. ->willReturn(true);
  920. $this->pluginManager->expects($this->once())
  921. ->method('deleteGroup')
  922. ->with('gid')
  923. ->willReturn(true);
  924. $mapper = $this->getMockBuilder(GroupMapping::class)
  925. ->setMethods(['unmap'])
  926. ->disableOriginalConstructor()
  927. ->getMock();
  928. $this->access->expects($this->any())
  929. ->method('getGroupMapper')
  930. ->willReturn($mapper);
  931. $this->initBackend();
  932. $this->assertTrue($this->groupBackend->deleteGroup('gid'));
  933. }
  934. public function testDeleteGroupFailing() {
  935. $this->expectException(\Exception::class);
  936. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  937. ->setMethods(['implementsActions', 'deleteGroup'])
  938. ->getMock();
  939. $this->pluginManager->expects($this->once())
  940. ->method('implementsActions')
  941. ->with(GroupInterface::DELETE_GROUP)
  942. ->willReturn(false);
  943. $this->initBackend();
  944. $this->groupBackend->deleteGroup('gid');
  945. }
  946. public function testAddToGroupWithPlugin() {
  947. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  948. ->setMethods(['implementsActions', 'addToGroup'])
  949. ->getMock();
  950. $this->pluginManager->expects($this->once())
  951. ->method('implementsActions')
  952. ->with(GroupInterface::ADD_TO_GROUP)
  953. ->willReturn(true);
  954. $this->pluginManager->expects($this->once())
  955. ->method('addToGroup')
  956. ->with('uid', 'gid')
  957. ->willReturn('result');
  958. $this->initBackend();
  959. $this->assertEquals($this->groupBackend->addToGroup('uid', 'gid'), 'result');
  960. }
  961. public function testAddToGroupFailing() {
  962. $this->expectException(\Exception::class);
  963. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  964. ->setMethods(['implementsActions', 'addToGroup'])
  965. ->getMock();
  966. $this->pluginManager->expects($this->once())
  967. ->method('implementsActions')
  968. ->with(GroupInterface::ADD_TO_GROUP)
  969. ->willReturn(false);
  970. $this->initBackend();
  971. $this->groupBackend->addToGroup('uid', 'gid');
  972. }
  973. public function testRemoveFromGroupWithPlugin() {
  974. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  975. ->setMethods(['implementsActions', 'removeFromGroup'])
  976. ->getMock();
  977. $this->pluginManager->expects($this->once())
  978. ->method('implementsActions')
  979. ->with(GroupInterface::REMOVE_FROM_GROUP)
  980. ->willReturn(true);
  981. $this->pluginManager->expects($this->once())
  982. ->method('removeFromGroup')
  983. ->with('uid', 'gid')
  984. ->willReturn('result');
  985. $this->initBackend();
  986. $this->assertEquals($this->groupBackend->removeFromGroup('uid', 'gid'), 'result');
  987. }
  988. public function testRemoveFromGroupFailing() {
  989. $this->expectException(\Exception::class);
  990. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  991. ->setMethods(['implementsActions', 'removeFromGroup'])
  992. ->getMock();
  993. $this->pluginManager->expects($this->once())
  994. ->method('implementsActions')
  995. ->with(GroupInterface::REMOVE_FROM_GROUP)
  996. ->willReturn(false);
  997. $this->initBackend();
  998. $this->groupBackend->removeFromGroup('uid', 'gid');
  999. }
  1000. public function testGetGroupDetailsWithPlugin() {
  1001. /** @var GroupPluginManager|MockObject $pluginManager */
  1002. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  1003. ->setMethods(['implementsActions', 'getGroupDetails'])
  1004. ->getMock();
  1005. $this->pluginManager->expects($this->once())
  1006. ->method('implementsActions')
  1007. ->with(GroupInterface::GROUP_DETAILS)
  1008. ->willReturn(true);
  1009. $this->pluginManager->expects($this->once())
  1010. ->method('getGroupDetails')
  1011. ->with('gid')
  1012. ->willReturn('result');
  1013. $this->initBackend();
  1014. $this->assertEquals($this->groupBackend->getGroupDetails('gid'), 'result');
  1015. }
  1016. public function testGetGroupDetailsFailing() {
  1017. $this->expectException(\Exception::class);
  1018. $this->pluginManager = $this->getMockBuilder(GroupPluginManager::class)
  1019. ->setMethods(['implementsActions', 'getGroupDetails'])
  1020. ->getMock();
  1021. $this->pluginManager->expects($this->once())
  1022. ->method('implementsActions')
  1023. ->with(GroupInterface::GROUP_DETAILS)
  1024. ->willReturn(false);
  1025. $this->initBackend();
  1026. $this->groupBackend->getGroupDetails('gid');
  1027. }
  1028. public function groupMemberProvider() {
  1029. $base = 'dc=species,dc=earth';
  1030. $birdsDn = [
  1031. 'uid=3723,' . $base,
  1032. 'uid=8372,' . $base,
  1033. 'uid=8427,' . $base,
  1034. 'uid=2333,' . $base,
  1035. 'uid=4754,' . $base,
  1036. ];
  1037. $birdsUid = [
  1038. '3723',
  1039. '8372',
  1040. '8427',
  1041. '2333',
  1042. '4754',
  1043. ];
  1044. $animalsDn = [
  1045. 'uid=lion,' . $base,
  1046. 'uid=tiger,' . $base,
  1047. ];
  1048. $plantsDn = [
  1049. 'uid=flower,' . $base,
  1050. 'uid=tree,' . $base,
  1051. ];
  1052. $thingsDn = [
  1053. 'uid=thing1,' . $base,
  1054. 'uid=thing2,' . $base,
  1055. ];
  1056. return [
  1057. [ #0 – test DNs
  1058. ['cn=Birds,' . $base => $birdsDn],
  1059. ['cn=Birds,' . $base => $birdsDn]
  1060. ],
  1061. [ #1 – test uids
  1062. ['cn=Birds,' . $base => $birdsUid],
  1063. ['cn=Birds,' . $base => $birdsUid]
  1064. ],
  1065. [ #2 – test simple nested group
  1066. ['cn=Animals,' . $base => array_merge($birdsDn, $animalsDn)],
  1067. [
  1068. 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base], $animalsDn),
  1069. 'cn=Birds,' . $base => $birdsDn,
  1070. ]
  1071. ],
  1072. [ #3 – test recursive nested group
  1073. [
  1074. 'cn=Animals,' . $base => array_merge($birdsDn, $animalsDn),
  1075. 'cn=Birds,' . $base => array_merge($birdsDn, $animalsDn),
  1076. ],
  1077. [
  1078. 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base,'cn=Birds,' . $base,'cn=Animals,' . $base], $animalsDn),
  1079. 'cn=Birds,' . $base => array_merge(['cn=Animals,' . $base,'cn=Birds,' . $base], $birdsDn),
  1080. ]
  1081. ],
  1082. [ #4 – Complicated nested group
  1083. ['cn=Things,' . $base => array_merge($birdsDn, $animalsDn, $thingsDn, $plantsDn)],
  1084. [
  1085. 'cn=Animals,' . $base => array_merge(['cn=Birds,' . $base], $animalsDn),
  1086. 'cn=Birds,' . $base => $birdsDn,
  1087. 'cn=Plants,' . $base => $plantsDn,
  1088. 'cn=Things,' . $base => array_merge(['cn=Animals,' . $base,'cn=Plants,' . $base], $thingsDn),
  1089. ]
  1090. ],
  1091. ];
  1092. }
  1093. /**
  1094. * @param string[] $expectedMembers
  1095. * @dataProvider groupMemberProvider
  1096. */
  1097. public function testGroupMembers(array $expectedResult, array $groupsInfo = null) {
  1098. $this->access->expects($this->any())
  1099. ->method('readAttribute')
  1100. ->willReturnCallback(function ($group) use ($groupsInfo) {
  1101. if (isset($groupsInfo[$group])) {
  1102. return $groupsInfo[$group];
  1103. }
  1104. return [];
  1105. });
  1106. $this->access->connection->expects($this->any())
  1107. ->method('__get')
  1108. ->willReturnCallback(function (string $name) {
  1109. if ($name === 'ldapNestedGroups') {
  1110. return 1;
  1111. } elseif ($name === 'ldapGroupMemberAssocAttr') {
  1112. return 'attr';
  1113. }
  1114. return null;
  1115. });
  1116. $this->initBackend();
  1117. foreach ($expectedResult as $groupDN => $expectedMembers) {
  1118. $resultingMembers = $this->invokePrivate($this->groupBackend, '_groupMembers', [$groupDN]);
  1119. $this->assertEqualsCanonicalizing($expectedMembers, $resultingMembers);
  1120. }
  1121. }
  1122. public function displayNameProvider() {
  1123. return [
  1124. ['Graphic Novelists', ['Graphic Novelists']],
  1125. ['', false],
  1126. ];
  1127. }
  1128. /**
  1129. * @dataProvider displayNameProvider
  1130. */
  1131. public function testGetDisplayName(string $expected, $ldapResult) {
  1132. $gid = 'graphic_novelists';
  1133. $this->access->expects($this->atLeastOnce())
  1134. ->method('readAttribute')
  1135. ->willReturn($ldapResult);
  1136. $this->access->connection->expects($this->any())
  1137. ->method('__get')
  1138. ->willReturnCallback(function ($name) {
  1139. if ($name === 'ldapGroupMemberAssocAttr') {
  1140. return 'member';
  1141. } elseif ($name === 'ldapGroupFilter') {
  1142. return 'objectclass=nextcloudGroup';
  1143. } elseif ($name === 'ldapGroupDisplayName') {
  1144. return 'cn';
  1145. }
  1146. return null;
  1147. });
  1148. $this->access->expects($this->any())
  1149. ->method('groupname2dn')
  1150. ->willReturn('fakedn');
  1151. $this->initBackend();
  1152. $this->assertSame($expected, $this->groupBackend->getDisplayName($gid));
  1153. }
  1154. }