diff --git a/lang/en/moodle.php b/lang/en/moodle.php index 53fea497843..bb24a80113e 100644 --- a/lang/en/moodle.php +++ b/lang/en/moodle.php @@ -1323,7 +1323,7 @@ $string['notice'] = 'Notice'; $string['noticenewerbackup'] = 'This backup file has been created with Moodle {$a->backuprelease} ({$a->backupversion}) and it\'s newer than your currently installed Moodle {$a->serverrelease} ({$a->serverversion}). This could cause some inconsistencies because backwards compatibility of backup files cannot be guaranteed.'; $string['notifications'] = 'Notifications'; $string['notifyloginfailuresmessage'] = '{$a->time}, IP: {$a->ip}, User: {$a->info}'; -$string['notifyloginfailuresmessageend'] = 'You can view these logs at {$a}/report/log/index.php?id=1&chooselog=1&modid=site_errors.'; +$string['notifyloginfailuresmessageend'] = 'You can view these logs at {$a}'; $string['notifyloginfailuresmessagestart'] = 'Here is a list of failed login attempts at {$a} since you were last notified'; $string['notifyloginfailuressubject'] = '{$a} :: Failed logins notification'; $string['notincluded'] = 'Not included'; diff --git a/lib/classes/task/send_failed_login_notifications_task.php b/lib/classes/task/send_failed_login_notifications_task.php index 1c71b6fdf1b..7791fcf8fc5 100644 --- a/lib/classes/task/send_failed_login_notifications_task.php +++ b/lib/classes/task/send_failed_login_notifications_task.php @@ -42,7 +42,7 @@ class send_failed_login_notifications_task extends scheduled_task { * Throw exceptions on errors (the job will be retried). */ public function execute() { - global $CFG, $DB, $OUTPUT; + global $CFG, $DB; if (empty($CFG->notifyloginfailures)) { return; @@ -66,13 +66,23 @@ class send_failed_login_notifications_task extends scheduled_task { // Get all the IPs with more than notifyloginthreshold failures since lastnotifyfailure // and insert them into the cache_flags temp table. + $logmang = get_log_manager(); + $readers = $logmang->get_readers('\core\log\sql_internal_reader'); + $reader = reset($readers); + $readername = key($readers); + if (empty($reader) || empty($readername)) { + // No readers, no processing. + return true; + } + $logtable = $reader->get_internal_log_table_name(); + $sql = "SELECT ip, COUNT(*) - FROM {log} - WHERE module = 'login' AND action = 'error' - AND time > ? - GROUP BY ip - HAVING COUNT(*) >= ?"; - $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold); + FROM {" . $logtable . "} + WHERE eventname = ? + AND timecreated > ? + GROUP BY ip + HAVING COUNT(*) >= ?"; + $params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, $CFG->notifyloginthreshold); $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $iprec) { if (!empty($iprec->ip)) { @@ -83,17 +93,17 @@ class send_failed_login_notifications_task extends scheduled_task { // Get all the INFOs with more than notifyloginthreshold failures since lastnotifyfailure // and insert them into the cache_flags temp table. - $sql = "SELECT info, count(*) - FROM {log} - WHERE module = 'login' AND action = 'error' - AND time > ? - GROUP BY info + $sql = "SELECT userid, count(*) + FROM {" . $logtable . "} + WHERE eventname = ? + AND timecreated > ? + GROUP BY userid HAVING count(*) >= ?"; - $params = array($CFG->lastnotifyfailure, $CFG->notifyloginthreshold); + $params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, $CFG->notifyloginthreshold); $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $inforec) { if (!empty($inforec->info)) { - set_cache_flag('login_failure_by_info', $inforec->info, '1', 0); + set_cache_flag('login_failure_by_id', $inforec->userid, '1', 0); } } $rs->close(); @@ -101,23 +111,23 @@ class send_failed_login_notifications_task extends scheduled_task { // Now, select all the login error logged records belonging to the ips and infos // since lastnotifyfailure, that we have stored in the cache_flags table. $sql = "SELECT * FROM ( - SELECT l.*, u.firstname, u.lastname - FROM {log} l - JOIN {cache_flags} cf ON l.ip = cf.name - LEFT JOIN {user} u ON l.userid = u.id - WHERE l.module = 'login' AND l.action = 'error' - AND l.time > ? - AND cf.flagtype = 'login_failure_by_ip' - UNION ALL - SELECT l.*, u.firstname, u.lastname - FROM {log} l - JOIN {cache_flags} cf ON l.info = cf.name - LEFT JOIN {user} u ON l.userid = u.id - WHERE l.module = 'login' AND l.action = 'error' - AND l.time > ? - AND cf.flagtype = 'login_failure_by_info') t - ORDER BY t.time DESC"; - $params = array($CFG->lastnotifyfailure, $CFG->lastnotifyfailure); + SELECT l.*, u.username + FROM {" . $logtable . "} l + JOIN {cache_flags} cf ON l.ip = cf.name + LEFT JOIN {user} u ON l.userid = u.id + WHERE l.eventname = ? + AND l.timecreated > ? + AND cf.flagtype = 'login_failure_by_ip' + UNION ALL + SELECT l.*, u.username + FROM {" . $logtable . "} l + JOIN {cache_flags} cf ON l.userid = " . $DB->sql_cast_char2int('cf.name') . " + LEFT JOIN {user} u ON l.userid = u.id + WHERE l.eventname = ? + AND l.timecreated > ? + AND cf.flagtype = 'login_failure_by_info') t + ORDER BY t.timecreated DESC"; + $params = array('\core\event\user_login_failed', $CFG->lastnotifyfailure, '\core\event\user_login_failed', $CFG->lastnotifyfailure); // Init some variables. $count = 0; @@ -125,8 +135,17 @@ class send_failed_login_notifications_task extends scheduled_task { // Iterate over the logs recordset. $rs = $DB->get_recordset_sql($sql, $params); foreach ($rs as $log) { - $log->time = userdate($log->time); - $messages .= get_string('notifyloginfailuresmessage', '', $log) . "\n"; + $a = new \stdClass(); + $a->time = userdate($log->timecreated); + if (empty($log->username)) { + // Entries with no valid username. We get attempted username from the event's other field. + $other = unserialize($log->other); + $a->info = empty($other['username']) ? '' : $other['username']; + } else { + $a->info = $log->username; + } + $a->ip = $log->ip; + $messages .= get_string('notifyloginfailuresmessage', '', $a)."\n"; $count++; } $rs->close(); @@ -136,10 +155,12 @@ class send_failed_login_notifications_task extends scheduled_task { $site = get_site(); $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname)); // Calculate the complete body of notification (start + messages + end). + $params = array('id' => 0, 'modid' => 'site_errors', 'chooselog' => '1', 'logreader' => $readername); + $url = new \moodle_url('/report/log/index.php', $params); $body = get_string('notifyloginfailuresmessagestart', '', $CFG->wwwroot) . (($CFG->lastnotifyfailure != 0) ? '('.userdate($CFG->lastnotifyfailure).')' : '')."\n\n" . $messages . - "\n\n" . get_string('notifyloginfailuresmessageend', '', $CFG->wwwroot) . "\n\n"; + "\n\n".get_string('notifyloginfailuresmessageend', '', $url->out(false).' ')."\n\n"; // For each destination, send mail. mtrace('Emailing admins about '. $count .' failed login attempts');