UserPassword.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin;
  4. use PhpMyAdmin\Html\Generator;
  5. use PhpMyAdmin\Query\Compatibility;
  6. use PhpMyAdmin\Server\Privileges;
  7. use function __;
  8. use function strlen;
  9. /**
  10. * Functions for user password
  11. */
  12. class UserPassword
  13. {
  14. /** @var Privileges */
  15. private $serverPrivileges;
  16. /**
  17. * @param Privileges $serverPrivileges Privileges object
  18. */
  19. public function __construct(Privileges $serverPrivileges)
  20. {
  21. $this->serverPrivileges = $serverPrivileges;
  22. }
  23. /**
  24. * Generate the message
  25. *
  26. * @return array error value and message
  27. */
  28. public function setChangePasswordMsg()
  29. {
  30. $error = false;
  31. $message = Message::success(__('The profile has been updated.'));
  32. if ($_POST['nopass'] != '1') {
  33. if (strlen($_POST['pma_pw']) === 0 || strlen($_POST['pma_pw2']) === 0) {
  34. $message = Message::error(__('The password is empty!'));
  35. $error = true;
  36. } elseif ($_POST['pma_pw'] !== $_POST['pma_pw2']) {
  37. $message = Message::error(
  38. __('The passwords aren\'t the same!')
  39. );
  40. $error = true;
  41. } elseif (strlen($_POST['pma_pw']) > 256) {
  42. $message = Message::error(__('Password is too long!'));
  43. $error = true;
  44. }
  45. }
  46. return [
  47. 'error' => $error,
  48. 'msg' => $message,
  49. ];
  50. }
  51. /**
  52. * Change the password
  53. *
  54. * @param string $password New password
  55. */
  56. public function changePassword($password): string
  57. {
  58. global $auth_plugin, $dbi;
  59. $hashing_function = $this->changePassHashingFunction();
  60. [$username, $hostname] = $dbi->getCurrentUserAndHost();
  61. $serverVersion = $dbi->getVersion();
  62. $orig_auth_plugin = $this->serverPrivileges->getCurrentAuthenticationPlugin('change', $username, $hostname);
  63. $authPluginChanged = false;
  64. if (isset($_POST['authentication_plugin']) && ! empty($_POST['authentication_plugin'])) {
  65. if ($orig_auth_plugin !== $_POST['authentication_plugin']) {
  66. $authPluginChanged = true;
  67. }
  68. $orig_auth_plugin = $_POST['authentication_plugin'];
  69. }
  70. $sql_query = 'SET password = '
  71. . ($password == '' ? '\'\'' : $hashing_function . '(\'***\')');
  72. $isPerconaOrMySql = Compatibility::isMySqlOrPerconaDb();
  73. if ($isPerconaOrMySql && $serverVersion >= 50706) {
  74. $sql_query = $this->getChangePasswordQueryAlterUserMySQL(
  75. $serverVersion,
  76. $username,
  77. $hostname,
  78. $orig_auth_plugin,
  79. $password === '' ? '' : '***', // Mask it, preview mode
  80. $authPluginChanged
  81. );
  82. } elseif (
  83. ($isPerconaOrMySql && $serverVersion >= 50507)
  84. || (Compatibility::isMariaDb() && $serverVersion >= 50200)
  85. ) {
  86. // For MySQL and Percona versions 5.5.7+ and MariaDB versions 5.2+,
  87. // explicitly set value of `old_passwords` so that
  88. // it does not give an error while using
  89. // the PASSWORD() function
  90. if ($orig_auth_plugin === 'sha256_password') {
  91. $value = 2;
  92. } else {
  93. $value = 0;
  94. }
  95. $dbi->tryQuery('SET `old_passwords` = ' . $value . ';');
  96. }
  97. $this->changePassUrlParamsAndSubmitQuery(
  98. $username,
  99. $hostname,
  100. $password,
  101. $sql_query,
  102. $hashing_function,
  103. $orig_auth_plugin,
  104. $authPluginChanged
  105. );
  106. $auth_plugin->handlePasswordChange($password);
  107. return $sql_query;
  108. }
  109. private function getChangePasswordQueryAlterUserMySQL(
  110. int $serverVersion,
  111. string $username,
  112. string $hostname,
  113. string $authPlugin,
  114. string $password,
  115. bool $authPluginChanged
  116. ): string {
  117. global $dbi;
  118. // Starting with MySQL 5.7.37 the security check changed
  119. // See: https://github.com/mysql/mysql-server/commit/b31a8a5d7805834ca2d25629c0e584d2c53b1a5b
  120. // See: https://github.com/phpmyadmin/phpmyadmin/issues/17654
  121. // That means that you should not try to change or state a plugin using IDENTIFIED WITH
  122. // Or it will say: Access denied; you need (at least one of) the CREATE USER privilege(s) for this operation
  123. // So let's avoid stating a plugin if it's not needed/changed
  124. if ($serverVersion >= 50706 && $serverVersion < 50737) {
  125. return 'ALTER USER \'' . $dbi->escapeString($username)
  126. . '\'@\'' . $dbi->escapeString($hostname)
  127. . '\' IDENTIFIED WITH ' . $authPlugin . ' BY '
  128. . ($password === '' ? '\'\'' : '\'' . $dbi->escapeString($password) . '\'');
  129. }
  130. $sql_query = 'ALTER USER \'' . $dbi->escapeString($username)
  131. . '\'@\'' . $dbi->escapeString($hostname) . '\' IDENTIFIED';
  132. if ($authPluginChanged) {
  133. $sql_query .= ' WITH ' . $authPlugin;
  134. }
  135. return $sql_query . ' BY ' . ($password === '' ? '\'\'' : '\'' . $dbi->escapeString($password) . '\'');
  136. }
  137. /**
  138. * Generate the hashing function
  139. */
  140. private function changePassHashingFunction(): string
  141. {
  142. if (isset($_POST['authentication_plugin']) && $_POST['authentication_plugin'] === 'mysql_old_password') {
  143. $hashing_function = 'OLD_PASSWORD';
  144. } else {
  145. $hashing_function = 'PASSWORD';
  146. }
  147. return $hashing_function;
  148. }
  149. /**
  150. * Changes password for a user
  151. */
  152. private function changePassUrlParamsAndSubmitQuery(
  153. string $username,
  154. string $hostname,
  155. string $password,
  156. string $sql_query,
  157. string $hashing_function,
  158. string $orig_auth_plugin,
  159. bool $authPluginChanged
  160. ): void {
  161. global $dbi;
  162. $err_url = Url::getFromRoute('/user-password');
  163. $serverVersion = $dbi->getVersion();
  164. $isPerconaOrMySql = Compatibility::isMySqlOrPerconaDb();
  165. if ($isPerconaOrMySql && $serverVersion >= 50706) {
  166. $local_query = $this->getChangePasswordQueryAlterUserMySQL(
  167. $serverVersion,
  168. $username,
  169. $hostname,
  170. $orig_auth_plugin,
  171. $password,
  172. $authPluginChanged
  173. );
  174. } elseif (
  175. Compatibility::isMariaDb()
  176. && $serverVersion >= 50200
  177. && $serverVersion < 100100
  178. && $orig_auth_plugin !== ''
  179. ) {
  180. if ($orig_auth_plugin === 'mysql_native_password') {
  181. // Set the hashing method used by PASSWORD()
  182. // to be 'mysql_native_password' type
  183. $dbi->tryQuery('SET old_passwords = 0;');
  184. } elseif ($orig_auth_plugin === 'sha256_password') {
  185. // Set the hashing method used by PASSWORD()
  186. // to be 'sha256_password' type
  187. $dbi->tryQuery('SET `old_passwords` = 2;');
  188. }
  189. $hashedPassword = $this->serverPrivileges->getHashedPassword($_POST['pma_pw']);
  190. $local_query = 'UPDATE `mysql`.`user` SET'
  191. . " `authentication_string` = '" . $hashedPassword
  192. . "', `Password` = '', "
  193. . " `plugin` = '" . $orig_auth_plugin . "'"
  194. . " WHERE `User` = '" . $dbi->escapeString($username)
  195. . "' AND Host = '" . $dbi->escapeString($hostname) . "';";
  196. } else {
  197. $local_query = 'SET password = ' . ($password == ''
  198. ? '\'\''
  199. : $hashing_function . '(\''
  200. . $dbi->escapeString($password) . '\')');
  201. }
  202. if (! @$dbi->tryQuery($local_query)) {
  203. Generator::mysqlDie(
  204. $dbi->getError(),
  205. $sql_query,
  206. false,
  207. $err_url
  208. );
  209. }
  210. // Flush privileges after successful password change
  211. $dbi->tryQuery('FLUSH PRIVILEGES;');
  212. }
  213. public function getFormForChangePassword(?string $username, ?string $hostname): string
  214. {
  215. return $this->serverPrivileges->getFormForChangePassword($username ?? '', $hostname ?? '', false);
  216. }
  217. }