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/>.
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');
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
{
40 * Perform setup tasks generic to each test.
42 * * configuring the messageinbound_mailbox.
44 public function setUp(): void
{
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';
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\\') {
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);
79 * Test that the enabled check perform as expected.
81 public function test_is_enabled() {
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.
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
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.
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.
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);
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() {
216 // Generate a handler to use for this set of tests.
217 $handler = $this->helper_create_handler('handler_one');
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() {
244 $user = $this->getDataGenerator()->create_user();
245 $handler = $this->helper_create_handler('handler_one');
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() {
275 $user = $this->getDataGenerator()->create_user();
276 $handler = $this->helper_create_handler('handler_one');
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() {
304 $user = $this->getDataGenerator()->create_user();
305 $handler = $this->helper_create_handler('handler_one');
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() {
333 $user = $this->getDataGenerator()->create_user();
334 $handler = $this->helper_create_handler('handler_one');
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() {
357 $user = $this->getDataGenerator()->create_user();
358 $handler = $this->helper_create_handler('handler_one');
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() {
385 $user = $this->getDataGenerator()->create_user();
386 $handler = $this->helper_create_handler('handler_one');
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() {
415 $user = $this->getDataGenerator()->create_user();
416 $handler = $this->helper_create_handler('handler_one');
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() {
448 $user = $this->getDataGenerator()->create_user();
449 $handler = $this->helper_create_handler('handler_one');
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() {
481 $user = $this->getDataGenerator()->create_user();
482 $handler = $this->helper_create_handler('handler_one');
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() {
505 $user = $this->getDataGenerator()->create_user();
506 $handler = $this->helper_create_handler('handler_one');
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() {
529 // Set the default expiry of the handler to 0 - no expiration.
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.
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() {
560 // Set the default expiry of the handler to 60 seconds.
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.
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() {
591 // Set the default expiry of the handler to 3600 seconds.
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.
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
631 public function validate($address) {
632 return parent
::validate($address);
636 * The get_data function.
640 public function get_data() {
641 return parent
::get_data();
645 * The address processor function.
647 * @param string $address
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);