2 // This file is part of Moodle - http://moodle.org/
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.
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');
21 * Tests for antivirus manager.
23 * @package core_antivirus
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
{
31 * @var string Path to the tempfile created for use with AV scanner tests
35 protected function setUp(): void
{
38 // Use our special testable fixture plugin.
39 $CFG->antiviruses
= 'testable';
41 $this->resetAfterTest();
44 $tempfolder = make_request_directory(false);
45 $this->tempfile
= $tempfolder . '/' . rand();
46 touch($this->tempfile
);
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');
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
);
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
{
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
{
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
{
103 $CFG->antiviruses
= '';
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
{
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
{
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();
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) {
175 $this->assertNotEmpty($exception);
176 $result = $sink->get_messages();
177 $this->assertCount(1, $result);
178 $this->assertStringContainsString('fake@example.com', $result[0]->to
);
182 public function test_manager_send_message_to_admin_email_scan_file_virus(): void
{
183 $sink = $this->redirectMessages();
186 \core\antivirus\manager
::scan_file($this->tempfile
, 'FOUND', true);
187 } catch (\core\antivirus\scanner_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));
199 public function test_manager_quarantine_file_virus(): void
{
201 set_config('enablequarantine', true, 'antivirus');
202 \core\antivirus\manager
::scan_file($this->tempfile
, 'FOUND', true);
203 } catch (\core\antivirus\scanner_exception
$ex) {
206 $this->assertNotEmpty($exception);
207 // Quarantined files.
208 $quarantinedfiles = \core\antivirus\quarantine
::get_quarantined_files();
209 $this->assertEquals(1, count($quarantinedfiles));
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
{
218 \core\antivirus\manager
::scan_file($this->tempfile
, 'FOUND', true);
219 } catch (\core\antivirus\scanner_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');
248 \core\antivirus\manager
::scan_data('FOUND');
249 } catch (\core\antivirus\scanner_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
);
259 public function test_manager_send_message_to_admin_email_scan_data_virus(): void
{
260 $sink = $this->redirectMessages();
263 \core\antivirus\manager
::scan_data('FOUND');
264 } catch (\core\antivirus\scanner_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));
276 public function test_manager_quarantine_data_virus(): void
{
277 set_config('enablequarantine', true, 'antivirus');
280 \core\antivirus\manager
::scan_data('FOUND');
281 } catch (\core\antivirus\scanner_exception
$ex) {
284 $this->assertNotEmpty($exception);
285 // Quarantined files.
286 $quarantinedfiles = \core\antivirus\quarantine
::get_quarantined_files();
287 $this->assertEquals(1, count($quarantinedfiles));
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
{
298 \core\antivirus\manager
::scan_data('FOUND');
299 } catch (\core\antivirus\scanner_exception
$ex) {
302 $this->assertNotEmpty($exception);
303 // No Quarantined files.
304 $quarantinedfiles = \core\antivirus\quarantine
::get_quarantined_files();
305 $this->assertEquals(0, count($quarantinedfiles));