mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
Merge branch 'MDL-67309' of https://github.com/Peterburnett/moodle
This commit is contained in:
commit
245ac91748
@ -100,6 +100,9 @@ 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));
|
||||
$temp->add(new admin_setting_configcheckbox('passwordpolicycheckonlogin',
|
||||
new lang_string('passwordpolicycheckonlogin', 'admin'),
|
||||
new lang_string('configpasswordpolicycheckonlogin', 'admin'), 0));
|
||||
|
||||
$temp->add(new admin_setting_configtext('passwordreuselimit',
|
||||
new lang_string('passwordreuselimit', 'admin'),
|
||||
|
@ -307,7 +307,9 @@ $string['confignotifyloginthreshold'] = 'If notifications about failed logins ar
|
||||
$string['confignotloggedinroleid'] = 'Users who are not logged in to the site will be treated as if they have this role granted to them at the site context. Guest is almost always what you want here, but you might want to create roles that are less or more restrictive. Things like creating posts still require the user to log in properly.';
|
||||
$string['configopentowebcrawlers'] = 'If you enable this setting, then search engines will be allowed to enter your site as a guest. In addition, people coming in to your site via a search engine 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'] = 'If enabled, user passwords will be checked against the password policy as specified in the settings below. Enabling the password policy will not affect existing users until they decide to, or are required to, change their password.';
|
||||
$string['configpasswordpolicy'] = 'If enabled, user passwords will be checked against the password policy as specified in the settings below. Enabling the password policy will not affect existing users until they decide to, or are required to, change their password, or the \'Check password on login\' setting is enabled.';
|
||||
$string['configpasswordpolicycheckonlogin'] = 'If enabled, user passwords will be checked against the password policy each time users log in. If the check fails, the user will be required to change their password before proceeding.
|
||||
It is useful to enable this setting after updating the password policy.';
|
||||
$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['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['configpathtophp'] = 'Path to PHP CLI. Probably something like /usr/bin/php. If you enter this, cron scripts can be executed from admin web interface.';
|
||||
@ -895,6 +897,7 @@ $string['passwordchangelogout_desc'] = 'If enabled, when a password is changed,
|
||||
$string['passwordchangetokendeletion'] = 'Remove web service access tokens after password change';
|
||||
$string['passwordchangetokendeletion_desc'] = 'If enabled, when a password is changed, all the user web service access tokens are deleted.';
|
||||
$string['passwordpolicy'] = 'Password policy';
|
||||
$string['passwordpolicycheckonlogin'] = 'Check password on login';
|
||||
$string['passwordresettime'] = 'Maximum time to validate password reset request';
|
||||
$string['passwordreuselimit'] = 'Password rotation limit';
|
||||
$string['passwordreuselimit_desc'] = 'Number of times a user must change their password before they are allowed to reuse a password. Hashes of previously used passwords are stored in local database table. This feature might not be compatible with some external authentication plugins.';
|
||||
|
@ -799,6 +799,7 @@ $string['eventusercreated'] = 'User created';
|
||||
$string['eventuserdeleted'] = 'User deleted';
|
||||
$string['eventuserlistviewed'] = 'User list viewed';
|
||||
$string['eventuserloggedout'] = 'User logged out';
|
||||
$string['eventuserpasswordpolicyfailed'] = 'User password failed password policy';
|
||||
$string['eventuserpasswordupdated'] = 'User password updated';
|
||||
$string['eventuserprofileviewed'] = 'User profile viewed';
|
||||
$string['eventuserupdated'] = 'User updated';
|
||||
@ -859,6 +860,10 @@ $string['forcepasswordchange_help'] = 'If this checkbox is ticked, the user will
|
||||
$string['forcepasswordchangecheckfull'] = 'Are you absolutely sure you want to force a password change to {$a} ?';
|
||||
$string['forcepasswordchangenot'] = 'Could not force a password change to {$a}';
|
||||
$string['forcepasswordchangenotice'] = 'You must change your password to proceed.';
|
||||
$string['forcepasswordresetfailurenotice'] = 'Your current password no longer passes the set password policy. Please contact your Moodle administrator for assistance.
|
||||
{$a}';
|
||||
$string['forcepasswordresetnotice'] = 'Your current password no longer passes the set password policy, you must reset your password to login.
|
||||
{$a}';
|
||||
$string['forcetheme'] = 'Force theme';
|
||||
$string['forgotaccount'] = 'Lost password?';
|
||||
$string['forgotten'] = 'Forgotten your username or password?';
|
||||
@ -1517,6 +1522,8 @@ $string['passwordforgotteninstructions'] = 'Your details must first be found in
|
||||
$string['passwordforgotteninstructions2'] = 'To reset your password, submit your username or your email address below. If we can find you in the database, an email will be sent to your email address, with instructions how to get access again.';
|
||||
$string['passwordchanged'] = 'Password has been changed';
|
||||
$string['passwordnohelp'] = 'No help is available to find your lost password. Please contact your Moodle administrator.';
|
||||
$string['passwordpolicynomatch'] = 'Your current password no longer matches the set password policy.
|
||||
{$a}';
|
||||
$string['passwordrecovery'] = 'Yes, help me log in';
|
||||
$string['passwordsdiffer'] = 'These passwords do not match';
|
||||
$string['passwordsent'] = 'Password has been sent';
|
||||
|
89
lib/classes/event/user_password_policy_failed.php
Normal file
89
lib/classes/event/user_password_policy_failed.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Password policy failed event.
|
||||
*
|
||||
* @package core
|
||||
* @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace core\event;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Event when user's current password fails the password policy
|
||||
*
|
||||
* @package core
|
||||
* @since Moodle 3.9
|
||||
* @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class user_password_policy_failed extends base {
|
||||
/**
|
||||
* Create event for user's current password failing password policy.
|
||||
*
|
||||
* @param \stdClass $user
|
||||
* @return user_password_updated
|
||||
*/
|
||||
public static function create_from_user(\stdClass $user) {
|
||||
$data = array(
|
||||
'context' => \context_user::instance($user->id),
|
||||
'userid' => $user->id,
|
||||
'relateduserid' => $user->id,
|
||||
);
|
||||
$event = self::create($data);
|
||||
$event->add_record_snapshot('user', $user);
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise required event data properties.
|
||||
*/
|
||||
protected function init() {
|
||||
$this->data['crud'] = 'r';
|
||||
$this->data['edulevel'] = self::LEVEL_OTHER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns localised event name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_name() {
|
||||
return get_string('eventuserpasswordpolicyfailed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns non-localised event description with id's for admin use only.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_description() {
|
||||
return "The password for user with id '$this->userid' failed the current password policy.";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns relevant URL.
|
||||
*
|
||||
* @return \moodle_url
|
||||
*/
|
||||
public function get_url() {
|
||||
return new \moodle_url('/user/profile.php', array('id' => $this->userid));
|
||||
}
|
||||
}
|
@ -4398,7 +4398,7 @@ function guest_user() {
|
||||
* @return stdClass|false A {@link $USER} object or false if error
|
||||
*/
|
||||
function authenticate_user_login($username, $password, $ignorelockout=false, &$failurereason=null, $logintoken=false) {
|
||||
global $CFG, $DB;
|
||||
global $CFG, $DB, $PAGE;
|
||||
require_once("$CFG->libdir/authlib.php");
|
||||
|
||||
if ($user = get_complete_user_data('username', $username, $CFG->mnet_localhost_id)) {
|
||||
@ -4512,6 +4512,42 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
|
||||
continue;
|
||||
}
|
||||
|
||||
// Before performing login actions, check if user still passes password policy, if admin setting is enabled.
|
||||
if (!empty($CFG->passwordpolicycheckonlogin)) {
|
||||
$errmsg = '';
|
||||
$passed = check_password_policy($password, $errmsg, $user);
|
||||
if (!$passed) {
|
||||
// First trigger event for failure.
|
||||
$failedevent = \core\event\user_password_policy_failed::create_from_user($user);
|
||||
$failedevent->trigger();
|
||||
|
||||
// If able to change password, set flag and move on.
|
||||
if ($authplugin->can_change_password()) {
|
||||
// Check if we are on internal change password page, or service is external, don't show notification.
|
||||
$internalchangeurl = new moodle_url('/login/change_password.php');
|
||||
if (!($PAGE->has_set_url() && $internalchangeurl->compare($PAGE->url)) && $authplugin->is_internal()) {
|
||||
\core\notification::error(get_string('passwordpolicynomatch', '', $errmsg));
|
||||
}
|
||||
set_user_preference('auth_forcepasswordchange', 1, $user);
|
||||
} else if ($authplugin->can_reset_password()) {
|
||||
// Else force a reset if possible.
|
||||
\core\notification::error(get_string('forcepasswordresetnotice', '', $errmsg));
|
||||
redirect(new moodle_url('/login/forgot_password.php'));
|
||||
} else {
|
||||
$notifymsg = get_string('forcepasswordresetfailurenotice', '', $errmsg);
|
||||
// If support page is set, add link for help.
|
||||
if (!empty($CFG->supportpage)) {
|
||||
$link = \html_writer::link($CFG->supportpage, $CFG->supportpage);
|
||||
$link = \html_writer::tag('p', $link);
|
||||
$notifymsg .= $link;
|
||||
}
|
||||
|
||||
// If no change or reset is possible, add a notification for user.
|
||||
\core\notification::error($notifymsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Successful authentication.
|
||||
if ($user->id) {
|
||||
// User already exists in database.
|
||||
@ -4980,7 +5016,8 @@ function get_complete_user_data($field, $value, $mnethostid = null, $throwexcept
|
||||
*
|
||||
* @param string $password the password to be checked against the password policy
|
||||
* @param string $errmsg the error message to display when the password doesn't comply with the policy.
|
||||
* @param stdClass $user the user object to perform password validation against. Defaults to null if not provided
|
||||
* @param stdClass $user the user object to perform password validation against. Defaults to null if not provided.
|
||||
*
|
||||
* @return bool true if the password is valid according to the policy. false otherwise.
|
||||
*/
|
||||
function check_password_policy($password, &$errmsg, $user = null) {
|
||||
|
@ -335,6 +335,75 @@ class core_authlib_testcase extends advanced_testcase {
|
||||
$this->assertEquals(AUTH_LOGIN_OK, $reason);
|
||||
|
||||
ini_set('error_log', $oldlog);
|
||||
|
||||
// Test password policy check on login.
|
||||
$CFG->passwordpolicy = 0;
|
||||
$CFG->passwordpolicycheckonlogin = 1;
|
||||
|
||||
// First test with password policy disabled.
|
||||
$user4 = $this->getDataGenerator()->create_user(array('username' => 'username4', 'password' => 'a'));
|
||||
$sink = $this->redirectEvents();
|
||||
$reason = null;
|
||||
$result = authenticate_user_login('username4', 'a', false, $reason);
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
$notifications = \core\notification::fetch();
|
||||
$this->assertInstanceOf('stdClass', $result);
|
||||
$this->assertEquals(AUTH_LOGIN_OK, $reason);
|
||||
$this->assertEquals(get_user_preferences('auth_forcepasswordchange', false, $result), false);
|
||||
// Check no events.
|
||||
$this->assertEquals(count($events), 0);
|
||||
// Check no notifications.
|
||||
$this->assertEquals(count($notifications), 0);
|
||||
|
||||
// Now test with the password policy enabled, flip reset flag.
|
||||
$sink = $this->redirectEvents();
|
||||
$reason = null;
|
||||
$CFG->passwordpolicy = 1;
|
||||
$result = authenticate_user_login('username4', 'a', false, $reason);
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
$this->assertInstanceOf('stdClass', $result);
|
||||
$this->assertEquals(AUTH_LOGIN_OK, $reason);
|
||||
$this->assertEquals(get_user_preferences('auth_forcepasswordchange', true, $result), true);
|
||||
// Check that an event was emitted for the policy failure.
|
||||
$this->assertEquals(count($events), 1);
|
||||
$this->assertEquals(reset($events)->eventname, '\core\event\user_password_policy_failed');
|
||||
// Check notification fired.
|
||||
$notifications = \core\notification::fetch();
|
||||
$this->assertEquals(count($notifications), 1);
|
||||
|
||||
// Now the same tests with a user that passes the password policy.
|
||||
$user5 = $this->getDataGenerator()->create_user(array('username' => 'username5', 'password' => 'ThisPassword1sSecure!'));
|
||||
$reason = null;
|
||||
$CFG->passwordpolicy = 0;
|
||||
$sink = $this->redirectEvents();
|
||||
$result = authenticate_user_login('username5', 'ThisPassword1sSecure!', false, $reason);
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
$notifications = \core\notification::fetch();
|
||||
$this->assertInstanceOf('stdClass', $result);
|
||||
$this->assertEquals(AUTH_LOGIN_OK, $reason);
|
||||
$this->assertEquals(get_user_preferences('auth_forcepasswordchange', false, $result), false);
|
||||
// Check no events.
|
||||
$this->assertEquals(count($events), 0);
|
||||
// Check no notifications.
|
||||
$this->assertEquals(count($notifications), 0);
|
||||
|
||||
$reason = null;
|
||||
$CFG->passwordpolicy = 1;
|
||||
$sink = $this->redirectEvents();
|
||||
$result = authenticate_user_login('username5', 'ThisPassword1sSecure!', false, $reason);
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
$notifications = \core\notification::fetch();
|
||||
$this->assertInstanceOf('stdClass', $result);
|
||||
$this->assertEquals(AUTH_LOGIN_OK, $reason);
|
||||
$this->assertEquals(get_user_preferences('auth_forcepasswordchange', false, $result), false);
|
||||
// Check no events.
|
||||
$this->assertEquals(count($events), 0);
|
||||
// Check no notifications.
|
||||
$this->assertEquals(count($notifications), 0);
|
||||
}
|
||||
|
||||
public function test_user_loggedin_event_exceptions() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user