mirror of
https://we.phorge.it/source/phorge.git
synced 2024-09-20 09:18:48 +02:00
Mailgun receive support
Summary: As you've suggested, I took the SendGrid code and massaged it until it played nice with Mailgun. btw - unless I'm missing something, it appears that the SendGrid receiver lets you spoof emails (it performs no validation on the data received). Test Plan: Opened a task with Mailgun. Felt great. Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley CC: Korvin, epriestley, aran Maniphest Tasks: T4326 Differential Revision: https://secure.phabricator.com/D7989
This commit is contained in:
parent
dc74da0abe
commit
a9612fac24
5 changed files with 233 additions and 0 deletions
|
@ -1575,6 +1575,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMail' => 'applications/metamta/PhabricatorMail.php',
|
'PhabricatorMail' => 'applications/metamta/PhabricatorMail.php',
|
||||||
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php',
|
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php',
|
||||||
'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php',
|
'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php',
|
||||||
|
'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php',
|
||||||
'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php',
|
'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php',
|
||||||
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php',
|
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php',
|
||||||
'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php',
|
'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php',
|
||||||
|
@ -1590,6 +1591,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php',
|
'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php',
|
||||||
'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php',
|
'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php',
|
||||||
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
|
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
|
||||||
|
'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php',
|
||||||
'PhabricatorMailingListPHIDTypeList' => 'applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php',
|
'PhabricatorMailingListPHIDTypeList' => 'applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php',
|
||||||
'PhabricatorMailingListQuery' => 'applications/mailinglists/query/PhabricatorMailingListQuery.php',
|
'PhabricatorMailingListQuery' => 'applications/mailinglists/query/PhabricatorMailingListQuery.php',
|
||||||
'PhabricatorMailingListSearchEngine' => 'applications/mailinglists/query/PhabricatorMailingListSearchEngine.php',
|
'PhabricatorMailingListSearchEngine' => 'applications/mailinglists/query/PhabricatorMailingListSearchEngine.php',
|
||||||
|
@ -1621,6 +1623,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php',
|
'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php',
|
||||||
'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php',
|
'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php',
|
||||||
'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php',
|
'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php',
|
||||||
|
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
|
||||||
'PhabricatorMetaMTAMailingList' => 'applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php',
|
'PhabricatorMetaMTAMailingList' => 'applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php',
|
||||||
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
|
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
|
||||||
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
|
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
|
||||||
|
@ -4211,6 +4214,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||||
'PhabricatorMacroViewController' => 'PhabricatorMacroController',
|
'PhabricatorMacroViewController' => 'PhabricatorMacroController',
|
||||||
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
|
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
|
||||||
|
'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||||
'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter',
|
'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||||
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
|
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||||
'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
|
'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||||
|
@ -4224,6 +4228,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
|
'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
|
||||||
'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||||
'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase',
|
'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase',
|
||||||
|
'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||||
'PhabricatorMailingListPHIDTypeList' => 'PhabricatorPHIDType',
|
'PhabricatorMailingListPHIDTypeList' => 'PhabricatorPHIDType',
|
||||||
'PhabricatorMailingListQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
'PhabricatorMailingListQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||||
'PhabricatorMailingListSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
'PhabricatorMailingListSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||||
|
@ -4253,6 +4258,7 @@ phutil_register_library_map(array(
|
||||||
'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO',
|
'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO',
|
||||||
'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
|
'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
|
||||||
'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
|
'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
|
||||||
|
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
|
||||||
'PhabricatorMetaMTAMailingList' =>
|
'PhabricatorMetaMTAMailingList' =>
|
||||||
array(
|
array(
|
||||||
0 => 'PhabricatorMetaMTADAO',
|
0 => 'PhabricatorMetaMTADAO',
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorMailgunConfigOptions
|
||||||
|
extends PhabricatorApplicationConfigOptions {
|
||||||
|
|
||||||
|
public function getName() {
|
||||||
|
return pht("Integration with Mailgun");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDescription() {
|
||||||
|
return pht("Configure Mailgun integration.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getOptions() {
|
||||||
|
return array(
|
||||||
|
$this->newOption('mailgun.api-key', 'string', null)
|
||||||
|
->setMasked(true)
|
||||||
|
->setDescription(pht('Mailgun API key.')),
|
||||||
|
$this->newOption('mailgun.domain', 'string', null)
|
||||||
|
->setDescription(
|
||||||
|
pht(
|
||||||
|
'Mailgun domain name. See https://mailgun.com/cp/domains'))
|
||||||
|
->addExample('mycompany.com', 'Use specific domain'),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mail adapter that uses Mailgun's web API to deliver email.
|
||||||
|
*/
|
||||||
|
final class PhabricatorMailImplementationMailgunAdapter
|
||||||
|
extends PhabricatorMailImplementationAdapter {
|
||||||
|
|
||||||
|
private $params = array();
|
||||||
|
|
||||||
|
public function setFrom($email, $name = '') {
|
||||||
|
$this->params['from'] = $email;
|
||||||
|
$this->params['from-name'] = $name;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addReplyTo($email, $name = '') {
|
||||||
|
if (empty($this->params['reply-to'])) {
|
||||||
|
$this->params['reply-to'] = array();
|
||||||
|
}
|
||||||
|
$this->params['reply-to'][] = array(
|
||||||
|
'email' => $email,
|
||||||
|
'name' => $name,
|
||||||
|
);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addTos(array $emails) {
|
||||||
|
foreach ($emails as $email) {
|
||||||
|
$this->params['tos'][] = $email;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCCs(array $emails) {
|
||||||
|
foreach ($emails as $email) {
|
||||||
|
$this->params['ccs'][] = $email;
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addAttachment($data, $filename, $mimetype) {
|
||||||
|
// TODO: implement attachments. Requires changes in HTTPSFuture
|
||||||
|
throw new Exception(
|
||||||
|
"Mailgun adapter does not currently support attachments.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addHeader($header_name, $header_value) {
|
||||||
|
$this->params['headers'][] = array($header_name, $header_value);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBody($body) {
|
||||||
|
$this->params['body'] = $body;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setSubject($subject) {
|
||||||
|
$this->params['subject'] = $subject;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setIsHTML($is_html) {
|
||||||
|
$this->params['is-html'] = $is_html;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function supportsMessageIDHeader() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send() {
|
||||||
|
$key = PhabricatorEnv::getEnvConfig('mailgun.api-key');
|
||||||
|
$domain = PhabricatorEnv::getEnvConfig('mailgun.domain');
|
||||||
|
$params = array();
|
||||||
|
|
||||||
|
$params['to'] = idx($this->params, 'tos', array());
|
||||||
|
$params['subject'] = idx($this->params, 'subject');
|
||||||
|
|
||||||
|
if (idx($this->params, 'is-html')) {
|
||||||
|
$params['html'] = idx($this->params, 'body');
|
||||||
|
} else {
|
||||||
|
$params['text'] = idx($this->params, 'body');
|
||||||
|
}
|
||||||
|
|
||||||
|
$from = idx($this->params, 'from');
|
||||||
|
if (idx($this->params, 'from-name')) {
|
||||||
|
$params['from'] = "{$this->params['from-name']} <{$from}>";
|
||||||
|
} else {
|
||||||
|
$params['from'] = $from;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx($this->params, 'reply-to')) {
|
||||||
|
$replyto = $this->params['reply-to'];
|
||||||
|
$params['h:reply-to'] = $replyto;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idx($this->params, 'ccs')) {
|
||||||
|
$params['cc'] = $this->params['ccs'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$future = new HTTPSFuture(
|
||||||
|
"https://api:{$key}@api.mailgun.net/v2/{$domain}/messages",
|
||||||
|
$params);
|
||||||
|
$future->setMethod('POST');
|
||||||
|
|
||||||
|
list($body) = $future->resolvex();
|
||||||
|
|
||||||
|
$response = json_decode($body, true);
|
||||||
|
if (!is_array($response)) {
|
||||||
|
throw new Exception("Failed to JSON decode response: {$body}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!idx($response, 'id')) {
|
||||||
|
$message = $response['message'];
|
||||||
|
throw new Exception("Request failed with errors: {$message}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -34,6 +34,7 @@ final class PhabricatorApplicationMetaMTA extends PhabricatorApplication {
|
||||||
return array(
|
return array(
|
||||||
$this->getBaseURI() => array(
|
$this->getBaseURI() => array(
|
||||||
'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController',
|
'sendgrid/' => 'PhabricatorMetaMTASendGridReceiveController',
|
||||||
|
'mailgun/' => 'PhabricatorMetaMTAMailgunReceiveController',
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PhabricatorMetaMTAMailgunReceiveController
|
||||||
|
extends PhabricatorMetaMTAController {
|
||||||
|
|
||||||
|
public function shouldRequireLogin() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function verifyMessage() {
|
||||||
|
$api_key = PhabricatorEnv::getEnvConfig('mailgun.api-key');
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$timestamp = $request->getStr('timestamp');
|
||||||
|
$token = $request->getStr('token');
|
||||||
|
$sig = $request->getStr('signature');
|
||||||
|
return hash_hmac('sha256', $timestamp.$token, $api_key) == $sig;
|
||||||
|
|
||||||
|
}
|
||||||
|
public function processRequest() {
|
||||||
|
|
||||||
|
// No CSRF for Mailgun.
|
||||||
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||||
|
|
||||||
|
if (!$this->verifyMessage()) {
|
||||||
|
throw new Exception(
|
||||||
|
'Mail signature is not valid. Check your Mailgun API key.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$request = $this->getRequest();
|
||||||
|
$user = $request->getUser();
|
||||||
|
|
||||||
|
$raw_headers = $request->getStr('headers');
|
||||||
|
$raw_headers = explode("\n", rtrim($raw_headers));
|
||||||
|
$raw_dict = array();
|
||||||
|
foreach (array_filter($raw_headers) as $header) {
|
||||||
|
list($name, $value) = explode(':', $header, 2);
|
||||||
|
$raw_dict[$name] = ltrim($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = array(
|
||||||
|
'to' => $request->getStr('recipient'),
|
||||||
|
'from' => $request->getStr('from'),
|
||||||
|
'subject' => $request->getStr('subject'),
|
||||||
|
) + $raw_dict;
|
||||||
|
|
||||||
|
$received = new PhabricatorMetaMTAReceivedMail();
|
||||||
|
$received->setHeaders($headers);
|
||||||
|
$received->setBodies(array(
|
||||||
|
'text' => $request->getStr('stripped-text'),
|
||||||
|
'html' => $request->getStr('stripped-html'),
|
||||||
|
));
|
||||||
|
|
||||||
|
$file_phids = array();
|
||||||
|
foreach ($_FILES as $file_raw) {
|
||||||
|
try {
|
||||||
|
$file = PhabricatorFile::newFromPHPUpload(
|
||||||
|
$file_raw,
|
||||||
|
array(
|
||||||
|
'authorPHID' => $user->getPHID(),
|
||||||
|
));
|
||||||
|
$file_phids[] = $file->getPHID();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
phlog($ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$received->setAttachments($file_phids);
|
||||||
|
$received->save();
|
||||||
|
|
||||||
|
$received->processReceivedMail();
|
||||||
|
|
||||||
|
$response = new AphrontWebpageResponse();
|
||||||
|
$response->setContent(pht("Got it! Thanks, Mailgun!\n"));
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue