PageRenderTime 36ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/test/classes/Plugins/Auth/AuthenticationCookieTest.php

http://github.com/phpmyadmin/phpmyadmin
PHP | 1326 lines | 1038 code | 200 blank | 88 comment | 7 complexity | 540125d6440edce0f7acf1b242555cde MD5 | raw file
Possible License(s): GPL-2.0, MIT, LGPL-3.0
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Tests\Plugins\Auth;
  4. use PhpMyAdmin\DatabaseInterface;
  5. use PhpMyAdmin\ErrorHandler;
  6. use PhpMyAdmin\Footer;
  7. use PhpMyAdmin\Header;
  8. use PhpMyAdmin\Plugins\Auth\AuthenticationCookie;
  9. use PhpMyAdmin\ResponseRenderer;
  10. use PhpMyAdmin\Tests\AbstractNetworkTestCase;
  11. use ReflectionException;
  12. use ReflectionMethod;
  13. use function base64_encode;
  14. use function is_readable;
  15. use function json_encode;
  16. use function ob_get_clean;
  17. use function ob_start;
  18. use function str_repeat;
  19. use function str_shuffle;
  20. use function strlen;
  21. use function time;
  22. /**
  23. * @covers \PhpMyAdmin\Plugins\Auth\AuthenticationCookie
  24. */
  25. class AuthenticationCookieTest extends AbstractNetworkTestCase
  26. {
  27. /** @var AuthenticationCookie */
  28. protected $object;
  29. /**
  30. * Configures global environment.
  31. */
  32. protected function setUp(): void
  33. {
  34. parent::setUp();
  35. parent::setLanguage();
  36. parent::setTheme();
  37. parent::setGlobalConfig();
  38. $GLOBALS['server'] = 0;
  39. $GLOBALS['text_dir'] = 'ltr';
  40. $GLOBALS['db'] = 'db';
  41. $GLOBALS['table'] = 'table';
  42. $_POST['pma_password'] = '';
  43. $this->object = new AuthenticationCookie();
  44. $GLOBALS['PMA_PHP_SELF'] = '/phpmyadmin/';
  45. $GLOBALS['cfg']['Server']['DisableIS'] = false;
  46. }
  47. /**
  48. * tearDown for test cases
  49. */
  50. protected function tearDown(): void
  51. {
  52. parent::tearDown();
  53. unset($this->object);
  54. }
  55. /**
  56. * @group medium
  57. */
  58. public function testAuthErrorAJAX(): void
  59. {
  60. $mockResponse = $this->mockResponse();
  61. $mockResponse->expects($this->once())
  62. ->method('isAjax')
  63. ->with()
  64. ->will($this->returnValue(true));
  65. $mockResponse->expects($this->once())
  66. ->method('setRequestStatus')
  67. ->with(false);
  68. $mockResponse->expects($this->once())
  69. ->method('addJSON')
  70. ->with('redirect_flag', '1');
  71. $GLOBALS['conn_error'] = true;
  72. $this->assertTrue(
  73. $this->object->showLoginForm()
  74. );
  75. }
  76. private function getAuthErrorMockResponse(): void
  77. {
  78. $mockResponse = $this->mockResponse();
  79. $mockResponse->expects($this->once())
  80. ->method('isAjax')
  81. ->with()
  82. ->will($this->returnValue(false));
  83. // mock footer
  84. $mockFooter = $this->getMockBuilder(Footer::class)
  85. ->disableOriginalConstructor()
  86. ->onlyMethods(['setMinimal'])
  87. ->getMock();
  88. $mockFooter->expects($this->once())
  89. ->method('setMinimal')
  90. ->with();
  91. // mock header
  92. $mockHeader = $this->getMockBuilder(Header::class)
  93. ->disableOriginalConstructor()
  94. ->onlyMethods(
  95. [
  96. 'setBodyId',
  97. 'setTitle',
  98. 'disableMenuAndConsole',
  99. 'disableWarnings',
  100. ]
  101. )
  102. ->getMock();
  103. $mockHeader->expects($this->once())
  104. ->method('setBodyId')
  105. ->with('loginform');
  106. $mockHeader->expects($this->once())
  107. ->method('setTitle')
  108. ->with('phpMyAdmin');
  109. $mockHeader->expects($this->once())
  110. ->method('disableMenuAndConsole')
  111. ->with();
  112. $mockHeader->expects($this->once())
  113. ->method('disableWarnings')
  114. ->with();
  115. // set mocked headers and footers
  116. $mockResponse->expects($this->once())
  117. ->method('getFooter')
  118. ->with()
  119. ->will($this->returnValue($mockFooter));
  120. $mockResponse->expects($this->once())
  121. ->method('getHeader')
  122. ->with()
  123. ->will($this->returnValue($mockHeader));
  124. $GLOBALS['cfg']['Servers'] = [
  125. 1,
  126. 2,
  127. ];
  128. // mock error handler
  129. $mockErrorHandler = $this->getMockBuilder(ErrorHandler::class)
  130. ->disableOriginalConstructor()
  131. ->onlyMethods(['hasDisplayErrors'])
  132. ->getMock();
  133. $mockErrorHandler->expects($this->once())
  134. ->method('hasDisplayErrors')
  135. ->with()
  136. ->will($this->returnValue(true));
  137. $GLOBALS['errorHandler'] = $mockErrorHandler;
  138. }
  139. /**
  140. * @group medium
  141. */
  142. public function testAuthError(): void
  143. {
  144. $_REQUEST = [];
  145. ResponseRenderer::getInstance()->setAjax(false);
  146. $_REQUEST['old_usr'] = '';
  147. $GLOBALS['cfg']['LoginCookieRecall'] = true;
  148. $GLOBALS['cfg']['blowfish_secret'] = 'secret';
  149. $this->object->user = 'pmauser';
  150. $GLOBALS['pma_auth_server'] = 'localhost';
  151. $GLOBALS['conn_error'] = true;
  152. $GLOBALS['cfg']['Lang'] = 'en';
  153. $GLOBALS['cfg']['AllowArbitraryServer'] = true;
  154. $GLOBALS['cfg']['CaptchaApi'] = '';
  155. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  156. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  157. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  158. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  159. $GLOBALS['db'] = 'testDb';
  160. $GLOBALS['table'] = 'testTable';
  161. $GLOBALS['cfg']['Servers'] = [1, 2];
  162. $GLOBALS['errorHandler'] = new ErrorHandler();
  163. ob_start();
  164. $this->object->showLoginForm();
  165. $result = ob_get_clean();
  166. $this->assertIsString($result);
  167. $this->assertStringContainsString(' id="imLogo"', $result);
  168. $this->assertStringContainsString('<div class="alert alert-danger" role="alert">', $result);
  169. $this->assertStringContainsString(
  170. '<form method="post" id="login_form" action="index.php?route=/" name="login_form" ' .
  171. 'class="disableAjax hide js-show">',
  172. $result
  173. );
  174. $this->assertStringContainsString(
  175. '<input type="text" name="pma_servername" id="serverNameInput" value="localhost"',
  176. $result
  177. );
  178. $this->assertStringContainsString(
  179. '<input type="text" name="pma_username" id="input_username" ' .
  180. 'value="pmauser" class="form-control" autocomplete="username">',
  181. $result
  182. );
  183. $this->assertStringContainsString(
  184. '<input type="password" name="pma_password" id="input_password" ' .
  185. 'value="" class="form-control" autocomplete="current-password">',
  186. $result
  187. );
  188. $this->assertStringContainsString(
  189. '<select name="server" id="select_server" class="form-select" ' .
  190. 'onchange="document.forms[\'login_form\'].' .
  191. 'elements[\'pma_servername\'].value = \'\'">',
  192. $result
  193. );
  194. $this->assertStringContainsString('<input type="hidden" name="db" value="testDb">', $result);
  195. $this->assertStringContainsString('<input type="hidden" name="table" value="testTable">', $result);
  196. }
  197. /**
  198. * @group medium
  199. */
  200. public function testAuthCaptcha(): void
  201. {
  202. $mockResponse = $this->mockResponse();
  203. $mockResponse->expects($this->once())
  204. ->method('isAjax')
  205. ->with()
  206. ->will($this->returnValue(false));
  207. $mockResponse->expects($this->once())
  208. ->method('getFooter')
  209. ->with()
  210. ->will($this->returnValue(new Footer()));
  211. $mockResponse->expects($this->once())
  212. ->method('getHeader')
  213. ->with()
  214. ->will($this->returnValue(new Header()));
  215. $_REQUEST['old_usr'] = '';
  216. $GLOBALS['cfg']['LoginCookieRecall'] = false;
  217. $GLOBALS['cfg']['Lang'] = '';
  218. $GLOBALS['cfg']['AllowArbitraryServer'] = false;
  219. $GLOBALS['cfg']['Servers'] = [1];
  220. $GLOBALS['cfg']['CaptchaApi'] = 'https://www.google.com/recaptcha/api.js';
  221. $GLOBALS['cfg']['CaptchaRequestParam'] = 'g-recaptcha';
  222. $GLOBALS['cfg']['CaptchaResponseParam'] = 'g-recaptcha-response';
  223. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = 'testprivkey';
  224. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = 'testpubkey';
  225. $GLOBALS['server'] = 0;
  226. $GLOBALS['errorHandler'] = new ErrorHandler();
  227. ob_start();
  228. $this->object->showLoginForm();
  229. $result = ob_get_clean();
  230. $this->assertIsString($result);
  231. $this->assertStringContainsString('id="imLogo"', $result);
  232. // Check for language selection if locales are there
  233. $loc = LOCALE_PATH . '/cs/LC_MESSAGES/phpmyadmin.mo';
  234. if (is_readable($loc)) {
  235. $this->assertStringContainsString(
  236. '<select name="lang" class="form-select autosubmit" lang="en" dir="ltr"'
  237. . ' id="languageSelect" aria-labelledby="languageSelectLabel">',
  238. $result
  239. );
  240. }
  241. $this->assertStringContainsString(
  242. '<form method="post" id="login_form" action="index.php?route=/" name="login_form"' .
  243. ' class="disableAjax hide js-show" autocomplete="off">',
  244. $result
  245. );
  246. $this->assertStringContainsString('<input type="hidden" name="server" value="0">', $result);
  247. $this->assertStringContainsString(
  248. '<script src="https://www.google.com/recaptcha/api.js?hl=en" async defer></script>',
  249. $result
  250. );
  251. $this->assertStringContainsString(
  252. '<input class="btn btn-primary g-recaptcha" data-sitekey="testpubkey"'
  253. . ' data-callback="Functions_recaptchaCallback" value="Log in" type="submit" id="input_go">',
  254. $result
  255. );
  256. }
  257. /**
  258. * @group medium
  259. */
  260. public function testAuthCaptchaCheckbox(): void
  261. {
  262. $mockResponse = $this->mockResponse();
  263. $mockResponse->expects($this->once())
  264. ->method('isAjax')
  265. ->with()
  266. ->will($this->returnValue(false));
  267. $mockResponse->expects($this->once())
  268. ->method('getFooter')
  269. ->with()
  270. ->will($this->returnValue(new Footer()));
  271. $mockResponse->expects($this->once())
  272. ->method('getHeader')
  273. ->with()
  274. ->will($this->returnValue(new Header()));
  275. $_REQUEST['old_usr'] = '';
  276. $GLOBALS['cfg']['LoginCookieRecall'] = false;
  277. $GLOBALS['cfg']['Lang'] = '';
  278. $GLOBALS['cfg']['AllowArbitraryServer'] = false;
  279. $GLOBALS['cfg']['Servers'] = [1];
  280. $GLOBALS['cfg']['CaptchaApi'] = 'https://www.google.com/recaptcha/api.js';
  281. $GLOBALS['cfg']['CaptchaRequestParam'] = 'g-recaptcha';
  282. $GLOBALS['cfg']['CaptchaResponseParam'] = 'g-recaptcha-response';
  283. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = 'testprivkey';
  284. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = 'testpubkey';
  285. $GLOBALS['cfg']['CaptchaMethod'] = 'checkbox';
  286. $GLOBALS['server'] = 0;
  287. $GLOBALS['errorHandler'] = new ErrorHandler();
  288. ob_start();
  289. $this->object->showLoginForm();
  290. $result = ob_get_clean();
  291. $this->assertIsString($result);
  292. $this->assertStringContainsString('id="imLogo"', $result);
  293. // Check for language selection if locales are there
  294. $loc = LOCALE_PATH . '/cs/LC_MESSAGES/phpmyadmin.mo';
  295. if (is_readable($loc)) {
  296. $this->assertStringContainsString(
  297. '<select name="lang" class="form-select autosubmit" lang="en" dir="ltr"'
  298. . ' id="languageSelect" aria-labelledby="languageSelectLabel">',
  299. $result
  300. );
  301. }
  302. $this->assertStringContainsString(
  303. '<form method="post" id="login_form" action="index.php?route=/" name="login_form"' .
  304. ' class="disableAjax hide js-show" autocomplete="off">',
  305. $result
  306. );
  307. $this->assertStringContainsString('<input type="hidden" name="server" value="0">', $result);
  308. $this->assertStringContainsString(
  309. '<script src="https://www.google.com/recaptcha/api.js?hl=en" async defer></script>',
  310. $result
  311. );
  312. $this->assertStringContainsString('<div class="g-recaptcha" data-sitekey="testpubkey"></div>', $result);
  313. $this->assertStringContainsString(
  314. '<input class="btn btn-primary" value="Log in" type="submit" id="input_go">',
  315. $result
  316. );
  317. }
  318. public function testAuthHeader(): void
  319. {
  320. $GLOBALS['cfg']['LoginCookieDeleteAll'] = false;
  321. $GLOBALS['cfg']['Servers'] = [1];
  322. $this->mockResponse('Location: https://example.com/logout');
  323. $GLOBALS['cfg']['Server']['LogoutURL'] = 'https://example.com/logout';
  324. $GLOBALS['cfg']['Server']['auth_type'] = 'cookie';
  325. $this->object->logOut();
  326. }
  327. public function testAuthHeaderPartial(): void
  328. {
  329. $GLOBALS['config']->set('is_https', false);
  330. $GLOBALS['cfg']['LoginCookieDeleteAll'] = false;
  331. $GLOBALS['cfg']['Servers'] = [
  332. 1,
  333. 2,
  334. 3,
  335. ];
  336. $GLOBALS['cfg']['Server']['LogoutURL'] = 'https://example.com/logout';
  337. $GLOBALS['cfg']['Server']['auth_type'] = 'cookie';
  338. $_COOKIE['pmaAuth-2'] = '';
  339. $this->mockResponse('Location: /phpmyadmin/index.php?route=/&server=2&lang=en');
  340. $this->object->logOut();
  341. }
  342. public function testAuthCheckCaptcha(): void
  343. {
  344. $GLOBALS['cfg']['CaptchaApi'] = 'https://www.google.com/recaptcha/api.js';
  345. $GLOBALS['cfg']['CaptchaRequestParam'] = 'g-recaptcha';
  346. $GLOBALS['cfg']['CaptchaResponseParam'] = 'g-recaptcha-response';
  347. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = 'testprivkey';
  348. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = 'testpubkey';
  349. $_POST['g-recaptcha-response'] = '';
  350. $_POST['pma_username'] = 'testPMAUser';
  351. $this->assertFalse(
  352. $this->object->readCredentials()
  353. );
  354. $this->assertEquals(
  355. 'Missing reCAPTCHA verification, maybe it has been blocked by adblock?',
  356. $GLOBALS['conn_error']
  357. );
  358. }
  359. public function testLogoutDelete(): void
  360. {
  361. $this->mockResponse('Location: /phpmyadmin/index.php?route=/');
  362. $GLOBALS['cfg']['CaptchaApi'] = '';
  363. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  364. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  365. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  366. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  367. $GLOBALS['cfg']['LoginCookieDeleteAll'] = true;
  368. $GLOBALS['config']->set('PmaAbsoluteUri', '');
  369. $GLOBALS['config']->set('is_https', false);
  370. $GLOBALS['cfg']['Servers'] = [1];
  371. $_COOKIE['pmaAuth-0'] = 'test';
  372. $this->object->logOut();
  373. $this->assertArrayNotHasKey('pmaAuth-0', $_COOKIE);
  374. }
  375. public function testLogout(): void
  376. {
  377. $this->mockResponse('Location: /phpmyadmin/index.php?route=/');
  378. $GLOBALS['cfg']['CaptchaApi'] = '';
  379. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  380. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  381. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  382. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  383. $GLOBALS['cfg']['LoginCookieDeleteAll'] = false;
  384. $GLOBALS['config']->set('PmaAbsoluteUri', '');
  385. $GLOBALS['config']->set('is_https', false);
  386. $GLOBALS['cfg']['Servers'] = [1];
  387. $GLOBALS['server'] = 1;
  388. $GLOBALS['cfg']['Server'] = ['auth_type' => 'cookie'];
  389. $_COOKIE['pmaAuth-1'] = 'test';
  390. $this->object->logOut();
  391. $this->assertArrayNotHasKey('pmaAuth-1', $_COOKIE);
  392. }
  393. public function testAuthCheckArbitrary(): void
  394. {
  395. $GLOBALS['cfg']['CaptchaApi'] = '';
  396. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  397. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  398. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  399. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  400. $_REQUEST['old_usr'] = '';
  401. $_POST['pma_username'] = 'testPMAUser';
  402. $_REQUEST['pma_servername'] = 'testPMAServer';
  403. $_POST['pma_password'] = 'testPMAPSWD';
  404. $GLOBALS['cfg']['AllowArbitraryServer'] = true;
  405. $this->assertTrue(
  406. $this->object->readCredentials()
  407. );
  408. $this->assertEquals('testPMAUser', $this->object->user);
  409. $this->assertEquals('testPMAPSWD', $this->object->password);
  410. $this->assertEquals('testPMAServer', $GLOBALS['pma_auth_server']);
  411. $this->assertArrayNotHasKey('pmaAuth-1', $_COOKIE);
  412. }
  413. public function testAuthCheckInvalidCookie(): void
  414. {
  415. $GLOBALS['cfg']['AllowArbitraryServer'] = true;
  416. $_REQUEST['pma_servername'] = 'testPMAServer';
  417. $_POST['pma_password'] = 'testPMAPSWD';
  418. $_POST['pma_username'] = '';
  419. $GLOBALS['server'] = 1;
  420. $_COOKIE['pmaUser-1'] = '';
  421. $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
  422. $this->assertFalse(
  423. $this->object->readCredentials()
  424. );
  425. }
  426. public function testAuthCheckExpires(): void
  427. {
  428. $GLOBALS['server'] = 1;
  429. $_COOKIE['pmaServer-1'] = 'pmaServ1';
  430. $_COOKIE['pmaUser-1'] = 'pmaUser1';
  431. $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
  432. $_COOKIE['pmaAuth-1'] = '';
  433. $GLOBALS['cfg']['blowfish_secret'] = 'secret';
  434. $_SESSION['last_access_time'] = time() - 1000;
  435. $GLOBALS['cfg']['LoginCookieValidity'] = 1440;
  436. $this->assertFalse(
  437. $this->object->readCredentials()
  438. );
  439. }
  440. public function testAuthCheckDecryptUser(): void
  441. {
  442. $GLOBALS['server'] = 1;
  443. $_REQUEST['old_usr'] = '';
  444. $_POST['pma_username'] = '';
  445. $_COOKIE['pmaServer-1'] = 'pmaServ1';
  446. $_COOKIE['pmaUser-1'] = 'pmaUser1';
  447. $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
  448. $GLOBALS['cfg']['blowfish_secret'] = 'secret';
  449. $_SESSION['last_access_time'] = '';
  450. $GLOBALS['cfg']['CaptchaApi'] = '';
  451. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  452. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  453. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  454. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  455. $GLOBALS['config']->set('is_https', false);
  456. // mock for blowfish function
  457. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  458. ->disableOriginalConstructor()
  459. ->onlyMethods(['cookieDecrypt'])
  460. ->getMock();
  461. $this->object->expects($this->once())
  462. ->method('cookieDecrypt')
  463. ->will($this->returnValue('testBF'));
  464. $this->assertFalse(
  465. $this->object->readCredentials()
  466. );
  467. $this->assertEquals('testBF', $this->object->user);
  468. }
  469. public function testAuthCheckDecryptPassword(): void
  470. {
  471. $GLOBALS['server'] = 1;
  472. $_REQUEST['old_usr'] = '';
  473. $_POST['pma_username'] = '';
  474. $_COOKIE['pmaServer-1'] = 'pmaServ1';
  475. $_COOKIE['pmaUser-1'] = 'pmaUser1';
  476. $_COOKIE['pmaAuth-1'] = 'pmaAuth1';
  477. $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
  478. $GLOBALS['cfg']['blowfish_secret'] = 'secret';
  479. $GLOBALS['cfg']['CaptchaApi'] = '';
  480. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  481. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  482. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  483. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  484. $_SESSION['browser_access_time']['default'] = time() - 1000;
  485. $GLOBALS['cfg']['LoginCookieValidity'] = 1440;
  486. $GLOBALS['config']->set('is_https', false);
  487. // mock for blowfish function
  488. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  489. ->disableOriginalConstructor()
  490. ->onlyMethods(['cookieDecrypt'])
  491. ->getMock();
  492. $this->object->expects($this->exactly(2))
  493. ->method('cookieDecrypt')
  494. ->will($this->returnValue('{"password":""}'));
  495. $this->assertTrue(
  496. $this->object->readCredentials()
  497. );
  498. $this->assertTrue($GLOBALS['from_cookie']);
  499. $this->assertEquals('', $this->object->password);
  500. }
  501. public function testAuthCheckAuthFails(): void
  502. {
  503. $GLOBALS['server'] = 1;
  504. $_REQUEST['old_usr'] = '';
  505. $_POST['pma_username'] = '';
  506. $_COOKIE['pmaServer-1'] = 'pmaServ1';
  507. $_COOKIE['pmaUser-1'] = 'pmaUser1';
  508. $_COOKIE['pma_iv-1'] = base64_encode('testiv09testiv09');
  509. $GLOBALS['cfg']['blowfish_secret'] = 'secret';
  510. $_SESSION['last_access_time'] = 1;
  511. $GLOBALS['cfg']['CaptchaApi'] = '';
  512. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  513. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  514. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  515. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  516. $GLOBALS['cfg']['LoginCookieValidity'] = 0;
  517. $_SESSION['browser_access_time']['default'] = -1;
  518. $GLOBALS['config']->set('is_https', false);
  519. // mock for blowfish function
  520. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  521. ->disableOriginalConstructor()
  522. ->onlyMethods(['showFailure', 'cookieDecrypt'])
  523. ->getMock();
  524. $this->object->expects($this->once())
  525. ->method('cookieDecrypt')
  526. ->will($this->returnValue('testBF'));
  527. $this->object->expects($this->once())
  528. ->method('showFailure');
  529. $this->assertFalse(
  530. $this->object->readCredentials()
  531. );
  532. }
  533. public function testAuthSetUser(): void
  534. {
  535. $this->object->user = 'pmaUser2';
  536. $arr = [
  537. 'host' => 'a',
  538. 'port' => 1,
  539. 'socket' => true,
  540. 'ssl' => true,
  541. 'user' => 'pmaUser2',
  542. ];
  543. $GLOBALS['cfg']['Server'] = $arr;
  544. $GLOBALS['cfg']['Server']['user'] = 'pmaUser';
  545. $GLOBALS['cfg']['Servers'][1] = $arr;
  546. $GLOBALS['cfg']['AllowArbitraryServer'] = true;
  547. $GLOBALS['pma_auth_server'] = 'b 2';
  548. $this->object->password = 'testPW';
  549. $GLOBALS['server'] = 2;
  550. $GLOBALS['cfg']['LoginCookieStore'] = true;
  551. $GLOBALS['from_cookie'] = true;
  552. $GLOBALS['config']->set('is_https', false);
  553. $this->object->storeCredentials();
  554. $this->object->rememberCredentials();
  555. $this->assertArrayHasKey('pmaUser-2', $_COOKIE);
  556. $this->assertArrayHasKey('pmaAuth-2', $_COOKIE);
  557. $arr['password'] = 'testPW';
  558. $arr['host'] = 'b';
  559. $arr['port'] = '2';
  560. $this->assertEquals($arr, $GLOBALS['cfg']['Server']);
  561. }
  562. public function testAuthSetUserWithHeaders(): void
  563. {
  564. $this->object->user = 'pmaUser2';
  565. $arr = [
  566. 'host' => 'a',
  567. 'port' => 1,
  568. 'socket' => true,
  569. 'ssl' => true,
  570. 'user' => 'pmaUser2',
  571. ];
  572. $GLOBALS['cfg']['Server'] = $arr;
  573. $GLOBALS['cfg']['Server']['host'] = 'b';
  574. $GLOBALS['cfg']['Server']['user'] = 'pmaUser';
  575. $GLOBALS['cfg']['Servers'][1] = $arr;
  576. $GLOBALS['cfg']['AllowArbitraryServer'] = true;
  577. $GLOBALS['pma_auth_server'] = 'b 2';
  578. $this->object->password = 'testPW';
  579. $GLOBALS['server'] = 2;
  580. $GLOBALS['cfg']['LoginCookieStore'] = true;
  581. $GLOBALS['from_cookie'] = false;
  582. $this->mockResponse(
  583. $this->stringContains('&server=2&lang=en')
  584. );
  585. $this->object->storeCredentials();
  586. $this->object->rememberCredentials();
  587. }
  588. public function testAuthFailsNoPass(): void
  589. {
  590. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  591. ->disableOriginalConstructor()
  592. ->onlyMethods(['showLoginForm'])
  593. ->getMock();
  594. $GLOBALS['server'] = 2;
  595. $_COOKIE['pmaAuth-2'] = 'pass';
  596. $this->mockResponse(
  597. ['Cache-Control: no-store, no-cache, must-revalidate'],
  598. ['Pragma: no-cache']
  599. );
  600. $this->object->showFailure('empty-denied');
  601. $this->assertEquals(
  602. $GLOBALS['conn_error'],
  603. 'Login without a password is forbidden by configuration (see AllowNoPassword)'
  604. );
  605. }
  606. public function dataProviderPasswordLength(): array
  607. {
  608. return [
  609. [
  610. str_repeat('a', 1000),
  611. false,
  612. 'Your password is too long. To prevent denial-of-service attacks,'
  613. . ' phpMyAdmin restricts passwords to less than 1000 characters.',
  614. ],
  615. [
  616. str_repeat('a', 1001),
  617. false,
  618. 'Your password is too long. To prevent denial-of-service attacks,'
  619. . ' phpMyAdmin restricts passwords to less than 1000 characters.',
  620. ],
  621. [
  622. str_repeat('a', 3000),
  623. false,
  624. 'Your password is too long. To prevent denial-of-service attacks,'
  625. . ' phpMyAdmin restricts passwords to less than 1000 characters.',
  626. ],
  627. [
  628. str_repeat('a', 256),
  629. true,
  630. null,
  631. ],
  632. [
  633. '',
  634. true,
  635. null,
  636. ],
  637. ];
  638. }
  639. /**
  640. * @dataProvider dataProviderPasswordLength
  641. */
  642. public function testAuthFailsTooLongPass(string $password, bool $trueFalse, ?string $connError): void
  643. {
  644. $_POST['pma_username'] = str_shuffle('123456987rootfoobar');
  645. $_POST['pma_password'] = $password;
  646. if ($trueFalse === false) {
  647. $this->assertFalse(
  648. $this->object->readCredentials()
  649. );
  650. } else {
  651. $this->assertTrue(
  652. $this->object->readCredentials()
  653. );
  654. }
  655. $this->assertEquals($GLOBALS['conn_error'], $connError);
  656. }
  657. public function testAuthFailsDeny(): void
  658. {
  659. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  660. ->disableOriginalConstructor()
  661. ->onlyMethods(['showLoginForm'])
  662. ->getMock();
  663. $GLOBALS['server'] = 2;
  664. $_COOKIE['pmaAuth-2'] = 'pass';
  665. $this->mockResponse(
  666. ['Cache-Control: no-store, no-cache, must-revalidate'],
  667. ['Pragma: no-cache']
  668. );
  669. $this->object->showFailure('allow-denied');
  670. $this->assertEquals($GLOBALS['conn_error'], 'Access denied!');
  671. }
  672. public function testAuthFailsActivity(): void
  673. {
  674. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  675. ->disableOriginalConstructor()
  676. ->onlyMethods(['showLoginForm'])
  677. ->getMock();
  678. $GLOBALS['server'] = 2;
  679. $_COOKIE['pmaAuth-2'] = 'pass';
  680. $GLOBALS['allowDeny_forbidden'] = '';
  681. $GLOBALS['cfg']['LoginCookieValidity'] = 10;
  682. $this->mockResponse(
  683. ['Cache-Control: no-store, no-cache, must-revalidate'],
  684. ['Pragma: no-cache']
  685. );
  686. $this->object->showFailure('no-activity');
  687. $this->assertEquals(
  688. $GLOBALS['conn_error'],
  689. 'You have been automatically logged out due to inactivity of 10 seconds.'
  690. . ' Once you log in again, you should be able to resume the work where you left off.'
  691. );
  692. }
  693. public function testAuthFailsDBI(): void
  694. {
  695. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  696. ->disableOriginalConstructor()
  697. ->onlyMethods(['showLoginForm'])
  698. ->getMock();
  699. $GLOBALS['server'] = 2;
  700. $_COOKIE['pmaAuth-2'] = 'pass';
  701. $dbi = $this->getMockBuilder(DatabaseInterface::class)
  702. ->disableOriginalConstructor()
  703. ->getMock();
  704. $dbi->expects($this->once())
  705. ->method('getError')
  706. ->will($this->returnValue(false));
  707. $GLOBALS['dbi'] = $dbi;
  708. $GLOBALS['errno'] = 42;
  709. $this->mockResponse(
  710. ['Cache-Control: no-store, no-cache, must-revalidate'],
  711. ['Pragma: no-cache']
  712. );
  713. $this->object->showFailure('');
  714. $this->assertEquals($GLOBALS['conn_error'], '#42 Cannot log in to the MySQL server');
  715. }
  716. public function testAuthFailsErrno(): void
  717. {
  718. $this->object = $this->getMockBuilder(AuthenticationCookie::class)
  719. ->disableOriginalConstructor()
  720. ->onlyMethods(['showLoginForm'])
  721. ->getMock();
  722. $dbi = $this->getMockBuilder(DatabaseInterface::class)
  723. ->disableOriginalConstructor()
  724. ->getMock();
  725. $dbi->expects($this->once())
  726. ->method('getError')
  727. ->will($this->returnValue(false));
  728. $GLOBALS['dbi'] = $dbi;
  729. $GLOBALS['server'] = 2;
  730. $_COOKIE['pmaAuth-2'] = 'pass';
  731. unset($GLOBALS['errno']);
  732. $this->mockResponse(
  733. ['Cache-Control: no-store, no-cache, must-revalidate'],
  734. ['Pragma: no-cache']
  735. );
  736. $this->object->showFailure('');
  737. $this->assertEquals($GLOBALS['conn_error'], 'Cannot log in to the MySQL server');
  738. }
  739. public function testGetEncryptionSecretEmpty(): void
  740. {
  741. $method = new ReflectionMethod(AuthenticationCookie::class, 'getEncryptionSecret');
  742. $method->setAccessible(true);
  743. $GLOBALS['cfg']['blowfish_secret'] = '';
  744. $_SESSION['encryption_key'] = '';
  745. $result = $method->invoke($this->object, null);
  746. $this->assertEquals($result, $_SESSION['encryption_key']);
  747. $this->assertEquals(
  748. 32,
  749. strlen($result)
  750. );
  751. }
  752. public function testGetEncryptionSecretConfigured(): void
  753. {
  754. $method = new ReflectionMethod(AuthenticationCookie::class, 'getEncryptionSecret');
  755. $method->setAccessible(true);
  756. $GLOBALS['cfg']['blowfish_secret'] = 'notEmpty';
  757. $result = $method->invoke($this->object, null);
  758. $this->assertEquals('notEmpty', $result);
  759. }
  760. public function testCookieEncrypt(): void
  761. {
  762. $this->object->setIV('testiv09testiv09');
  763. // works with the openssl extension active or inactive
  764. $this->assertEquals(
  765. '{"iv":"dGVzdGl2MDl0ZXN0aXYwOQ==","mac":"347aa45ae1ade00c980f31129ec2def'
  766. . 'ef18b2bfd","payload":"YDEaxOfP9nD9q\/2pC6hjfQ=="}',
  767. $this->object->cookieEncrypt('data123', 'sec321')
  768. );
  769. }
  770. public function testCookieEncryptPHPSecLib(): void
  771. {
  772. $this->object->setUseOpenSSL(false);
  773. $this->testCookieEncrypt();
  774. }
  775. /**
  776. * @requires extension openssl
  777. */
  778. public function testCookieEncryptOpenSSL(): void
  779. {
  780. $this->object->setUseOpenSSL(true);
  781. $this->testCookieEncrypt();
  782. }
  783. public function testCookieDecrypt(): void
  784. {
  785. // works with the openssl extension active or inactive
  786. $this->assertEquals(
  787. 'data123',
  788. $this->object->cookieDecrypt(
  789. '{"iv":"dGVzdGl2MDl0ZXN0aXYwOQ==","mac":"347aa45ae1ade00c980f31129ec'
  790. . '2defef18b2bfd","payload":"YDEaxOfP9nD9q\/2pC6hjfQ=="}',
  791. 'sec321'
  792. )
  793. );
  794. $this->assertEquals(
  795. 'root',
  796. $this->object->cookieDecrypt(
  797. '{"iv":"AclJhCM7ryNiuPnw3Y8cXg==","mac":"d0ef75e852bc162e81496e116dc'
  798. . '571182cb2cba6","payload":"O4vrt9R1xyzAw7ypvrLmQA=="}',
  799. ':Kb1?)c(r{]-{`HW*hOzuufloK(M~!p'
  800. )
  801. );
  802. $this->assertFalse(
  803. $this->object->cookieDecrypt(
  804. '{"iv":"AclJhCM7ryNiuPnw3Y8cXg==","mac":"d0ef75e852bc162e81496e116dc'
  805. . '571182cb2cba6","payload":"O4vrt9R1xyzAw7ypvrLmQA=="}',
  806. 'aedzoiefpzf,zf1z7ef6ef84'
  807. )
  808. );
  809. }
  810. public function testCookieDecryptPHPSecLib(): void
  811. {
  812. $this->object->setUseOpenSSL(false);
  813. $this->testCookieDecrypt();
  814. }
  815. /**
  816. * @requires extension openssl
  817. */
  818. public function testCookieDecryptOpenSSL(): void
  819. {
  820. $this->object->setUseOpenSSL(true);
  821. $this->testCookieDecrypt();
  822. }
  823. public function testCookieDecryptInvalid(): void
  824. {
  825. // works with the openssl extension active or inactive
  826. $this->assertFalse(
  827. $this->object->cookieDecrypt(
  828. '{"iv":0,"mac":0,"payload":0}',
  829. 'sec321'
  830. )
  831. );
  832. }
  833. /**
  834. * Test for secret splitting using getAESSecret
  835. *
  836. * @param string $secret secret
  837. * @param string $mac mac
  838. * @param string $aes aes
  839. *
  840. * @dataProvider secretsProvider
  841. */
  842. public function testMACSecretSplit(string $secret, string $mac, string $aes): void
  843. {
  844. $this->assertNotEmpty($aes);// Useless check
  845. $this->assertEquals(
  846. $mac,
  847. $this->object->getMACSecret($secret)
  848. );
  849. }
  850. /**
  851. * Test for secret splitting using getMACSecret and getAESSecret
  852. *
  853. * @param string $secret secret
  854. * @param string $mac mac
  855. * @param string $aes aes
  856. *
  857. * @dataProvider secretsProvider
  858. */
  859. public function testAESSecretSplit(string $secret, string $mac, string $aes): void
  860. {
  861. $this->assertNotEmpty($mac);// Useless check
  862. $this->assertEquals(
  863. $aes,
  864. $this->object->getAESSecret($secret)
  865. );
  866. }
  867. /**
  868. * @throws ReflectionException
  869. */
  870. public function testPasswordChange(): void
  871. {
  872. $newPassword = 'PMAPASSWD2';
  873. $GLOBALS['config']->set('is_https', false);
  874. $GLOBALS['cfg']['AllowArbitraryServer'] = true;
  875. $GLOBALS['pma_auth_server'] = 'b 2';
  876. $_SESSION['encryption_key'] = '';
  877. $this->object->setIV('testiv09testiv09');
  878. $this->object->handlePasswordChange($newPassword);
  879. $payload = [
  880. 'password' => $newPassword,
  881. 'server' => 'b 2',
  882. ];
  883. $method = new ReflectionMethod(AuthenticationCookie::class, 'getSessionEncryptionSecret');
  884. $method->setAccessible(true);
  885. $encryptedCookie = $this->object->cookieEncrypt(
  886. (string) json_encode($payload),
  887. $method->invoke($this->object, null)
  888. );
  889. $this->assertEquals($_COOKIE['pmaAuth-' . $GLOBALS['server']], $encryptedCookie);
  890. }
  891. /**
  892. * Data provider for secrets splitting.
  893. *
  894. * @return array
  895. */
  896. public function secretsProvider(): array
  897. {
  898. return [
  899. // Optimal case
  900. [
  901. '1234567890123456abcdefghijklmnop',
  902. '1234567890123456',
  903. 'abcdefghijklmnop',
  904. ],
  905. // Overlapping secret
  906. [
  907. '12345678901234567',
  908. '1234567890123456',
  909. '2345678901234567',
  910. ],
  911. // Short secret
  912. [
  913. '1234567890123456',
  914. '1234567890123451',
  915. '2345678901234562',
  916. ],
  917. // Really short secret
  918. [
  919. '12',
  920. '1111111111111111',
  921. '2222222222222222',
  922. ],
  923. // Too short secret
  924. [
  925. '1',
  926. '1111111111111111',
  927. '1111111111111111',
  928. ],
  929. ];
  930. }
  931. public function testAuthenticate(): void
  932. {
  933. $GLOBALS['cfg']['CaptchaApi'] = '';
  934. $GLOBALS['cfg']['CaptchaRequestParam'] = '';
  935. $GLOBALS['cfg']['CaptchaResponseParam'] = '';
  936. $GLOBALS['cfg']['CaptchaLoginPrivateKey'] = '';
  937. $GLOBALS['cfg']['CaptchaLoginPublicKey'] = '';
  938. $GLOBALS['cfg']['Server']['AllowRoot'] = false;
  939. $GLOBALS['cfg']['Server']['AllowNoPassword'] = false;
  940. $_REQUEST['old_usr'] = '';
  941. $_POST['pma_username'] = 'testUser';
  942. $_POST['pma_password'] = 'testPassword';
  943. ob_start();
  944. $this->object->authenticate();
  945. $result = ob_get_clean();
  946. /* Nothing should be printed */
  947. $this->assertEquals('', $result);
  948. /* Verify readCredentials worked */
  949. $this->assertEquals('testUser', $this->object->user);
  950. $this->assertEquals('testPassword', $this->object->password);
  951. /* Verify storeCredentials worked */
  952. $this->assertEquals('testUser', $GLOBALS['cfg']['Server']['user']);
  953. $this->assertEquals('testPassword', $GLOBALS['cfg']['Server']['password']);
  954. }
  955. /**
  956. * @param string $user user
  957. * @param string $pass pass
  958. * @param string $ip ip
  959. * @param bool $root root
  960. * @param bool $nopass nopass
  961. * @param array $rules rules
  962. * @param string $expected expected result
  963. *
  964. * @dataProvider checkRulesProvider
  965. */
  966. public function testCheckRules(
  967. string $user,
  968. string $pass,
  969. string $ip,
  970. bool $root,
  971. bool $nopass,
  972. array $rules,
  973. string $expected
  974. ): void {
  975. $this->object->user = $user;
  976. $this->object->password = $pass;
  977. $this->object->storeCredentials();
  978. $_SERVER['REMOTE_ADDR'] = $ip;
  979. $GLOBALS['cfg']['Server']['AllowRoot'] = $root;
  980. $GLOBALS['cfg']['Server']['AllowNoPassword'] = $nopass;
  981. $GLOBALS['cfg']['Server']['AllowDeny'] = $rules;
  982. if (! empty($expected)) {
  983. $this->getAuthErrorMockResponse();
  984. }
  985. ob_start();
  986. $this->object->checkRules();
  987. $result = ob_get_clean();
  988. $this->assertIsString($result);
  989. if (empty($expected)) {
  990. $this->assertEquals($expected, $result);
  991. } else {
  992. $this->assertStringContainsString($expected, $result);
  993. }
  994. }
  995. public function checkRulesProvider(): array
  996. {
  997. return [
  998. 'nopass-ok' => [
  999. 'testUser',
  1000. '',
  1001. '1.2.3.4',
  1002. true,
  1003. true,
  1004. [],
  1005. '',
  1006. ],
  1007. 'nopass' => [
  1008. 'testUser',
  1009. '',
  1010. '1.2.3.4',
  1011. true,
  1012. false,
  1013. [],
  1014. 'Login without a password is forbidden',
  1015. ],
  1016. 'root-ok' => [
  1017. 'root',
  1018. 'root',
  1019. '1.2.3.4',
  1020. true,
  1021. true,
  1022. [],
  1023. '',
  1024. ],
  1025. 'root' => [
  1026. 'root',
  1027. 'root',
  1028. '1.2.3.4',
  1029. false,
  1030. true,
  1031. [],
  1032. 'Access denied!',
  1033. ],
  1034. 'rules-deny-allow-ok' => [
  1035. 'root',
  1036. 'root',
  1037. '1.2.3.4',
  1038. true,
  1039. true,
  1040. [
  1041. 'order' => 'deny,allow',
  1042. 'rules' => [
  1043. 'allow root 1.2.3.4',
  1044. 'deny % from all',
  1045. ],
  1046. ],
  1047. '',
  1048. ],
  1049. 'rules-deny-allow-reject' => [
  1050. 'user',
  1051. 'root',
  1052. '1.2.3.4',
  1053. true,
  1054. true,
  1055. [
  1056. 'order' => 'deny,allow',
  1057. 'rules' => [
  1058. 'allow root 1.2.3.4',
  1059. 'deny % from all',
  1060. ],
  1061. ],
  1062. 'Access denied!',
  1063. ],
  1064. 'rules-allow-deny-ok' => [
  1065. 'root',
  1066. 'root',
  1067. '1.2.3.4',
  1068. true,
  1069. true,
  1070. [
  1071. 'order' => 'allow,deny',
  1072. 'rules' => [
  1073. 'deny user from all',
  1074. 'allow root 1.2.3.4',
  1075. ],
  1076. ],
  1077. '',
  1078. ],
  1079. 'rules-allow-deny-reject' => [
  1080. 'user',
  1081. 'root',
  1082. '1.2.3.4',
  1083. true,
  1084. true,
  1085. [
  1086. 'order' => 'allow,deny',
  1087. 'rules' => [
  1088. 'deny user from all',
  1089. 'allow root 1.2.3.4',
  1090. ],
  1091. ],
  1092. 'Access denied!',
  1093. ],
  1094. 'rules-explicit-ok' => [
  1095. 'root',
  1096. 'root',
  1097. '1.2.3.4',
  1098. true,
  1099. true,
  1100. [
  1101. 'order' => 'explicit',
  1102. 'rules' => [
  1103. 'deny user from all',
  1104. 'allow root 1.2.3.4',
  1105. ],
  1106. ],
  1107. '',
  1108. ],
  1109. 'rules-explicit-reject' => [
  1110. 'user',
  1111. 'root',
  1112. '1.2.3.4',
  1113. true,
  1114. true,
  1115. [
  1116. 'order' => 'explicit',
  1117. 'rules' => [
  1118. 'deny user from all',
  1119. 'allow root 1.2.3.4',
  1120. ],
  1121. ],
  1122. 'Access denied!',
  1123. ],
  1124. ];
  1125. }
  1126. }