1
0

AccessTest.php 21 KB

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