1 | <?php |
||||
2 | namespace Elgg; |
||||
3 | |||||
4 | /** |
||||
5 | * PRIVATE CLASS. API IN FLUX. DO NOT USE DIRECTLY. |
||||
6 | * |
||||
7 | * @package Elgg.Core |
||||
8 | * @access private |
||||
9 | * @since 1.10.0 |
||||
10 | */ |
||||
11 | final class PasswordService { |
||||
12 | |||||
13 | /** |
||||
14 | * Constructor |
||||
15 | */ |
||||
16 | 12 | public function __construct() { |
|||
17 | 12 | if (!function_exists('password_hash')) { |
|||
18 | throw new \RuntimeException("password_hash and associated functions are required."); |
||||
19 | } |
||||
20 | 12 | } |
|||
21 | |||||
22 | /** |
||||
23 | * Determine if the password hash needs to be rehashed |
||||
24 | * |
||||
25 | * If the answer is true, after validating the password using password_verify, rehash it. |
||||
26 | * |
||||
27 | * @param string $hash The hash to test |
||||
28 | * |
||||
29 | * @return boolean True if the password needs to be rehashed. |
||||
30 | */ |
||||
31 | 5 | function needsRehash($hash) { |
|||
0 ignored issues
–
show
|
|||||
32 | 5 | return password_needs_rehash($hash, PASSWORD_DEFAULT); |
|||
33 | } |
||||
34 | |||||
35 | /** |
||||
36 | * Verify a password against a hash using a timing attack resistant approach |
||||
37 | * |
||||
38 | * @param string $password The password to verify |
||||
39 | * @param string $hash The hash to verify against |
||||
40 | * |
||||
41 | * @return boolean If the password matches the hash |
||||
42 | */ |
||||
43 | 6 | function verify($password, $hash) { |
|||
0 ignored issues
–
show
|
|||||
44 | 6 | return password_verify($password, $hash); |
|||
45 | } |
||||
46 | |||||
47 | /** |
||||
48 | * Hash a password for storage using password_hash() |
||||
49 | * |
||||
50 | * @param string $password Password in clear text |
||||
51 | * |
||||
52 | * @return string |
||||
53 | */ |
||||
54 | 162 | function generateHash($password) { |
|||
0 ignored issues
–
show
|
|||||
55 | 162 | return password_hash($password, PASSWORD_DEFAULT); |
|||
56 | } |
||||
57 | |||||
58 | /** |
||||
59 | * Generate and send a password request email to a given user's registered email address. |
||||
60 | * |
||||
61 | * @param int $user_guid User GUID |
||||
62 | * |
||||
63 | * @return bool |
||||
64 | */ |
||||
65 | function sendNewPasswordRequest($user_guid) { |
||||
0 ignored issues
–
show
|
|||||
66 | $user_guid = (int) $user_guid; |
||||
67 | |||||
68 | $user = _elgg_services()->entityTable->get($user_guid); |
||||
69 | if (!$user instanceof \ElggUser) { |
||||
70 | return false; |
||||
71 | } |
||||
72 | |||||
73 | // generate code |
||||
74 | $code = generate_random_cleartext_password(); |
||||
75 | $user->setPrivateSetting('passwd_conf_code', $code); |
||||
76 | $user->setPrivateSetting('passwd_conf_time', time()); |
||||
77 | |||||
78 | // generate link |
||||
79 | $link = _elgg_config()->wwwroot . "changepassword?u=$user_guid&c=$code"; |
||||
80 | $link = _elgg_services()->urlSigner->sign($link, '+1 day'); |
||||
81 | |||||
82 | // generate email |
||||
83 | $ip_address = _elgg_services()->request->getClientIp(); |
||||
84 | $message = _elgg_services()->translator->translate( |
||||
85 | 'email:changereq:body', [$user->name, $ip_address, $link], $user->language); |
||||
86 | $subject = _elgg_services()->translator->translate( |
||||
87 | 'email:changereq:subject', [], $user->language); |
||||
88 | |||||
89 | $params = [ |
||||
90 | 'action' => 'requestnewpassword', |
||||
91 | 'object' => $user, |
||||
92 | 'ip_address' => $ip_address, |
||||
93 | 'link' => $link, |
||||
94 | ]; |
||||
95 | |||||
96 | return notify_user($user->guid, elgg_get_site_entity()->guid, $subject, $message, $params, 'email'); |
||||
97 | } |
||||
98 | |||||
99 | /** |
||||
100 | * Set a user's new password and save the entity. |
||||
101 | * |
||||
102 | * This can only be called from execute_new_password_request(). |
||||
103 | * |
||||
104 | * @param \ElggUser|int $user The user GUID or entity |
||||
105 | * @param string $password Text (which will then be converted into a hash and stored) |
||||
106 | * |
||||
107 | * @return bool |
||||
108 | */ |
||||
109 | function forcePasswordReset($user, $password) { |
||||
0 ignored issues
–
show
|
|||||
110 | if (!$user instanceof \ElggUser) { |
||||
111 | $user = _elgg_services()->entityTable->get($user, 'user'); |
||||
112 | if (!$user) { |
||||
113 | return false; |
||||
114 | } |
||||
115 | } |
||||
116 | |||||
117 | $user->setPassword($password); |
||||
118 | |||||
119 | $ia = elgg_set_ignore_access(true); |
||||
120 | $result = (bool) $user->save(); |
||||
121 | elgg_set_ignore_access($ia); |
||||
122 | |||||
123 | return $result; |
||||
124 | } |
||||
125 | |||||
126 | /** |
||||
127 | * Validate and change password for a user. |
||||
128 | * |
||||
129 | * @param int $user_guid The user id |
||||
130 | * @param string $conf_code Confirmation code as sent in the request email. |
||||
131 | * @param string $password Optional new password, if not randomly generated. |
||||
132 | * |
||||
133 | * @return bool True on success |
||||
134 | */ |
||||
135 | function executeNewPasswordReset($user_guid, $conf_code, $password = null) { |
||||
0 ignored issues
–
show
|
|||||
136 | $user_guid = (int) $user_guid; |
||||
137 | $user = get_entity($user_guid); |
||||
138 | |||||
139 | if ($password === null) { |
||||
140 | $password = generate_random_cleartext_password(); |
||||
141 | $reset = true; |
||||
142 | } else { |
||||
143 | $reset = false; |
||||
144 | } |
||||
145 | |||||
146 | if (!$user instanceof \ElggUser) { |
||||
147 | return false; |
||||
148 | } |
||||
149 | |||||
150 | $saved_code = $user->getPrivateSetting('passwd_conf_code'); |
||||
151 | $code_time = (int) $user->getPrivateSetting('passwd_conf_time'); |
||||
152 | $codes_match = _elgg_services()->crypto->areEqual($saved_code, $conf_code); |
||||
0 ignored issues
–
show
It seems like
$saved_code can also be of type false ; however, parameter $str1 of ElggCrypto::areEqual() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
153 | |||||
154 | if (!$saved_code || !$codes_match) { |
||||
155 | return false; |
||||
156 | } |
||||
157 | |||||
158 | // Discard for security if it is 24h old |
||||
159 | if (!$code_time || $code_time < time() - 24 * 60 * 60) { |
||||
160 | return false; |
||||
161 | } |
||||
162 | |||||
163 | if (!$this->forcePasswordReset($user, $password)) { |
||||
164 | return false; |
||||
165 | } |
||||
166 | |||||
167 | remove_private_setting($user_guid, 'passwd_conf_code'); |
||||
168 | remove_private_setting($user_guid, 'passwd_conf_time'); |
||||
169 | // clean the logins failures |
||||
170 | reset_login_failure_count($user_guid); |
||||
171 | |||||
172 | $ns = $reset ? 'resetpassword' : 'changepassword'; |
||||
173 | |||||
174 | $message = _elgg_services()->translator->translate( |
||||
175 | "email:$ns:body", [$user->username, $password], $user->language); |
||||
176 | $subject = _elgg_services()->translator->translate("email:$ns:subject", [], $user->language); |
||||
177 | |||||
178 | $params = [ |
||||
179 | 'action' => $ns, |
||||
180 | 'object' => $user, |
||||
181 | 'password' => $password, |
||||
182 | ]; |
||||
183 | |||||
184 | notify_user($user->guid, elgg_get_site_entity()->guid, $subject, $message, $params, 'email'); |
||||
185 | |||||
186 | return true; |
||||
187 | } |
||||
188 | } |
||||
189 |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.