Merge branch 'passwordreset-2013-10-07-1031Z' of https://github.com/peterbulmer/moodle

Conflicts:
	lib/db/install.xml
	lib/db/upgrade.php
	version.php
This commit is contained in:
Damyon Wiese 2013-10-08 10:30:57 +08:00
commit 18ef6201c0
13 changed files with 546 additions and 172 deletions

View File

@ -70,6 +70,22 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
$temp->add(new admin_setting_configtext('minpasswordupper', new lang_string('minpasswordupper', 'admin'), new lang_string('configminpasswordupper', 'admin'), 1, PARAM_INT));
$temp->add(new admin_setting_configtext('minpasswordnonalphanum', new lang_string('minpasswordnonalphanum', 'admin'), new lang_string('configminpasswordnonalphanum', 'admin'), 1, PARAM_INT));
$temp->add(new admin_setting_configtext('maxconsecutiveidentchars', new lang_string('maxconsecutiveidentchars', 'admin'), new lang_string('configmaxconsecutiveidentchars', 'admin'), 0, PARAM_INT));
$pwresetoptions = array(
300 => new lang_string('numminutes', '', 5),
900 => new lang_string('numminutes', '', 15),
1800 => new lang_string('numminutes', '', 30),
2700 => new lang_string('numminutes', '', 45),
3600 => new lang_string('numminutes', '', 60),
7200 => new lang_string('numminutes', '', 120),
14400 => new lang_string('numminutes', '', 240)
);
$adminsetting = new admin_setting_configselect(
'pwresettime',
new lang_string('passwordresettime','admin'),
new lang_string('configpasswordresettime','admin'),
1800,
$pwresetoptions);
$temp->add($adminsetting);
$temp->add(new admin_setting_configcheckbox('groupenrolmentkeypolicy', new lang_string('groupenrolmentkeypolicy', 'admin'), new lang_string('groupenrolmentkeypolicy_desc', 'admin'), 1));
$temp->add(new admin_setting_configcheckbox('disableuserimages', new lang_string('disableuserimages', 'admin'), new lang_string('configdisableuserimages', 'admin'), 0));
$temp->add(new admin_setting_configcheckbox('emailchangeconfirmation', new lang_string('emailchangeconfirmation', 'admin'), new lang_string('configemailchangeconfirmation', 'admin'), 1));

View File

@ -278,6 +278,7 @@ $string['confignotloggedinroleid'] = 'Users who are not logged in to the site wi
$string['configopentogoogle'] = 'If you enable this setting, then Google will be allowed to enter your site as a Guest. In addition, people coming in to your site via a Google search will automatically be logged in as a Guest. Note that this only provides transparent access to courses that already allow guest access.';
$string['configoverride'] = 'Defined in config.php';
$string['configpasswordpolicy'] = 'Turning this on will make Moodle check user passwords against a valid password policy. Use the settings below to specify your policy (they will be ignored if you set this to \'No\').';
$string['configpasswordresettime'] = 'This specifies the amount of time people have to validate a password reset request before it expires. Usually 30 minutes is a good value.';
$string['configpathtoclam'] = 'Path to clam AV. Probably something like /usr/bin/clamscan or /usr/bin/clamdscan. You need this in order for clam AV to run.';
$string['configpathtodu'] = 'Path to du. Probably something like /usr/bin/du. If you enter this, pages that display directory contents will run much faster for directories with a lot of files.';
$string['configperfdebug'] = 'If you turn this on, performance info will be printed in the footer of the standard theme';
@ -779,6 +780,7 @@ $string['order2'] = 'Second';
$string['order3'] = 'Third';
$string['order4'] = 'Fourth';
$string['passwordpolicy'] = 'Password policy';
$string['passwordresettime'] = 'Maximum time to validate password reset request';
$string['pathconvert'] = 'Path of <i>convert</i> binary';
$string['pathdvips'] = 'Path of <i>dvips</i> binary';
$string['pathlatex'] = 'Path of <i>latex</i> binary';

View File

