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 21KB

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