mirror of
https://github.com/moodle/moodle.git
synced 2025-08-04 16:36:37 +02:00
MDL-21342 add user login lockout
This commit is contained in:
parent
0dc5a532ec
commit
b28247fe90
13 changed files with 550 additions and 49 deletions
|
@ -59,6 +59,11 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
|
|||
$temp->add(new admin_setting_configcheckbox('cronclionly', new lang_string('cronclionly', 'admin'), new lang_string('configcronclionly', 'admin'), 0));
|
||||
$temp->add(new admin_setting_configpasswordunmask('cronremotepassword', new lang_string('cronremotepassword', 'admin'), new lang_string('configcronremotepassword', 'admin'), ''));
|
||||
|
||||
$options = array(0=>get_string('no'), 3=>3, 5=>5, 7=>7, 10=>10, 20=>20, 30=>30, 50=>50, 100=>100);
|
||||
$temp->add(new admin_setting_configselect('lockoutthreshold', new lang_string('lockoutthreshold', 'admin'), new lang_string('lockoutthreshold_desc', 'admin'), 0, $options));
|
||||
$temp->add(new admin_setting_configduration('lockoutwindow', new lang_string('lockoutwindow', 'admin'), new lang_string('lockoutwindow_desc', 'admin'), 60*30));
|
||||
$temp->add(new admin_setting_configduration('lockoutduration', new lang_string('lockoutduration', 'admin'), new lang_string('lockoutduration_desc', 'admin'), 60*30));
|
||||
|
||||
$temp->add(new admin_setting_configcheckbox('passwordpolicy', new lang_string('passwordpolicy', 'admin'), new lang_string('configpasswordpolicy', 'admin'), 1));
|
||||
$temp->add(new admin_setting_configtext('minpasswordlength', new lang_string('minpasswordlength', 'admin'), new lang_string('configminpasswordlength', 'admin'), 8, PARAM_INT));
|
||||
$temp->add(new admin_setting_configtext('minpassworddigits', new lang_string('minpassworddigits', 'admin'), new lang_string('configminpassworddigits', 'admin'), 1, PARAM_INT));
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require_once('../config.php');
|
||||
require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->libdir.'/authlib.php');
|
||||
require_once($CFG->dirroot.'/user/filters/lib.php');
|
||||
|
||||
$delete = optional_param('delete', 0, PARAM_INT);
|
||||
|
@ -16,6 +17,7 @@
|
|||
$acl = optional_param('acl', '0', PARAM_INT); // id of user to tweak mnet ACL (requires $access)
|
||||
$suspend = optional_param('suspend', 0, PARAM_INT);
|
||||
$unsuspend = optional_param('unsuspend', 0, PARAM_INT);
|
||||
$unlock = optional_param('unlock', 0, PARAM_INT);
|
||||
|
||||
admin_externalpage_setup('editusers');
|
||||
|
||||
|
@ -32,6 +34,7 @@
|
|||
$strshowallusers = get_string('showallusers');
|
||||
$strsuspend = get_string('suspenduser', 'admin');
|
||||
$strunsuspend = get_string('unsuspenduser', 'admin');
|
||||
$strunlock = get_string('unlockaccount', 'admin');
|
||||
$strconfirm = get_string('confirm');
|
||||
|
||||
if (empty($CFG->loginhttps)) {
|
||||
|
@ -143,6 +146,14 @@
|
|||
}
|
||||
}
|
||||
redirect($returnurl);
|
||||
|
||||
} else if ($unlock and confirm_sesskey()) {
|
||||
require_capability('moodle/user:update', $sitecontext);
|
||||
|
||||
if ($user = $DB->get_record('user', array('id'=>$unlock, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>0))) {
|
||||
login_unlock_account($user);
|
||||
}
|
||||
redirect($returnurl);
|
||||
}
|
||||
|
||||
// create the user filter form
|
||||
|
@ -303,6 +314,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
if (login_is_lockedout($user)) {
|
||||
$buttons[] = html_writer::link(new moodle_url($returnurl, array('unlock'=>$user->id, 'sesskey'=>sesskey())), html_writer::empty_tag('img', array('src'=>$OUTPUT->pix_url('t/unlock'), 'alt'=>$strunlock, 'class'=>'iconsmall')), array('title'=>$strunlock));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1643,7 +1643,7 @@ class auth_plugin_ldap extends auth_plugin_base {
|
|||
$username = $cf[$key];
|
||||
// Here we want to trigger the whole authentication machinery
|
||||
// to make sure no step is bypassed...
|
||||
$user = authenticate_user_login($username, $key);
|
||||
$user = authenticate_user_login($username, $key, false);
|
||||
if ($user) {
|
||||
add_to_log(SITEID, 'user', 'login', "view.php?id=$USER->id&course=".SITEID,
|
||||
$user->id, 0, $user->id);
|
||||
|
|
|
@ -625,6 +625,28 @@ $string['localstringcustomization'] = 'Local string customization';
|
|||
$string['location'] = 'Location';
|
||||
$string['locationsettings'] = 'Location settings';
|
||||
$string['locked'] = 'locked';
|
||||
$string['lockoutduration'] = 'Account lockout duration';
|
||||
$string['lockoutduration_desc'] = 'Locked out account is automatically unlocked after this duration.';
|
||||
$string['lockoutemailbody'] = 'Your account with username {$a->username} on server \'{$a->sitename}\'
|
||||
was locked out after multiple invalid login attempts.
|
||||
|
||||
To unlock the account immediately go to the following address
|
||||
|
||||
{$a->link}
|
||||
|
||||
In most mail programs, this should appear as a blue link
|
||||
which you can just click on. If that doesn\'t work,
|
||||
then cut and paste the address into the address
|
||||
line at the top of your web browser window.
|
||||
|
||||
If you need help, please contact the site administrator,
|
||||
{$a->admin}';
|
||||
$string['lockoutemailsubject'] = 'Your account on {$a} was locked out';
|
||||
$string['lockouterrorunlock'] = 'Invalid account unlock information supplied.';
|
||||
$string['lockoutthreshold'] = 'Account lockout threshold';
|
||||
$string['lockoutthreshold_desc'] = 'Select number of failed login attempts that result in account lockout. This feature may be abused in denial of service attacks.';
|
||||
$string['lockoutwindow'] = 'Account lockout observation window';
|
||||
$string['lockoutwindow_desc'] = 'Observation time for lockout threshold, if there are no failed attempts the threshold counter is reset after this time.';
|
||||
$string['log'] = 'Logs';
|
||||
$string['logguests'] = 'Log guest access';
|
||||
$string['logguests_help'] = 'This setting enables logging of actions by guest account and not logged in users. High profile sites may want to disable this logging for performance reasons. It is recommended to keep this setting enabled on production sites.';
|
||||
|
@ -989,6 +1011,7 @@ $string['unbookmarkthispage'] = 'Unbookmark this page';
|
|||
$string['unicoderecommended'] = 'Storing all your data in Unicode (UTF-8) is recommended. New installations should be performed into databases that have their default character set as Unicode. If you are upgrading, you should perform the UTF-8 migration process (see the Admin page).';
|
||||
$string['unicoderequired'] = 'It is required that you store all your data in Unicode format (UTF-8). New installations must be performed into databases that have their default character set as Unicode. If you are upgrading, you should perform the UTF-8 migration process (see the Admin page).';
|
||||
$string['uninstallplugin'] = 'Uninstall';
|
||||
$string['unlockaccount'] = 'Unlock account';
|
||||
$string['unsettheme'] = 'Unset theme';
|
||||
$string['unsupported'] = 'Unsupported';
|
||||
$string['unsuspenduser'] = 'Activate user account';
|
||||
|
|
191
lib/authlib.php
191
lib/authlib.php
|
@ -61,6 +61,22 @@ define('AUTH_REMOVEUSER_KEEP', 0);
|
|||
define('AUTH_REMOVEUSER_SUSPEND', 1);
|
||||
define('AUTH_REMOVEUSER_FULLDELETE', 2);
|
||||
|
||||
/** Login attempt successful. */
|
||||
define('AUTH_LOGIN_OK', 0);
|
||||
|
||||
/** Can not login because user does not exist. */
|
||||
define('AUTH_LOGIN_NOUSER', 1);
|
||||
|
||||
/** Can not login because user is suspended. */
|
||||
define('AUTH_LOGIN_SUSPENDED', 2);
|
||||
|
||||
/** Can not login, most probably password did not match. */
|
||||
define('AUTH_LOGIN_FAILED', 3);
|
||||
|
||||
/** Can not login because user is locked out. */
|
||||
define('AUTH_LOGIN_LOCKOUT', 4);
|
||||
|
||||
|
||||
/**
|
||||
* Abstract authentication plugin.
|
||||
*
|
||||
|
@ -507,3 +523,178 @@ class auth_plugin_base {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if user is locked out.
|
||||
*
|
||||
* @param stdClass $user
|
||||
* @return bool true if user locked out
|
||||
*/
|
||||
function login_is_lockedout($user) {
|
||||
global $CFG;
|
||||
|
||||
if ($user->mnethostid != $CFG->mnet_localhost_id) {
|
||||
return false;
|
||||
}
|
||||
if (isguestuser($user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($CFG->lockoutthreshold)) {
|
||||
// Lockout not enabled.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_user_preferences('login_lockout_ignored', 0, $user)) {
|
||||
// This preference may be used for accounts that must not be locked out.
|
||||
return false;
|
||||
}
|
||||
|
||||
$locked = get_user_preferences('login_lockout', 0, $user);
|
||||
if (!$locked) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($CFG->lockoutduration)) {
|
||||
// Locked out forever.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (time() - $locked < $CFG->lockoutduration) {
|
||||
return true;
|
||||
}
|
||||
|
||||
login_unlock_account($user);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called after valid user login.
|
||||
* @param stdClass $user
|
||||
*/
|
||||
function login_attempt_valid($user) {
|
||||
global $CFG;
|
||||
|
||||
if ($user->mnethostid != $CFG->mnet_localhost_id) {
|
||||
return;
|
||||
}
|
||||
if (isguestuser($user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always unlock here, there might be some race conditions or leftovers when switching threshold.
|
||||
login_unlock_account($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called after failed user login.
|
||||
* @param stdClass $user
|
||||
*/
|
||||
function login_attempt_failed($user) {
|
||||
global $CFG;
|
||||
|
||||
if ($user->mnethostid != $CFG->mnet_localhost_id) {
|
||||
return;
|
||||
}
|
||||
if (isguestuser($user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($CFG->lockoutthreshold)) {
|
||||
// No threshold means no lockout.
|
||||
// Always unlock here, there might be some race conditions or leftovers when switching threshold.
|
||||
login_unlock_account($user);
|
||||
return;
|
||||
}
|
||||
|
||||
$count = get_user_preferences('login_failed_count', 0, $user);
|
||||
$last = get_user_preferences('login_failed_last', 0, $user);
|
||||
|
||||
if (!empty($CFG->lockoutwindow) and time() - $last > $CFG->lockoutwindow) {
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
$count = $count+1;
|
||||
|
||||
set_user_preference('login_failed_count', $count, $user);
|
||||
set_user_preference('login_failed_last', time(), $user);
|
||||
|
||||
if ($count >= $CFG->lockoutthreshold) {
|
||||
login_lock_account($user);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lockout user and send notification email.
|
||||
*
|
||||
* @param stdClass $user
|
||||
*/
|
||||
function login_lock_account($user) {
|
||||
global $CFG, $SESSION;
|
||||
|
||||
if ($user->mnethostid != $CFG->mnet_localhost_id) {
|
||||
return;
|
||||
}
|
||||
if (isguestuser($user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_user_preferences('login_lockout_ignored', 0, $user)) {
|
||||
// This user can not be locked out.
|
||||
return;
|
||||
}
|
||||
|
||||
$alreadylockedout = get_user_preferences('login_lockout', 0, $user);
|
||||
|
||||
set_user_preference('login_lockout', time(), $user);
|
||||
|
||||
if ($alreadylockedout == 0) {
|
||||
$secret = random_string(15);
|
||||
set_user_preference('login_lockout_secret', $secret, $user);
|
||||
|
||||
// Some nasty hackery to get strings and dates localised for target user.
|
||||
$sessionlang = isset($SESSION->lang) ? $SESSION->lang : null;
|
||||
if (get_string_manager()->translation_exists($user->lang, false)) {
|
||||
$SESSION->lang = $user->lang;
|
||||
moodle_setlocale();
|
||||
}
|
||||
|
||||
$site = get_site();
|
||||
$supportuser = generate_email_supportuser();
|
||||
|
||||
$data = new stdClass();
|
||||
$data->firstname = $user->firstname;
|
||||
$data->lastname = $user->lastname;
|
||||
$data->username = $user->username;
|
||||
$data->sitename = format_string($site->fullname);
|
||||
$data->link = $CFG->wwwroot.'/login/unlock_account.php?u='.$user->id.'&s='.$secret;
|
||||
$data->admin = generate_email_signoff();
|
||||
|
||||
$message = get_string('lockoutemailbody', 'admin', $data);
|
||||
$subject = get_string('lockoutemailsubject', 'admin', format_string($site->fullname));
|
||||
|
||||
if ($message) {
|
||||
// Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
|
||||
email_to_user($user, $supportuser, $subject, $message);
|
||||
}
|
||||
|
||||
if ($SESSION->lang !== $sessionlang) {
|
||||
$SESSION->lang = $sessionlang;
|
||||
moodle_setlocale();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock user account and reset timers.
|
||||
*
|
||||
* @param stdClass $user
|
||||
*/
|
||||
function login_unlock_account($user) {
|
||||
unset_user_preference('login_lockout', $user);
|
||||
unset_user_preference('login_failed_count', $user);
|
||||
unset_user_preference('login_failed_last', $user);
|
||||
|
||||
// Note: do not clear the lockout secret because user might click on the link repeatedly.
|
||||
}
|
||||
|
|
|
@ -30,6 +30,26 @@
|
|||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Not used any more, the account lockout handling is now
|
||||
* part of authenticate_user_login().
|
||||
* @deprecated
|
||||
*/
|
||||
function update_login_count() {
|
||||
// note: remove 'errortoomanylogins' string from moodle.php too
|
||||
// TODO: uncomment in Moodle 2.5, delete function in Moodle 2.6
|
||||
//debugging('update_login_count() is deprecated, all calls need to be removed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Not used any more, replaced by proper account lockout.
|
||||
* @deprecated
|
||||
*/
|
||||
function reset_login_count() {
|
||||
// TODO: uncomment in Moodle 2.5, delete function in Moodle 2.6
|
||||
//debugging('reset_login_count() is deprecated, all calls need to be removed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsupported session id rewriting.
|
||||
* @deprecated
|
||||
|
|
|
@ -3468,39 +3468,6 @@ function set_bounce_count($user,$reset=false) {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeps track of login attempts
|
||||
*
|
||||
* @global object
|
||||
*/
|
||||
function update_login_count() {
|
||||
global $SESSION;
|
||||
|
||||
$max_logins = 10;
|
||||
|
||||
if (empty($SESSION->logincount)) {
|
||||
$SESSION->logincount = 1;
|
||||
} else {
|
||||
$SESSION->logincount++;
|
||||
}
|
||||
|
||||
if ($SESSION->logincount > $max_logins) {
|
||||
unset($SESSION->wantsurl);
|
||||
print_error('errortoomanylogins');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets login attempts
|
||||
*
|
||||
* @global object
|
||||
*/
|
||||
function reset_login_count() {
|
||||
global $SESSION;
|
||||
|
||||
$SESSION->logincount = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the currently logged in user is in editing mode.
|
||||
* Note: originally this function had $userid parameter - it was not usable anyway
|
||||
|
@ -4134,10 +4101,13 @@ function guest_user() {
|
|||
*
|
||||
* @param string $username User's username
|
||||
* @param string $password User's password
|
||||
* @return user|flase A {@link $USER} object or false if error
|
||||
* @param bool $ignorelockout useful when guessing is prevented by other mechanism such as captcha or SSO
|
||||
* @param int $failurereason login failure reason, can be used in renderers (it may disclose if account exists)
|
||||
* @return stdClass|false A {@link $USER} object or false if error
|
||||
*/
|
||||
function authenticate_user_login($username, $password) {
|
||||
function authenticate_user_login($username, $password, $ignorelockout=false, &$failurereason=null) {
|
||||
global $CFG, $DB;
|
||||
require_once("$CFG->libdir/authlib.php");
|
||||
|
||||
$authsenabled = get_enabled_auth_plugins();
|
||||
|
||||
|
@ -4146,11 +4116,13 @@ function authenticate_user_login($username, $password) {
|
|||
if (!empty($user->suspended)) {
|
||||
add_to_log(SITEID, 'login', 'error', 'index.php', $username);
|
||||
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
||||
$failurereason = AUTH_LOGIN_SUSPENDED;
|
||||
return false;
|
||||
}
|
||||
if ($auth=='nologin' or !is_enabled_auth($auth)) {
|
||||
add_to_log(SITEID, 'login', 'error', 'index.php', $username);
|
||||
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Disabled Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
||||
$failurereason = AUTH_LOGIN_SUSPENDED; // Legacy way to suspend user.
|
||||
return false;
|
||||
}
|
||||
$auths = array($auth);
|
||||
|
@ -4159,6 +4131,7 @@ function authenticate_user_login($username, $password) {
|
|||
// Check if there's a deleted record (cheaply), this should not happen because we mangle usernames in delete_user().
|
||||
if ($DB->get_field('user', 'id', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'deleted'=>1))) {
|
||||
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Deleted Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
||||
$failurereason = AUTH_LOGIN_NOUSER;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -4166,6 +4139,7 @@ function authenticate_user_login($username, $password) {
|
|||
if (!empty($CFG->authpreventaccountcreation)) {
|
||||
add_to_log(SITEID, 'login', 'error', 'index.php', $username);
|
||||
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Unknown user, can not create new accounts: $username ".$_SERVER['HTTP_USER_AGENT']);
|
||||
$failurereason = AUTH_LOGIN_NOUSER;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -4175,6 +4149,24 @@ function authenticate_user_login($username, $password) {
|
|||
$user->id = 0;
|
||||
}
|
||||
|
||||
if ($ignorelockout) {
|
||||
// Some other mechanism protects against brute force password guessing,
|
||||
// for example login form might include reCAPTCHA or this function
|
||||
// is called from a SSO script.
|
||||
|
||||
} else if ($user->id) {
|
||||
// Verify login lockout after other ways that may prevent user login.
|
||||
if (login_is_lockedout($user)) {
|
||||
add_to_log(SITEID, 'login', 'error', 'index.php', $username);
|
||||
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Login lockout: $username ".$_SERVER['HTTP_USER_AGENT']);
|
||||
$failurereason = AUTH_LOGIN_LOCKOUT;
|
||||
return false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// We can not lockout non-existing accounts.
|
||||
}
|
||||
|
||||
foreach ($auths as $auth) {
|
||||
$authplugin = get_auth_plugin($auth);
|
||||
|
||||
|
@ -4208,6 +4200,7 @@ function authenticate_user_login($username, $password) {
|
|||
}
|
||||
|
||||
if (empty($user->id)) {
|
||||
$failurereason = AUTH_LOGIN_NOUSER;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -4215,9 +4208,12 @@ function authenticate_user_login($username, $password) {
|
|||
// just in case some auth plugin suspended account
|
||||
add_to_log(SITEID, 'login', 'error', 'index.php', $username);
|
||||
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Suspended Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
||||
$failurereason = AUTH_LOGIN_SUSPENDED;
|
||||
return false;
|
||||
}
|
||||
|
||||
login_attempt_valid($user);
|
||||
$failurereason = AUTH_LOGIN_OK;
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
@ -4226,6 +4222,14 @@ function authenticate_user_login($username, $password) {
|
|||
if (debugging('', DEBUG_ALL)) {
|
||||
error_log('[client '.getremoteaddr()."] $CFG->wwwroot Failed Login: $username ".$_SERVER['HTTP_USER_AGENT']);
|
||||
}
|
||||
|
||||
if ($user->id) {
|
||||
login_attempt_failed($user);
|
||||
$failurereason = AUTH_LOGIN_FAILED;
|
||||
} else {
|
||||
$failurereason = AUTH_LOGIN_NOUSER;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
194
lib/tests/authlib_test.php
Normal file
194
lib/tests/authlib_test.php
Normal file
|
@ -0,0 +1,194 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Authentication related tests.
|
||||
*
|
||||
* @package core_auth
|
||||
* @category phpunit
|
||||
* @copyright 2012 Petr Skoda {@link http://skodak.org}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
|
||||
/**
|
||||
* Functional test for authentication related APIs.
|
||||
*/
|
||||
class authlib_testcase extends advanced_testcase {
|
||||
public function test_lockout() {
|
||||
global $CFG;
|
||||
require_once("$CFG->libdir/authlib.php");
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$oldlog = ini_get('error_log');
|
||||
ini_set('error_log', "$CFG->dataroot/testlog.log"); // Prevent standard logging.
|
||||
|
||||
set_config('lockoutthreshold', 0);
|
||||
set_config('lockoutwindow', 60*20);
|
||||
set_config('lockoutduration', 60*30);
|
||||
|
||||
$user = $this->getDataGenerator()->create_user();
|
||||
|
||||
|
||||
// Test lockout is disabled when threshold not set.
|
||||
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
|
||||
|
||||
// Test lockout threshold works.
|
||||
|
||||
set_config('lockoutthreshold', 3);
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
ob_start();
|
||||
login_attempt_failed($user);
|
||||
$output = ob_get_clean();
|
||||
$this->assertContains('noemailever', $output);
|
||||
$this->assertTrue(login_is_lockedout($user));
|
||||
|
||||
|
||||
// Test unlock works.
|
||||
|
||||
login_unlock_account($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
|
||||
|
||||
// Test lockout window works.
|
||||
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
set_user_preference('login_failed_last', time()-60*20-10, $user);
|
||||
login_attempt_failed($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
|
||||
|
||||
// Test valid login resets window.
|
||||
|
||||
login_attempt_valid($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
|
||||
|
||||
// Test lock duration works.
|
||||
|
||||
ob_start(); // Prevent nomailever notice.
|
||||
login_attempt_failed($user);
|
||||
$output = ob_get_clean();
|
||||
$this->assertContains('noemailever', $output);
|
||||
$this->assertTrue(login_is_lockedout($user));
|
||||
set_user_preference('login_lockout', time()-60*30+10, $user);
|
||||
$this->assertTrue(login_is_lockedout($user));
|
||||
set_user_preference('login_lockout', time()-60*30-10, $user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
|
||||
|
||||
// Test lockout ignored pref works.
|
||||
|
||||
set_user_preference('login_lockout_ignored', 1, $user);
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
login_attempt_failed($user);
|
||||
$this->assertFalse(login_is_lockedout($user));
|
||||
|
||||
ini_set('error_log', $oldlog);
|
||||
}
|
||||
|
||||
public function test_authenticate_user_login() {
|
||||
global $CFG;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$oldlog = ini_get('error_log');
|
||||
ini_set('error_log', "$CFG->dataroot/testlog.log"); // Prevent standard logging.
|
||||
|
||||
set_config('lockoutthreshold', 0);
|
||||
set_config('lockoutwindow', 60*20);
|
||||
set_config('lockoutduration', 60*30);
|
||||
|
||||
$_SERVER['HTTP_USER_AGENT'] = 'no browser'; // Hack around missing user agent in CLI scripts.
|
||||
|
||||
$user1 = $this->getDataGenerator()->create_user(array('username'=>'username1', 'password'=>'password1'));
|
||||
$user2 = $this->getDataGenerator()->create_user(array('username'=>'username2', 'password'=>'password2', 'suspended'=>1));
|
||||
$user3 = $this->getDataGenerator()->create_user(array('username'=>'username3', 'password'=>'password3', 'auth'=>'nologin'));
|
||||
|
||||
$result = authenticate_user_login('username1', 'password1');
|
||||
$this->assertInstanceOf('stdClass', $result);
|
||||
$this->assertEquals($user1->id, $result->id);
|
||||
|
||||
$reason = null;
|
||||
$result = authenticate_user_login('username1', 'password1', false, $reason);
|
||||
$this->assertInstanceOf('stdClass', $result);
|
||||
$this->assertEquals(AUTH_LOGIN_OK, $reason);
|
||||
|
||||
$reason = null;
|
||||
$result = authenticate_user_login('username1', 'nopass', false, $reason);
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_FAILED, $reason);
|
||||
|
||||
$reason = null;
|
||||
$result = authenticate_user_login('username2', 'password2', false, $reason);
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_SUSPENDED, $reason);
|
||||
|
||||
$reason = null;
|
||||
$result = authenticate_user_login('username3', 'password3', false, $reason);
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_SUSPENDED, $reason);
|
||||
|
||||
$reason = null;
|
||||
$result = authenticate_user_login('username4', 'password3', false, $reason);
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_NOUSER, $reason);
|
||||
|
||||
|
||||
set_config('lockoutthreshold', 3);
|
||||
$reason = null;
|
||||
$result = authenticate_user_login('username1', 'nopass', false, $reason);
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_FAILED, $reason);
|
||||
$result = authenticate_user_login('username1', 'nopass', false, $reason);
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_FAILED, $reason);
|
||||
ob_start(); // Prevent nomailever notice.
|
||||
$result = authenticate_user_login('username1', 'nopass', false, $reason);
|
||||
ob_end_clean();
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_FAILED, $reason);
|
||||
|
||||
$result = authenticate_user_login('username1', 'password1', false, $reason);
|
||||
$this->assertFalse($result);
|
||||
$this->assertEquals(AUTH_LOGIN_LOCKOUT, $reason);
|
||||
|
||||
$result = authenticate_user_login('username1', 'password1', true, $reason);
|
||||
$this->assertInstanceOf('stdClass', $result);
|
||||
$this->assertEquals(AUTH_LOGIN_OK, $reason);
|
||||
|
||||
ini_set('error_log', $oldlog);
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
require('../config.php');
|
||||
require_once('change_password_form.php');
|
||||
require_once($CFG->libdir.'/authlib.php');
|
||||
|
||||
$id = optional_param('id', SITEID, PARAM_INT); // current course
|
||||
$return = optional_param('return', 0, PARAM_BOOL); // redirect after password change
|
||||
|
@ -110,6 +111,9 @@ if ($mform->is_cancelled()) {
|
|||
print_error('errorpasswordupdate', 'auth');
|
||||
}
|
||||
|
||||
// Reset login lockout - we want to prevent any accidental confusion here.
|
||||
login_unlock_account($user);
|
||||
|
||||
// register success changing password
|
||||
unset_user_preference('auth_forcepasswordchange', $USER);
|
||||
unset_user_preference('create_password', $USER);
|
||||
|
|
|
@ -73,16 +73,12 @@ class login_change_password_form extends moodleform {
|
|||
global $USER;
|
||||
$errors = parent::validation($data, $files);
|
||||
|
||||
update_login_count();
|
||||
|
||||
// ignore submitted username
|
||||
if (!$user = authenticate_user_login($USER->username, $data['password'])) {
|
||||
if (!$user = authenticate_user_login($USER->username, $data['password'], true)) {
|
||||
$errors['password'] = get_string('invalidlogin');
|
||||
return $errors;
|
||||
}
|
||||
|
||||
reset_login_count();
|
||||
|
||||
if ($data['newpassword1'] <> $data['newpassword2']) {
|
||||
$errors['newpassword1'] = get_string('passwordsdiffer');
|
||||
$errors['newpassword2'] = get_string('passwordsdiffer');
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
*/
|
||||
|
||||
require('../config.php');
|
||||
require_once($CFG->libdir.'/authlib.php');
|
||||
require_once('forgot_password_form.php');
|
||||
|
||||
$p_secret = optional_param('p', false, PARAM_RAW);
|
||||
|
@ -63,8 +64,6 @@ if ($p_secret !== false) {
|
|||
/// user clicked on link in email message
|
||||
///=====================
|
||||
|
||||
update_login_count();
|
||||
|
||||
$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))) {
|
||||
|
@ -83,6 +82,9 @@ if ($p_secret !== false) {
|
|||
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);
|
||||
|
||||
|
@ -94,8 +96,6 @@ if ($p_secret !== false) {
|
|||
$user->secret = '';
|
||||
$DB->set_field('user', 'secret', $user->secret, array('id'=>$user->id));
|
||||
|
||||
reset_login_count();
|
||||
|
||||
$changepasswordurl = "{$CFG->httpswwwroot}/login/change_password.php";
|
||||
$a = new stdClass();
|
||||
$a->email = $user->email;
|
||||
|
|
|
@ -148,8 +148,6 @@ if ($frm and isset($frm->username)) { // Login WITH
|
|||
die;
|
||||
}
|
||||
|
||||
update_login_count();
|
||||
|
||||
if ($user) {
|
||||
|
||||
// language setup
|
||||
|
@ -242,8 +240,6 @@ if ($frm and isset($frm->username)) { // Login WITH
|
|||
}
|
||||
}
|
||||
|
||||
reset_login_count();
|
||||
|
||||
// test the session actually works by redirecting to self
|
||||
$SESSION->wantsurl = $urltogo;
|
||||
redirect(new moodle_url(get_login_url(), array('testsession'=>$USER->id)));
|
||||
|
|
54
login/unlock_account.php
Normal file
54
login/unlock_account.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?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/>.
|
||||
|
||||
/**
|
||||
* Reset locked-out accounts.
|
||||
*
|
||||
* @package core_auth
|
||||
* @copyright 2012 Petr Skoda {@link http://skodak.org}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
require('../config.php');
|
||||
require_once($CFG->libdir.'/authlib.php');
|
||||
|
||||
$userid = optional_param('u', 0, PARAM_INT);
|
||||
$secret = optional_param('s', '', PARAM_RAW);
|
||||
|
||||
$PAGE->set_url('/login/unlock_account.php');
|
||||
$PAGE->set_context(context_system::instance());
|
||||
|
||||
// Override wanted URL, we do not want to end up here again after login!
|
||||
$SESSION->wantsurl = "$CFG->wwwroot/";
|
||||
|
||||
// Do not disclose details about existence or status of user accounts here.
|
||||
|
||||
if (!$user = $DB->get_record('user', array('id'=>$userid, 'deleted'=>0, 'suspended'=>0))) {
|
||||
print_error('lockouterrorunlock', 'admin', get_login_url());
|
||||
}
|
||||
|
||||
$usersecret = get_user_preferences('login_lockout_secret', false, $user);
|
||||
|
||||
if ($secret === $usersecret) {
|
||||
login_unlock_account($user);
|
||||
if ($USER->id == $user->id) {
|
||||
redirect("$CFG->wwwroot/");
|
||||
} else {
|
||||
redirect(get_login_url());
|
||||
}
|
||||
}
|
||||
|
||||
print_error('lockouterrorunlock', 'admin', get_login_url());
|
Loading…
Add table
Add a link
Reference in a new issue