@ -544,6 +544,7 @@ $string['editthisactivity'] = 'Edit this activity';
$string['editthiscategory'] = 'Edit this category';
$string['edituser'] = 'Edit user accounts';
$string['email'] = 'Email address';
$string['emailalreadysent'] = 'A password reset email has already been sent. Please check your email.';
$string['emailactive'] = 'Email activated';
$string['emailagain'] = 'Email (again)';
$string['emailconfirm'] = 'Confirm your account';
@ -615,6 +616,11 @@ $string['emailpasswordconfirmationsubject'] = '{$a}: Change password confirmatio
$string['emailpasswordconfirmmaybesent'] = '<p>If you supplied a correct username or email address then an email should have been sent to you.</p>
<p>It contains easy instructions to confirm and complete this password change.
If you continue to have difficulty, please contact the site administrator.</p>';
$string['emailpasswordconfirmnoemail'] = '<p>The user account you specified does not have a recorded email address.</p>
<p>Please contact the site administrator.</p>';
$string['emailpasswordconfirmnotsent'] = '<p>The user detail you supplied does not identify an existing user account.</p>
<p>Please check the information you entered, then try again.
If you continue to have difficulty, please contact the site administrator.</p>';
$string['emailpasswordconfirmsent'] = 'An email should have been sent to your address at <b>{$a}</b>.
<br />It contains easy instructions to confirm and complete this password change.
If you continue to have difficulty, contact the site administrator.';
@ -655,6 +661,24 @@ $string['emailpasswordsent'] = 'Thank you for confirming the change of password.
An email containing your new password has been sent to your address at<br /><b>{$a->email}</b>.<br />
The new password was automatically generated - you might like to
<a href="{$a->link}">change your password</a> to something easier to remember.';
$string['emailresetconfirmation'] = 'Hi {$a->firstname},
A password reset was requested for your account \'{$a->username}\' at {$a->sitename}.
To confirm this request, and set a new password for your account, please
go to the following web address:
{$a->link}
(This link is valid for {$a->resetminutes} minutes from the time this reset was first requested)
If this password reset was not requested by you, no action is needed.
If you need help, please contact the site administrator,
{$a->admin}';
$string['emailresetconfirmationsubject'] = '{$a}: Password reset request';
$string['emailresetconfirmsent'] = 'An email has been sent to your address at <b>{$a}</b>.
<br />It contains easy instructions to confirm and complete this password change.
If you continue to have difficulty, contact the site administrator.';
$string['emptydragdropregion'] = 'empty region';
$string['enable'] = 'Enable';
$string['encryptedcode'] = 'Encrypted code';
@ -1238,6 +1262,7 @@ $string['noreplybouncemessage'] = 'You have replied to a no-reply email address.
Following is the content of your email:';
$string['noreplybouncesubject'] = '{$a} - bounced email.';
$string['noreplyname'] = 'Do not reply to this email';
$string['noresetrecord'] = 'There is no record of that reset request. Please initiate a new password reset request.';
$string['noresults'] = 'No results';
$string['normal'] = 'Normal';
$string['normalfilter'] = 'Normal search';
@ -1317,6 +1342,7 @@ $string['passwordnohelp'] = 'No help is available to find your lost password. Pl
$string['passwordrecovery'] = 'Yes, help me log in';
$string['passwordsdiffer'] = 'These passwords do not match';
$string['passwordsent'] = 'Password has been sent';
$string['passwordset'] = 'Your password has been set.';
$string['passwordsenttext'] = '<p>An email has been sent to your address at {$a->email}.</p>
<p><b>Please check your email for your new password</b></p>
<p>The new password was automatically generated, so you might like to
@ -1434,6 +1460,7 @@ $string['resetcomponent'] = 'Component';
$string['resetcourse'] = 'Reset course';
$string['resetinfo'] = 'This page allows you to empty a course of user data, while retaining the activities and other settings. Please be warned that by choosing items below and submitting this page you will delete your chosen user data from this course forever!';
$string['resetnotimplemented'] = 'Reset not implemented';
$string['resetrecordexpired'] = 'The password reset link you used is more than {$a} minutes old and has expired. Please initiate a new password reset.';
$string['resetstartdate'] = 'Reset start date';
$string['resetstatus'] = 'Status';
$string['resettask'] = 'Task';
@ -1568,6 +1595,8 @@ $string['separateandconnectedinfo'] = 'The scale based on the theory of separate
$string['servererror'] = 'An error occurred whilst communicating with the server';
$string['serverlocaltime'] = 'Server\'s local time';
$string['setcategorytheme'] = 'Set category theme';
$string['setpassword'] = 'Set password';
$string['setpasswordinstructions'] = 'Please enter and repeat your new password below, then click "Set password". <br />Your new password will be saved, and you will be logged in.';
$string['settings'] = 'Settings';
$string['shortname'] = 'Short name'; // @deprecated MDL-34652 - use shortnamecourse or shortnameuser or some own context specific string
$string['shortnamecollisionwarning'] = '[*] = This shortname is already in use by a course and will need to be changed upon approval';

