AuthenticationPlugin.php 9.4 KB

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