AuthenticationPlugin.php 9.8 KB

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