View File

@ -173,6 +173,15 @@ function cron_run() {
mtrace(' Deleting temporary files...');
cron_delete_from_temp();
// Cleanup user password reset records
// Delete any reset request records which are expired by more than a day.
// (We keep recently expired requests around so we can give a different error msg to users who
// are trying to user a recently expired reset attempt).
$pwresettime = isset($CFG->pwresettime) ? $CFG->pwresettime : 1800;
$earliestvalid = time() - $pwresettime - DAYSECS;
$DB->delete_records_select('user_password_resets', "timerequested < ?", array($earliestvalid));
mtrace(' Cleaned up old password reset records');
mtrace("...finished clean-up tasks");
} // End of occasional clean-up tasks

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="lib/db" VERSION="20131004" COMMENT="XMLDB file for core Moodle tables"
<XMLDB PATH="lib/db" VERSION="20131008" COMMENT="XMLDB file for core Moodle tables"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
>
@ -3070,5 +3070,18 @@
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
<TABLE NAME="user_password_resets" COMMENT="table tracking password reset confirmation tokens">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
<FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="id of the user account which requester claimed to be"/>
<FIELD NAME="timerequested" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" COMMENT="The time that the user first requested this password reset"/>
<FIELD NAME="timererequested" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The time the user re-requested the password reset."/>
<FIELD NAME="token" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false" COMMENT="secret set and emailed to user"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
<KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id"/>
</KEYS>
</TABLE>
</TABLES>
</XMLDB>

View File

@ -2622,5 +2622,25 @@ function xmldb_main_upgrade($oldversion) {
upgrade_main_savepoint(true, 2013100800.00);
}
if ($oldversion < 2013100800.01) {
// Create a new 'user_password_resets' table.
$table = new xmldb_table('user_password_resets');
$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null, null);
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, null);
$table->add_field('timerequested', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null, null);
$table->add_field('timererequested', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 0, null);
$table->add_field('token', XMLDB_TYPE_CHAR, '32', null, XMLDB_NOTNULL, null, null, null);
// Adding keys to table.
$table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
$table->add_key('fk_userid', XMLDB_KEY_FOREIGN, array('userid'), 'user', array('id'));
// Conditionally launch create table.
if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}
upgrade_main_savepoint(true, 2013100800.01);
}
return true;
}

View File

