Skip to content

Commit

Permalink
Merge branch 'MDL-66776-master' of https://github.com/jleyva/moodle
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols committed Nov 8, 2021
2 parents d135a12 + 39d9694 commit 9776479
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 6 deletions.
13 changes: 13 additions & 0 deletions lang/en/moodle.php
Expand Up @@ -1264,6 +1264,7 @@
$string['messageprovider:errors_help'] = 'These are important errors that an administrator should know about.';
$string['messageprovider:gradenotifications'] = 'Grade notifications';
$string['messageprovider:messagecontactrequests'] = 'Message contact requests notification';
$string['messageprovider:newlogin'] = 'New login notifications';
$string['messageprovider:notices'] = 'Notices about minor problems';
$string['messageprovider:notices_help'] = 'These are notices that an administrator might be interested in seeing.';
$string['messageprovider:infected'] = 'Antivirus failure notifications.';
Expand Down Expand Up @@ -1386,6 +1387,18 @@
$string['newaccount'] = 'New account';
$string['newactivityname'] = 'New name for activity {$a}';
$string['newcourse'] = 'New course';
$string['newloginnotificationtitle'] = 'New sign in to your {$a} account';
$string['newloginnotificationbodysmall'] = 'Your {$a} account was just signed in to from a new device.';
$string['newloginnotificationbodyfull'] = '<p>Hi {$a->userfullname},</p>
<p>Your {$a->sitename} account was just signed in to from a new device.</p>
<ul>
<li>Your account: {$a->username} {$a->useremail}</li>
<li>{$a->logintime}</li>
<li>Device: {$a->logindevice}</li>
<li>IP: {$a->loginip}</li>
</ul>
<p>If this was you, then you don\'t need to do anything.</p>
<p>If you don\'t recognise this activity, please <a href="{$a->changepasswordlink}">change your password</a></p>';
$string['newpassword'] = 'New password';
$string['newpassword_help'] = 'Enter a new password or leave blank to keep current password.';
$string['newpasswordfromlost'] = '<strong>NOTICE:</strong> Your <strong>Current password</strong> will have been sent to you in the <strong>second</strong> of the two emails sent as part of the lost password recovery process. Make sure you have received your replacement password before continuing with this screen.';
Expand Down
11 changes: 8 additions & 3 deletions lib/classes/session/manager.php
Expand Up @@ -274,15 +274,20 @@ protected static function load_handler() {
* This is intended for installation scripts, unit tests and other
* special areas. Do NOT use for logout and session termination
* in normal requests!
*
* @param mixed $newsid only used after initialising a user session, is this a new user session?
*/
public static function init_empty_session() {
public static function init_empty_session(?bool $newsid = null) {
global $CFG;

if (isset($GLOBALS['SESSION']->notifications)) {
// Backup notifications. These should be preserved across session changes until the user fetches and clears them.
$notifications = $GLOBALS['SESSION']->notifications;
}
$GLOBALS['SESSION'] = new \stdClass();
if (isset($newsid)) {
$GLOBALS['SESSION']->isnewsessioncookie = $newsid;
}

$GLOBALS['USER'] = new \stdClass();
$GLOBALS['USER']->id = 0;
Expand Down Expand Up @@ -419,7 +424,7 @@ protected static function initialise_user_session($newsid) {
if (!$sid) {
// No session, very weird.
error_log('Missing session ID, session not started!');
self::init_empty_session();
self::init_empty_session($newsid);
return;
}

Expand Down Expand Up @@ -547,7 +552,7 @@ protected static function initialise_user_session($newsid) {
self::set_user($user);
self::add_session_record($user->id);
} else {
self::init_empty_session();
self::init_empty_session($newsid);
self::add_session_record(0);
}

Expand Down
96 changes: 96 additions & 0 deletions lib/classes/task/send_login_notifications.php
@@ -0,0 +1,96 @@
<?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/>.

namespace core\task;

/**
* Adhoc task that send login notifications.
*
* @package core
* @copyright 2021 Moodle Pty Ltd.
* @author Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class send_login_notifications extends adhoc_task {

use \core\task\logging_trait;

/**
* Run the adhoc task and preform the backup.
*/
public function execute() {
global $CFG, $DB, $SITE, $USER, $PAGE;

$customdata = $this->get_custom_data();

// First check the mobile app special case, to detect if the user is not using a new device after login from a different IP.
if (!empty($customdata->ismoodleapp)) {
$where = 'userid = ? AND timecreated >= ?';
if (!$DB->count_records_select('user_devices', $where, [$USER->id, $customdata->logintime])) {
// Do nothing, seems to be the same person doing login from a new IP using a known device.
return;
}
}

$this->log_start("Sending login notification to {$USER->username}");
$sitename = format_string($SITE->fullname);
$siteurl = $CFG->wwwroot;
$userfullname = fullname($USER);
$username = $USER->username;
$useremail = ($USER->username != $USER->email) ? $USER->email : '';
$logindevice = $customdata->ismoodleapp ? get_string('mobileapp', 'tool_mobile') : '';
$logindevice .= ' ' . $customdata->useragent;
$loginip = $customdata->loginip;
$logintime = userdate($customdata->logintime);

$changepasswordlink = (new \moodle_url('/user/preferences.php', ['userid' => $USER->id]))->out(false);
// Find a better final URL for changing password.
$userauth = get_auth_plugin($USER->auth);
if ($userauth->can_change_password()) {
if ($changepwurl = $userauth->change_password_url()) {
$changepasswordlink = $changepwurl;
} else {
$changepasswordlink = (new \moodle_url('/login/change_password.php'))->out(false);
}
}

$eventdata = new \core\message\message();
$eventdata->courseid = SITEID;
$eventdata->component = 'moodle';
$eventdata->name = 'newlogin';
$eventdata->userfrom = \core_user::get_noreply_user();
$eventdata->userto = $USER;
$eventdata->notification = 1;
$eventdata->subject = get_string('newloginnotificationtitle', 'moodle', $sitename);
$eventdata->fullmessageformat = FORMAT_HTML;
$info = compact('sitename', 'siteurl', 'userfullname', 'username', 'useremail',
'logindevice', 'logintime', 'loginip', 'changepasswordlink');
$eventdata->fullmessagehtml = get_string('newloginnotificationbodyfull', 'moodle', $info);
$eventdata->fullmessage = html_to_text($eventdata->fullmessagehtml);
$eventdata->smallmessage = get_string('newloginnotificationbodysmall', 'moodle', $username);

$userpicture = new \user_picture($USER);
$userpicture->size = 1; // Use f1 size.
$userpicture->includetoken = $USER->id; // Generate an out-of-session token for the user receiving the message.
$eventdata->customdata = ['notificationiconurl' => $userpicture->get_url($PAGE)->out(false)];

if (message_send($eventdata)) {
$this->log_finish("Notification successfully sent");
} else {
$this->log_finish("Failed to send notification");
}
}
}
7 changes: 7 additions & 0 deletions lib/db/messages.php
Expand Up @@ -35,6 +35,13 @@

$messageproviders = array (

'newlogin' => array (
'defaults' => array(
'email' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
'airnotifier' => MESSAGE_PERMITTED + MESSAGE_DEFAULT_LOGGEDIN + MESSAGE_DEFAULT_LOGGEDOFF,
),
),

// Notices that an admin might be interested in
'notices' => array (
'capability' => 'moodle/site:config'
Expand Down
21 changes: 20 additions & 1 deletion lib/externallib.php
Expand Up @@ -1172,7 +1172,7 @@ function external_generate_token_for_current_user($service) {
* @since Moodle 3.2
*/
function external_log_token_request($token) {
global $DB;
global $DB, $USER;

$token->privatetoken = null;

Expand All @@ -1185,6 +1185,25 @@ function external_log_token_request($token) {
$event = \core\event\webservice_token_sent::create($params);
$event->add_record_snapshot('external_tokens', $token);
$event->trigger();

// Check if we need to notify the user about the new login via token.
$loginip = getremoteaddr();
if ($USER->lastip != $loginip &&
((!WS_SERVER && !CLI_SCRIPT && NO_MOODLE_COOKIES) || PHPUNIT_TEST)) {

$logintime = time();
$useragent = \core_useragent::get_user_agent_string();
$ismoodleapp = \core_useragent::is_moodle_app();

// Schedule adhoc task to sent a login notification to the user.
$task = new \core\task\send_login_notifications();
$task->set_userid($USER->id);
$task->set_custom_data(compact('ismoodleapp', 'useragent', 'loginip', 'logintime'));
$task->set_component('core');
// We need sometime so the mobile app will send to Moodle the device information after login.
$task->set_next_run_time($logintime + (2 * MINSECS));
\core\task\manager::reschedule_or_queue_adhoc_task($task);
}
}

/**
Expand Down
24 changes: 23 additions & 1 deletion lib/moodlelib.php
Expand Up @@ -3351,7 +3351,7 @@ function get_user_key($script, $userid, $instance=null, $iprestriction=null, $va
* @return bool Always returns true
*/
function update_user_login_times() {
global $USER, $DB;
global $USER, $DB, $SESSION;

if (isguestuser()) {
// Do not update guest access times/ips for performance.
Expand All @@ -3375,6 +3375,7 @@ function update_user_login_times() {

// Function user_accesstime_log() may not update immediately, better do it here.
$USER->lastaccess = $user->lastaccess = $now;
$SESSION->userpreviousip = $USER->lastip;
$USER->lastip = $user->lastip = getremoteaddr();

// Note: do not call user_update_user() here because this is part of the login process,
Expand Down Expand Up @@ -4545,6 +4546,27 @@ function complete_user_login($user) {
);
$event->trigger();

// Check if the user is using a new browser or session (a new MoodleSession cookie is set in that case).
// If the user is accessing from the same IP, ignore everything (most of the time will be a new session in the same browser).
// Skip Web Service requests, CLI scripts, AJAX scripts, and request from the mobile app itself.
$loginip = getremoteaddr();
$isnewip = isset($SESSION->userpreviousip) && $SESSION->userpreviousip != $loginip;
$isvalidenv = (!WS_SERVER && !CLI_SCRIPT && !NO_MOODLE_COOKIES) || PHPUNIT_TEST;

if (!empty($SESSION->isnewsessioncookie) && $isnewip && $isvalidenv && !\core_useragent::is_moodle_app()) {

$logintime = time();
$ismoodleapp = false;
$useragent = \core_useragent::get_user_agent_string();

// Schedule adhoc task to sent a login notification to the user.
$task = new \core\task\send_login_notifications();
$task->set_userid($USER->id);
$task->set_custom_data(compact('ismoodleapp', 'useragent', 'loginip', 'logintime'));
$task->set_component('core');
\core\task\manager::queue_adhoc_task($task);
}

// Queue migrating the messaging data, if we need to.
if (!get_user_preferences('core_message_migrate_data', false, $USER->id)) {
// Check if there are any legacy messages to migrate.
Expand Down
1 change: 1 addition & 0 deletions lib/upgrade.txt
Expand Up @@ -115,6 +115,7 @@ completely removed from Moodle core too.
* Final deprecation: The following functions along with associated tests have been removed:
- core_grades_external::get_grades
- core_grades_external::get_grade_item
* \core\session\manager::init_empty_session() has a new optional parameter $newsid to indicate whether this is a new user session

=== 3.11.4 ===
* A new option dontforcesvgdownload has been added to the $options parameter of the send_file() function.
Expand Down

0 comments on commit 9776479

Please sign in to comment.