WizardTest.php 12 KB

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