AccessTest.php 22 KB

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