Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]

   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  defined('MOODLE_INTERNAL') || die();
  18  require_once (__DIR__ . '/fixtures/testable_antivirus.php');
  19  
  20  /**
  21   * Tests for antivirus manager.
  22   *
  23   * @package    core_antivirus
  24   * @category   test
  25   * @copyright  2016 Ruslan Kabalin, Lancaster University.
  26   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  27   */
  28  class antivirus_test extends advanced_testcase {
  29  
  30      /**
  31       * @var string Path to the tempfile created for use with AV scanner tests
  32       */
  33      protected $tempfile;
  34  
  35      protected function setUp(): void {
  36          global $CFG;
  37          // Use our special testable fixture plugin.
  38          $CFG->antiviruses = 'testable';
  39  
  40          $this->resetAfterTest();
  41  
  42          // Create tempfile.
  43          $tempfolder = make_request_directory(false);
  44          $this->tempfile = $tempfolder . '/' . rand();
  45          touch($this->tempfile);
  46      }
  47  
  48      /**
  49       * Enable logging.
  50       *
  51       * @return void
  52       */
  53      protected function enable_logging() {
  54          $this->preventResetByRollback();
  55          set_config('enabled_stores', 'logstore_standard', 'tool_log');
  56          set_config('buffersize', 0, 'logstore_standard');
  57          set_config('logguests', 1, 'logstore_standard');
  58      }
  59  
  60      /**
  61       * Return check api status for the antivirus check.
  62       *
  63       * @return    string Based on status of \core\check\result.
  64       */
  65      protected function get_check_api_antivirus_status_result() {
  66          $av = new \core\check\environment\antivirus();
  67          return $av->get_result()->status;
  68      }
  69  
  70      protected function tearDown(): void {
  71          @unlink($this->tempfile);
  72      }
  73  
  74      public function test_manager_get_antivirus() {
  75          // We are using clamav plugin in the test,
  76          // as the only plugin we know exists for sure.
  77          $antivirusviaget = \core\antivirus\manager::get_antivirus('clamav');
  78          $antivirusdirect = new \antivirus_clamav\scanner();
  79          $this->assertEquals($antivirusdirect, $antivirusviaget);
  80      }
  81  
  82      public function test_manager_scan_file_no_virus() {
  83          // Run mock scanning.
  84          $this->assertFileExists($this->tempfile);
  85          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
  86          // File expected to remain in place.
  87          $this->assertFileExists($this->tempfile);
  88      }
  89  
  90      public function test_manager_scan_file_error() {
  91          // Run mock scanning.
  92          $this->assertFileExists($this->tempfile);
  93          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'ERROR', true));
  94          // File expected to remain in place.
  95          $this->assertFileExists($this->tempfile);
  96      }
  97  
  98      // Check API for NA status i.e. when no scanners are enabled.
  99      public function test_antivirus_check_na() {
 100          global $CFG;
 101          $CFG->antiviruses = '';
 102          // Enable logs.
 103          $this->enable_logging();
 104          set_config('enabled_stores', 'logstore_standard', 'tool_log');
 105          // Run mock scanning.
 106          $this->assertFileExists($this->tempfile);
 107          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
 108          $this->assertEquals(\core\check\result::NA, $this->get_check_api_antivirus_status_result());
 109          // File expected to remain in place.
 110          $this->assertFileExists($this->tempfile);
 111      }
 112  
 113      // Check API for UNKNOWN status i.e. when the system's logstore reader is not '\core\log\sql_internal_table_reader'.
 114      public function test_antivirus_check_unknown() {
 115          // Run mock scanning.
 116          $this->assertFileExists($this->tempfile);
 117          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
 118          $this->assertEquals(\core\check\result::UNKNOWN, $this->get_check_api_antivirus_status_result());
 119          // File expected to remain in place.
 120          $this->assertFileExists($this->tempfile);
 121      }
 122  
 123      // Check API for OK status i.e. antivirus enabled, logstore is ok, no scanner issues occurred recently.
 124      public function test_antivirus_check_ok() {
 125          // Enable logs.
 126          $this->enable_logging();
 127          // Run mock scanning.
 128          $this->assertFileExists($this->tempfile);
 129          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
 130          $this->assertEquals(\core\check\result::OK, $this->get_check_api_antivirus_status_result());
 131          // File expected to remain in place.
 132          $this->assertFileExists($this->tempfile);
 133      }
 134  
 135      // Check API for ERROR status i.e. scanner issue within a certain timeframe/threshold.
 136      public function test_antivirus_check_error() {
 137          global $USER, $DB;
 138          // Enable logs.
 139          $this->enable_logging();
 140          // Set threshold / lookback.
 141          // Run mock scanning.
 142          $this->assertFileExists($this->tempfile);
 143          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'ERROR', true));
 144          $this->assertEquals(\core\check\result::ERROR, $this->get_check_api_antivirus_status_result());
 145          // File expected to remain in place.
 146          $this->assertFileExists($this->tempfile);
 147      }
 148  
 149      public function test_manager_scan_file_virus() {
 150          // Run mock scanning without deleting infected file.
 151          $this->assertFileExists($this->tempfile);
 152          $this->expectException(\core\antivirus\scanner_exception::class);
 153          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'FOUND', false));
 154          // File expected to remain in place.
 155          $this->assertFileExists($this->tempfile);
 156  
 157          // Run mock scanning with deleting infected file.
 158          $this->expectException(\core\antivirus\scanner_exception::class);
 159          $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true));
 160          // File expected to be deleted.
 161          $this->assertFileDoesNotExist($this->tempfile);
 162      }
 163  
 164      public function test_manager_send_message_to_user_email_scan_file_virus() {
 165          $sink = $this->redirectEmails();
 166          $exception = null;
 167          try {
 168              set_config('notifyemail', 'fake@example.com', 'antivirus');
 169              \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
 170          } catch (\core\antivirus\scanner_exception $ex) {
 171              $exception = $ex;
 172          }
 173          $this->assertNotEmpty($exception);
 174          $result = $sink->get_messages();
 175          $this->assertCount(1, $result);
 176          $this->assertStringContainsString('fake@example.com', $result[0]->to);
 177          $sink->close();
 178      }
 179  
 180      public function test_manager_send_message_to_admin_email_scan_file_virus() {
 181          $sink = $this->redirectMessages();
 182          $exception = null;
 183          try {
 184              \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
 185          } catch (\core\antivirus\scanner_exception $ex) {
 186              $exception = $ex;
 187          }
 188          $this->assertNotEmpty($exception);
 189          $result = $sink->get_messages();
 190          $admins = array_keys(get_admins());
 191          $this->assertCount(1, $admins);
 192          $this->assertCount(1, $result);
 193          $this->assertEquals($result[0]->useridto, reset($admins));
 194          $sink->close();
 195      }
 196  
 197      public function test_manager_quarantine_file_virus() {
 198          try {
 199              set_config('enablequarantine', true, 'antivirus');
 200              \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
 201          } catch (\core\antivirus\scanner_exception $ex) {
 202              $exception = $ex;
 203          }
 204          $this->assertNotEmpty($exception);
 205          // Quarantined files.
 206          $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
 207          $this->assertEquals(1, count($quarantinedfiles));
 208          // Clean up.
 209          \core\antivirus\quarantine::clean_up_quarantine_folder(time());
 210          $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
 211          $this->assertEquals(0, count($quarantinedfiles));
 212      }
 213  
 214      public function test_manager_none_quarantine_file_virus() {
 215          try {
 216              \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
 217          } catch (\core\antivirus\scanner_exception $ex) {
 218              $exception = $ex;
 219          }
 220          $this->assertNotEmpty($exception);
 221          $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
 222          $this->assertEquals(0, count($quarantinedfiles));
 223      }
 224  
 225      public function test_manager_scan_data_no_virus() {
 226          // Run mock scanning.
 227          $this->assertEmpty(\core\antivirus\manager::scan_data('OK'));
 228      }
 229  
 230      public function test_manager_scan_data_error() {
 231          // Run mock scanning.
 232          $this->assertEmpty(\core\antivirus\manager::scan_data('ERROR'));
 233      }
 234  
 235      public function test_manager_scan_data_virus() {
 236          // Run mock scanning.
 237          $this->expectException(\core\antivirus\scanner_exception::class);
 238          $this->assertEmpty(\core\antivirus\manager::scan_data('FOUND'));
 239      }
 240  
 241      public function test_manager_send_message_to_user_email_scan_data_virus() {
 242          $sink = $this->redirectEmails();
 243          set_config('notifyemail', 'fake@example.com', 'antivirus');
 244          $exception = null;
 245          try {
 246              \core\antivirus\manager::scan_data('FOUND');
 247          } catch (\core\antivirus\scanner_exception $ex) {
 248              $exception = $ex;
 249          }
 250          $this->assertNotEmpty($exception);
 251          $result = $sink->get_messages();
 252          $this->assertCount(1, $result);
 253          $this->assertStringContainsString('fake@example.com', $result[0]->to);
 254          $sink->close();
 255      }
 256  
 257      public function test_manager_send_message_to_admin_email_scan_data_virus() {
 258          $sink = $this->redirectMessages();
 259          $exception = null;
 260          try {
 261              \core\antivirus\manager::scan_data('FOUND');
 262          } catch (\core\antivirus\scanner_exception $ex) {
 263              $exception = $ex;
 264          }
 265          $this->assertNotEmpty($exception);
 266          $result = $sink->get_messages();
 267          $admins = array_keys(get_admins());
 268          $this->assertCount(1, $admins);
 269          $this->assertCount(1, $result);
 270          $this->assertEquals($result[0]->useridto, reset($admins));
 271          $sink->close();
 272      }
 273  
 274      public function test_manager_quarantine_data_virus() {
 275          set_config('enablequarantine', true, 'antivirus');
 276          $exception = null;
 277          try {
 278              \core\antivirus\manager::scan_data('FOUND');
 279          } catch (\core\antivirus\scanner_exception $ex) {
 280              $exception = $ex;
 281          }
 282          $this->assertNotEmpty($exception);
 283          // Quarantined files.
 284          $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
 285          $this->assertEquals(1, count($quarantinedfiles));
 286          // Clean up.
 287          \core\antivirus\quarantine::clean_up_quarantine_folder(time());
 288          $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
 289          $this->assertEquals(0, count($quarantinedfiles));
 290      }
 291  
 292  
 293      public function test_manager_none_quarantine_data_virus() {
 294          $exception = null;
 295          try {
 296              \core\antivirus\manager::scan_data('FOUND');
 297          } catch (\core\antivirus\scanner_exception $ex) {
 298              $exception = $ex;
 299          }
 300          $this->assertNotEmpty($exception);
 301          // No Quarantined files.
 302          $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
 303          $this->assertEquals(0, count($quarantinedfiles));
 304      }
 305  }