AccessTest.php 21 KB

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