Implement IMAP email-to-ticket gateway
This commit is contained in:
1 parent
b7e5884457
commit
6f25869345
33 files changed
+5619
-5
No files matched your search
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
require __DIR__ . '/tracking.php';
|
||||
|
||||
$policy = new \Box\Mod\Mailgateway\InboundPolicy();
|
||||
$cases = [
|
||||
[['auto_submitted' => 'auto-replied'], 'auto_submitted'],
|
||||
[['auto_submitted' => 'Auto-Generated; owner-email="support@example.com"'], 'auto_submitted'],
|
||||
[['headers' => ['AUTO-SUBMITTED' => ['no', 'auto-replied']]], 'auto_submitted'],
|
||||
[['return_path' => '<>'], 'null_return_path'],
|
||||
[['content_type' => 'multipart/report; report-type=delivery-status; boundary=abc'], 'delivery_report'],
|
||||
[['content_type' => 'message/disposition-notification'], 'delivery_report'],
|
||||
[['precedence' => 'bulk'], 'bulk_mail'],
|
||||
[['precedence' => 'LIST'], 'bulk_mail'],
|
||||
[['list_id' => 'Announcements <list.example.com>'], 'mailing_list'],
|
||||
[['x_auto_response_suppress' => 'All'], 'response_suppressed'],
|
||||
[['x_auto_response_suppress' => 'DR, OOF, AutoReply'], 'response_suppressed'],
|
||||
[['x_autoreply' => 'yes'], 'legacy_autoresponder'],
|
||||
[['x_autorespond' => 'true'], 'legacy_autoresponder'],
|
||||
];
|
||||
$messageCount = (int) $connection->fetchOne('SELECT COUNT(*) FROM test_messages');
|
||||
$notifications = $trackingService->notifications;
|
||||
foreach ($cases as $index => [$fields, $reason]) {
|
||||
$input = array_merge([
|
||||
'mailbox' => 'support@dev.sebs.space',
|
||||
'message_id' => '<automatic-' . $index . '@example.com>',
|
||||
// Would normally reject this mismatched reply and send a notification.
|
||||
'subject' => 'Re: [Ticket #41] Test',
|
||||
'sender_email' => 'wrong@example.com',
|
||||
'content' => 'Away from the office',
|
||||
], $fields);
|
||||
$result = $tracker->process($input, $trackingService);
|
||||
if ($result !== ['action' => 'ignored', 'reason' => $reason, 'duplicate' => false]) {
|
||||
throw new RuntimeException('Automatic message was not ignored: ' . $index);
|
||||
}
|
||||
$repeat = $tracker->process($input, $trackingService);
|
||||
if (!$repeat['duplicate'] || $repeat['action'] !== 'ignored') {
|
||||
throw new RuntimeException('Ignored message retry was not deduplicated');
|
||||
}
|
||||
$row = $connection->fetchAssociative('SELECT ticket_id FROM mailgateway_message WHERE message_id = ?', [$input['message_id']]);
|
||||
if (!$row || $row['ticket_id'] !== null) {
|
||||
throw new RuntimeException('Ignored mail acquired a ticket association');
|
||||
}
|
||||
// The underlying service methods must also skip without database or mail access.
|
||||
$guardedService = new \Box\Mod\Mailgateway\Service();
|
||||
foreach (['import', 'reply'] as $method) {
|
||||
if ($guardedService->$method($fields) !== ['action' => 'ignored', 'reason' => $reason]) {
|
||||
throw new RuntimeException('Service method responded to automatic mail');
|
||||
}
|
||||
}
|
||||
}
|
||||
$minimal = $tracker->process([
|
||||
'mailbox' => 'support@dev.sebs.space', 'message_id' => '<minimal-auto@example.com>',
|
||||
'auto_submitted' => 'auto-replied',
|
||||
], $trackingService);
|
||||
if ($minimal['action'] !== 'ignored'
|
||||
|| (int) $connection->fetchOne('SELECT COUNT(*) FROM test_messages') !== $messageCount
|
||||
|| $trackingService->notifications !== $notifications) {
|
||||
throw new RuntimeException('Automatic mail created a message or sent a notification');
|
||||
}
|
||||
foreach ([[], ['auto_submitted' => 'NO'], ['auto_submitted' => 'no; extension=value'],
|
||||
['return_path' => '<customer@example.com>'], ['content_type' => 'text/plain; charset=utf-8'],
|
||||
['x_auto_response_suppress' => 'None'], ['x_autoreply' => 'false']] as $fields) {
|
||||
if ($policy->ignoreReason($policy->headers($fields)) !== null) {
|
||||
throw new RuntimeException('Normal message classified as automatic');
|
||||
}
|
||||
}
|
||||
$folded = $policy->headers(['headers' => ['Auto-Submitted' => "auto-replied;\r\n owner=test"]]);
|
||||
if ($policy->ignoreReason($folded) !== 'auto_submitted') {
|
||||
throw new RuntimeException('Folded header failed');
|
||||
}
|
||||
try {
|
||||
$policy->headers(['auto_submitted' => "no\r\nInjected: value"]);
|
||||
throw new RuntimeException('Header injection accepted');
|
||||
} catch (\FOSSBilling\InformationException) {}
|
||||
try {
|
||||
$tracker->process(array_merge($input, ['auto_submitted' => 'no']), $trackingService);
|
||||
throw new RuntimeException('Changed detection headers bypassed deduplication');
|
||||
} catch (\FOSSBilling\InformationException) {}
|
||||
echo "Passed automatic-message filtering, repeated/mixed/folded headers, ignored-message deduplication, no ticket writes or notifications, service method protection, and normal-mail exceptions.\n";
|
||||
Reference in New Issue
Block a user