Merge branch 'MDL-81456-main' of https://github.com/andrewnicols/moodle
[moodle.git] / message / tests / inbound_test.php
blob1fe6ed972e3219ac6df82bda0fc9cf0ee9e2f9b7
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 /**
18 * Tests for core_message_inbound to test Variable Envelope Return Path functionality.
20 * @package core_message
21 * @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 namespace core_message;
27 defined('MOODLE_INTERNAL') || die();
28 require_once(__DIR__ . '/fixtures/inbound_fixtures.php');
30 /**
31 * Tests for core_message_inbound to test Variable Envelope Return Path functionality.
33 * @package core_message
34 * @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class inbound_test extends \advanced_testcase {
39 /**
40 * Perform setup tasks generic to each test.
41 * This includes:
42 * * configuring the messageinbound_mailbox.
44 public function setUp(): void {
45 global $CFG;
47 $this->resetAfterTest(true);
49 // Setup the default Inbound Message mailbox settings.
50 $CFG->messageinbound_domain = 'example.com';
51 $CFG->messageinbound_enabled = true;
53 // Must be no longer than 15 characters.
54 $CFG->messageinbound_mailbox = 'moodlemoodle123';
57 /**
58 * Helper to create a new Inbound Message handler.
60 * @param $handlerclass The class of the handler to create
61 * @param $enabled Whether the handler should be enabled
62 * @param $component The component
63 * @param $namepace The namepace
65 public function helper_create_handler($handlerclass, $enabled = true, $component = 'core_test', $namespace = '\\core\\test\\') {
66 global $DB;
68 $classname = $namespace . $handlerclass;
69 $record = \core\message\inbound\manager::record_from_handler(new $classname());
70 $record->component = $component;
71 $record->enabled = $enabled;
72 $record->id = $DB->insert_record('messageinbound_handlers', $record);
73 $handler = core_message_inbound_test_manager::handler_from_record($record);
75 return $handler;
78 /**
79 * Test that the enabled check perform as expected.
81 public function test_is_enabled() {
82 global $CFG;
84 // First clear all of the settings set in the setUp.
85 $CFG->messageinbound_domain = null;
86 $CFG->messageinbound_enabled = null;
87 $CFG->messageinbound_mailbox = null;
89 $this->assertFalse(\core\message\inbound\manager::is_enabled());
91 // Check whether only setting the enabled flag keeps it disabled.
92 $CFG->messageinbound_enabled = true;
93 $this->assertFalse(\core\message\inbound\manager::is_enabled());
95 // Check that the mailbox entry on it's own does not enable Inbound Message handling.
96 $CFG->messageinbound_mailbox = 'moodlemoodle123';
97 $CFG->messageinbound_domain = null;
98 $this->assertFalse(\core\message\inbound\manager::is_enabled());
100 // And that the domain on it's own does not.
101 $CFG->messageinbound_domain = 'example.com';
102 $CFG->messageinbound_mailbox = null;
103 $this->assertFalse(\core\message\inbound\manager::is_enabled());
105 // And that an invalid mailbox does not.
106 $CFG->messageinbound_mailbox = '';
107 $CFG->messageinbound_domain = 'example.com';
108 $this->assertFalse(\core\message\inbound\manager::is_enabled());
110 // And that an invalid domain does not.
111 $CFG->messageinbound_domain = '';
112 $CFG->messageinbound_mailbox = 'moodlemoodle123';
113 $this->assertFalse(\core\message\inbound\manager::is_enabled());
115 // Finally a test that ensures that all settings correct enables the system.
116 $CFG->messageinbound_mailbox = 'moodlemoodle123';
117 $CFG->messageinbound_domain = 'example.com';
118 $CFG->messageinbound_enabled = true;
120 $this->assertTrue(\core\message\inbound\manager::is_enabled());
124 * Test that data items conform to RFCs 5231, and 5322 standards for
125 * addressing, and to RFC 5233 for sub-addressing.
127 public function test_address_constraints() {
128 $handler = $this->helper_create_handler('handler_one');
130 // Using the handler created, generate an address for our data entry.
131 $processor = new core_message_inbound_test_helper();
132 $processor->set_handler($handler->classname);
134 // Generate some IDs for the data and generate addresses for them.
135 $dataids = array(
139 1073741823,
140 2147483647,
143 $user = $this->getDataGenerator()->create_user();
144 foreach ($dataids as $dataid) {
145 $processor->set_data($dataid);
146 $address = $processor->generate($user->id);
147 $this->assertNotNull($address);
148 $this->assertTrue(strlen($address) > 0, 'No address generated.');
149 $this->assertTrue(strpos($address, '@') !== false, 'No domain found.');
150 $this->assertTrue(strpos($address, '+') !== false, 'No subaddress found.');
152 // The localpart must be less than 64 characters.
153 list($localpart) = explode('@', $address);
154 $this->assertTrue(strlen($localpart) <= 64, 'Localpart section of address too long');
156 // And the data section should be no more than 48 characters.
157 list(, $datasection) = explode('+', $localpart);
158 $this->assertTrue(strlen($datasection) <= 48, 'Data section of address too long');
163 * Test that the generated e-mail addresses are sufficiently random by
164 * testing the multiple handlers, multiple users, and multiple data
165 * items.
167 public function test_address_uniqueness() {
168 // Generate a set of handlers. These are in two components, and each
169 // component has two different generators.
170 $handlers = array();
171 $handlers[] = $this->helper_create_handler('handler_one', true, 'core_test');
172 $handlers[] = $this->helper_create_handler('handler_two', true, 'core_test');
173 $handlers[] = $this->helper_create_handler('handler_three', true, 'core_test_example');
174 $handlers[] = $this->helper_create_handler('handler_four', true, 'core_test_example');
176 // Generate some IDs for the data and generate addresses for them.
177 $dataids = array(
180 1073741823,
181 2147483647,
184 $users = array();
185 for ($i = 0; $i < 5; $i++) {
186 $users[] = $this->getDataGenerator()->create_user();
189 // Store the addresses for later comparison.
190 $addresses = array();
192 foreach ($handlers as $handler) {
193 $processor = new core_message_inbound_test_helper();
194 $processor->set_handler($handler->classname);
196 // Check each dataid.
197 foreach ($dataids as $dataid) {
198 $processor->set_data($dataid);
200 // Check each user.
201 foreach ($users as $user) {
202 $address = $processor->generate($user->id);
203 $this->assertFalse(isset($addresses[$address]));
204 $addresses[$address] = true;
211 * Test address parsing of a generated address.
213 public function test_address_parsing() {
214 $dataid = 42;
216 // Generate a handler to use for this set of tests.
217 $handler = $this->helper_create_handler('handler_one');
219 // And a user.
220 $user = $this->getDataGenerator()->create_user();
222 // Using the handler created, generate an address for our data entry.
223 $processor = new core_message_inbound_test_helper();
224 $processor->set_handler($handler->classname);
225 $processor->set_data($dataid);
226 $address = $processor->generate($user->id);
228 // We should be able to parse the address.
229 $parser = new core_message_inbound_test_helper();
230 $parser->process($address);
231 $parsedresult = $parser->get_data();
232 $this->assertEquals($user->id, $parsedresult->userid);
233 $this->assertEquals($dataid, $parsedresult->datavalue);
234 $this->assertEquals($dataid, $parsedresult->data->datavalue);
235 $this->assertEquals($handler->id, $parsedresult->handlerid);
236 $this->assertEquals($handler->id, $parsedresult->data->handler);
240 * Test address parsing of an address with an unrecognised format.
242 public function test_address_validation_invalid_format_failure() {
243 // Create test data.
244 $user = $this->getDataGenerator()->create_user();
245 $handler = $this->helper_create_handler('handler_one');
246 $dataid = 42;
248 $parser = new core_message_inbound_test_helper();
250 $generator = new core_message_inbound_test_helper();
251 $generator->set_handler($handler->classname);
253 // Check that validation fails when no address has been processed.
254 $result = $parser->validate($user->email);
255 $this->assertEquals(\core\message\inbound\address_manager::VALIDATION_INVALID_ADDRESS_FORMAT, $result);
257 // Test that an address without data fails validation.
258 $parser->process('bob@example.com');
259 $result = $parser->validate($user->email);
260 $this->assertEquals(\core\message\inbound\address_manager::VALIDATION_INVALID_ADDRESS_FORMAT, $result);
262 // Test than address with a subaddress but invalid data fails with VALIDATION_UNKNOWN_DATAKEY.
263 $parser->process('bob+nodata@example.com');
264 $result = $parser->validate($user->email);
265 $this->assertEquals(\core\message\inbound\address_manager::VALIDATION_INVALID_ADDRESS_FORMAT, $result);
269 * Test address parsing of an address with an unknown handler.
271 public function test_address_validation_unknown_handler() {
272 global $DB;
274 // Create test data.
275 $user = $this->getDataGenerator()->create_user();
276 $handler = $this->helper_create_handler('handler_one');
277 $dataid = 42;
279 $parser = new core_message_inbound_test_helper();
281 $generator = new core_message_inbound_test_helper();
282 $generator->set_handler($handler->classname);
283 $generator->set_data($dataid);
284 $address = $generator->generate($user->id);
286 // Remove the handler record to invalidate it.
287 $DB->delete_records('messageinbound_handlers', array(
288 'id' => $handler->id,
291 $parser->process($address);
292 $result = $parser->validate($user->email);
293 $expectedfail = \core\message\inbound\address_manager::VALIDATION_UNKNOWN_HANDLER;
294 $this->assertEquals($expectedfail, $result & $expectedfail);
298 * Test address parsing of an address with a disabled handler.
300 public function test_address_validation_disabled_handler() {
301 global $DB;
303 // Create test data.
304 $user = $this->getDataGenerator()->create_user();
305 $handler = $this->helper_create_handler('handler_one');
306 $dataid = 42;
308 $parser = new core_message_inbound_test_helper();
310 $generator = new core_message_inbound_test_helper();
311 $generator->set_handler($handler->classname);
312 $generator->set_data($dataid);
313 $address = $generator->generate($user->id);
315 // Disable the handler.
316 $record = \core\message\inbound\manager::record_from_handler($handler);
317 $record->enabled = false;
318 $DB->update_record('messageinbound_handlers', $record);
320 $parser->process($address);
321 $result = $parser->validate($user->email);
322 $expectedfail = \core\message\inbound\address_manager::VALIDATION_DISABLED_HANDLER;
323 $this->assertEquals($expectedfail, $result & $expectedfail);
327 * Test address parsing of an address for an invalid user.
329 public function test_address_validation_invalid_user() {
330 global $DB;
332 // Create test data.
333 $user = $this->getDataGenerator()->create_user();
334 $handler = $this->helper_create_handler('handler_one');
335 $dataid = 42;
337 $parser = new core_message_inbound_test_helper();
339 $generator = new core_message_inbound_test_helper();
340 $generator->set_handler($handler->classname);
341 $generator->set_data($dataid);
342 $address = $generator->generate(-1);
344 $parser->process($address);
345 $result = $parser->validate($user->email);
346 $expectedfail = \core\message\inbound\address_manager::VALIDATION_UNKNOWN_USER;
347 $this->assertEquals($expectedfail, $result & $expectedfail);
351 * Test address parsing of an address for a disabled user.
353 public function test_address_validation_disabled_user() {
354 global $DB;
356 // Create test data.
357 $user = $this->getDataGenerator()->create_user();
358 $handler = $this->helper_create_handler('handler_one');
359 $dataid = 42;
361 $parser = new core_message_inbound_test_helper();
363 $generator = new core_message_inbound_test_helper();
364 $generator->set_handler($handler->classname);
365 $generator->set_data($dataid);
366 $address = $generator->generate($user->id);
368 // Unconfirm the user.
369 $user->confirmed = 0;
370 $DB->update_record('user', $user);
372 $parser->process($address);
373 $result = $parser->validate($user->email);
374 $expectedfail = \core\message\inbound\address_manager::VALIDATION_DISABLED_USER;
375 $this->assertEquals($expectedfail, $result & $expectedfail);
379 * Test address parsing of an address for an invalid key.
381 public function test_address_validation_invalid_key() {
382 global $DB;
384 // Create test data.
385 $user = $this->getDataGenerator()->create_user();
386 $handler = $this->helper_create_handler('handler_one');
387 $dataid = 42;
389 $parser = new core_message_inbound_test_helper();
391 $generator = new core_message_inbound_test_helper();
392 $generator->set_handler($handler->classname);
393 $generator->set_data($dataid);
394 $address = $generator->generate($user->id);
396 // Remove the data record to invalidate it.
397 $DB->delete_records('messageinbound_datakeys', array(
398 'handler' => $handler->id,
399 'datavalue' => $dataid,
402 $parser->process($address);
403 $result = $parser->validate($user->email);
404 $expectedfail = \core\message\inbound\address_manager::VALIDATION_UNKNOWN_DATAKEY;
405 $this->assertEquals($expectedfail, $result & $expectedfail);
409 * Test address parsing of an address for an expired key.
411 public function test_address_validation_expired_key() {
412 global $DB;
414 // Create test data.
415 $user = $this->getDataGenerator()->create_user();
416 $handler = $this->helper_create_handler('handler_one');
417 $dataid = 42;
419 $parser = new core_message_inbound_test_helper();
421 $generator = new core_message_inbound_test_helper();
422 $generator->set_handler($handler->classname);
423 $generator->set_data($dataid);
424 $address = $generator->generate($user->id);
426 // Expire the key by setting it's expiry time in the past.
427 $key = $DB->get_record('messageinbound_datakeys', array(
428 'handler' => $handler->id,
429 'datavalue' => $dataid,
432 $key->expires = time() - 3600;
433 $DB->update_record('messageinbound_datakeys', $key);
435 $parser->process($address);
436 $result = $parser->validate($user->email);
437 $expectedfail = \core\message\inbound\address_manager::VALIDATION_EXPIRED_DATAKEY;
438 $this->assertEquals($expectedfail, $result & $expectedfail);
442 * Test address parsing of an address for an invalid hash.
444 public function test_address_validation_invalid_hash() {
445 global $DB;
447 // Create test data.
448 $user = $this->getDataGenerator()->create_user();
449 $handler = $this->helper_create_handler('handler_one');
450 $dataid = 42;
452 $parser = new core_message_inbound_test_helper();
454 $generator = new core_message_inbound_test_helper();
455 $generator->set_handler($handler->classname);
456 $generator->set_data($dataid);
457 $address = $generator->generate($user->id);
459 // Expire the key by setting it's expiry time in the past.
460 $key = $DB->get_record('messageinbound_datakeys', array(
461 'handler' => $handler->id,
462 'datavalue' => $dataid,
465 $key->datakey = 'invalid value';
466 $DB->update_record('messageinbound_datakeys', $key);
468 $parser->process($address);
469 $result = $parser->validate($user->email);
470 $expectedfail = \core\message\inbound\address_manager::VALIDATION_INVALID_HASH;
471 $this->assertEquals($expectedfail, $result & $expectedfail);
475 * Test address parsing of an address for an invalid sender.
477 public function test_address_validation_invalid_sender() {
478 global $DB;
480 // Create test data.
481 $user = $this->getDataGenerator()->create_user();
482 $handler = $this->helper_create_handler('handler_one');
483 $dataid = 42;
485 $parser = new core_message_inbound_test_helper();
487 $generator = new core_message_inbound_test_helper();
488 $generator->set_handler($handler->classname);
489 $generator->set_data($dataid);
490 $address = $generator->generate($user->id);
492 $parser->process($address);
493 $result = $parser->validate('incorrectuser@example.com');
494 $expectedfail = \core\message\inbound\address_manager::VALIDATION_ADDRESS_MISMATCH;
495 $this->assertEquals($expectedfail, $result & $expectedfail);
499 * Test address parsing of an address for an address which is correct.
501 public function test_address_validation_success() {
502 global $DB;
504 // Create test data.
505 $user = $this->getDataGenerator()->create_user();
506 $handler = $this->helper_create_handler('handler_one');
507 $dataid = 42;
509 $parser = new core_message_inbound_test_helper();
511 $generator = new core_message_inbound_test_helper();
512 $generator->set_handler($handler->classname);
513 $generator->set_data($dataid);
514 $address = $generator->generate($user->id);
516 $parser->process($address);
517 $result = $parser->validate($user->email);
518 $this->assertEquals(\core\message\inbound\address_manager::VALIDATION_SUCCESS, $result);
523 * Test that a handler with no default expiration does not have an
524 * expiration time applied.
526 public function test_default_hander_expiry_unlimited() {
527 global $DB;
529 // Set the default expiry of the handler to 0 - no expiration.
530 $expiration = 0;
532 // Create test data.
533 $user = $this->getDataGenerator()->create_user();
534 $handler = $this->helper_create_handler('handler_one');
536 $record = \core\message\inbound\manager::record_from_handler($handler);
537 $record->defaultexpiration = $expiration;
538 $DB->update_record('messageinbound_handlers', $record);
540 // Generate an address for the handler.
541 $dataid = 42;
543 $generator = new core_message_inbound_test_helper();
544 $generator->set_handler($handler->classname);
545 $generator->set_data($dataid);
546 $address = $generator->generate($user->id);
548 // Check that the datakey created matches the expirytime.
549 $key = $DB->get_record('messageinbound_datakeys', array('handler' => $record->id, 'datavalue' => $dataid));
551 $this->assertNull($key->expires);
555 * Test application of the default expiry on a handler.
557 public function test_default_hander_expiry_low() {
558 global $DB;
560 // Set the default expiry of the handler to 60 seconds.
561 $expiration = 60;
563 // Create test data.
564 $user = $this->getDataGenerator()->create_user();
565 $handler = $this->helper_create_handler('handler_one');
567 $record = \core\message\inbound\manager::record_from_handler($handler);
568 $record->defaultexpiration = $expiration;
569 $DB->update_record('messageinbound_handlers', $record);
571 // Generate an address for the handler.
572 $dataid = 42;
574 $generator = new core_message_inbound_test_helper();
575 $generator->set_handler($handler->classname);
576 $generator->set_data($dataid);
577 $address = $generator->generate($user->id);
579 // Check that the datakey created matches the expirytime.
580 $key = $DB->get_record('messageinbound_datakeys', array('handler' => $record->id, 'datavalue' => $dataid));
582 $this->assertEquals($key->timecreated + $expiration, $key->expires);
586 * Test application of the default expiry on a handler.
588 public function test_default_hander_expiry_medium() {
589 global $DB;
591 // Set the default expiry of the handler to 3600 seconds.
592 $expiration = 3600;
594 // Create test data.
595 $user = $this->getDataGenerator()->create_user();
596 $handler = $this->helper_create_handler('handler_one');
598 $record = \core\message\inbound\manager::record_from_handler($handler);
599 $record->defaultexpiration = $expiration;
600 $DB->update_record('messageinbound_handlers', $record);
602 // Generate an address for the handler.
603 $dataid = 42;
605 $generator = new core_message_inbound_test_helper();
606 $generator->set_handler($handler->classname);
607 $generator->set_data($dataid);
608 $address = $generator->generate($user->id);
610 // Check that the datakey created matches the expirytime.
611 $key = $DB->get_record('messageinbound_datakeys', array('handler' => $record->id, 'datavalue' => $dataid));
613 $this->assertEquals($key->timecreated + $expiration, $key->expires);
619 * A helper function for unit testing to expose protected functions in the core_message_inbound API for testing.
621 * @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
622 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
624 class core_message_inbound_test_helper extends \core\message\inbound\address_manager {
626 * The validate function.
628 * @param string $address
629 * @return int
631 public function validate($address) {
632 return parent::validate($address);
636 * The get_data function.
638 * @return stdClass
640 public function get_data() {
641 return parent::get_data();
645 * The address processor function.
647 * @param string $address
648 * @return void
650 public function process($address) {
651 return parent::process($address);
656 * A helper function for unit testing to expose protected functions in the core_message_inbound API for testing.
658 * @copyright 2014 Andrew Nicols <andrew@nicols.co.uk>
659 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
661 class core_message_inbound_test_manager extends \core\message\inbound\manager {
663 * Helper to fetch make the handler_from_record public for unit testing.
665 * @param $record The handler record to fetch
667 public static function handler_from_record($record) {
668 return parent::handler_from_record($record);