AuthenticationPlugin.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. <?php
  2. /**
  3. * Abstract class for the authentication plugins
  4. */
  5. declare(strict_types=1);
  6. namespace PhpMyAdmin\Plugins;
  7. use PhpMyAdmin\Config;
  8. use PhpMyAdmin\Core;
  9. use PhpMyAdmin\IpAllowDeny;
  10. use PhpMyAdmin\Logging;
  11. use PhpMyAdmin\Message;
  12. use PhpMyAdmin\Response;
  13. use PhpMyAdmin\Session;
  14. use PhpMyAdmin\Template;
  15. use PhpMyAdmin\TwoFactor;
  16. use PhpMyAdmin\Url;
  17. use function defined;
  18. use function htmlspecialchars;
  19. use function intval;
  20. use function max;
  21. use function min;
  22. use function session_destroy;
  23. use function session_unset;
  24. use function sprintf;
  25. use function time;
  26. /**
  27. * Provides a common interface that will have to be implemented by all of the
  28. * authentication plugins.
  29. */
  30. abstract class AuthenticationPlugin
  31. {
  32. /**
  33. * Username
  34. *
  35. * @var string
  36. */
  37. public $user = '';
  38. /**
  39. * Password
  40. *
  41. * @var string
  42. */
  43. public $password = '';
  44. /** @var IpAllowDeny */
  45. protected $ipAllowDeny;
  46. /** @var Template */
  47. public $template;
  48. public function __construct()
  49. {
  50. $this->ipAllowDeny = new IpAllowDeny();
  51. $this->template = new Template();
  52. }
  53. /**
  54. * Displays authentication form
  55. *
  56. * @return bool
  57. */
  58. abstract public function showLoginForm();
  59. /**
  60. * Gets authentication credentials
  61. *
  62. * @return bool
  63. */
  64. abstract public function readCredentials();
  65. /**
  66. * Set the user and password after last checkings if required
  67. *
  68. * @return bool
  69. */
  70. public function storeCredentials()
  71. {
  72. global $cfg;
  73. $this->setSessionAccessTime();
  74. $cfg['Server']['user'] = $this->user;
  75. $cfg['Server']['password'] = $this->password;
  76. return true;
  77. }
  78. /**
  79. * Stores user credentials after successful login.
  80. *
  81. * @return void
  82. */
  83. public function rememberCredentials()
  84. {
  85. }
  86. /**
  87. * User is not allowed to login to MySQL -> authentication failed
  88. *
  89. * @param string $failure String describing why authentication has failed
  90. *
  91. * @return void
  92. */
  93. public function showFailure($failure)
  94. {
  95. Logging::logUser($this->user, $failure);
  96. }
  97. /**
  98. * Perform logout
  99. *
  100. * @return void
  101. */
  102. public function logOut()
  103. {
  104. global $PMA_Config;
  105. /* Obtain redirect URL (before doing logout) */
  106. if (! empty($GLOBALS['cfg']['Server']['LogoutURL'])) {
  107. $redirect_url = $GLOBALS['cfg']['Server']['LogoutURL'];
  108. } else {
  109. $redirect_url = $this->getLoginFormURL();
  110. }
  111. /* Clear credentials */
  112. $this->user = '';
  113. $this->password = '';
  114. /*
  115. * Get a logged-in server count in case of LoginCookieDeleteAll is disabled.
  116. */
  117. $server = 0;
  118. if ($GLOBALS['cfg']['LoginCookieDeleteAll'] === false
  119. && $GLOBALS['cfg']['Server']['auth_type'] === 'cookie'
  120. ) {
  121. foreach ($GLOBALS['cfg']['Servers'] as $key => $val) {
  122. if (! $PMA_Config->issetCookie('pmaAuth-' . $key)) {
  123. continue;
  124. }
  125. $server = $key;
  126. }
  127. }
  128. if ($server === 0) {
  129. /* delete user's choices that were stored in session */
  130. if (! defined('TESTSUITE')) {
  131. session_unset();
  132. session_destroy();
  133. }
  134. /* Redirect to login form (or configured URL) */
  135. Core::sendHeaderLocation($redirect_url);
  136. } else {
  137. /* Redirect to other authenticated server */
  138. $_SESSION['partial_logout'] = true;
  139. Core::sendHeaderLocation(
  140. './index.php?route=/' . Url::getCommonRaw(['server' => $server], '&')
  141. );
  142. }
  143. }
  144. /**
  145. * Returns URL for login form.
  146. *
  147. * @return string
  148. */
  149. public function getLoginFormURL()
  150. {
  151. return './index.php?route=/';
  152. }
  153. /**
  154. * Returns error message for failed authentication.
  155. *
  156. * @param string $failure String describing why authentication has failed
  157. *
  158. * @return string
  159. */
  160. public function getErrorMessage($failure)
  161. {
  162. global $dbi;
  163. if ($failure === 'empty-denied') {
  164. return __(
  165. 'Login without a password is forbidden by configuration'
  166. . ' (see AllowNoPassword)'
  167. );
  168. }
  169. if ($failure === 'root-denied' || $failure === 'allow-denied') {
  170. return __('Access denied!');
  171. }
  172. if ($failure === 'no-activity') {
  173. return sprintf(
  174. __('You have been automatically logged out due to inactivity of %s seconds.'
  175. . ' Once you log in again, you should be able to resume the work where you left off.'),
  176. intval($GLOBALS['cfg']['LoginCookieValidity'])
  177. );
  178. }
  179. $dbi_error = $dbi->getError();
  180. if (! empty($dbi_error)) {
  181. return htmlspecialchars($dbi_error);
  182. }
  183. if (isset($GLOBALS['errno'])) {
  184. return '#' . $GLOBALS['errno'] . ' '
  185. . __('Cannot log in to the MySQL server');
  186. }
  187. return __('Cannot log in to the MySQL server');
  188. }
  189. /**
  190. * Callback when user changes password.
  191. *
  192. * @param string $password New password to set
  193. *
  194. * @return void
  195. */
  196. public function handlePasswordChange($password)
  197. {
  198. }
  199. /**
  200. * Store session access time in session.
  201. *
  202. * Tries to workaround PHP 5 session garbage collection which
  203. * looks at the session file's last modified time
  204. *
  205. * @return void
  206. */
  207. public function setSessionAccessTime()
  208. {
  209. if (isset($_REQUEST['guid'])) {
  210. $guid = (string) $_REQUEST['guid'];
  211. } else {
  212. $guid = 'default';
  213. }
  214. if (isset($_REQUEST['access_time'])) {
  215. // Ensure access_time is in range <0, LoginCookieValidity + 1>
  216. // to avoid excessive extension of validity.
  217. //
  218. // Negative values can cause session expiry extension
  219. // Too big values can cause overflow and lead to same
  220. $time = time() - min(max(0, intval($_REQUEST['access_time'])), $GLOBALS['cfg']['LoginCookieValidity'] + 1);
  221. } else {
  222. $time = time();
  223. }
  224. $_SESSION['browser_access_time'][$guid] = $time;
  225. }
  226. /**
  227. * High level authentication interface
  228. *
  229. * Gets the credentials or shows login form if necessary
  230. *
  231. * @return void
  232. */
  233. public function authenticate()
  234. {
  235. $success = $this->readCredentials();
  236. /* Show login form (this exits) */
  237. if (! $success) {
  238. /* Force generating of new session */
  239. Session::secure();
  240. $this->showLoginForm();
  241. }
  242. /* Store credentials (eg. in cookies) */
  243. $this->storeCredentials();
  244. /* Check allow/deny rules */
  245. $this->checkRules();
  246. }
  247. /**
  248. * Check configuration defined restrictions for authentication
  249. *
  250. * @return void
  251. */
  252. public function checkRules()
  253. {
  254. global $cfg;
  255. // Check IP-based Allow/Deny rules as soon as possible to reject the
  256. // user based on mod_access in Apache
  257. if (isset($cfg['Server']['AllowDeny']['order'])) {
  258. $allowDeny_forbidden = false; // default
  259. if ($cfg['Server']['AllowDeny']['order'] === 'allow,deny') {
  260. $allowDeny_forbidden = true;
  261. if ($this->ipAllowDeny->allow()) {
  262. $allowDeny_forbidden = false;
  263. }
  264. if ($this->ipAllowDeny->deny()) {
  265. $allowDeny_forbidden = true;
  266. }
  267. } elseif ($cfg['Server']['AllowDeny']['order'] === 'deny,allow') {
  268. if ($this->ipAllowDeny->deny()) {
  269. $allowDeny_forbidden = true;
  270. }
  271. if ($this->ipAllowDeny->allow()) {
  272. $allowDeny_forbidden = false;
  273. }
  274. } elseif ($cfg['Server']['AllowDeny']['order'] === 'explicit') {
  275. if ($this->ipAllowDeny->allow() && ! $this->ipAllowDeny->deny()) {
  276. $allowDeny_forbidden = false;
  277. } else {
  278. $allowDeny_forbidden = true;
  279. }
  280. }
  281. // Ejects the user if banished
  282. if ($allowDeny_forbidden) {
  283. $this->showFailure('allow-denied');
  284. }
  285. }
  286. // is root allowed?
  287. if (! $cfg['Server']['AllowRoot'] && $cfg['Server']['user'] === 'root') {
  288. $this->showFailure('root-denied');
  289. }
  290. // is a login without password allowed?
  291. if ($cfg['Server']['AllowNoPassword']
  292. || $cfg['Server']['password'] !== ''
  293. ) {
  294. return;
  295. }
  296. $this->showFailure('empty-denied');
  297. }
  298. /**
  299. * Checks whether two factor authentication is active
  300. * for given user and performs it.
  301. */
  302. public function checkTwoFactor(): void
  303. {
  304. $twofactor = new TwoFactor($this->user);
  305. /* Do we need to show the form? */
  306. if ($twofactor->check()) {
  307. return;
  308. }
  309. $response = Response::getInstance();
  310. if ($response->loginPage()) {
  311. if (defined('TESTSUITE')) {
  312. return;
  313. }
  314. exit;
  315. }
  316. echo $this->template->render('login/header', ['theme' => $GLOBALS['PMA_Theme']]);
  317. echo Message::rawNotice(
  318. __('You have enabled two factor authentication, please confirm your login.')
  319. )->getDisplay();
  320. echo $this->template->render('login/twofactor', [
  321. 'form' => $twofactor->render(),
  322. 'show_submit' => $twofactor->showSubmit(),
  323. ]);
  324. echo $this->template->render('login/footer');
  325. echo Config::renderFooter();
  326. if (! defined('TESTSUITE')) {
  327. exit;
  328. }
  329. }
  330. }