1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-09 16:32:39 +01:00

Allow configuration of a task-creation email address

Summary: This lets you configure an email address which will create tasks when
emails are sent to it. It's pretty basic but should get us most of the way
there.
Test Plan: Configured an address and created a task via email. Replied to a task
via email to check that I didn't break that.
Reviewed By: tuomaspelkonen
Reviewers: davidreuss, jungejason, tuomaspelkonen, aran
CC: aran, epriestley, tuomaspelkonen
Differential Revision: 590
This commit is contained in:
epriestley 2011-07-04 09:45:42 -07:00
parent cdd25f427a
commit f9599f4499
7 changed files with 160 additions and 59 deletions

View file

@ -201,6 +201,16 @@ return array(
// single public email address, so objects can not be replied to blindly.
'metamta.public-replies' => false,
// You can configure an email address like "bugs@phabricator.example.com"
// which will automatically create Maniphest tasks when users send email
// to it. This relies on the "From" address to authenticate users, so it is
// is not completely secure. To set this up, enter a complete email
// address like "bugs@phabricator.example.com" and then configure mail to
// that address so it routed to Phabricator (if you've already configured
// reply handlers, you're probably already done). See "Configuring Inbound
// Email" in the documentation for more information.
'metamta.maniphest.public-create-email' => null,
// -- Auth ------------------------------------------------------------------ //

View file

@ -285,11 +285,20 @@ class ManiphestTaskEditController extends ManiphestController {
}
}
$email_create = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.public-create-email');
$email_hint = null;
if (!$task->getID() && $email_create) {
$email_hint = 'You can also create tasks by sending an email to: '.
'<tt>'.phutil_escape_html($email_create).'</tt>';
}
$form
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Description')
->setName('description')
->setCaption($email_hint)
->setValue($task->getDescription()))
->appendChild(
id(new AphrontFormSubmitControl())

View file

@ -19,6 +19,7 @@ phutil_require_module('phabricator', 'applications/maniphest/storage/transaction
phutil_require_module('phabricator', 'applications/phid/constants');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'infrastructure/javelin/api');
phutil_require_module('phabricator', 'infrastructure/javelin/markup');
phutil_require_module('phabricator', 'view/form/base');

View file

@ -52,31 +52,85 @@ class ManiphestReplyHandler extends PhabricatorMailReplyHandler {
public function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
// NOTE: We'll drop in here on both the "reply to a task" and "create a
// new task" workflows! Make sure you test both if you make changes!
$task = $this->getMailReceiver();
$is_new_task = !$task->getID();
$user = $this->getActor();
$body = $mail->getCleanTextBody();
$body = trim($body);
$lines = explode("\n", trim($body));
$first_line = head($lines);
$xactions = array();
$command = null;
$matches = null;
if (preg_match('/^!(\w+)/', $first_line, $matches)) {
$lines = array_slice($lines, 1);
$body = implode("\n", $lines);
$body = trim($body);
$template = new ManiphestTransaction();
$template->setAuthorPHID($user->getPHID());
$command = $matches[1];
if ($is_new_task) {
// If this is a new task, create a "User created this task." transaction
// and then set the title and description.
$xaction = clone $template;
$xaction->setTransactionType(ManiphestTransactionType::TYPE_STATUS);
$xaction->setNewValue(ManiphestTaskStatus::STATUS_OPEN);
$xactions[] = $xaction;
$task->setAuthorPHID($user->getPHID());
$task->setTitle(nonempty($mail->getSubject(), 'Untitled Task'));
$task->setDescription($body);
} else {
$lines = explode("\n", trim($body));
$first_line = head($lines);
$command = null;
$matches = null;
if (preg_match('/^!(\w+)/', $first_line, $matches)) {
$lines = array_slice($lines, 1);
$body = implode("\n", $lines);
$body = trim($body);
$command = $matches[1];
}
$ttype = ManiphestTransactionType::TYPE_NONE;
$new_value = null;
switch ($command) {
case 'close':
$ttype = ManiphestTransactionType::TYPE_STATUS;
$new_value = ManiphestTaskStatus::STATUS_CLOSED_RESOLVED;
break;
case 'claim':
$ttype = ManiphestTransactionType::TYPE_OWNER;
$new_value = $user->getPHID();
break;
case 'unsubscribe':
$ttype = ManiphestTransactionType::TYPE_CCS;
$ccs = $task->getCCPHIDs();
foreach ($ccs as $k => $phid) {
if ($phid == $user->getPHID()) {
unset($ccs[$k]);
}
}
$new_value = array_values($ccs);
break;
}
$xaction = clone $template;
$xaction->setTransactionType($ttype);
$xaction->setNewValue($new_value);
$xaction->setComments($body);
$xactions[] = $xaction;
}
$xactions = array();
// TODO: We should look at CCs on the mail and add them as CCs.
$files = $mail->getAttachments();
if ($files) {
$file_xaction = new ManiphestTransaction();
$file_xaction->setAuthorPHID($user->getPHID());
$file_xaction = clone $template;
$file_xaction->setTransactionType(ManiphestTransactionType::TYPE_ATTACH);
$phid_type = PhabricatorPHIDConstants::PHID_TYPE_FILE;
@ -89,37 +143,6 @@ class ManiphestReplyHandler extends PhabricatorMailReplyHandler {
$xactions[] = $file_xaction;
}
$ttype = ManiphestTransactionType::TYPE_NONE;
$new_value = null;
switch ($command) {
case 'close':
$ttype = ManiphestTransactionType::TYPE_STATUS;
$new_value = ManiphestTaskStatus::STATUS_CLOSED_RESOLVED;
break;
case 'claim':
$ttype = ManiphestTransactionType::TYPE_OWNER;
$new_value = $user->getPHID();
break;
case 'unsubscribe':
$ttype = ManiphestTransactionType::TYPE_CCS;
$ccs = $task->getCCPHIDs();
foreach ($ccs as $k => $phid) {
if ($phid == $user->getPHID()) {
unset($ccs[$k]);
}
}
$new_value = array_values($ccs);
break;
}
$xaction = new ManiphestTransaction();
$xaction->setAuthorPHID($user->getPHID());
$xaction->setTransactionType($ttype);
$xaction->setNewValue($new_value);
$xaction->setComments($body);
$xactions[] = $xaction;
$editor = new ManiphestTransactionEditor();
$editor->setParentMessageID($mail->getMessageID());
$editor->applyTransactions($task, $xactions);

View file

@ -50,15 +50,50 @@ class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
return idx($this->headers, 'message-id');
}
public function getSubject() {
return idx($this->headers, 'subject');
}
public function processReceivedMail() {
$to = idx($this->headers, 'to');
$to = $this->getRawEmailAddress($to);
// Accept a match either at the beginning of the address or after an open
// angle bracket, as in:
// "some display name" <D1+xyz+asdf@example.com>
$from = idx($this->headers, 'from');
$create_task = PhabricatorEnv::getEnvConfig(
'metamta.maniphest.public-create-email');
if ($create_task && $to == $create_task) {
$user = $this->lookupPublicUser();
if (!$user) {
// TODO: We should probably bounce these since from the user's
// perspective their email vanishes into a black hole.
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
$this->setAuthorPHID($user->getPHID());
$receiver = new ManiphestTask();
$receiver->setAuthorPHID($user->getPHID());
$receiver->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$editor = new ManiphestTransactionEditor();
$handler = $editor->buildReplyHandler($receiver);
$handler->setActor($user);
$handler->receiveEmail($this);
$this->setRelatedPHID($receiver->getPHID());
$this->setMessage('OK');
return $this->save();
}
// We've already stripped this, so look for an object address which has
// a format like: D291+291+b0a41ca848d66dcc@example.com
$matches = null;
$ok = preg_match(
'/(?:^|<)((?:D|T)\d+)\+([\w]+)\+([a-f0-9]{16})@/U',
'/^((?:D|T)\d+)\+([\w]+)\+([a-f0-9]{16})@/U',
$to,
$matches);
@ -75,18 +110,8 @@ class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
return $this->setMessage("Public replies not enabled.")->save();
}
// Strip the email address out of the 'from' if necessary, since it might
// have some formatting like '"Abraham Lincoln" <alincoln@logcab.in>'.
$from = idx($this->headers, 'from');
$matches = null;
$ok = preg_match('/<(.*)>/', $from, $matches);
if ($ok) {
$from = $matches[1];
}
$user = $this->lookupPublicUser();
$user = id(new PhabricatorUser())->loadOneWhere(
'email = %s',
$from);
if (!$user) {
return $this->setMessage("Invalid public user '{$from}'.")->save();
}
@ -181,5 +206,27 @@ class PhabricatorMetaMTAReceivedMail extends PhabricatorMetaMTADAO {
return substr($hash, 0, 16);
}
/**
* Strip an email address down to the actual user@domain.tld part if
* necessary, since sometimes it will have formatting like
* '"Abraham Lincoln" <alincoln@logcab.in>'.
*/
private function getRawEmailAddress($address) {
$matches = null;
$ok = preg_match('/<(.*)>/', $address, $matches);
if ($ok) {
$address = $matches[1];
}
return $address;
}
private function lookupPublicUser() {
$from = idx($this->headers, 'from');
$from = $this->getRawEmailAddress($from);
return id(new PhabricatorUser())->loadOneWhere(
'email = %s',
$from);
}
}

View file

@ -7,7 +7,9 @@
phutil_require_module('phabricator', 'applications/differential/mail/base');
phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');
phutil_require_module('phabricator', 'applications/maniphest/storage/task');
phutil_require_module('phabricator', 'applications/metamta/parser');
phutil_require_module('phabricator', 'applications/metamta/storage/base');
phutil_require_module('phabricator', 'applications/people/storage/user');

View file

@ -2,7 +2,8 @@
@group config
This document contains instructions for configuring inbound email, so users
may update Differential and Maniphest by replying to messages.
may update Differential and Maniphest by replying to messages and create
Maniphest tasks via email.
= Preamble =
@ -42,6 +43,11 @@ configured correctly, according to the instructions below -- will parse incoming
email and allow users to interact with Maniphest tasks and Differential
revisions over email.
You can also set up a task creation email address, like ##bugs@example.com##,
which will create a Maniphest task out of any email which is set to it. To do
this, set ##metamta.maniphest.public-create-email## in your configuration. This
has some mild security implications, see below.
= Security =
The email reply channel is "somewhat" authenticated. Each reply-to address is
@ -74,6 +80,9 @@ practically, is a reasonable setting for many installs. The reply-to address
will still contain a hash unique to the object it represents, so users who have
not received an email about an object can not blindly interact with it.
If you enable ##metamta.maniphest.public-create-email##, that address also uses
the weaker "From" authentication mechanism.
NOTE: Phabricator does not currently attempt to verify "From" addresses because
this is technically complex, seems unreasonably difficult in the general case,
and no installs have had a need for it yet. If you have a specific case where a