@ -5953,23 +5953,27 @@ function send_confirmation_email($user) {
* Sends a password change confirmation email.
*
* @param stdClass $user A {@link $USER} object
* @param stdClass $resetrecord An object tracking metadata regarding password reset request
* @return bool Returns true if mail was sent OK and false if there was an error.
*/
function send_password_change_confirmation_email($user) {
function send_password_change_confirmation_email($user, $resetrecord) {
global $CFG;
$site = get_site();
$supportuser = core_user::get_support_user();
$pwresetmins = isset($CFG->pwresettime) ? floor($CFG->pwresettime / MINSECS) : 30;
$data = new stdClass();
$data->firstname = $user->firstname;
$data->lastname = $user->lastname;
$data->username = $user->username;
$data->sitename = format_string($site->fullname);
$data->link = $CFG->httpswwwroot .'/login/forgot_password.php?p='. $user->secret .'&s='. urlencode($user->username);
$data->link = $CFG->httpswwwroot .'/login/forgot_password.php?token='. $resetrecord->token;
$data->admin = generate_email_signoff();
$data->resetminutes = $pwresetmins;
$message = get_string('emailpasswordconfirmation', '', $data);
$subject = get_string('emailpasswordconfirmationsubject', '', format_string($site->fullname));
$message = get_string('emailresetconfirmation', '', $data);
$subject = get_string('emailresetconfirmationsubject', '', format_string($site->fullname));
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
return email_to_user($user, $supportuser, $subject, $message);

View File

@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -28,10 +27,11 @@
require('../config.php');
require_once($CFG->libdir.'/authlib.php');
require_once(__DIR__ . '/lib.php');
require_once('forgot_password_form.php');
require_once('set_password_form.php');
$p_secret = optional_param('p', false, PARAM_RAW);
$p_username = optional_param('s', false, PARAM_RAW);
$token = optional_param('token', false, PARAM_ALPHANUM);
//HTTPS is required in this page when $CFG->loginhttps enabled
$PAGE->https_required();
@ -59,136 +59,12 @@ if (isloggedin() and !isguestuser()) {
redirect($CFG->wwwroot.'/index.php', get_string('loginalready'), 5);
}
if ($p_secret !== false) {
///=====================
/// user clicked on link in email message
///=====================
$user = $DB->get_record('user', array('username'=>$p_username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0, 'suspended'=>0));
if ($user and ($user->auth === 'nologin' or !is_enabled_auth($user->auth))) {
// bad luck - user is not able to login, do not let them reset password
$user = false;
}
if (!empty($user) and $user->secret === '') {
echo $OUTPUT->header();
print_error('secretalreadyused');
} else if (!empty($user) and $user->secret == $p_secret) {
// make sure that url relates to a valid user
// check this isn't guest user
if (isguestuser($user)) {
print_error('cannotresetguestpwd');
}
// Reset login lockout even of the password reset fails.
login_unlock_account($user);
// make sure user is allowed to change password
require_capability('moodle/user:changeownpassword', $systemcontext, $user->id);
if (!reset_password_and_mail($user)) {
print_error('cannotresetmail');
}
// Clear secret so that it can not be used again
$user->secret = '';
$DB->set_field('user', 'secret', $user->secret, array('id'=>$user->id));
$changepasswordurl = "{$CFG->httpswwwroot}/login/change_password.php";
$a = new stdClass();
$a->email = $user->email;
$a->link = $changepasswordurl;
echo $OUTPUT->header();
notice(get_string('emailpasswordsent', '', $a), $changepasswordurl);
} else {
if (!empty($user) and strlen($p_secret) === 15) {
// somebody probably tries to hack in by guessing secret - stop them!
$DB->set_field('user', 'secret', '', array('id'=>$user->id));
}
echo $OUTPUT->header();
print_error('forgotteninvalidurl');
}
die; //never reached
if (empty($token)) {
// This is a new password reset request.
// Process the request; identify the user & send confirmation email.
core_login_process_password_reset_request();
} else {
// User clicked on confirmation link in email message
// validate the token & set new password
core_login_process_password_set($token);
}
$mform = new login_forgot_password_form();
if ($mform->is_cancelled()) {
redirect(get_login_url());
} else if ($data = $mform->get_data()) {
/// find the user in the database and mail info
// first try the username
if (!empty($data->username)) {
$username = core_text::strtolower($data->username); // mimic the login page process, if they forget username they need to use email for reset
$user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0, 'suspended'=>0));
} else {
// this is tricky because
// 1/ the email is not guaranteed to be unique - TODO: send email with all usernames to select the correct account for pw reset
// 2/ mailbox may be case sensitive, the email domain is case insensitive - let's pretend it is all case-insensitive
$select = $DB->sql_like('email', ':email', false, true, false, '|'). " AND mnethostid = :mnethostid AND deleted=0 AND suspended=0";
$params = array('email'=>$DB->sql_like_escape($data->email, '|'), 'mnethostid'=>$CFG->mnet_localhost_id);
$user = $DB->get_record_select('user', $select, $params, '*', IGNORE_MULTIPLE);
}
if ($user and !empty($user->confirmed)) {
$userauth = get_auth_plugin($user->auth);
if (has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
// send email
}
if ($userauth->can_reset_password() and is_enabled_auth($user->auth)
and has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
// send reset password confirmation
// set 'secret' string
$user->secret = random_string(15);
$DB->set_field('user', 'secret', $user->secret, array('id'=>$user->id));
if (!send_password_change_confirmation_email($user)) {
print_error('cannotmailconfirm');
}
} else {
if (!send_password_change_info($user)) {
print_error('cannotmailconfirm');
}
}
}
echo $OUTPUT->header();
if (empty($user->email) or !empty($CFG->protectusernames)) {
// Print general confirmation message
notice(get_string('emailpasswordconfirmmaybesent'), $CFG->wwwroot.'/index.php');
} else {
// Confirm email sent
$protectedemail = preg_replace('/([^@]*)@(.*)/', '******@$2', $user->email); // obfuscate the email address to protect privacy
$stremailpasswordconfirmsent = get_string('emailpasswordconfirmsent', '', $protectedemail);
notice($stremailpasswordconfirmsent, $CFG->wwwroot.'/index.php');
}
die; // never reached
}
// make sure we really are on the https page when https login required
$PAGE->verify_https_required();
/// DISPLAY FORM
echo $OUTPUT->header();
echo $OUTPUT->box(get_string('passwordforgotteninstructions2'), 'generalbox boxwidthnormal boxaligncenter');
$mform->display();
echo $OUTPUT->footer();

View File

@ -1,5 +1,4 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
@ -15,6 +14,18 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Forgot password page.
*
* @package core
* @subpackage auth
* @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Reset forgotten password form definition.
*
@ -23,13 +34,11 @@
* @copyright 2006 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once $CFG->libdir.'/formslib.php';
class login_forgot_password_form extends moodleform {
/**
* Define the forgot password form.
*/
function definition() {
$mform = $this->_form;
$mform->setDisableShortforms(true);
@ -51,6 +60,12 @@ class login_forgot_password_form extends moodleform {
$mform->addElement('submit', 'submitbuttonemail', $submitlabel);
}
/**
* Validate user input from the forgot password form.
* @param array $data array of submitted form fields.
* @param array $files submitted with the form.
* @return array errors occuring during validation.
*/
function validation($data, $files) {
global $CFG, $DB;

View File

@ -25,6 +25,7 @@
*/
require('../config.php');
require_once('lib.php');
// Try to prevent searching for sites that allow sign-up.
if (!isset($CFG->additionalhtmlhead)) {
@ -193,31 +194,7 @@ if ($frm and isset($frm->username)) { // Login WITH
set_moodle_cookie($USER->username);
}
/// Prepare redirection
if (user_not_fully_set_up($USER)) {
$urltogo = $CFG->wwwroot.'/user/edit.php';
// We don't delete $SESSION->wantsurl yet, so we get there later
} else if (isset($SESSION->wantsurl) and (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0 or strpos($SESSION->wantsurl, str_replace('http://', 'https://', $CFG->wwwroot)) === 0)) {
$urltogo = $SESSION->wantsurl; /// Because it's an address in this site
unset($SESSION->wantsurl);
} else {
// no wantsurl stored or external - go to homepage
$urltogo = $CFG->wwwroot.'/';
unset($SESSION->wantsurl);
}
// If the url to go to is the same as the site page, check for default homepage.
if ($urltogo == ($CFG->wwwroot . '/')) {
$home_page = get_home_page();
// Go to my-moodle page instead of site homepage if defaulthomepage set to homepage_my
if ($home_page == HOMEPAGE_MY && !is_siteadmin() && !isguestuser()) {
if ($urltogo == $CFG->wwwroot or $urltogo == $CFG->wwwroot.'/' or $urltogo == $CFG->wwwroot.'/index.php') {
$urltogo = $CFG->wwwroot.'/my/';
}
}
}
$urltogo = core_login_get_return_url();
/// check if user password has expired
/// Currently supported only for ldap-authentication module

305
login/lib.php Normal file
View File

@ -0,0 +1,305 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
*
* Login library file of login/password related Moodle functions.
*
* @package core
* @subpackage lib
* @copyright Catalyst IT
* @copyright Peter Bulmer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define('PWRESET_STATUS_NOEMAILSENT', 1);
define('PWRESET_STATUS_TOKENSENT', 2);
define('PWRESET_STATUS_OTHEREMAILSENT', 3);
define('PWRESET_STATUS_ALREADYSENT', 4);
/**
* Processes a user's request to set a new password in the event they forgot the old one.
* If no user identifier has been supplied, it displays a form where they can submit their identifier.
* Where they have supplied identifier, the function will check their status, and send email as appropriate.
*/
function core_login_process_password_reset_request() {
global $DB, $OUTPUT, $CFG, $PAGE;
$systemcontext = context_system::instance();
$mform = new login_forgot_password_form();
if ($mform->is_cancelled()) {
redirect(get_login_url());
} else if ($data = $mform->get_data()) {
// Requesting user has submitted form data.
// Next find the user account in the database which the requesting user claims to own.
if (!empty($data->username)) {
// Username has been specified - load the user record based on that.
$username = core_text::strtolower($data->username); // Mimic the login page process.
$userparams = array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id, 'deleted' => 0, 'suspended' => 0);
$user = $DB->get_record('user', $userparams);
} else {
// Try to load the user record based on email address.
// this is tricky because
// 1/ the email is not guaranteed to be unique - TODO: send email with all usernames to select the account for pw reset
// 2/ mailbox may be case sensitive, the email domain is case insensitive - let's pretend it is all case-insensitive.
$select = $DB->sql_like('email', ':email', false, true, false, '|') .
" AND mnethostid = :mnethostid AND deleted=0 AND suspended=0";
$params = array('email' => $DB->sql_like_escape($data->email, '|'), 'mnethostid' => $CFG->mnet_localhost_id);
$user = $DB->get_record_select('user', $select, $params, '*', IGNORE_MULTIPLE);
}
// Target user details have now been identified, or we know that there is no such account.
// Send email address to account's email address if appropriate.
$pwresetstatus = PWRESET_STATUS_NOEMAILSENT;
if ($user and !empty($user->confirmed)) {
$userauth = get_auth_plugin($user->auth);
if (!$userauth->can_reset_password() or !is_enabled_auth($user->auth)
or !has_capability('moodle/user:changeownpassword', $systemcontext, $user->id)) {
if (send_password_change_info($user)) {
$pwresetstatus = PWRESET_STATUS_OTHEREMAILSENT;
} else {
print_error('cannotmailconfirm');
}
} else {
// The account the requesting user claims to be is entitled to change their password.
// Next, check if they have an existing password reset in progress.
$resetinprogress = $DB->get_record('user_password_resets', array('userid' => $user->id));
if (empty($resetinprogress)) {
// Completely new reset request - common case.
$resetrecord = core_login_generate_password_reset($user);
$sendemail = true;
} else if ($resetinprogress->timerequested < (time() - $CFG->pwresettime)) {
// Preexisting, but expired request - delete old record & create new one.
// Uncommon case - expired requests are cleaned up by cron.
$DB->delete_records('user_password_resets', array('id' => $resetinprogress->id));
$resetrecord = core_login_generate_password_reset($user);
$sendemail = true;
} else if (empty($resetinprogress->timererequested)) {
// Preexisting, valid request. This is the first time user has re-requested the reset.
// Re-sending the same email once can actually help in certain circumstances
// eg by reducing the delay caused by greylisting.
$resetinprogress->timererequested = time();
$DB->update_record('user_password_resets', $resetinprogress);
$resetrecord = $resetinprogress;
$sendemail = true;
} else {
// Preexisting, valid request. User has already re-requested email.
$pwresetstatus = PWRESET_STATUS_ALREADYSENT;
$sendemail = false;
}
if ($sendemail) {
$sendresult = send_password_change_confirmation_email($user, $resetrecord);
if ($sendresult) {
$pwresetstatus = PWRESET_STATUS_TOKENSENT;
} else {
print_error('cannotmailconfirm');
}
}
}
}
// Any email has now been sent.
// Next display results to requesting user if settings permit.
echo $OUTPUT->header();
if (!empty($CFG->protectusernames)) {
// Neither confirm, nor deny existance of any username or email address in database.
// Print general (non-commital) message.
notice(get_string('emailpasswordconfirmmaybesent'), $CFG->wwwroot.'/index.php');
die; // Never reached.
} else if (empty($user)) {
// Protect usernames is off, and we couldn't find the user with details specified.
// Print failure advice.
notice(get_string('emailpasswordconfirmnotsent'), $CFG->wwwroot.'/forgot_password.php');
die; // Never reached.
} else if (empty($user->email)) {
// User doesn't have an email set - can't send a password change confimation email.
notice(get_string('emailpasswordconfirmnoemail'), $CFG->wwwroot.'/index.php');
die; // Never reached.
} else if ($pwresetstatus == PWRESET_STATUS_ALREADYSENT) {
// User found, protectusernames is off, but user has already (re) requested a reset.
// Don't send a 3rd reset email.
$stremailalreadysent = get_string('emailalreadysent');
notice($stremailalreadysent, $CFG->wwwroot.'/index.php');
die; // Never reached.
} else if ($pwresetstatus == PWRESET_STATUS_NOEMAILSENT) {
// User found, protectusernames is off, but user is not confirmed.
// Pretend we sent them an email.
// This is a big usability problem - need to tell users why we didn't send them an email.
// Obfuscate email address to protect privacy.
$protectedemail = preg_replace('/([^@]*)@(.*)/', '******@$2', $user->email);
$stremailpasswordconfirmsent = get_string('emailpasswordconfirmsent', '', $protectedemail);
notice($stremailpasswordconfirmsent, $CFG->wwwroot.'/index.php');
die; // Never reached.
} else {
// Confirm email sent. (Obfuscate email address to protect privacy).
$protectedemail = preg_replace('/([^@]*)@(.*)/', '******@$2', $user->email);
// This is a small usability problem - may be obfuscating the email address which the user has just supplied.
$stremailresetconfirmsent = get_string('emailresetconfirmsent', '', $protectedemail);
notice($stremailresetconfirmsent, $CFG->wwwroot.'/index.php');
die; // Never reached.
}
die; // Never reached.
}
// Make sure we really are on the https page when https login required.
$PAGE->verify_https_required();
// DISPLAY FORM.
echo $OUTPUT->header();
echo $OUTPUT->box(get_string('passwordforgotteninstructions2'), 'generalbox boxwidthnormal boxaligncenter');
$mform->display();
echo $OUTPUT->footer();
}
/** This function processes a user's submitted token to validate the request to set a new password.
* If the user's token is validated, they are prompted to set a new password.
* @param string $token the one-use identifier which should verify the password reset request as being valid.
* @return null
*/
function core_login_process_password_set($token) {
global $DB, $CFG, $OUTPUT, $PAGE, $SESSION;
$pwresettime = isset($CFG->pwresettime) ? $CFG->pwresettime : 1800;
$sql = "SELECT u.*, upr.token, upr.timerequested, upr.id as tokenid
FROM {user} u
JOIN {user_password_resets} upr ON upr.userid = u.id
WHERE upr.token = ?";
$user = $DB->get_record_sql($sql, array($token));
$forgotpasswordurl = "{$CFG->httpswwwroot}/login/forgot_password.php";
if (empty($user) or ($user->timerequested < (time() - $pwresettime - DAYSECS))) {
// There is no valid reset request record - not even a recently expired one.
// (suspicious)
// Direct the user to the forgot password page to request a password reset.
echo $OUTPUT->header();
notice(get_string('noresetrecord'), $forgotpasswordurl);
die; // Never reached.
}
if ($user->timerequested < (time() - $pwresettime)) {
// There is a reset record, but it's expired.
// Direct the user to the forgot password page to request a password reset.
$pwresetmins = floor($pwresettime / MINSECS);
echo $OUTPUT->header();
notice(get_string('resetrecordexpired', '', $pwresetmins), $forgotpasswordurl);
die; // Never reached.
}
if ($user->auth === 'nologin' or !is_enabled_auth($user->auth)) {
// Bad luck - user is not able to login, do not let them set password.
echo $OUTPUT->header();
print_error('forgotteninvalidurl');
die; // Never reached.
}
// Check this isn't guest user.
if (isguestuser($user)) {
print_error('cannotresetguestpwd');
}
// Token is correct, and unexpired.
$mform = new login_set_password_form(null, null, 'post', '', 'autocomplete="yes"');
$data = $mform->get_data();
if (empty($data)) {
// User hasn't submitted form, they got here directly from email link.
// Next, display the form.
$setdata = new stdClass();
$setdata->username = $user->username;
$setdata->username2 = $user->username;
$setdata->token = $user->token;
$mform->set_data($setdata);
$PAGE->verify_https_required();
echo $OUTPUT->header();
echo $OUTPUT->box(get_string('setpasswordinstructions'), 'generalbox boxwidthnormal boxaligncenter');
$mform->display();
echo $OUTPUT->footer();
return;
} else {
// User has submitted form.
// Delete this token so it can't be used again.
$DB->delete_records('user_password_resets', array('id' => $user->tokenid));
$userauth = get_auth_plugin($user->auth);
if (!$userauth->user_update_password($user, $data->password)) {
print_error('errorpasswordupdate', 'auth');
}
add_to_log(SITEID, 'user', 'set password', "view.php?id=$user->id&amp;course=" . SITEID, $user->id);
// Reset login lockout (if present) before a new password is set.
login_unlock_account($user);
// Clear any requirement to change passwords.
unset_user_preference('auth_forcepasswordchange', $user);
unset_user_preference('create_password', $user);
if (!empty($user->lang)) {
// Unset previous session language - use user preference instead.
unset($SESSION->lang);
}
add_to_log(SITEID, 'user', 'login', "view.php?id=$user->id&course=".SITEID, $user->id, 0, $user->id);
complete_user_login($user);
$urltogo = core_login_get_return_url();
unset($SESSION->wantsurl);
redirect($urltogo, get_string('passwordset'), 1);
}
}
/** Create a new record in the database to track a new password set request for user.
* @param object $user the user record, the requester would like a new password set for.
* @return record created.
*/
function core_login_generate_password_reset ($user) {
global $DB;
$resetrecord = new stdClass();
$resetrecord->timerequested = time();
$resetrecord->userid = $user->id;
$resetrecord->token = random_string(32);
$resetrecord->id = $DB->insert_record('user_password_resets', $resetrecord);
return $resetrecord;
}
/** Determine where a user should be redirected after they have been logged in.
* @return string url the user should be redirected to.
*/
function core_login_get_return_url() {
global $CFG, $SESSION, $USER;
// Prepare redirection.
if (user_not_fully_set_up($USER)) {
$urltogo = $CFG->wwwroot.'/user/edit.php';
// We don't delete $SESSION->wantsurl yet, so we get there later.
} else if (isset($SESSION->wantsurl) and (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0
or strpos($SESSION->wantsurl, str_replace('http://', 'https://', $CFG->wwwroot)) === 0)) {
$urltogo = $SESSION->wantsurl; // Because it's an address in this site.
unset($SESSION->wantsurl);
} else {
// No wantsurl stored or external - go to homepage.
$urltogo = $CFG->wwwroot.'/';
unset($SESSION->wantsurl);
}
// If the url to go to is the same as the site page, check for default homepage.
if ($urltogo == ($CFG->wwwroot . '/')) {
$homepage = get_home_page();
// Go to my-moodle page instead of site homepage if defaulthomepage set to homepage_my.
if ($homepage == HOMEPAGE_MY && !is_siteadmin() && !isguestuser()) {
if ($urltogo == $CFG->wwwroot or $urltogo == $CFG->wwwroot.'/' or $urltogo == $CFG->wwwroot.'/index.php') {
$urltogo = $CFG->wwwroot.'/my/';
}
}
}
return $urltogo;
}

108
login/set_password_form.php Normal file
View File

@ -0,0 +1,108 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Set password form definition.
*
* @package core
* @subpackage auth
* @copyright 2006 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
/**
* Set forgotten password form definition.
*
* @package core
* @subpackage auth
* @copyright 2006 Petr Skoda {@link http://skodak.org}
* @copyright 2013 Peter Bulmer
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class login_set_password_form extends moodleform {
/**
* Define the set password form.
*/
public function definition() {
global $USER, $CFG;
// Prepare a string showing whether the site wants login password autocompletion to be available to user.
if (empty($CFG->loginpasswordautocomplete)) {
$autocomplete = 'autocomplete="on"';
} else {
$autocomplete = '';
}
$mform = $this->_form;
$mform->setDisableShortforms(true);
$mform->addElement('header', 'setpassword', get_string('setpassword'), '');
// Include the username in the form so browsers will recognise that a password is being set.
$mform->addElement('text', 'username', '', 'style="display: none;" ' . $autocomplete);
$mform->setType('username', PARAM_RAW);
// Token gives authority to change password.
$mform->addElement('hidden', 'token', '');
$mform->setType('token', PARAM_ALPHANUM);
// Visible elements.
$mform->addElement('static', 'username2', get_string('username'));
if (!empty($CFG->passwordpolicy)) {
$mform->addElement('static', 'passwordpolicyinfo', '', print_password_policy());
}
$mform->addElement('password', 'password', get_string('newpassword'), $autocomplete);
$mform->addRule('password', get_string('required'), 'required', null, 'client');
$mform->setType('password', PARAM_RAW);
$strpasswordagain = get_string('newpassword') . ' (' . get_string('again') . ')';
$mform->addElement('password', 'password2', $strpasswordagain, $autocomplete);
$mform->addRule('password2', get_string('required'), 'required', null, 'client');
$mform->setType('password2', PARAM_RAW);
$this->add_action_buttons(true);
}
/**
* Perform extra password change validation.
* @param array $data submitted form fields.
* @param array $files submitted with the form.
* @return array errors occuring during validation.
*/
public function validation($data, $files) {
global $USER;
$errors = parent::validation($data, $files);
// Ignore submitted username.
if ($data['password'] !== $data['password2']) {
$errors['password'] = get_string('passwordsdiffer');
$errors['password2'] = get_string('passwordsdiffer');
return $errors;
}
$errmsg = ''; // Prevents eclipse warnings.
if (!check_password_policy($data['password'], $errmsg)) {
$errors['password'] = $errmsg;
$errors['password2'] = $errmsg;
return $errors;
}
return $errors;
}
}

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2013100800.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2013100800.01; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.