AccessTest.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-only
  6. */
  7. namespace OCA\User_LDAP\Tests;
  8. use OCA\User_LDAP\Access;
  9. use OCA\User_LDAP\Connection;
  10. use OCA\User_LDAP\Exceptions\ConstraintViolationException;
  11. use OCA\User_LDAP\FilesystemHelper;
  12. use OCA\User_LDAP\Helper;
  13. use OCA\User_LDAP\ILDAPWrapper;
  14. use OCA\User_LDAP\LDAP;
  15. use OCA\User_LDAP\Mapping\GroupMapping;
  16. use OCA\User_LDAP\Mapping\UserMapping;
  17. use OCA\User_LDAP\User\Manager;
  18. use OCA\User_LDAP\User\OfflineUser;
  19. use OCA\User_LDAP\User\User;
  20. use OCP\IAvatarManager;
  21. use OCP\IConfig;
  22. use OCP\Image;
  23. use OCP\IUserManager;
  24. use OCP\Notification\IManager as INotificationManager;
  25. use OCP\Share\IManager;
  26. use Psr\Log\LoggerInterface;
  27. use Test\TestCase;
  28. /**
  29. * Class AccessTest
  30. *
  31. * @group DB
  32. *
  33. * @package OCA\User_LDAP\Tests
  34. */
  35. class AccessTest extends TestCase {
  36. /** @var UserMapping|\PHPUnit\Framework\MockObject\MockObject */
  37. protected $userMapper;
  38. /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */
  39. protected $shareManager;
  40. /** @var GroupMapping|\PHPUnit\Framework\MockObject\MockObject */
  41. protected $groupMapper;
  42. /** @var Connection|\PHPUnit\Framework\MockObject\MockObject */
  43. private $connection;
  44. /** @var LDAP|\PHPUnit\Framework\MockObject\MockObject */
  45. private $ldap;
  46. /** @var Manager|\PHPUnit\Framework\MockObject\MockObject */
  47. private $userManager;
  48. /** @var Helper|\PHPUnit\Framework\MockObject\MockObject */
  49. private $helper;
  50. /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject */
  51. private $config;
  52. /** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
  53. private $ncUserManager;
  54. /** @var LoggerInterface|MockObject */
  55. private $logger;
  56. /** @var Access */
  57. private $access;
  58. protected function setUp(): void {
  59. $this->connection = $this->createMock(Connection::class);
  60. $this->ldap = $this->createMock(LDAP::class);
  61. $this->userManager = $this->createMock(Manager::class);
  62. $this->helper = $this->createMock(Helper::class);
  63. $this->config = $this->createMock(IConfig::class);
  64. $this->userMapper = $this->createMock(UserMapping::class);
  65. $this->groupMapper = $this->createMock(GroupMapping::class);
  66. $this->ncUserManager = $this->createMock(IUserManager::class);
  67. $this->shareManager = $this->createMock(IManager::class);
  68. $this->logger = $this->createMock(LoggerInterface::class);
  69. $this->access = new Access(
  70. $this->connection,
  71. $this->ldap,
  72. $this->userManager,
  73. $this->helper,
  74. $this->config,
  75. $this->ncUserManager,
  76. $this->logger
  77. );
  78. $this->access->setUserMapper($this->userMapper);
  79. $this->access->setGroupMapper($this->groupMapper);
  80. }
  81. private function getConnectorAndLdapMock() {
  82. $lw = $this->createMock(ILDAPWrapper::class);
  83. $connector = $this->getMockBuilder(Connection::class)
  84. ->setConstructorArgs([$lw, '', null])
  85. ->getMock();
  86. $connector->expects($this->any())
  87. ->method('getConnectionResource')
  88. ->willReturn(ldap_connect('ldap://example.com'));
  89. $um = $this->getMockBuilder(Manager::class)
  90. ->setConstructorArgs([
  91. $this->createMock(IConfig::class),
  92. $this->createMock(FilesystemHelper::class),
  93. $this->createMock(LoggerInterface::class),
  94. $this->createMock(IAvatarManager::class),
  95. $this->createMock(Image::class),
  96. $this->createMock(IUserManager::class),
  97. $this->createMock(INotificationManager::class),
  98. $this->shareManager])
  99. ->getMock();
  100. $helper = new Helper(\OC::$server->getConfig(), \OC::$server->getDatabaseConnection());
  101. return [$lw, $connector, $um, $helper];
  102. }
  103. public function testEscapeFilterPartValidChars() {
  104. $input = 'okay';
  105. $this->assertTrue($input === $this->access->escapeFilterPart($input));
  106. }
  107. public function testEscapeFilterPartEscapeWildcard() {
  108. $input = '*';
  109. $expected = '\\2a';
  110. $this->assertTrue($expected === $this->access->escapeFilterPart($input));
  111. }
  112. public function testEscapeFilterPartEscapeWildcard2() {
  113. $input = 'foo*bar';
  114. $expected = 'foo\\2abar';
  115. $this->assertTrue($expected === $this->access->escapeFilterPart($input));
  116. }
  117. /**
  118. * @dataProvider convertSID2StrSuccessData
  119. * @param array $sidArray
  120. * @param $sidExpected
  121. */
  122. public function testConvertSID2StrSuccess(array $sidArray, $sidExpected) {
  123. $sidBinary = implode('', $sidArray);
  124. $this->assertSame($sidExpected, $this->access->convertSID2Str($sidBinary));
  125. }
  126. public function convertSID2StrSuccessData() {
  127. return [
  128. [
  129. [
  130. "\x01",
  131. "\x04",
  132. "\x00\x00\x00\x00\x00\x05",
  133. "\x15\x00\x00\x00",
  134. "\xa6\x81\xe5\x0e",
  135. "\x4d\x6c\x6c\x2b",
  136. "\xca\x32\x05\x5f",
  137. ],
  138. 'S-1-5-21-249921958-728525901-1594176202',
  139. ],
  140. [
  141. [
  142. "\x01",
  143. "\x02",
  144. "\xFF\xFF\xFF\xFF\xFF\xFF",
  145. "\xFF\xFF\xFF\xFF",
  146. "\xFF\xFF\xFF\xFF",
  147. ],
  148. 'S-1-281474976710655-4294967295-4294967295',
  149. ],
  150. ];
  151. }
  152. public function testConvertSID2StrInputError() {
  153. $sidIllegal = 'foobar';
  154. $sidExpected = '';
  155. $this->assertSame($sidExpected, $this->access->convertSID2Str($sidIllegal));
  156. }
  157. public function testGetDomainDNFromDNSuccess() {
  158. $inputDN = 'uid=zaphod,cn=foobar,dc=my,dc=server,dc=com';
  159. $domainDN = 'dc=my,dc=server,dc=com';
  160. $this->ldap->expects($this->once())
  161. ->method('explodeDN')
  162. ->with($inputDN, 0)
  163. ->willReturn(explode(',', $inputDN));
  164. $this->assertSame($domainDN, $this->access->getDomainDNFromDN($inputDN));
  165. }
  166. public function testGetDomainDNFromDNError() {
  167. $inputDN = 'foobar';
  168. $expected = '';
  169. $this->ldap->expects($this->once())
  170. ->method('explodeDN')
  171. ->with($inputDN, 0)
  172. ->willReturn(false);
  173. $this->assertSame($expected, $this->access->getDomainDNFromDN($inputDN));
  174. }
  175. public function dnInputDataProvider() {
  176. return [[
  177. [
  178. 'input' => 'foo=bar,bar=foo,dc=foobar',
  179. 'interResult' => [
  180. 'count' => 3,
  181. 0 => 'foo=bar',
  182. 1 => 'bar=foo',
  183. 2 => 'dc=foobar'
  184. ],
  185. 'expectedResult' => true
  186. ],
  187. [
  188. 'input' => 'foobarbarfoodcfoobar',
  189. 'interResult' => false,
  190. 'expectedResult' => false
  191. ]
  192. ]];
  193. }
  194. /**
  195. * @dataProvider dnInputDataProvider
  196. * @param array $case
  197. */
  198. public function testStringResemblesDN($case) {
  199. [$lw, $con, $um, $helper] = $this->getConnectorAndLdapMock();
  200. /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject $config */
  201. $config = $this->createMock(IConfig::class);
  202. $access = new Access($con, $lw, $um, $helper, $config, $this->ncUserManager, $this->logger);
  203. $lw->expects($this->exactly(1))
  204. ->method('explodeDN')
  205. ->willReturnCallback(function ($dn) use ($case) {
  206. if ($dn === $case['input']) {
  207. return $case['interResult'];
  208. }
  209. return null;
  210. });
  211. $this->assertSame($case['expectedResult'], $access->stringResemblesDN($case['input']));
  212. }
  213. /**
  214. * @dataProvider dnInputDataProvider
  215. * @param $case
  216. */
  217. public function testStringResemblesDNLDAPmod($case) {
  218. [, $con, $um, $helper] = $this->getConnectorAndLdapMock();
  219. /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject $config */
  220. $config = $this->createMock(IConfig::class);
  221. $lw = new LDAP();
  222. $access = new Access($con, $lw, $um, $helper, $config, $this->ncUserManager, $this->logger);
  223. if (!function_exists('ldap_explode_dn')) {
  224. $this->markTestSkipped('LDAP Module not available');
  225. }
  226. $this->assertSame($case['expectedResult'], $access->stringResemblesDN($case['input']));
  227. }
  228. public function testCacheUserHome() {
  229. $this->connection->expects($this->once())
  230. ->method('writeToCache');
  231. $this->access->cacheUserHome('foobar', '/foobars/path');
  232. }
  233. public function testBatchApplyUserAttributes() {
  234. $this->ldap->expects($this->any())
  235. ->method('isResource')
  236. ->willReturn(true);
  237. $this->connection
  238. ->expects($this->any())
  239. ->method('getConnectionResource')
  240. ->willReturn(ldap_connect('ldap://example.com'));
  241. $this->ldap->expects($this->any())
  242. ->method('getAttributes')
  243. ->willReturn(['displayname' => ['bar', 'count' => 1]]);
  244. /** @var UserMapping|\PHPUnit\Framework\MockObject\MockObject $mapperMock */
  245. $mapperMock = $this->createMock(UserMapping::class);
  246. $mapperMock->expects($this->any())
  247. ->method('getNameByDN')
  248. ->willReturn(false);
  249. $mapperMock->expects($this->any())
  250. ->method('map')
  251. ->willReturn(true);
  252. $userMock = $this->createMock(User::class);
  253. // also returns for userUuidAttribute
  254. $this->access->connection->expects($this->any())
  255. ->method('__get')
  256. ->willReturn('displayName');
  257. $this->access->setUserMapper($mapperMock);
  258. $displayNameAttribute = strtolower($this->access->connection->ldapUserDisplayName);
  259. $data = [
  260. [
  261. 'dn' => ['foobar'],
  262. $displayNameAttribute => 'barfoo'
  263. ],
  264. [
  265. 'dn' => ['foo'],
  266. $displayNameAttribute => 'bar'
  267. ],
  268. [
  269. 'dn' => ['raboof'],
  270. $displayNameAttribute => 'oofrab'
  271. ]
  272. ];
  273. $userMock->expects($this->exactly(count($data)))
  274. ->method('processAttributes');
  275. $this->userManager->expects($this->exactly(count($data) * 2))
  276. ->method('get')
  277. ->willReturn($userMock);
  278. $this->access->batchApplyUserAttributes($data);
  279. }
  280. public function testBatchApplyUserAttributesSkipped() {
  281. /** @var UserMapping|\PHPUnit\Framework\MockObject\MockObject $mapperMock */
  282. $mapperMock = $this->createMock(UserMapping::class);
  283. $mapperMock->expects($this->any())
  284. ->method('getNameByDN')
  285. ->willReturn('a_username');
  286. $userMock = $this->createMock(User::class);
  287. $this->access->connection->expects($this->any())
  288. ->method('__get')
  289. ->willReturn('displayName');
  290. $this->access->setUserMapper($mapperMock);
  291. $displayNameAttribute = strtolower($this->access->connection->ldapUserDisplayName);
  292. $data = [
  293. [
  294. 'dn' => ['foobar'],
  295. $displayNameAttribute => 'barfoo'
  296. ],
  297. [
  298. 'dn' => ['foo'],
  299. $displayNameAttribute => 'bar'
  300. ],
  301. [
  302. 'dn' => ['raboof'],
  303. $displayNameAttribute => 'oofrab'
  304. ]
  305. ];
  306. $userMock->expects($this->never())
  307. ->method('processAttributes');
  308. $this->userManager->expects($this->any())
  309. ->method('get')
  310. ->willReturn($this->createMock(User::class));
  311. $this->access->batchApplyUserAttributes($data);
  312. }
  313. public function testBatchApplyUserAttributesDontSkip() {
  314. /** @var UserMapping|\PHPUnit\Framework\MockObject\MockObject $mapperMock */
  315. $mapperMock = $this->createMock(UserMapping::class);
  316. $mapperMock->expects($this->any())
  317. ->method('getNameByDN')
  318. ->willReturn('a_username');
  319. $userMock = $this->createMock(User::class);
  320. $this->access->connection->expects($this->any())
  321. ->method('__get')
  322. ->willReturn('displayName');
  323. $this->access->setUserMapper($mapperMock);
  324. $displayNameAttribute = strtolower($this->access->connection->ldapUserDisplayName);
  325. $data = [
  326. [
  327. 'dn' => ['foobar'],
  328. $displayNameAttribute => 'barfoo'
  329. ],
  330. [
  331. 'dn' => ['foo'],
  332. $displayNameAttribute => 'bar'
  333. ],
  334. [
  335. 'dn' => ['raboof'],
  336. $displayNameAttribute => 'oofrab'
  337. ]
  338. ];
  339. $userMock->expects($this->exactly(count($data)))
  340. ->method('processAttributes');
  341. $this->userManager->expects($this->exactly(count($data) * 2))
  342. ->method('get')
  343. ->willReturn($userMock);
  344. $this->access->batchApplyUserAttributes($data);
  345. }
  346. public function dNAttributeProvider() {
  347. // corresponds to Access::resemblesDN()
  348. return [
  349. 'dn' => ['dn'],
  350. 'uniqueMember' => ['uniquemember'],
  351. 'member' => ['member'],
  352. 'memberOf' => ['memberof']
  353. ];
  354. }
  355. /**
  356. * @dataProvider dNAttributeProvider
  357. * @param $attribute
  358. */
  359. public function testSanitizeDN($attribute) {
  360. [$lw, $con, $um, $helper] = $this->getConnectorAndLdapMock();
  361. /** @var IConfig|\PHPUnit\Framework\MockObject\MockObject $config */
  362. $config = $this->createMock(IConfig::class);
  363. $dnFromServer = 'cn=Mixed Cases,ou=Are Sufficient To,ou=Test,dc=example,dc=org';
  364. $lw->expects($this->any())
  365. ->method('isResource')
  366. ->willReturn(true);
  367. $lw->expects($this->any())
  368. ->method('getAttributes')
  369. ->willReturn([
  370. $attribute => ['count' => 1, $dnFromServer]
  371. ]);
  372. $access = new Access($con, $lw, $um, $helper, $config, $this->ncUserManager, $this->logger);
  373. $values = $access->readAttribute('uid=whoever,dc=example,dc=org', $attribute);
  374. $this->assertSame($values[0], strtolower($dnFromServer));
  375. }
  376. public function testSetPasswordWithDisabledChanges() {
  377. $this->expectException(\Exception::class);
  378. $this->expectExceptionMessage('LDAP password changes are disabled');
  379. $this->connection
  380. ->method('__get')
  381. ->willReturn(false);
  382. /** @noinspection PhpUnhandledExceptionInspection */
  383. $this->access->setPassword('CN=foo', 'MyPassword');
  384. }
  385. public function testSetPasswordWithLdapNotAvailable() {
  386. $this->connection
  387. ->method('__get')
  388. ->willReturn(true);
  389. $connection = ldap_connect('ldap://example.com');
  390. $this->connection
  391. ->expects($this->once())
  392. ->method('getConnectionResource')
  393. ->willThrowException(new \OC\ServerNotAvailableException('Connection to LDAP server could not be established'));
  394. $this->ldap
  395. ->expects($this->never())
  396. ->method('isResource');
  397. $this->expectException(\OC\ServerNotAvailableException::class);
  398. $this->expectExceptionMessage('Connection to LDAP server could not be established');
  399. $this->access->setPassword('CN=foo', 'MyPassword');
  400. }
  401. public function testSetPasswordWithRejectedChange() {
  402. $this->expectException(\OCP\HintException::class);
  403. $this->expectExceptionMessage('Password change rejected.');
  404. $this->connection
  405. ->method('__get')
  406. ->willReturn(true);
  407. $connection = ldap_connect('ldap://example.com');
  408. $this->connection
  409. ->expects($this->any())
  410. ->method('getConnectionResource')
  411. ->willReturn($connection);
  412. $this->ldap
  413. ->expects($this->once())
  414. ->method('modReplace')
  415. ->with($connection, 'CN=foo', 'MyPassword')
  416. ->willThrowException(new ConstraintViolationException());
  417. /** @noinspection PhpUnhandledExceptionInspection */
  418. $this->access->setPassword('CN=foo', 'MyPassword');
  419. }
  420. public function testSetPassword() {
  421. $this->connection
  422. ->method('__get')
  423. ->willReturn(true);
  424. $connection = ldap_connect('ldap://example.com');
  425. $this->connection
  426. ->expects($this->any())
  427. ->method('getConnectionResource')
  428. ->willReturn($connection);
  429. $this->ldap
  430. ->expects($this->once())
  431. ->method('modReplace')
  432. ->with($connection, 'CN=foo', 'MyPassword')
  433. ->willReturn(true);
  434. /** @noinspection PhpUnhandledExceptionInspection */
  435. $this->assertTrue($this->access->setPassword('CN=foo', 'MyPassword'));
  436. }
  437. protected function prepareMocksForSearchTests(
  438. $base,
  439. $fakeConnection,
  440. $fakeSearchResultResource,
  441. $fakeLdapEntries
  442. ) {
  443. $this->connection
  444. ->expects($this->any())
  445. ->method('getConnectionResource')
  446. ->willReturn($fakeConnection);
  447. $this->connection->expects($this->any())
  448. ->method('__get')
  449. ->willReturnCallback(function ($key) use ($base) {
  450. if (stripos($key, 'base') !== false) {
  451. return [$base];
  452. }
  453. return null;
  454. });
  455. $this->ldap
  456. ->expects($this->any())
  457. ->method('isResource')
  458. ->willReturnCallback(function ($resource) {
  459. return is_object($resource);
  460. });
  461. $this->ldap
  462. ->expects($this->any())
  463. ->method('errno')
  464. ->willReturn(0);
  465. $this->ldap
  466. ->expects($this->once())
  467. ->method('search')
  468. ->willReturn($fakeSearchResultResource);
  469. $this->ldap
  470. ->expects($this->exactly(1))
  471. ->method('getEntries')
  472. ->willReturn($fakeLdapEntries);
  473. $this->helper->expects($this->any())
  474. ->method('sanitizeDN')
  475. ->willReturnArgument(0);
  476. }
  477. public function testSearchNoPagedSearch() {
  478. // scenario: no pages search, 1 search base
  479. $filter = 'objectClass=nextcloudUser';
  480. $base = 'ou=zombies,dc=foobar,dc=nextcloud,dc=com';
  481. $fakeConnection = ldap_connect();
  482. $fakeSearchResultResource = ldap_connect();
  483. $fakeLdapEntries = [
  484. 'count' => 2,
  485. [
  486. 'dn' => 'uid=sgarth,' . $base,
  487. ],
  488. [
  489. 'dn' => 'uid=wwilson,' . $base,
  490. ]
  491. ];
  492. $expected = $fakeLdapEntries;
  493. unset($expected['count']);
  494. $this->prepareMocksForSearchTests($base, $fakeConnection, $fakeSearchResultResource, $fakeLdapEntries);
  495. /** @noinspection PhpUnhandledExceptionInspection */
  496. $result = $this->access->search($filter, $base);
  497. $this->assertSame($expected, $result);
  498. }
  499. public function testFetchListOfUsers() {
  500. $filter = 'objectClass=nextcloudUser';
  501. $base = 'ou=zombies,dc=foobar,dc=nextcloud,dc=com';
  502. $attrs = ['dn', 'uid'];
  503. $fakeConnection = ldap_connect();
  504. $fakeSearchResultResource = ldap_connect();
  505. $fakeLdapEntries = [
  506. 'count' => 2,
  507. [
  508. 'dn' => 'uid=sgarth,' . $base,
  509. 'uid' => [ 'sgarth' ],
  510. ],
  511. [
  512. 'dn' => 'uid=wwilson,' . $base,
  513. 'uid' => [ 'wwilson' ],
  514. ]
  515. ];
  516. $expected = $fakeLdapEntries;
  517. unset($expected['count']);
  518. array_walk($expected, function (&$v) {
  519. $v['dn'] = [$v['dn']]; // dn is translated into an array internally for consistency
  520. });
  521. $this->prepareMocksForSearchTests($base, $fakeConnection, $fakeSearchResultResource, $fakeLdapEntries);
  522. $this->connection->expects($this->exactly($fakeLdapEntries['count']))
  523. ->method('writeToCache')
  524. ->with($this->stringStartsWith('userExists'), true);
  525. $this->userMapper->expects($this->exactly($fakeLdapEntries['count']))
  526. ->method('getNameByDN')
  527. ->willReturnCallback(function ($fdn) {
  528. $parts = ldap_explode_dn($fdn, false);
  529. return $parts[0];
  530. });
  531. /** @noinspection PhpUnhandledExceptionInspection */
  532. $list = $this->access->fetchListOfUsers($filter, $attrs);
  533. $this->assertSame($expected, $list);
  534. }
  535. public function testFetchListOfGroupsKnown() {
  536. $filter = 'objectClass=nextcloudGroup';
  537. $attributes = ['cn', 'gidNumber', 'dn'];
  538. $base = 'ou=SomeGroups,dc=my,dc=directory';
  539. $fakeConnection = ldap_connect();
  540. $fakeSearchResultResource = ldap_connect();
  541. $fakeLdapEntries = [
  542. 'count' => 2,
  543. [
  544. 'dn' => 'cn=Good Team,' . $base,
  545. 'cn' => ['Good Team'],
  546. ],
  547. [
  548. 'dn' => 'cn=Another Good Team,' . $base,
  549. 'cn' => ['Another Good Team'],
  550. ]
  551. ];
  552. $this->prepareMocksForSearchTests($base, $fakeConnection, $fakeSearchResultResource, $fakeLdapEntries);
  553. $this->groupMapper->expects($this->any())
  554. ->method('getListOfIdsByDn')
  555. ->willReturn([
  556. 'cn=Good Team,' . $base => 'Good_Team',
  557. 'cn=Another Good Team,' . $base => 'Another_Good_Team',
  558. ]);
  559. $this->groupMapper->expects($this->never())
  560. ->method('getNameByDN');
  561. $this->connection->expects($this->exactly(3))
  562. ->method('writeToCache');
  563. $groups = $this->access->fetchListOfGroups($filter, $attributes);
  564. $this->assertSame(2, count($groups));
  565. $this->assertSame('Good Team', $groups[0]['cn'][0]);
  566. $this->assertSame('Another Good Team', $groups[1]['cn'][0]);
  567. }
  568. public function intUsernameProvider() {
  569. return [
  570. ['alice', 'alice'],
  571. ['b/ob', 'bob'],
  572. ['charly🐬', 'charly'],
  573. ['debo rah', 'debo_rah'],
  574. ['epost@poste.test', 'epost@poste.test'],
  575. ['fränk', 'frank'],
  576. [' UPPÉR Case/[\]^`', 'UPPER_Case'],
  577. [' gerda ', 'gerda'],
  578. ['🕱🐵🐘🐑', null],
  579. [
  580. 'OneNameToRuleThemAllOneNameToFindThemOneNameToBringThemAllAndInTheDarknessBindThem',
  581. '81ff71b5dd0f0092e2dc977b194089120093746e273f2ef88c11003762783127'
  582. ]
  583. ];
  584. }
  585. public function groupIDCandidateProvider() {
  586. return [
  587. ['alice', 'alice'],
  588. ['b/ob', 'b/ob'],
  589. ['charly🐬', 'charly🐬'],
  590. ['debo rah', 'debo rah'],
  591. ['epost@poste.test', 'epost@poste.test'],
  592. ['fränk', 'fränk'],
  593. [' gerda ', 'gerda'],
  594. ['🕱🐵🐘🐑', '🕱🐵🐘🐑'],
  595. [
  596. 'OneNameToRuleThemAllOneNameToFindThemOneNameToBringThemAllAndInTheDarknessBindThem',
  597. '81ff71b5dd0f0092e2dc977b194089120093746e273f2ef88c11003762783127'
  598. ]
  599. ];
  600. }
  601. /**
  602. * @dataProvider intUsernameProvider
  603. *
  604. * @param $name
  605. * @param $expected
  606. */
  607. public function testSanitizeUsername($name, $expected) {
  608. if ($expected === null) {
  609. $this->expectException(\InvalidArgumentException::class);
  610. }
  611. $sanitizedName = $this->access->sanitizeUsername($name);
  612. $this->assertSame($expected, $sanitizedName);
  613. }
  614. /**
  615. * @dataProvider groupIDCandidateProvider
  616. */
  617. public function testSanitizeGroupIDCandidate(string $name, string $expected) {
  618. $sanitizedName = $this->access->sanitizeGroupIDCandidate($name);
  619. $this->assertSame($expected, $sanitizedName);
  620. }
  621. public function testUserStateUpdate() {
  622. $this->connection->expects($this->any())
  623. ->method('__get')
  624. ->willReturnMap([
  625. [ 'ldapUserDisplayName', 'displayName' ],
  626. [ 'ldapUserDisplayName2', null],
  627. ]);
  628. $offlineUserMock = $this->createMock(OfflineUser::class);
  629. $offlineUserMock->expects($this->once())
  630. ->method('unmark');
  631. $regularUserMock = $this->createMock(User::class);
  632. $this->userManager->expects($this->atLeastOnce())
  633. ->method('get')
  634. ->with('detta')
  635. ->willReturnOnConsecutiveCalls($offlineUserMock, $regularUserMock);
  636. /** @var UserMapping|\PHPUnit\Framework\MockObject\MockObject $mapperMock */
  637. $mapperMock = $this->createMock(UserMapping::class);
  638. $mapperMock->expects($this->any())
  639. ->method('getNameByDN')
  640. ->with('uid=detta,ou=users,dc=hex,dc=ample')
  641. ->willReturn('detta');
  642. $this->access->setUserMapper($mapperMock);
  643. $records = [
  644. [
  645. 'dn' => ['uid=detta,ou=users,dc=hex,dc=ample'],
  646. 'displayName' => ['Detta Detkova'],
  647. ]
  648. ];
  649. $this->access->nextcloudUserNames($records);
  650. }
  651. }