PasswordService.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <?php
  2. namespace Elgg;
  3. /**
  4. * PRIVATE CLASS. API IN FLUX. DO NOT USE DIRECTLY.
  5. *
  6. * @package Elgg.Core
  7. * @access private
  8. * @since 1.10.0
  9. */
  10. final class PasswordService {
  11. /**
  12. * Constructor
  13. */
  14. public function __construct() {
  15. if (!function_exists('password_hash')) {
  16. throw new \RuntimeException("password_hash and associated functions are required.");
  17. }
  18. }
  19. /**
  20. * Determine if the password hash needs to be rehashed
  21. *
  22. * If the answer is true, after validating the password using password_verify, rehash it.
  23. *
  24. * @param string $hash The hash to test
  25. *
  26. * @return boolean True if the password needs to be rehashed.
  27. */
  28. function needsRehash($hash) {
  29. return password_needs_rehash($hash, PASSWORD_DEFAULT);
  30. }
  31. /**
  32. * Verify a password against a hash using a timing attack resistant approach
  33. *
  34. * @param string $password The password to verify
  35. * @param string $hash The hash to verify against
  36. *
  37. * @return boolean If the password matches the hash
  38. */
  39. function verify($password, $hash) {
  40. return password_verify($password, $hash);
  41. }
  42. /**
  43. * Hash a password for storage using password_hash()
  44. *
  45. * @param string $password Password in clear text
  46. *
  47. * @return string
  48. */
  49. function generateHash($password) {
  50. return password_hash($password, PASSWORD_DEFAULT);
  51. }
  52. /**
  53. * Hash a password for storage. Currently salted MD5.
  54. *
  55. * @param \ElggUser $user The user this is being generated for.
  56. * @param string $password Password in clear text
  57. *
  58. * @return string
  59. */
  60. function generateLegacyHash(\ElggUser $user, $password) {
  61. return md5($password . $user->salt);
  62. }
  63. /**
  64. * Generate and send a password request email to a given user's registered email address.
  65. *
  66. * @param int $user_guid User GUID
  67. *
  68. * @return bool
  69. */
  70. function sendNewPasswordRequest($user_guid) {
  71. $user_guid = (int)$user_guid;
  72. $user = _elgg_services()->entityTable->get($user_guid);
  73. if (!$user instanceof \ElggUser) {
  74. return false;
  75. }
  76. // generate code
  77. $code = generate_random_cleartext_password();
  78. $user->setPrivateSetting('passwd_conf_code', $code);
  79. $user->setPrivateSetting('passwd_conf_time', time());
  80. // generate link
  81. $link = _elgg_services()->config->getSiteUrl() . "changepassword?u=$user_guid&c=$code";
  82. // generate email
  83. $ip_address = _elgg_services()->request->getClientIp();
  84. $message = _elgg_services()->translator->translate(
  85. 'email:changereq:body', array($user->name, $ip_address, $link), $user->language);
  86. $subject = _elgg_services()->translator->translate(
  87. 'email:changereq:subject', array(), $user->language);
  88. return notify_user($user->guid, elgg_get_site_entity()->guid, $subject, $message, array(), 'email');
  89. }
  90. /**
  91. * Set a user's new password and save the entity.
  92. *
  93. * This can only be called from execute_new_password_request().
  94. *
  95. * @param \ElggUser|int $user The user GUID or entity
  96. * @param string $password Text (which will then be converted into a hash and stored)
  97. *
  98. * @return bool
  99. */
  100. function forcePasswordReset($user, $password) {
  101. if (!$user instanceof \ElggUser) {
  102. $user = _elgg_services()->entityTable->get($user, 'user');
  103. if (!$user) {
  104. return false;
  105. }
  106. }
  107. $user->setPassword($password);
  108. $ia = elgg_set_ignore_access(true);
  109. $result = (bool)$user->save();
  110. elgg_set_ignore_access($ia);
  111. return $result;
  112. }
  113. /**
  114. * Validate and change password for a user.
  115. *
  116. * @param int $user_guid The user id
  117. * @param string $conf_code Confirmation code as sent in the request email.
  118. * @param string $password Optional new password, if not randomly generated.
  119. *
  120. * @return bool True on success
  121. */
  122. function executeNewPasswordReset($user_guid, $conf_code, $password = null) {
  123. $user_guid = (int)$user_guid;
  124. $user = get_entity($user_guid);
  125. if ($password === null) {
  126. $password = generate_random_cleartext_password();
  127. $reset = true;
  128. } else {
  129. $reset = false;
  130. }
  131. if (!$user instanceof \ElggUser) {
  132. return false;
  133. }
  134. $saved_code = $user->getPrivateSetting('passwd_conf_code');
  135. $code_time = (int) $user->getPrivateSetting('passwd_conf_time');
  136. $codes_match = _elgg_services()->crypto->areEqual($saved_code, $conf_code);
  137. if (!$saved_code || !$codes_match) {
  138. return false;
  139. }
  140. // Discard for security if it is 24h old
  141. if (!$code_time || $code_time < time() - 24 * 60 * 60) {
  142. return false;
  143. }
  144. if (!$this->forcePasswordReset($user, $password)) {
  145. return false;
  146. }
  147. remove_private_setting($user_guid, 'passwd_conf_code');
  148. remove_private_setting($user_guid, 'passwd_conf_time');
  149. // clean the logins failures
  150. reset_login_failure_count($user_guid);
  151. $ns = $reset ? 'resetpassword' : 'changepassword';
  152. $message = _elgg_services()->translator->translate(
  153. "email:$ns:body", array($user->username, $password), $user->language);
  154. $subject = _elgg_services()->translator->translate("email:$ns:subject", array(), $user->language);
  155. notify_user($user->guid, elgg_get_site_entity()->guid, $subject, $message, array(), 'email');
  156. return true;
  157. }
  158. }