AuthenticationPlugin.php 9.5 KB

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