mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01: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',
|
||||
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php',
|
||||
'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php',
|
||||
'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php',
|
||||
'PhabricatorMailImplementationPHPMailerAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerAdapter.php',
|
||||
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationPHPMailerLiteAdapter.php',
|
||||
'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationSendGridAdapter.php',
|
||||
|
@ -1590,6 +1591,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php',
|
||||
'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php',
|
||||
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
|
||||
'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php',
|
||||
'PhabricatorMailingListPHIDTypeList' => 'applications/mailinglists/phid/PhabricatorMailingListPHIDTypeList.php',
|
||||
'PhabricatorMailingListQuery' => 'applications/mailinglists/query/PhabricatorMailingListQuery.php',
|
||||
'PhabricatorMailingListSearchEngine' => 'applications/mailinglists/query/PhabricatorMailingListSearchEngine.php',
|
||||
|
@ -1621,6 +1623,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTAMailBody' => 'applications/metamta/view/PhabricatorMetaMTAMailBody.php',
|
||||
'PhabricatorMetaMTAMailBodyTestCase' => 'applications/metamta/view/__tests__/PhabricatorMetaMTAMailBodyTestCase.php',
|
||||
'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php',
|
||||
'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php',
|
||||
'PhabricatorMetaMTAMailingList' => 'applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php',
|
||||
'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php',
|
||||
'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php',
|
||||
|
@ -4211,6 +4214,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'PhabricatorMacroViewController' => 'PhabricatorMacroController',
|
||||
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
|
||||
'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||
'PhabricatorMailImplementationPHPMailerAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||
'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||
'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
|
||||
|
@ -4224,6 +4228,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
|
||||
'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions',
|
||||
'PhabricatorMailingListPHIDTypeList' => 'PhabricatorPHIDType',
|
||||
'PhabricatorMailingListQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorMailingListSearchEngine' => 'PhabricatorApplicationSearchEngine',
|
||||
|
@ -4253,6 +4258,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO',
|
||||
'PhabricatorMetaMTAMailBodyTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorMetaMTAMailgunReceiveController' => 'PhabricatorMetaMTAController',
|
||||
'PhabricatorMetaMTAMailingList' =>
|
||||
array(
|
||||
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(
|
||||
$this->getBaseURI() => array(
|
||||
'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