Commit | Line | Data |
---|---|---|
f1fc0d32 | 1 | <?php |
2f0dd8d5 PB |
2 | // This file is part of Moodle - http://moodle.org/ |
3 | // | |
4 | // Moodle is free software: you can redistribute it and/or modify | |
5 | // it under the terms of the GNU General Public License as published by | |
6 | // the Free Software Foundation, either version 3 of the License, or | |
7 | // (at your option) any later version. | |
8 | // | |
9 | // Moodle is distributed in the hope that it will be useful, | |
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | // GNU General Public License for more details. | |
13 | // | |
14 | // You should have received a copy of the GNU General Public License | |
15 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
16 | ||
17 | /** | |
18 | * | |
19 | * Login library file of login/password related Moodle functions. | |
20 | * | |
21 | * @package core | |
22 | * @subpackage lib | |
23 | * @copyright Catalyst IT | |
24 | * @copyright Peter Bulmer | |
25 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
26 | */ | |
f888862e PB |
27 | define('PWRESET_STATUS_NOEMAILSENT', 1); |
28 | define('PWRESET_STATUS_TOKENSENT', 2); | |
29 | define('PWRESET_STATUS_OTHEREMAILSENT', 3); | |
92de749f | 30 | define('PWRESET_STATUS_ALREADYSENT', 4); |
f1fc0d32 | 31 | |
2f0dd8d5 PB |
32 | /** |
33 | * Processes a user's request to set a new password in the event they forgot the old one. | |
34 | * If no user identifier has been supplied, it displays a form where they can submit their identifier. | |
35 | * Where they have supplied identifier, the function will check their status, and send email as appropriate. | |
36 | */ | |
37 | function core_login_process_password_reset_request() { | |
f1fc0d32 PB |
38 | global $DB, $OUTPUT, $CFG, $PAGE; |
39 | $systemcontext = context_system::instance(); | |
40 | $mform = new login_forgot_password_form(); | |
41 | ||
42 | if ($mform->is_cancelled()) { | |
43 | redirect(get_login_url()); | |
44 | ||
45 | } else if ($data = $mform->get_data()) { | |
f888862e | 46 | // Requesting user has submitted form data. |
2f0dd8d5 | 47 | // Next find the user account in the database which the requesting user claims to own. |
f1fc0d32 | 48 | if (!empty($data->username)) { |
f888862e | 49 | // Username has been specified - load the user record based on that. |
2f0dd8d5 PB |
50 | $username = core_text::strtolower($data->username); // Mimic the login page process. |
51 | $userparams = array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id, 'deleted' => 0, 'suspended' => 0); | |
52 | $user = $DB->get_record('user', $userparams); | |
f1fc0d32 | 53 | } else { |
2f0dd8d5 | 54 | // Try to load the user record based on email address. |
f1fc0d32 | 55 | // this is tricky because |
2f0dd8d5 PB |
56 | // 1/ the email is not guaranteed to be unique - TODO: send email with all usernames to select the account for pw reset |
57 | // 2/ mailbox may be case sensitive, the email domain is case insensitive - let's pretend it is all case-insensitive. | |
f1fc0d32 | 58 | |
2f0dd8d5 PB |
59 | $select = $DB->sql_like('email', ':email', false, true, false, '|') . |
60 | " AND mnethostid = :mnethostid AND deleted=0 AND suspended=0"; | |
61 | $params = array('email' => $DB->sql_like_escape($data->email, '|'), 'mnethostid' => $CFG->mnet_localhost_id); | |
f1fc0d32 PB |
62 | $user = $DB->get_record_select('user', $select, $params, '*', IGNORE_MULTIPLE); |
63 | } | |
64 | ||
f888862e | 65 | // Target user details have now been identified, or we know that there is no such account. |
2f0dd8d5 | 66 | // Send email address to account's email address if appropriate. |
f888862e | 67 | $pwresetstatus = PWRESET_STATUS_NOEMAILSENT; |
f1fc0d32 | 68 | if ($user and !empty($user->confirmed)) { |
f1fc0d32 | 69 | $userauth = get_auth_plugin($user->auth); |
92de749f PB |
70 | if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth) |
71 | or !has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) { | |
72 | if (send_password_change_info($user)) { | |
73 | $pwresetstatus = PWRESET_STATUS_OTHEREMAILSENT; | |
f888862e | 74 | } else { |
f1fc0d32 PB |
75 | print_error('cannotmailconfirm'); |
76 | } | |
f1fc0d32 | 77 | } else { |
92de749f | 78 | // The account the requesting user claims to be is entitled to change their password. |
2f0dd8d5 | 79 | // Next, check if they have an existing password reset in progress. |
92de749f PB |
80 | $resetinprogress = $DB->get_record('user_password_resets', array('userid' => $user->id)); |
81 | if (empty($resetinprogress)) { | |
2f0dd8d5 PB |
82 | // Completely new reset request - common case. |
83 | $resetrecord = core_login_generate_password_reset($user); | |
92de749f | 84 | $sendemail = true; |
2f0dd8d5 | 85 | } else if ($resetinprogress->timerequested < (time() - $CFG->pwresettime)) { |
92de749f PB |
86 | // Preexisting, but expired request - delete old record & create new one. |
87 | // Uncommon case - expired requests are cleaned up by cron. | |
88 | $DB->delete_records('user_password_resets', array('id' => $resetinprogress->id)); | |
2f0dd8d5 | 89 | $resetrecord = core_login_generate_password_reset($user); |
92de749f | 90 | $sendemail = true; |
2f0dd8d5 PB |
91 | } else if (empty($resetinprogress->timererequested)) { |
92 | // Preexisting, valid request. This is the first time user has re-requested the reset. | |
92de749f PB |
93 | // Re-sending the same email once can actually help in certain circumstances |
94 | // eg by reducing the delay caused by greylisting. | |
95 | $resetinprogress->timererequested = time(); | |
96 | $DB->update_record('user_password_resets', $resetinprogress); | |
97 | $resetrecord = $resetinprogress; | |
98 | $sendemail = true; | |
f888862e | 99 | } else { |
92de749f PB |
100 | // Preexisting, valid request. User has already re-requested email. |
101 | $pwresetstatus = PWRESET_STATUS_ALREADYSENT; | |
102 | $sendemail = false; | |
103 | } | |
104 | ||
105 | if ($sendemail) { | |
106 | $sendresult = send_password_change_confirmation_email($user, $resetrecord); | |
107 | if ($sendresult) { | |
108 | $pwresetstatus = PWRESET_STATUS_TOKENSENT; | |
109 | } else { | |
110 | print_error('cannotmailconfirm'); | |
111 | } | |
f1fc0d32 PB |
112 | } |
113 | } | |
114 | } | |
115 | ||
f888862e | 116 | // Any email has now been sent. |
2f0dd8d5 | 117 | // Next display results to requesting user if settings permit. |
f1fc0d32 PB |
118 | echo $OUTPUT->header(); |
119 | ||
f888862e PB |
120 | if (!empty($CFG->protectusernames)) { |
121 | // Neither confirm, nor deny existance of any username or email address in database. | |
2f0dd8d5 | 122 | // Print general (non-commital) message. |
f1fc0d32 | 123 | notice(get_string('emailpasswordconfirmmaybesent'), $CFG->wwwroot.'/index.php'); |
2f0dd8d5 PB |
124 | die; // Never reached. |
125 | } else if (empty($user)) { | |
4f3f6524 | 126 | // Protect usernames is off, and we couldn't find the user with details specified. |
2f0dd8d5 | 127 | // Print failure advice. |
4f3f6524 | 128 | notice(get_string('emailpasswordconfirmnotsent'), $CFG->wwwroot.'/forgot_password.php'); |
2f0dd8d5 PB |
129 | die; // Never reached. |
130 | } else if (empty($user->email)) { | |
4f3f6524 PB |
131 | // User doesn't have an email set - can't send a password change confimation email. |
132 | notice(get_string('emailpasswordconfirmnoemail'), $CFG->wwwroot.'/index.php'); | |
2f0dd8d5 | 133 | die; // Never reached. |
92de749f PB |
134 | } else if ($pwresetstatus == PWRESET_STATUS_ALREADYSENT) { |
135 | // User found, protectusernames is off, but user has already (re) requested a reset. | |
136 | // Don't send a 3rd reset email. | |
137 | $stremailalreadysent = get_string('emailalreadysent'); | |
138 | notice($stremailalreadysent, $CFG->wwwroot.'/index.php'); | |
2f0dd8d5 PB |
139 | die; // Never reached. |
140 | } else if ($pwresetstatus == PWRESET_STATUS_NOEMAILSENT) { | |
141 | // User found, protectusernames is off, but user is not confirmed. | |
142 | // Pretend we sent them an email. | |
143 | // This is a big usability problem - need to tell users why we didn't send them an email. | |
144 | // Obfuscate email address to protect privacy. | |
145 | $protectedemail = preg_replace('/([^@]*)@(.*)/', '******@$2', $user->email); | |
f888862e PB |
146 | $stremailpasswordconfirmsent = get_string('emailpasswordconfirmsent', '', $protectedemail); |
147 | notice($stremailpasswordconfirmsent, $CFG->wwwroot.'/index.php'); | |
2f0dd8d5 | 148 | die; // Never reached. |
f1fc0d32 | 149 | } else { |
2f0dd8d5 PB |
150 | // Confirm email sent. (Obfuscate email address to protect privacy). |
151 | $protectedemail = preg_replace('/([^@]*)@(.*)/', '******@$2', $user->email); | |
f888862e | 152 | // This is a small usability problem - may be obfuscating the email address which the user has just supplied. |
92de749f PB |
153 | $stremailresetconfirmsent = get_string('emailresetconfirmsent', '', $protectedemail); |
154 | notice($stremailresetconfirmsent, $CFG->wwwroot.'/index.php'); | |
2f0dd8d5 | 155 | die; // Never reached. |
f1fc0d32 | 156 | } |
2f0dd8d5 | 157 | die; // Never reached. |
f1fc0d32 PB |
158 | } |
159 | ||
2f0dd8d5 | 160 | // Make sure we really are on the https page when https login required. |
f1fc0d32 PB |
161 | $PAGE->verify_https_required(); |
162 | ||
2f0dd8d5 | 163 | // DISPLAY FORM. |
f1fc0d32 PB |
164 | |
165 | echo $OUTPUT->header(); | |
166 | echo $OUTPUT->box(get_string('passwordforgotteninstructions2'), 'generalbox boxwidthnormal boxaligncenter'); | |
167 | $mform->display(); | |
168 | ||
169 | echo $OUTPUT->footer(); | |
170 | } | |
171 | ||
cd25119e PS |
172 | /** |
173 | * This function processes a user's submitted token to validate the request to set a new password. | |
174 | * If the user's token is validated, they are prompted to set a new password. | |
2f0dd8d5 | 175 | * @param string $token the one-use identifier which should verify the password reset request as being valid. |
cd25119e | 176 | * @return void |
2f0dd8d5 PB |
177 | */ |
178 | function core_login_process_password_set($token) { | |
92de749f | 179 | global $DB, $CFG, $OUTPUT, $PAGE, $SESSION; |
1d658535 PS |
180 | require_once($CFG->dirroot.'/user/lib.php'); |
181 | ||
92de749f | 182 | $pwresettime = isset($CFG->pwresettime) ? $CFG->pwresettime : 1800; |
2f0dd8d5 PB |
183 | $sql = "SELECT u.*, upr.token, upr.timerequested, upr.id as tokenid |
184 | FROM {user} u | |
185 | JOIN {user_password_resets} upr ON upr.userid = u.id | |
186 | WHERE upr.token = ?"; | |
92de749f PB |
187 | $user = $DB->get_record_sql($sql, array($token)); |
188 | ||
189 | $forgotpasswordurl = "{$CFG->httpswwwroot}/login/forgot_password.php"; | |
190 | if (empty($user) or ($user->timerequested < (time() - $pwresettime - DAYSECS))) { | |
191 | // There is no valid reset request record - not even a recently expired one. | |
192 | // (suspicious) | |
2f0dd8d5 | 193 | // Direct the user to the forgot password page to request a password reset. |
92de749f PB |
194 | echo $OUTPUT->header(); |
195 | notice(get_string('noresetrecord'), $forgotpasswordurl); | |
2f0dd8d5 | 196 | die; // Never reached. |
f1fc0d32 | 197 | } |
92de749f | 198 | if ($user->timerequested < (time() - $pwresettime)) { |
2f0dd8d5 PB |
199 | // There is a reset record, but it's expired. |
200 | // Direct the user to the forgot password page to request a password reset. | |
92de749f | 201 | $pwresetmins = floor($pwresettime / MINSECS); |
f1fc0d32 | 202 | echo $OUTPUT->header(); |
92de749f | 203 | notice(get_string('resetrecordexpired', '', $pwresetmins), $forgotpasswordurl); |
2f0dd8d5 | 204 | die; // Never reached. |
92de749f | 205 | } |
f1fc0d32 | 206 | |
92de749f | 207 | if ($user->auth === 'nologin' or !is_enabled_auth($user->auth)) { |
2f0dd8d5 | 208 | // Bad luck - user is not able to login, do not let them set password. |
92de749f PB |
209 | echo $OUTPUT->header(); |
210 | print_error('forgotteninvalidurl'); | |
2f0dd8d5 | 211 | die; // Never reached. |
92de749f | 212 | } |
f1fc0d32 | 213 | |
2f0dd8d5 | 214 | // Check this isn't guest user. |
92de749f PB |
215 | if (isguestuser($user)) { |
216 | print_error('cannotresetguestpwd'); | |
217 | } | |
f1fc0d32 | 218 | |
92de749f | 219 | // Token is correct, and unexpired. |
30f6c4c0 | 220 | $mform = new login_set_password_form(null, $user, 'post', '', 'autocomplete="yes"'); |
92de749f PB |
221 | $data = $mform->get_data(); |
222 | if (empty($data)) { | |
223 | // User hasn't submitted form, they got here directly from email link. | |
2f0dd8d5 | 224 | // Next, display the form. |
92de749f PB |
225 | $setdata = new stdClass(); |
226 | $setdata->username = $user->username; | |
227 | $setdata->username2 = $user->username; | |
228 | $setdata->token = $user->token; | |
229 | $mform->set_data($setdata); | |
230 | $PAGE->verify_https_required(); | |
f1fc0d32 | 231 | echo $OUTPUT->header(); |
92de749f PB |
232 | echo $OUTPUT->box(get_string('setpasswordinstructions'), 'generalbox boxwidthnormal boxaligncenter'); |
233 | $mform->display(); | |
234 | echo $OUTPUT->footer(); | |
235 | return; | |
f1fc0d32 | 236 | } else { |
92de749f | 237 | // User has submitted form. |
2f0dd8d5 | 238 | // Delete this token so it can't be used again. |
92de749f PB |
239 | $DB->delete_records('user_password_resets', array('id' => $user->tokenid)); |
240 | $userauth = get_auth_plugin($user->auth); | |
241 | if (!$userauth->user_update_password($user, $data->password)) { | |
242 | print_error('errorpasswordupdate', 'auth'); | |
f1fc0d32 | 243 | } |
1d658535 | 244 | user_add_password_history($user->id, $data->password); |
866f03de PS |
245 | if (!empty($CFG->passwordchangelogout)) { |
246 | \core\session\manager::kill_user_sessions($user->id, session_id()); | |
247 | } | |
2f0dd8d5 | 248 | // Reset login lockout (if present) before a new password is set. |
92de749f | 249 | login_unlock_account($user); |
2f0dd8d5 | 250 | // Clear any requirement to change passwords. |
92de749f PB |
251 | unset_user_preference('auth_forcepasswordchange', $user); |
252 | unset_user_preference('create_password', $user); | |
253 | ||
254 | if (!empty($user->lang)) { | |
2f0dd8d5 | 255 | // Unset previous session language - use user preference instead. |
92de749f PB |
256 | unset($SESSION->lang); |
257 | } | |
cd25119e PS |
258 | complete_user_login($user); // Triggers the login event. |
259 | ||
89e9321f PS |
260 | \core\session\manager::apply_concurrent_login_limit($user->id, session_id()); |
261 | ||
2f0dd8d5 | 262 | $urltogo = core_login_get_return_url(); |
92de749f PB |
263 | unset($SESSION->wantsurl); |
264 | redirect($urltogo, get_string('passwordset'), 1); | |
f1fc0d32 | 265 | } |
92de749f | 266 | } |
f1fc0d32 | 267 | |
2f0dd8d5 | 268 | /** Create a new record in the database to track a new password set request for user. |
92de749f | 269 | * @param object $user the user record, the requester would like a new password set for. |
2f0dd8d5 PB |
270 | * @return record created. |
271 | */ | |
272 | function core_login_generate_password_reset ($user) { | |
273 | global $DB; | |
92de749f PB |
274 | $resetrecord = new stdClass(); |
275 | $resetrecord->timerequested = time(); | |
276 | $resetrecord->userid = $user->id; | |
277 | $resetrecord->token = random_string(32); | |
278 | $resetrecord->id = $DB->insert_record('user_password_resets', $resetrecord); | |
279 | return $resetrecord; | |
f1fc0d32 | 280 | } |
f4491c2a | 281 | |
2f0dd8d5 PB |
282 | /** Determine where a user should be redirected after they have been logged in. |
283 | * @return string url the user should be redirected to. | |
284 | */ | |
285 | function core_login_get_return_url() { | |
f4491c2a | 286 | global $CFG, $SESSION, $USER; |
2f0dd8d5 | 287 | // Prepare redirection. |
8df850ad | 288 | if (user_not_fully_set_up($USER, true)) { |
f4491c2a | 289 | $urltogo = $CFG->wwwroot.'/user/edit.php'; |
2f0dd8d5 | 290 | // We don't delete $SESSION->wantsurl yet, so we get there later. |
f4491c2a | 291 | |
2f0dd8d5 PB |
292 | } else if (isset($SESSION->wantsurl) and (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0 |
293 | or strpos($SESSION->wantsurl, str_replace('http://', 'https://', $CFG->wwwroot)) === 0)) { | |
294 | $urltogo = $SESSION->wantsurl; // Because it's an address in this site. | |
f4491c2a PB |
295 | unset($SESSION->wantsurl); |
296 | } else { | |
2f0dd8d5 | 297 | // No wantsurl stored or external - go to homepage. |
f4491c2a PB |
298 | $urltogo = $CFG->wwwroot.'/'; |
299 | unset($SESSION->wantsurl); | |
300 | } | |
301 | ||
302 | // If the url to go to is the same as the site page, check for default homepage. | |
303 | if ($urltogo == ($CFG->wwwroot . '/')) { | |
2f0dd8d5 PB |
304 | $homepage = get_home_page(); |
305 | // Go to my-moodle page instead of site homepage if defaulthomepage set to homepage_my. | |
306 | if ($homepage == HOMEPAGE_MY && !is_siteadmin() && !isguestuser()) { | |
f4491c2a PB |
307 | if ($urltogo == $CFG->wwwroot or $urltogo == $CFG->wwwroot.'/' or $urltogo == $CFG->wwwroot.'/index.php') { |
308 | $urltogo = $CFG->wwwroot.'/my/'; | |
309 | } | |
310 | } | |
311 | } | |
312 | return $urltogo; | |
313 | } |