AccessTest.php 21 KB

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