You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

AccessTest.php 22KB

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