WizardTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 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\Configuration;
  10. use OCA\User_LDAP\ILDAPWrapper;
  11. use OCA\User_LDAP\Wizard;
  12. use PHPUnit\Framework\MockObject\MockObject;
  13. use Test\TestCase;
  14. /**
  15. * Class Test_Wizard
  16. *
  17. * @group DB
  18. *
  19. * @package OCA\User_LDAP\Tests
  20. */
  21. class WizardTest extends TestCase {
  22. protected function setUp(): void {
  23. parent::setUp();
  24. //we need to make sure the consts are defined, otherwise tests will fail
  25. //on systems without php5_ldap
  26. $ldapConsts = ['LDAP_OPT_PROTOCOL_VERSION',
  27. 'LDAP_OPT_REFERRALS', 'LDAP_OPT_NETWORK_TIMEOUT'];
  28. foreach ($ldapConsts as $const) {
  29. if (!defined($const)) {
  30. define($const, 42);
  31. }
  32. }
  33. }
  34. private function getWizardAndMocks() {
  35. static $confMethods;
  36. static $connMethods;
  37. static $accMethods;
  38. if (is_null($confMethods)) {
  39. $confMethods = get_class_methods('\OCA\User_LDAP\Configuration');
  40. $connMethods = get_class_methods('\OCA\User_LDAP\Connection');
  41. $accMethods = get_class_methods('\OCA\User_LDAP\Access');
  42. }
  43. /** @var ILDAPWrapper|\PHPUnit\Framework\MockObject\MockObject $lw */
  44. $lw = $this->createMock(ILDAPWrapper::class);
  45. /** @var Configuration|\PHPUnit\Framework\MockObject\MockObject $conf */
  46. $conf = $this->getMockBuilder(Configuration::class)
  47. ->setMethods($confMethods)
  48. ->setConstructorArgs(['', true])
  49. ->getMock();
  50. /** @var Access|\PHPUnit\Framework\MockObject\MockObject $access */
  51. $access = $this->createMock(Access::class);
  52. return [new Wizard($conf, $lw, $access), $conf, $lw, $access];
  53. }
  54. private function prepareLdapWrapperForConnections(MockObject &$ldap) {
  55. $ldap->expects($this->once())
  56. ->method('connect')
  57. //dummy value
  58. ->willReturn(ldap_connect('ldap://example.com'));
  59. $ldap->expects($this->exactly(3))
  60. ->method('setOption')
  61. ->willReturn(true);
  62. $ldap->expects($this->once())
  63. ->method('bind')
  64. ->willReturn(true);
  65. }
  66. public function testCumulativeSearchOnAttributeLimited(): void {
  67. [$wizard, $configuration, $ldap] = $this->getWizardAndMocks();
  68. $configuration->expects($this->any())
  69. ->method('__get')
  70. ->willReturnCallback(function ($name) {
  71. if ($name === 'ldapBase') {
  72. return ['base'];
  73. }
  74. return null;
  75. });
  76. $this->prepareLdapWrapperForConnections($ldap);
  77. $ldap->expects($this->any())
  78. ->method('isResource')
  79. ->willReturn(true);
  80. $ldap->expects($this->exactly(2))
  81. ->method('search')
  82. //dummy value, usually invalid
  83. ->willReturn(true);
  84. $ldap->expects($this->exactly(2))
  85. ->method('countEntries')
  86. //an is_resource check will follow, so we need to return a dummy resource
  87. ->willReturn(23);
  88. //5 DNs per filter means 2x firstEntry and 8x nextEntry
  89. $ldap->expects($this->exactly(2))
  90. ->method('firstEntry')
  91. //dummy value, usually invalid
  92. ->willReturn(true);
  93. $ldap->expects($this->exactly(8))
  94. ->method('nextEntry')
  95. //dummy value, usually invalid
  96. ->willReturn(true);
  97. $ldap->expects($this->exactly(10))
  98. ->method('getAttributes')
  99. //dummy value, usually invalid
  100. ->willReturn(['cn' => ['foo'], 'count' => 1]);
  101. global $uidnumber;
  102. $uidnumber = 1;
  103. $ldap->expects($this->exactly(10))
  104. ->method('getDN')
  105. //dummy value, usually invalid
  106. ->willReturnCallback(function ($a, $b) {
  107. global $uidnumber;
  108. return $uidnumber++;
  109. });
  110. // The following expectations are the real test
  111. $filters = ['f1', 'f2', '*'];
  112. $wizard->cumulativeSearchOnAttribute($filters, 'cn', 5);
  113. unset($uidnumber);
  114. }
  115. public function testCumulativeSearchOnAttributeUnlimited(): void {
  116. [$wizard, $configuration, $ldap] = $this->getWizardAndMocks();
  117. $configuration->expects($this->any())
  118. ->method('__get')
  119. ->willReturnCallback(function ($name) {
  120. if ($name === 'ldapBase') {
  121. return ['base'];
  122. }
  123. return null;
  124. });
  125. $this->prepareLdapWrapperForConnections($ldap);
  126. $ldap->expects($this->any())
  127. ->method('isResource')
  128. ->willReturnCallback(function ($r) {
  129. if ($r instanceof \LDAP\Connection) {
  130. return true;
  131. }
  132. if ($r % 24 === 0) {
  133. global $uidnumber;
  134. $uidnumber++;
  135. return false;
  136. }
  137. return true;
  138. });
  139. $ldap->expects($this->exactly(2))
  140. ->method('search')
  141. //dummy value, usually invalid
  142. ->willReturn(true);
  143. $ldap->expects($this->exactly(2))
  144. ->method('countEntries')
  145. //an is_resource check will follow, so we need to return a dummy resource
  146. ->willReturn(23);
  147. //5 DNs per filter means 2x firstEntry and 8x nextEntry
  148. $ldap->expects($this->exactly(2))
  149. ->method('firstEntry')
  150. //dummy value, usually invalid
  151. ->willReturnCallback(function ($r) {
  152. global $uidnumber;
  153. return $uidnumber;
  154. });
  155. $ldap->expects($this->exactly(46))
  156. ->method('nextEntry')
  157. //dummy value, usually invalid
  158. ->willReturnCallback(function ($r) {
  159. global $uidnumber;
  160. return $uidnumber;
  161. });
  162. $ldap->expects($this->exactly(46))
  163. ->method('getAttributes')
  164. //dummy value, usually invalid
  165. ->willReturn(['cn' => ['foo'], 'count' => 1]);
  166. global $uidnumber;
  167. $uidnumber = 1;
  168. $ldap->expects($this->exactly(46))
  169. ->method('getDN')
  170. //dummy value, usually invalid
  171. ->willReturnCallback(function ($a, $b) {
  172. global $uidnumber;
  173. return $uidnumber++;
  174. });
  175. // The following expectations are the real test
  176. $filters = ['f1', 'f2', '*'];
  177. $wizard->cumulativeSearchOnAttribute($filters, 'cn', 0);
  178. unset($uidnumber);
  179. }
  180. public function testDetectEmailAttributeAlreadySet(): void {
  181. [$wizard, $configuration, $ldap, $access]
  182. = $this->getWizardAndMocks();
  183. $configuration->expects($this->any())
  184. ->method('__get')
  185. ->willReturnCallback(function ($name) {
  186. if ($name === 'ldapEmailAttribute') {
  187. return 'myEmailAttribute';
  188. } else {
  189. //for requirement checks
  190. return 'let me pass';
  191. }
  192. });
  193. $access->expects($this->once())
  194. ->method('countUsers')
  195. ->willReturn(42);
  196. $wizard->detectEmailAttribute();
  197. }
  198. public function testDetectEmailAttributeOverrideSet(): void {
  199. [$wizard, $configuration, $ldap, $access]
  200. = $this->getWizardAndMocks();
  201. $configuration->expects($this->any())
  202. ->method('__get')
  203. ->willReturnCallback(function ($name) {
  204. if ($name === 'ldapEmailAttribute') {
  205. return 'myEmailAttribute';
  206. } else {
  207. //for requirement checks
  208. return 'let me pass';
  209. }
  210. });
  211. $access->expects($this->exactly(3))
  212. ->method('combineFilterWithAnd')
  213. ->willReturnCallback(function ($filterParts) {
  214. return str_replace('=*', '', array_pop($filterParts));
  215. });
  216. $access->expects($this->exactly(3))
  217. ->method('countUsers')
  218. ->willReturnCallback(function ($filter) {
  219. if ($filter === 'myEmailAttribute') {
  220. return 0;
  221. } elseif ($filter === 'mail') {
  222. return 3;
  223. } elseif ($filter === 'mailPrimaryAddress') {
  224. return 17;
  225. }
  226. throw new \Exception('Untested filter: ' . $filter);
  227. });
  228. $result = $wizard->detectEmailAttribute()->getResultArray();
  229. $this->assertSame('mailPrimaryAddress',
  230. $result['changes']['ldap_email_attr']);
  231. }
  232. public function testDetectEmailAttributeFind(): void {
  233. [$wizard, $configuration, $ldap, $access]
  234. = $this->getWizardAndMocks();
  235. $configuration->expects($this->any())
  236. ->method('__get')
  237. ->willReturnCallback(function ($name) {
  238. if ($name === 'ldapEmailAttribute') {
  239. return '';
  240. } else {
  241. //for requirement checks
  242. return 'let me pass';
  243. }
  244. });
  245. $access->expects($this->exactly(2))
  246. ->method('combineFilterWithAnd')
  247. ->willReturnCallback(function ($filterParts) {
  248. return str_replace('=*', '', array_pop($filterParts));
  249. });
  250. $access->expects($this->exactly(2))
  251. ->method('countUsers')
  252. ->willReturnCallback(function ($filter) {
  253. if ($filter === 'myEmailAttribute') {
  254. return 0;
  255. } elseif ($filter === 'mail') {
  256. return 3;
  257. } elseif ($filter === 'mailPrimaryAddress') {
  258. return 17;
  259. }
  260. throw new \Exception('Untested filter: ' . $filter);
  261. });
  262. $result = $wizard->detectEmailAttribute()->getResultArray();
  263. $this->assertSame('mailPrimaryAddress',
  264. $result['changes']['ldap_email_attr']);
  265. }
  266. public function testDetectEmailAttributeFindNothing(): void {
  267. [$wizard, $configuration, $ldap, $access]
  268. = $this->getWizardAndMocks();
  269. $configuration->expects($this->any())
  270. ->method('__get')
  271. ->willReturnCallback(function ($name) {
  272. if ($name === 'ldapEmailAttribute') {
  273. return 'myEmailAttribute';
  274. } else {
  275. //for requirement checks
  276. return 'let me pass';
  277. }
  278. });
  279. $access->expects($this->exactly(3))
  280. ->method('combineFilterWithAnd')
  281. ->willReturnCallback(function ($filterParts) {
  282. return str_replace('=*', '', array_pop($filterParts));
  283. });
  284. $access->expects($this->exactly(3))
  285. ->method('countUsers')
  286. ->willReturnCallback(function ($filter) {
  287. if ($filter === 'myEmailAttribute') {
  288. return 0;
  289. } elseif ($filter === 'mail') {
  290. return 0;
  291. } elseif ($filter === 'mailPrimaryAddress') {
  292. return 0;
  293. }
  294. throw new \Exception('Untested filter: ' . $filter);
  295. });
  296. $result = $wizard->detectEmailAttribute();
  297. $this->assertSame(false, $result->hasChanges());
  298. }
  299. public function testCumulativeSearchOnAttributeSkipReadDN(): void {
  300. // tests that there is no infinite loop, when skipping already processed
  301. // DNs (they can be returned multiple times for multiple filters )
  302. [$wizard, $configuration, $ldap] = $this->getWizardAndMocks();
  303. $configuration->expects($this->any())
  304. ->method('__get')
  305. ->willReturnCallback(function ($name) {
  306. if ($name === 'ldapBase') {
  307. return ['base'];
  308. }
  309. return null;
  310. });
  311. $this->prepareLdapWrapperForConnections($ldap);
  312. $ldap->expects($this->any())
  313. ->method('isResource')
  314. ->willReturnCallback(function ($res) {
  315. return (bool)$res;
  316. });
  317. $ldap->expects($this->any())
  318. ->method('search')
  319. //dummy value, usually invalid
  320. ->willReturn(true);
  321. $ldap->expects($this->any())
  322. ->method('countEntries')
  323. //an is_resource check will follow, so we need to return a dummy resource
  324. ->willReturn(7);
  325. //5 DNs per filter means 2x firstEntry and 8x nextEntry
  326. $ldap->expects($this->any())
  327. ->method('firstEntry')
  328. //dummy value, usually invalid
  329. ->willReturn(1);
  330. global $mark;
  331. $mark = false;
  332. // entries return order: 1, 2, 3, 4, 4, 5, 6
  333. $ldap->expects($this->any())
  334. ->method('nextEntry')
  335. //dummy value, usually invalid
  336. ->willReturnCallback(function ($a, $prev) {
  337. $current = $prev + 1;
  338. if ($current === 7) {
  339. return false;
  340. }
  341. global $mark;
  342. if ($prev === 4 && !$mark) {
  343. $mark = true;
  344. return 4;
  345. }
  346. return $current;
  347. });
  348. $ldap->expects($this->any())
  349. ->method('getAttributes')
  350. //dummy value, usually invalid
  351. ->willReturnCallback(function ($a, $entry) {
  352. return ['cn' => [$entry], 'count' => 1];
  353. });
  354. $ldap->expects($this->any())
  355. ->method('getDN')
  356. //dummy value, usually invalid
  357. ->willReturnCallback(function ($a, $b) {
  358. return $b;
  359. });
  360. // The following expectations are the real test
  361. $filters = ['f1', 'f2', '*'];
  362. $resultArray = $wizard->cumulativeSearchOnAttribute($filters, 'cn', 0);
  363. $this->assertSame(6, count($resultArray));
  364. unset($mark);
  365. }
  366. }