Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  
  21  global $CFG;
  22  require_once($CFG->dirroot . '/lib/externallib.php');
  23  
  24  /**
  25   * Contains tests for course related notifications.
  26   *
  27   * @package    core
  28   * @copyright  2021 Juan Leyva <juan@moodle.com>
  29   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  30   */
  31  class login_notifications_test extends \advanced_testcase {
  32  
  33      /**
  34       * Load required classes
  35       */
  36      public static function setUpBeforeClass(): void {
  37          global $CFG;
  38  
  39          require_once($CFG->libdir . '/externallib.php');
  40      }
  41  
  42      /**
  43       * Test new login notification.
  44       */
  45      public function test_login_notification() {
  46          global $SESSION;
  47  
  48          $this->resetAfterTest();
  49  
  50          $loginuser = self::getDataGenerator()->create_user();
  51          $this->setUser(0);
  52  
  53          // Mock data for test.
  54          $loginuser->lastip = '1.2.3.4.6'; // Different ip that current.
  55          $SESSION->isnewsessioncookie = true; // New session cookie.
  56          @complete_user_login($loginuser);
  57  
  58          // Redirect messages to sink and stop buffer output from CLI task.
  59          $sink = $this->redirectMessages();
  60          ob_start();
  61          $this->runAdhocTasks('\core\task\send_login_notifications');
  62          $output = ob_get_contents();
  63          ob_end_clean();
  64          $messages = $sink->get_messages();
  65          $sink->close();
  66  
  67          // Send notification, new IP and new session.
  68          $this->assertCount(1, $messages);
  69          $this->assertEquals($loginuser->id, $messages[0]->useridto);
  70          $this->assertEquals('newlogin', $messages[0]->eventtype);
  71      }
  72  
  73      /**
  74       * Test new login notification is skipped because of same IP from last login.
  75       */
  76      public function test_login_notification_skip_same_ip() {
  77          global $SESSION;
  78  
  79          $this->resetAfterTest();
  80  
  81          $loginuser = self::getDataGenerator()->create_user();
  82          $this->setUser(0);
  83  
  84          // Mock data for test.
  85          $SESSION->isnewsessioncookie = true;    // New session cookie.
  86          @complete_user_login($loginuser);
  87  
  88          // Redirect messages to sink and stop buffer output from CLI task.
  89          $sink = $this->redirectMessages();
  90          ob_start();
  91          $this->runAdhocTasks('\core\task\send_login_notifications');
  92          $output = ob_get_contents();
  93          ob_end_clean();
  94          $messages = $sink->get_messages();
  95          $sink->close();
  96  
  97          // Skip notification when we have the same previous IP even if the browser used to connect is new.
  98          $this->assertCount(0, $messages);
  99      }
 100  
 101      /**
 102       * Test new login notification is skipped because of same browser from last login.
 103       */
 104      public function test_login_notification_skip_same_browser() {
 105          global $SESSION;
 106  
 107          $this->resetAfterTest();
 108  
 109          $loginuser = self::getDataGenerator()->create_user();
 110          $this->setUser(0);
 111  
 112          // Mock data for test.
 113          $loginuser->lastip = '1.2.3.4.6'; // Different ip that current.
 114          $SESSION->isnewsessioncookie = false;
 115          @complete_user_login($loginuser);
 116  
 117          // Redirect messages to sink and stop buffer output from CLI task.
 118          $sink = $this->redirectMessages();
 119          ob_start();
 120          $this->runAdhocTasks('\core\task\send_login_notifications');
 121          $output = ob_get_contents();
 122          ob_end_clean();
 123          $messages = $sink->get_messages();
 124          $sink->close();
 125  
 126          // Skip notification, different ip but same browser (probably, mobile phone browser).
 127          $this->assertCount(0, $messages);
 128      }
 129  
 130      /**
 131       * Test new login notification is skipped because of auto-login from the mobile app (skip duplicated notifications).
 132       */
 133      public function test_login_notification_skip_mobileapp() {
 134          global $SESSION;
 135  
 136          $this->resetAfterTest();
 137  
 138          $loginuser = self::getDataGenerator()->create_user();
 139          $this->setUser(0);
 140  
 141          // Mock data for test.
 142          $loginuser->lastip = '1.2.3.4.6';   // Different ip that current.
 143          $SESSION->isnewsessioncookie = true;    // New session cookie.
 144          \core_useragent::instance(true, 'MoodleMobile'); // Force fake mobile app user agent.
 145          @complete_user_login($loginuser);
 146  
 147          // Redirect messages to sink and stop buffer output from CLI task.
 148          $sink = $this->redirectMessages();
 149          ob_start();
 150          $this->runAdhocTasks('\core\task\send_login_notifications');
 151          $output = ob_get_contents();
 152          ob_end_clean();
 153          $messages = $sink->get_messages();
 154          $sink->close();
 155  
 156          $this->assertCount(0, $messages);
 157      }
 158  
 159      /**
 160       * Test new mobile app login notification.
 161       */
 162      public function test_mobile_app_login_notification() {
 163          global $USER, $DB, $SESSION;
 164  
 165          $this->resetAfterTest();
 166  
 167          $loginuser = self::getDataGenerator()->create_user();
 168          $this->setUser($loginuser);
 169  
 170          // Mock data for test.
 171          $USER->lastip = '1.2.3.4.6'; // Different ip that current.
 172  
 173          $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
 174          $token = external_generate_token_for_current_user($service);
 175          \core_useragent::instance(true, 'MoodleMobile'); // Force fake mobile app user agent.
 176  
 177          // Simulate we are using an new device.
 178          $fakedevice = (object) [
 179              'userid' => $USER->id,
 180              'appid' => 'com.moodle.moodlemobile',
 181              'name' => 'occam',
 182              'model' => 'Nexus 4',
 183              'platform' => 'Android',
 184              'version' => '4.2.2',
 185              'pushid' => 'kishUhd',
 186              'uuid' => 'KIhud7s',
 187              'timecreated' => time() + MINSECS,
 188              'timemodified' => time() + MINSECS
 189          ];
 190          $DB->insert_record('user_devices', $fakedevice);
 191  
 192          external_log_token_request($token);
 193  
 194          // Redirect messages to sink and stop buffer output from CLI task.
 195          $sink = $this->redirectMessages();
 196          ob_start();
 197          $this->runAdhocTasks('\core\task\send_login_notifications');
 198          $output = ob_get_contents();
 199          ob_end_clean();
 200          $messages = $sink->get_messages();
 201          $sink->close();
 202  
 203          // We sent a login notification because we are using a new device and different IP.
 204          $this->assertCount(1, $messages);
 205          $this->assertEquals($loginuser->id, $messages[0]->useridto);
 206          $this->assertEquals('newlogin', $messages[0]->eventtype);
 207      }
 208  
 209      /**
 210       * Test new mobile app login notification skipped becase of same last ip.
 211       */
 212      public function test_mobile_app_login_notification_skip_same_ip() {
 213          global $USER, $DB, $SESSION;
 214  
 215          $this->resetAfterTest();
 216  
 217          $loginuser = self::getDataGenerator()->create_user();
 218          $this->setUser($loginuser);
 219  
 220          // Mock data for test.
 221          $USER->lastip = '0.0.0.0';
 222          $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
 223          $token = external_generate_token_for_current_user($service);
 224          \core_useragent::instance(true, 'MoodleMobile'); // Force fake mobile app user agent.
 225  
 226          // Simulate we are using an new device.
 227          $fakedevice = (object) [
 228              'userid' => $USER->id,
 229              'appid' => 'com.moodle.moodlemobile',
 230              'name' => 'occam',
 231              'model' => 'Nexus 4',
 232              'platform' => 'Android',
 233              'version' => '4.2.2',
 234              'pushid' => 'kishUhd',
 235              'uuid' => 'KIhud7s',
 236              'timecreated' => time() + MINSECS,
 237              'timemodified' => time() + MINSECS
 238          ];
 239          $DB->insert_record('user_devices', $fakedevice);
 240  
 241          external_log_token_request($token);
 242  
 243          // Redirect messages to sink and stop buffer output from CLI task.
 244          $sink = $this->redirectMessages();
 245          ob_start();
 246          $this->runAdhocTasks('\core\task\send_login_notifications');
 247          $output = ob_get_contents();
 248          ob_end_clean();
 249          $messages = $sink->get_messages();
 250          $sink->close();
 251  
 252          // While using the same IP avoid sending new login notifications even if we are using a new device.
 253          $this->assertCount(0, $messages);
 254      }
 255  
 256      /**
 257       * Test new mobile app login notification skipped becase of same device.
 258       */
 259      public function test_mobile_app_login_notification_skip_same_device() {
 260          global $USER, $DB, $SESSION;
 261  
 262          $this->resetAfterTest();
 263  
 264          $loginuser = self::getDataGenerator()->create_user();
 265          $this->setUser($loginuser);
 266  
 267          // Mock data for test.
 268          $USER->lastip = '1.2.3.4.6';    // New ip.
 269          $service = $DB->get_record('external_services', array('shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE));
 270          $token = external_generate_token_for_current_user($service);
 271          \core_useragent::instance(true, 'MoodleMobile'); // Force fake mobile app user agent.
 272  
 273          external_log_token_request($token);
 274  
 275          // Redirect messages to sink and stop buffer output from CLI task.
 276          $sink = $this->redirectMessages();
 277          ob_start();
 278          $this->runAdhocTasks('\core\task\send_login_notifications');
 279          $output = ob_get_contents();
 280          ob_end_clean();
 281          $messages = $sink->get_messages();
 282          $sink->close();
 283  
 284          // While using the same device avoid sending new login notifications even if the IP changes.
 285          $this->assertCount(0, $messages);
 286      }
 287  }