1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-26 16:52:41 +01:00

Support Postmark inbound mail via webhook

Summary: Depends on D19016. Ref T13053. Adds a listener for the Postmark webhook.

Test Plan:
Processed some test mail locally, at least:

{F5416053}

Reviewers: amckinley

Maniphest Tasks: T13053

Differential Revision: https://secure.phabricator.com/D19017
This commit is contained in:
epriestley 2018-02-07 04:51:09 -08:00
parent 0986c7f673
commit 5792032dc9
7 changed files with 125 additions and 22 deletions

View file

@ -3272,6 +3272,7 @@ phutil_register_library_map(array(
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php',
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
'PhabricatorMetaMTAPostmarkReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAPostmarkReceiveController.php',
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
'PhabricatorMetaMTAReceivedMailProcessingException' => 'applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php',
'PhabricatorMetaMTAReceivedMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAReceivedMailTestCase.php',
@ -8787,6 +8788,7 @@ phutil_register_library_map(array(
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery',
'PhabricatorMetaMTAPermanentFailureException' => 'Exception',
'PhabricatorMetaMTAPostmarkReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
'PhabricatorMetaMTAReceivedMailProcessingException' => 'Exception',
'PhabricatorMetaMTAReceivedMailTestCase' => 'PhabricatorTestCase',

View file

@ -42,6 +42,7 @@ final class PhabricatorMetaMTAApplication extends PhabricatorApplication {
'detail/(?P<id>[1-9]\d*)/' => 'PhabricatorMetaMTAMailViewController',
'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController',
'mailgun/' => 'PhabricatorMetaMTAMailgunReceiveController',
'postmark/' => 'PhabricatorMetaMTAPostmarkReceiveController',
),
);
}

View file

@ -17,15 +17,12 @@ final class PhabricatorMetaMTAMailgunReceiveController
// inbound mail from any of them. Test the signature to see if it matches
// any configured Mailgun mailer.
$mailers = PhabricatorMetaMTAMail::newMailers();
$mailgun_type = PhabricatorMailImplementationMailgunAdapter::ADAPTERTYPE;
$mailers = PhabricatorMetaMTAMail::newMailersWithTypes(
array(
PhabricatorMailImplementationMailgunAdapter::ADAPTERTYPE,
));
foreach ($mailers as $mailer) {
if ($mailer->getAdapterType() != $mailgun_type) {
continue;
}
$api_key = $mailer->getOption('api-key');
$hash = hash_hmac('sha256', $timestamp.$token, $api_key);
if (phutil_hashes_are_identical($sig, $hash)) {
return true;

View file

@ -0,0 +1,87 @@
<?php
final class PhabricatorMetaMTAPostmarkReceiveController
extends PhabricatorMetaMTAController {
public function shouldRequireLogin() {
return false;
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
public function handleRequest(AphrontRequest $request) {
// Don't process requests if we don't have a configured Postmark adapter.
$mailers = PhabricatorMetaMTAMail::newMailersWithTypes(
array(
PhabricatorMailImplementationPostmarkAdapter::ADAPTERTYPE,
));
if (!$mailers) {
return new Aphront404Response();
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$raw_input = PhabricatorStartup::getRawInput();
try {
$data = phutil_json_decode($raw_input);
} catch (Exception $ex) {
return new Aphront400Response();
}
$raw_headers = array();
$header_items = idx($data, 'Headers', array());
foreach ($header_items as $header_item) {
$name = idx($header_item, 'Name');
$value = idx($header_item, 'Value');
$raw_headers[$name] = $value;
}
$headers = array(
'to' => idx($data, 'To'),
'from' => idx($data, 'From'),
'cc' => idx($data, 'Cc'),
'subject' => idx($data, 'Subject'),
) + $raw_headers;
$received = id(new PhabricatorMetaMTAReceivedMail())
->setHeaders($headers)
->setBodies(
array(
'text' => idx($data, 'TextBody'),
'html' => idx($data, 'HtmlBody'),
));
$file_phids = array();
$attachments = idx($data, 'Attachments', array());
foreach ($attachments as $attachment) {
$file_data = idx($attachment, 'Content');
$file_data = base64_decode($file_data);
try {
$file = PhabricatorFile::newFromFileData(
$file_data,
array(
'name' => idx($attachment, 'Name'),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$file_phids[] = $file->getPHID();
} catch (Exception $ex) {
phlog($ex);
}
}
$received->setAttachments($file_phids);
try {
$received->save();
$received->processReceivedMail();
} catch (Exception $ex) {
phlog($ex);
}
return id(new AphrontWebpageResponse())
->setContent(pht("Got it! Thanks, Postmark!\n"));
}
}

View file

@ -8,24 +8,14 @@ final class PhabricatorMetaMTASendGridReceiveController
}
public function handleRequest(AphrontRequest $request) {
$mailers = PhabricatorMetaMTAMail::newMailers();
$sendgrid_type = PhabricatorMailImplementationSendGridAdapter::ADAPTERTYPE;
// SendGrid doesn't sign payloads so we can't be sure that SendGrid
// actually sent this request, but require a configured SendGrid mailer
// before we activate this endpoint.
$has_sendgrid = false;
foreach ($mailers as $mailer) {
if ($mailer->getAdapterType() != $sendgrid_type) {
continue;
}
$has_sendgrid = true;
break;
}
if (!$has_sendgrid) {
$mailers = PhabricatorMetaMTAMail::newMailersWithTypes(
array(
PhabricatorMailImplementationSendGridAdapter::ADAPTERTYPE,
));
if (!$mailers) {
return new Aphront404Response();
}

View file

@ -482,6 +482,20 @@ final class PhabricatorMetaMTAMail
return $this->sendWithMailers($mailers);
}
public static function newMailersWithTypes(array $types) {
$mailers = self::newMailers();
$types = array_fuse($types);
foreach ($mailers as $key => $mailer) {
$mailer_type = $mailer->getAdapterType();
if (!isset($types[$mailer_type])) {
unset($mailers[$key]);
}
}
return array_values($mailers);
}
public static function newMailers() {
$mailers = array();

View file

@ -14,6 +14,7 @@ There are a few approaches available:
| Receive Mail With | Setup | Cost | Notes |
|--------|-------|------|-------|
| Mailgun | Easy | Cheap | Recommended |
| Postmark | Easy | Cheap | Recommended |
| SendGrid | Easy | Cheap | |
| Local MTA | Extremely Difficult | Free | Strongly discouraged! |
@ -130,6 +131,17 @@ like this:
example domain with your actual domain.
- Set the `mailgun.api-key` config key to your Mailgun API key.
Postmark Setup
==============
To process inbound mail from Postmark, configure this URI as your inbound
webhook URI in the Postmark control panel:
```
https://<phabricator.yourdomain.com>/mail/postmark/
```
= SendGrid Setup =
To use SendGrid, you need a SendGrid account with access to the "Parse API" for