weekly release 5.0dev
[moodle.git] / lib / tests / antivirus_test.php
blob325e0e8d8e46147d2b63d4a6e45288cc7be7913a
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 parent::setUp();
38 // Use our special testable fixture plugin.
39 $CFG->antiviruses = 'testable';
41 $this->resetAfterTest();
43 // Create tempfile.
44 $tempfolder = make_request_directory(false);
45 $this->tempfile = $tempfolder . '/' . rand();
46 touch($this->tempfile);
49 /**
50 * Enable logging.
52 * @return void
54 protected function enable_logging() {
55 $this->preventResetByRollback();
56 set_config('enabled_stores', 'logstore_standard', 'tool_log');
57 set_config('buffersize', 0, 'logstore_standard');
58 set_config('logguests', 1, 'logstore_standard');
61 /**
62 * Return check api status for the antivirus check.
64 * @return string Based on status of \core\check\result.
66 protected function get_check_api_antivirus_status_result() {
67 $av = new \core\check\environment\antivirus();
68 return $av->get_result()->get_status();
71 protected function tearDown(): void {
72 @unlink($this->tempfile);
73 parent::tearDown();
76 public function test_manager_get_antivirus(): void {
77 // We are using clamav plugin in the test,
78 // as the only plugin we know exists for sure.
79 $antivirusviaget = \core\antivirus\manager::get_antivirus('clamav');
80 $antivirusdirect = new \antivirus_clamav\scanner();
81 $this->assertEquals($antivirusdirect, $antivirusviaget);
84 public function test_manager_scan_file_no_virus(): void {
85 // Run mock scanning.
86 $this->assertFileExists($this->tempfile);
87 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
88 // File expected to remain in place.
89 $this->assertFileExists($this->tempfile);
92 public function test_manager_scan_file_error(): void {
93 // Run mock scanning.
94 $this->assertFileExists($this->tempfile);
95 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'ERROR', true));
96 // File expected to remain in place.
97 $this->assertFileExists($this->tempfile);
100 // Check API for NA status i.e. when no scanners are enabled.
101 public function test_antivirus_check_na(): void {
102 global $CFG;
103 $CFG->antiviruses = '';
104 // Enable logs.
105 $this->enable_logging();
106 set_config('enabled_stores', 'logstore_standard', 'tool_log');
107 // Run mock scanning.
108 $this->assertFileExists($this->tempfile);
109 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
110 $this->assertEquals(\core\check\result::NA, $this->get_check_api_antivirus_status_result());
111 // File expected to remain in place.
112 $this->assertFileExists($this->tempfile);
115 // Check API for UNKNOWN status i.e. when the system's logstore reader is not '\core\log\sql_internal_table_reader'.
116 public function test_antivirus_check_unknown(): void {
117 // Run mock scanning.
118 $this->assertFileExists($this->tempfile);
119 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
120 $this->assertEquals(\core\check\result::UNKNOWN, $this->get_check_api_antivirus_status_result());
121 // File expected to remain in place.
122 $this->assertFileExists($this->tempfile);
125 // Check API for OK status i.e. antivirus enabled, logstore is ok, no scanner issues occurred recently.
126 public function test_antivirus_check_ok(): void {
127 // Enable logs.
128 $this->enable_logging();
129 // Run mock scanning.
130 $this->assertFileExists($this->tempfile);
131 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'OK', true));
132 $this->assertEquals(\core\check\result::OK, $this->get_check_api_antivirus_status_result());
133 // File expected to remain in place.
134 $this->assertFileExists($this->tempfile);
137 // Check API for ERROR status i.e. scanner issue within a certain timeframe/threshold.
138 public function test_antivirus_check_error(): void {
139 global $USER, $DB;
140 // Enable logs.
141 $this->enable_logging();
142 // Set threshold / lookback.
143 // Run mock scanning.
144 $this->assertFileExists($this->tempfile);
145 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'ERROR', true));
146 $this->assertEquals(\core\check\result::ERROR, $this->get_check_api_antivirus_status_result());
147 // File expected to remain in place.
148 $this->assertFileExists($this->tempfile);
151 public function test_manager_scan_file_virus(): void {
152 // Run mock scanning without deleting infected file.
153 $this->assertFileExists($this->tempfile);
154 $this->expectException(\core\antivirus\scanner_exception::class);
155 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'FOUND', false));
156 // File expected to remain in place.
157 $this->assertFileExists($this->tempfile);
159 // Run mock scanning with deleting infected file.
160 $this->expectException(\core\antivirus\scanner_exception::class);
161 $this->assertEmpty(\core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true));
162 // File expected to be deleted.
163 $this->assertFileDoesNotExist($this->tempfile);
166 public function test_manager_send_message_to_user_email_scan_file_virus(): void {
167 $sink = $this->redirectEmails();
168 $exception = null;
169 try {
170 set_config('notifyemail', 'fake@example.com', 'antivirus');
171 \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
172 } catch (\core\antivirus\scanner_exception $ex) {
173 $exception = $ex;
175 $this->assertNotEmpty($exception);
176 $result = $sink->get_messages();
177 $this->assertCount(1, $result);
178 $this->assertStringContainsString('fake@example.com', $result[0]->to);
179 $sink->close();
182 public function test_manager_send_message_to_admin_email_scan_file_virus(): void {
183 $sink = $this->redirectMessages();
184 $exception = null;
185 try {
186 \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
187 } catch (\core\antivirus\scanner_exception $ex) {
188 $exception = $ex;
190 $this->assertNotEmpty($exception);
191 $result = $sink->get_messages();
192 $admins = array_keys(get_admins());
193 $this->assertCount(1, $admins);
194 $this->assertCount(1, $result);
195 $this->assertEquals($result[0]->useridto, reset($admins));
196 $sink->close();
199 public function test_manager_quarantine_file_virus(): void {
200 try {
201 set_config('enablequarantine', true, 'antivirus');
202 \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
203 } catch (\core\antivirus\scanner_exception $ex) {
204 $exception = $ex;
206 $this->assertNotEmpty($exception);
207 // Quarantined files.
208 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
209 $this->assertEquals(1, count($quarantinedfiles));
210 // Clean up.
211 \core\antivirus\quarantine::clean_up_quarantine_folder(time());
212 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
213 $this->assertEquals(0, count($quarantinedfiles));
216 public function test_manager_none_quarantine_file_virus(): void {
217 try {
218 \core\antivirus\manager::scan_file($this->tempfile, 'FOUND', true);
219 } catch (\core\antivirus\scanner_exception $ex) {
220 $exception = $ex;
222 $this->assertNotEmpty($exception);
223 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
224 $this->assertEquals(0, count($quarantinedfiles));
227 public function test_manager_scan_data_no_virus(): void {
228 // Run mock scanning.
229 $this->assertEmpty(\core\antivirus\manager::scan_data('OK'));
232 public function test_manager_scan_data_error(): void {
233 // Run mock scanning.
234 $this->assertEmpty(\core\antivirus\manager::scan_data('ERROR'));
237 public function test_manager_scan_data_virus(): void {
238 // Run mock scanning.
239 $this->expectException(\core\antivirus\scanner_exception::class);
240 $this->assertEmpty(\core\antivirus\manager::scan_data('FOUND'));
243 public function test_manager_send_message_to_user_email_scan_data_virus(): void {
244 $sink = $this->redirectEmails();
245 set_config('notifyemail', 'fake@example.com', 'antivirus');
246 $exception = null;
247 try {
248 \core\antivirus\manager::scan_data('FOUND');
249 } catch (\core\antivirus\scanner_exception $ex) {
250 $exception = $ex;
252 $this->assertNotEmpty($exception);
253 $result = $sink->get_messages();
254 $this->assertCount(1, $result);
255 $this->assertStringContainsString('fake@example.com', $result[0]->to);
256 $sink->close();
259 public function test_manager_send_message_to_admin_email_scan_data_virus(): void {
260 $sink = $this->redirectMessages();
261 $exception = null;
262 try {
263 \core\antivirus\manager::scan_data('FOUND');
264 } catch (\core\antivirus\scanner_exception $ex) {
265 $exception = $ex;
267 $this->assertNotEmpty($exception);
268 $result = $sink->get_messages();
269 $admins = array_keys(get_admins());
270 $this->assertCount(1, $admins);
271 $this->assertCount(1, $result);
272 $this->assertEquals($result[0]->useridto, reset($admins));
273 $sink->close();
276 public function test_manager_quarantine_data_virus(): void {
277 set_config('enablequarantine', true, 'antivirus');
278 $exception = null;
279 try {
280 \core\antivirus\manager::scan_data('FOUND');
281 } catch (\core\antivirus\scanner_exception $ex) {
282 $exception = $ex;
284 $this->assertNotEmpty($exception);
285 // Quarantined files.
286 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
287 $this->assertEquals(1, count($quarantinedfiles));
288 // Clean up.
289 \core\antivirus\quarantine::clean_up_quarantine_folder(time());
290 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
291 $this->assertEquals(0, count($quarantinedfiles));
295 public function test_manager_none_quarantine_data_virus(): void {
296 $exception = null;
297 try {
298 \core\antivirus\manager::scan_data('FOUND');
299 } catch (\core\antivirus\scanner_exception $ex) {
300 $exception = $ex;
302 $this->assertNotEmpty($exception);
303 // No Quarantined files.
304 $quarantinedfiles = \core\antivirus\quarantine::get_quarantined_files();
305 $this->assertEquals(0, count($quarantinedfiles));