AuthenticationCookie.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. <?php
  2. /**
  3. * Cookie Authentication plugin for phpMyAdmin
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Plugins\Auth;
  7. use PhpMyAdmin\Config;
  8. use PhpMyAdmin\Core;
  9. use PhpMyAdmin\LanguageManager;
  10. use PhpMyAdmin\Message;
  11. use PhpMyAdmin\Plugins\AuthenticationPlugin;
  12. use PhpMyAdmin\ResponseRenderer;
  13. use PhpMyAdmin\Server\Select;
  14. use PhpMyAdmin\Session;
  15. use PhpMyAdmin\Url;
  16. use PhpMyAdmin\Util;
  17. use PhpMyAdmin\Utils\SessionCache;
  18. use ReCaptcha;
  19. use Throwable;
  20. use function __;
  21. use function array_keys;
  22. use function base64_decode;
  23. use function base64_encode;
  24. use function count;
  25. use function defined;
  26. use function explode;
  27. use function function_exists;
  28. use function in_array;
  29. use function ini_get;
  30. use function intval;
  31. use function is_array;
  32. use function is_string;
  33. use function json_decode;
  34. use function json_encode;
  35. use function mb_strlen;
  36. use function mb_substr;
  37. use function preg_match;
  38. use function random_bytes;
  39. use function session_id;
  40. use function sodium_crypto_secretbox;
  41. use function sodium_crypto_secretbox_open;
  42. use function strlen;
  43. use function time;
  44. use const SODIUM_CRYPTO_SECRETBOX_KEYBYTES;
  45. use const SODIUM_CRYPTO_SECRETBOX_NONCEBYTES;
  46. /**
  47. * Handles the cookie authentication method
  48. */
  49. class AuthenticationCookie extends AuthenticationPlugin
  50. {
  51. /**
  52. * Displays authentication form
  53. *
  54. * this function MUST exit/quit the application
  55. *
  56. * @global string $conn_error the last connection error
  57. */
  58. public function showLoginForm(): bool
  59. {
  60. global $conn_error, $route;
  61. $response = ResponseRenderer::getInstance();
  62. /**
  63. * When sending login modal after session has expired, send the
  64. * new token explicitly with the response to update the token
  65. * in all the forms having a hidden token.
  66. */
  67. $session_expired = isset($_REQUEST['check_timeout']) || isset($_REQUEST['session_timedout']);
  68. if (! $session_expired && $response->loginPage()) {
  69. if (defined('TESTSUITE')) {
  70. return true;
  71. }
  72. exit;
  73. }
  74. /**
  75. * When sending login modal after session has expired, send the
  76. * new token explicitly with the response to update the token
  77. * in all the forms having a hidden token.
  78. */
  79. if ($session_expired) {
  80. $response->setRequestStatus(false);
  81. $response->addJSON('new_token', $_SESSION[' PMA_token ']);
  82. }
  83. /**
  84. * logged_in response parameter is used to check if the login,
  85. * using the modal was successful after session expiration.
  86. */
  87. if (isset($_REQUEST['session_timedout'])) {
  88. $response->addJSON('logged_in', 0);
  89. }
  90. // No recall if blowfish secret is not configured as it would produce
  91. // garbage
  92. if ($GLOBALS['cfg']['LoginCookieRecall'] && ! empty($GLOBALS['cfg']['blowfish_secret'])) {
  93. $default_user = $this->user;
  94. $default_server = $GLOBALS['pma_auth_server'];
  95. $hasAutocomplete = true;
  96. } else {
  97. $default_user = '';
  98. $default_server = '';
  99. $hasAutocomplete = false;
  100. }
  101. // wrap the login form in a div which overlays the whole page.
  102. if ($session_expired) {
  103. $loginHeader = $this->template->render('login/header', [
  104. 'add_class' => ' modal_form',
  105. 'session_expired' => 1,
  106. ]);
  107. } else {
  108. $loginHeader = $this->template->render('login/header', [
  109. 'add_class' => '',
  110. 'session_expired' => 0,
  111. ]);
  112. }
  113. $errorMessages = '';
  114. // Show error message
  115. if (! empty($conn_error)) {
  116. $errorMessages = Message::rawError((string) $conn_error)->getDisplay();
  117. } elseif (isset($_GET['session_expired']) && intval($_GET['session_expired']) == 1) {
  118. $errorMessages = Message::rawError(
  119. __('Your session has expired. Please log in again.')
  120. )->getDisplay();
  121. }
  122. $languageManager = LanguageManager::getInstance();
  123. $availableLanguages = [];
  124. if (empty($GLOBALS['cfg']['Lang']) && $languageManager->hasChoice()) {
  125. $availableLanguages = $languageManager->sortedLanguages();
  126. }
  127. $serversOptions = '';
  128. $hasServers = count($GLOBALS['cfg']['Servers']) > 1;
  129. if ($hasServers) {
  130. $serversOptions = Select::render(false, false);
  131. }
  132. $_form_params = [];
  133. if (strlen($GLOBALS['db'])) {
  134. $_form_params['db'] = $GLOBALS['db'];
  135. }
  136. if (strlen($GLOBALS['table'])) {
  137. $_form_params['table'] = $GLOBALS['table'];
  138. }
  139. $errors = '';
  140. if ($GLOBALS['errorHandler']->hasDisplayErrors()) {
  141. $errors = $GLOBALS['errorHandler']->getDispErrors();
  142. }
  143. // close the wrapping div tag, if the request is after session timeout
  144. if ($session_expired) {
  145. $loginFooter = $this->template->render('login/footer', ['session_expired' => 1]);
  146. } else {
  147. $loginFooter = $this->template->render('login/footer', ['session_expired' => 0]);
  148. }
  149. $configFooter = Config::renderFooter();
  150. echo $this->template->render('login/form', [
  151. 'login_header' => $loginHeader,
  152. 'is_demo' => $GLOBALS['cfg']['DBG']['demo'],
  153. 'error_messages' => $errorMessages,
  154. 'available_languages' => $availableLanguages,
  155. 'is_session_expired' => $session_expired,
  156. 'has_autocomplete' => $hasAutocomplete,
  157. 'session_id' => session_id(),
  158. 'is_arbitrary_server_allowed' => $GLOBALS['cfg']['AllowArbitraryServer'],
  159. 'default_server' => $default_server,
  160. 'default_user' => $default_user,
  161. 'has_servers' => $hasServers,
  162. 'server_options' => $serversOptions,
  163. 'server' => $GLOBALS['server'],
  164. 'lang' => $GLOBALS['lang'],
  165. 'has_captcha' => ! empty($GLOBALS['cfg']['CaptchaApi'])
  166. && ! empty($GLOBALS['cfg']['CaptchaRequestParam'])
  167. && ! empty($GLOBALS['cfg']['CaptchaResponseParam'])
  168. && ! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
  169. && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey']),
  170. 'use_captcha_checkbox' => ($GLOBALS['cfg']['CaptchaMethod'] ?? '') === 'checkbox',
  171. 'captcha_api' => $GLOBALS['cfg']['CaptchaApi'],
  172. 'captcha_req' => $GLOBALS['cfg']['CaptchaRequestParam'],
  173. 'captcha_resp' => $GLOBALS['cfg']['CaptchaResponseParam'],
  174. 'captcha_key' => $GLOBALS['cfg']['CaptchaLoginPublicKey'],
  175. 'form_params' => $_form_params,
  176. 'errors' => $errors,
  177. 'login_footer' => $loginFooter,
  178. 'config_footer' => $configFooter,
  179. ]);
  180. if (! defined('TESTSUITE')) {
  181. exit;
  182. }
  183. return true;
  184. }
  185. /**
  186. * Gets authentication credentials
  187. *
  188. * this function DOES NOT check authentication - it just checks/provides
  189. * authentication credentials required to connect to the MySQL server
  190. * usually with $dbi->connect()
  191. *
  192. * it returns false if something is missing - which usually leads to
  193. * showLoginForm() which displays login form
  194. *
  195. * it returns true if all seems ok which usually leads to auth_set_user()
  196. *
  197. * it directly switches to showFailure() if user inactivity timeout is reached
  198. */
  199. public function readCredentials(): bool
  200. {
  201. global $conn_error;
  202. // Initialization
  203. /**
  204. * @global $GLOBALS['pma_auth_server'] the user provided server to
  205. * connect to
  206. */
  207. $GLOBALS['pma_auth_server'] = '';
  208. $this->user = $this->password = '';
  209. $GLOBALS['from_cookie'] = false;
  210. if (isset($_POST['pma_username']) && strlen($_POST['pma_username']) > 0) {
  211. // Verify Captcha if it is required.
  212. if (
  213. ! empty($GLOBALS['cfg']['CaptchaApi'])
  214. && ! empty($GLOBALS['cfg']['CaptchaRequestParam'])
  215. && ! empty($GLOBALS['cfg']['CaptchaResponseParam'])
  216. && ! empty($GLOBALS['cfg']['CaptchaLoginPrivateKey'])
  217. && ! empty($GLOBALS['cfg']['CaptchaLoginPublicKey'])
  218. ) {
  219. if (empty($_POST[$GLOBALS['cfg']['CaptchaResponseParam']])) {
  220. $conn_error = __('Missing reCAPTCHA verification, maybe it has been blocked by adblock?');
  221. return false;
  222. }
  223. $captchaSiteVerifyURL = $GLOBALS['cfg']['CaptchaSiteVerifyURL'] ?? '';
  224. $captchaSiteVerifyURL = empty($captchaSiteVerifyURL) ? null : $captchaSiteVerifyURL;
  225. if (function_exists('curl_init') && function_exists('curl_exec')) {
  226. $reCaptcha = new ReCaptcha\ReCaptcha(
  227. $GLOBALS['cfg']['CaptchaLoginPrivateKey'],
  228. new ReCaptcha\RequestMethod\CurlPost(null, $captchaSiteVerifyURL)
  229. );
  230. } elseif (ini_get('allow_url_fopen')) {
  231. $reCaptcha = new ReCaptcha\ReCaptcha(
  232. $GLOBALS['cfg']['CaptchaLoginPrivateKey'],
  233. new ReCaptcha\RequestMethod\Post($captchaSiteVerifyURL)
  234. );
  235. } else {
  236. $reCaptcha = new ReCaptcha\ReCaptcha(
  237. $GLOBALS['cfg']['CaptchaLoginPrivateKey'],
  238. new ReCaptcha\RequestMethod\SocketPost(null, $captchaSiteVerifyURL)
  239. );
  240. }
  241. // verify captcha status.
  242. $resp = $reCaptcha->verify(
  243. $_POST[$GLOBALS['cfg']['CaptchaResponseParam']],
  244. Core::getIp()
  245. );
  246. // Check if the captcha entered is valid, if not stop the login.
  247. if ($resp == null || ! $resp->isSuccess()) {
  248. $codes = $resp->getErrorCodes();
  249. if (in_array('invalid-json', $codes)) {
  250. $conn_error = __('Failed to connect to the reCAPTCHA service!');
  251. } else {
  252. $conn_error = __('Entered captcha is wrong, try again!');
  253. }
  254. return false;
  255. }
  256. }
  257. // The user just logged in
  258. $this->user = Core::sanitizeMySQLUser($_POST['pma_username']);
  259. $password = $_POST['pma_password'] ?? '';
  260. if (strlen($password) >= 2000) {
  261. $conn_error = __('Your password is too long. To prevent denial-of-service attacks, ' .
  262. 'phpMyAdmin restricts passwords to less than 2000 characters.');
  263. return false;
  264. }
  265. $this->password = $password;
  266. if ($GLOBALS['cfg']['AllowArbitraryServer'] && isset($_REQUEST['pma_servername'])) {
  267. if ($GLOBALS['cfg']['ArbitraryServerRegexp']) {
  268. $parts = explode(' ', $_REQUEST['pma_servername']);
  269. if (count($parts) === 2) {
  270. $tmp_host = $parts[0];
  271. } else {
  272. $tmp_host = $_REQUEST['pma_servername'];
  273. }
  274. $match = preg_match($GLOBALS['cfg']['ArbitraryServerRegexp'], $tmp_host);
  275. if (! $match) {
  276. $conn_error = __('You are not allowed to log in to this MySQL server!');
  277. return false;
  278. }
  279. }
  280. $GLOBALS['pma_auth_server'] = Core::sanitizeMySQLHost($_REQUEST['pma_servername']);
  281. }
  282. /* Secure current session on login to avoid session fixation */
  283. Session::secure();
  284. return true;
  285. }
  286. // At the end, try to set the $this->user
  287. // and $this->password variables from cookies
  288. // check cookies
  289. $serverCookie = $GLOBALS['config']->getCookie('pmaUser-' . $GLOBALS['server']);
  290. if (empty($serverCookie)) {
  291. return false;
  292. }
  293. $value = $this->cookieDecrypt(
  294. $serverCookie,
  295. $this->getEncryptionSecret()
  296. );
  297. if ($value === null) {
  298. return false;
  299. }
  300. $this->user = $value;
  301. // user was never logged in since session start
  302. if (empty($_SESSION['browser_access_time'])) {
  303. return false;
  304. }
  305. // User inactive too long
  306. $last_access_time = time() - $GLOBALS['cfg']['LoginCookieValidity'];
  307. foreach ($_SESSION['browser_access_time'] as $key => $value) {
  308. if ($value >= $last_access_time) {
  309. continue;
  310. }
  311. unset($_SESSION['browser_access_time'][$key]);
  312. }
  313. // All sessions expired
  314. if (empty($_SESSION['browser_access_time'])) {
  315. SessionCache::remove('is_create_db_priv');
  316. SessionCache::remove('is_reload_priv');
  317. SessionCache::remove('db_to_create');
  318. SessionCache::remove('dbs_where_create_table_allowed');
  319. SessionCache::remove('dbs_to_test');
  320. SessionCache::remove('db_priv');
  321. SessionCache::remove('col_priv');
  322. SessionCache::remove('table_priv');
  323. SessionCache::remove('proc_priv');
  324. $this->showFailure('no-activity');
  325. if (! defined('TESTSUITE')) {
  326. exit;
  327. }
  328. return false;
  329. }
  330. // check password cookie
  331. $serverCookie = $GLOBALS['config']->getCookie('pmaAuth-' . $GLOBALS['server']);
  332. if (empty($serverCookie)) {
  333. return false;
  334. }
  335. $value = $this->cookieDecrypt(
  336. $serverCookie,
  337. $this->getSessionEncryptionSecret()
  338. );
  339. if ($value === null) {
  340. return false;
  341. }
  342. $auth_data = json_decode($value, true);
  343. if (! is_array($auth_data) || ! isset($auth_data['password'])) {
  344. return false;
  345. }
  346. $this->password = $auth_data['password'];
  347. if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($auth_data['server'])) {
  348. $GLOBALS['pma_auth_server'] = $auth_data['server'];
  349. }
  350. $GLOBALS['from_cookie'] = true;
  351. return true;
  352. }
  353. /**
  354. * Set the user and password after last checkings if required
  355. *
  356. * @return bool always true
  357. */
  358. public function storeCredentials(): bool
  359. {
  360. global $cfg;
  361. if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($GLOBALS['pma_auth_server'])) {
  362. /* Allow to specify 'host port' */
  363. $parts = explode(' ', $GLOBALS['pma_auth_server']);
  364. if (count($parts) === 2) {
  365. $tmp_host = $parts[0];
  366. $tmp_port = $parts[1];
  367. } else {
  368. $tmp_host = $GLOBALS['pma_auth_server'];
  369. $tmp_port = '';
  370. }
  371. if ($cfg['Server']['host'] != $GLOBALS['pma_auth_server']) {
  372. $cfg['Server']['host'] = $tmp_host;
  373. if (! empty($tmp_port)) {
  374. $cfg['Server']['port'] = $tmp_port;
  375. }
  376. }
  377. unset($tmp_host, $tmp_port, $parts);
  378. }
  379. return parent::storeCredentials();
  380. }
  381. /**
  382. * Stores user credentials after successful login.
  383. */
  384. public function rememberCredentials(): void
  385. {
  386. // Name and password cookies need to be refreshed each time
  387. // Duration = one month for username
  388. $this->storeUsernameCookie($this->user);
  389. // Duration = as configured
  390. // Do not store password cookie on password change as we will
  391. // set the cookie again after password has been changed
  392. if (! isset($_POST['change_pw'])) {
  393. $this->storePasswordCookie($this->password);
  394. }
  395. // any parameters to pass?
  396. $url_params = [];
  397. if (strlen($GLOBALS['db']) > 0) {
  398. $url_params['db'] = $GLOBALS['db'];
  399. }
  400. if (strlen($GLOBALS['table']) > 0) {
  401. $url_params['table'] = $GLOBALS['table'];
  402. }
  403. // user logged in successfully after session expiration
  404. if (isset($_REQUEST['session_timedout'])) {
  405. $response = ResponseRenderer::getInstance();
  406. $response->addJSON('logged_in', 1);
  407. $response->addJSON('success', 1);
  408. $response->addJSON('new_token', $_SESSION[' PMA_token ']);
  409. if (! defined('TESTSUITE')) {
  410. exit;
  411. }
  412. return;
  413. }
  414. // Set server cookies if required (once per session) and, in this case,
  415. // force reload to ensure the client accepts cookies
  416. if ($GLOBALS['from_cookie']) {
  417. return;
  418. }
  419. /**
  420. * Clear user cache.
  421. */
  422. Util::clearUserCache();
  423. ResponseRenderer::getInstance()->disable();
  424. Core::sendHeaderLocation(
  425. './index.php?route=/' . Url::getCommonRaw($url_params, '&'),
  426. true
  427. );
  428. if (! defined('TESTSUITE')) {
  429. exit;
  430. }
  431. }
  432. /**
  433. * Stores username in a cookie.
  434. *
  435. * @param string $username User name
  436. */
  437. public function storeUsernameCookie($username): void
  438. {
  439. // Name and password cookies need to be refreshed each time
  440. // Duration = one month for username
  441. $GLOBALS['config']->setCookie(
  442. 'pmaUser-' . $GLOBALS['server'],
  443. $this->cookieEncrypt(
  444. $username,
  445. $this->getEncryptionSecret()
  446. )
  447. );
  448. }
  449. /**
  450. * Stores password in a cookie.
  451. *
  452. * @param string $password Password
  453. */
  454. public function storePasswordCookie($password): void
  455. {
  456. $payload = ['password' => $password];
  457. if ($GLOBALS['cfg']['AllowArbitraryServer'] && ! empty($GLOBALS['pma_auth_server'])) {
  458. $payload['server'] = $GLOBALS['pma_auth_server'];
  459. }
  460. // Duration = as configured
  461. $GLOBALS['config']->setCookie(
  462. 'pmaAuth-' . $GLOBALS['server'],
  463. $this->cookieEncrypt(
  464. (string) json_encode($payload),
  465. $this->getSessionEncryptionSecret()
  466. ),
  467. null,
  468. (int) $GLOBALS['cfg']['LoginCookieStore']
  469. );
  470. }
  471. /**
  472. * User is not allowed to login to MySQL -> authentication failed
  473. *
  474. * prepares error message and switches to showLoginForm() which display the error
  475. * and the login form
  476. *
  477. * this function MUST exit/quit the application,
  478. * currently done by call to showLoginForm()
  479. *
  480. * @param string $failure String describing why authentication has failed
  481. */
  482. public function showFailure($failure): void
  483. {
  484. global $conn_error;
  485. parent::showFailure($failure);
  486. // Deletes password cookie and displays the login form
  487. $GLOBALS['config']->removeCookie('pmaAuth-' . $GLOBALS['server']);
  488. $conn_error = $this->getErrorMessage($failure);
  489. $response = ResponseRenderer::getInstance();
  490. // needed for PHP-CGI (not need for FastCGI or mod-php)
  491. $response->header('Cache-Control: no-store, no-cache, must-revalidate');
  492. $response->header('Pragma: no-cache');
  493. $this->showLoginForm();
  494. }
  495. /**
  496. * Returns blowfish secret or generates one if needed.
  497. */
  498. private function getEncryptionSecret(): string
  499. {
  500. /** @var mixed $key */
  501. $key = $GLOBALS['cfg']['blowfish_secret'] ?? null;
  502. if (! is_string($key)) {
  503. return $this->getSessionEncryptionSecret();
  504. }
  505. $length = mb_strlen($key, '8bit');
  506. if ($length === SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
  507. return $key;
  508. }
  509. if ($length > SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
  510. return mb_substr($key, 0, SODIUM_CRYPTO_SECRETBOX_KEYBYTES, '8bit');
  511. }
  512. return $this->getSessionEncryptionSecret();
  513. }
  514. /**
  515. * Returns blowfish secret or generates one if needed.
  516. */
  517. private function getSessionEncryptionSecret(): string
  518. {
  519. /** @var mixed $key */
  520. $key = $_SESSION['encryption_key'] ?? null;
  521. if (is_string($key) && mb_strlen($key, '8bit') === SODIUM_CRYPTO_SECRETBOX_KEYBYTES) {
  522. return $key;
  523. }
  524. $key = random_bytes(SODIUM_CRYPTO_SECRETBOX_KEYBYTES);
  525. $_SESSION['encryption_key'] = $key;
  526. return $key;
  527. }
  528. public function cookieEncrypt(string $data, string $secret): string
  529. {
  530. try {
  531. $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);
  532. $ciphertext = sodium_crypto_secretbox($data, $nonce, $secret);
  533. } catch (Throwable $throwable) {
  534. return '';
  535. }
  536. return base64_encode($nonce . $ciphertext);
  537. }
  538. public function cookieDecrypt(string $encryptedData, string $secret): ?string
  539. {
  540. $encrypted = base64_decode($encryptedData);
  541. $nonce = mb_substr($encrypted, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit');
  542. $ciphertext = mb_substr($encrypted, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit');
  543. try {
  544. $decrypted = sodium_crypto_secretbox_open($ciphertext, $nonce, $secret);
  545. } catch (Throwable $throwable) {
  546. return null;
  547. }
  548. if (! is_string($decrypted)) {
  549. return null;
  550. }
  551. return $decrypted;
  552. }
  553. /**
  554. * Callback when user changes password.
  555. *
  556. * @param string $password New password to set
  557. */
  558. public function handlePasswordChange($password): void
  559. {
  560. $this->storePasswordCookie($password);
  561. }
  562. /**
  563. * Perform logout
  564. */
  565. public function logOut(): void
  566. {
  567. global $config;
  568. // -> delete password cookie(s)
  569. if ($GLOBALS['cfg']['LoginCookieDeleteAll']) {
  570. foreach (array_keys($GLOBALS['cfg']['Servers']) as $key) {
  571. $config->removeCookie('pmaAuth-' . $key);
  572. if (! $config->issetCookie('pmaAuth-' . $key)) {
  573. continue;
  574. }
  575. $config->removeCookie('pmaAuth-' . $key);
  576. }
  577. } else {
  578. $cookieName = 'pmaAuth-' . $GLOBALS['server'];
  579. $config->removeCookie($cookieName);
  580. if ($config->issetCookie($cookieName)) {
  581. $config->removeCookie($cookieName);
  582. }
  583. }
  584. parent::logOut();
  585. }
  586. }