MDL-81616 upgrade: add the 4.4.0 separation line to all upgrade scripts
[moodle.git] / lib / tests / antivirus_test.php
blob9367fd1f2d4dee8a7b6ed2a0d380ef2386cd501f
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.
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/>.
17 defined('MOODLE_INTERNAL') || die();
18 require_once(__DIR__ . '/fixtures/testable_antivirus.php');
20 /**
21 * Tests for antivirus manager.
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
28 class antivirus_test extends advanced_testcase {
30 /**
31 * @var string Path to the tempfile created for use with AV scanner tests
33 protected $tempfile;
35 protected function setUp(): void {
36 global $CFG;
37 // Use our special testable fixture plugin.
38 $CFG->antiviruses = 'testable';
40 $this->resetAfterTest();
42 // Create tempfile.
43 $tempfolder = make_request_directory(false);
44 $this->tempfile = $tempfolder . '/' . rand();
45 touch($this->tempfile);
48 /**
49 * Enable logging.
51 * @return void
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');
60 /**
61 * Return check api status for the antivirus check.
63 * @return string Based on status of \core\check\result.
65 protected function get_check_api_antivirus_status_result() {
66 $av = new \core\check\environment\antivirus();
67 return $av->get_result()->get_status();
70 protected function tearDown(): void {
71 @unlink($this->tempfile);
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);
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);
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);
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);
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);
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);
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);
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);
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);
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;
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();
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;
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();
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;
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));
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;
220 $this->assertNotEmpty($exception);
221 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
222 $this->assertEquals(0, count($quarantinedfiles));
225 public function test_manager_scan_data_no_virus() {
226 // Run mock scanning.
227 $this->assertEmpty(\core\antivirus\manager::scan_data('OK'));
230 public function test_manager_scan_data_error() {
231 // Run mock scanning.
232 $this->assertEmpty(\core\antivirus\manager::scan_data('ERROR'));
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'));
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;
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();
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;
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();
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;
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));
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;
300 $this->assertNotEmpty($exception);
301 // No Quarantined files.
302 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
303 $this->assertEquals(0, count($quarantinedfiles));