Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15300489
D18983.id45526.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
13 KB
Referenced Files
None
Subscribers
None
D18983.id45526.diff
View Options
diff --git a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
--- a/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
+++ b/src/applications/metamta/controller/PhabricatorMetaMTAMailViewController.php
@@ -32,6 +32,23 @@
$color = PhabricatorMailOutboundStatus::getStatusColor($status);
$header->setStatus($icon, $color, $name);
+ if ($mail->getMustEncrypt()) {
+ Javelin::initBehavior('phabricator-tooltips');
+ $header->addTag(
+ id(new PHUITagView())
+ ->setType(PHUITagView::TYPE_SHADE)
+ ->setColor('blue')
+ ->setName(pht('Must Encrypt'))
+ ->setIcon('fa-shield blue')
+ ->addSigil('has-tooltip')
+ ->setMetadata(
+ array(
+ 'tip' => pht(
+ 'Message content can only be transmitted over secure '.
+ 'channels.'),
+ )));
+ }
+
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Mail %d', $mail->getID()))
->setBorder(true);
diff --git a/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php
--- a/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php
+++ b/src/applications/metamta/management/PhabricatorMailManagementListOutboundWorkflow.php
@@ -37,6 +37,7 @@
$table = id(new PhutilConsoleTable())
->setShowHeader(false)
->addColumn('id', array('title' => pht('ID')))
+ ->addColumn('encrypt', array('title' => pht('#')))
->addColumn('status', array('title' => pht('Status')))
->addColumn('subject', array('title' => pht('Subject')));
@@ -45,6 +46,7 @@
$table->addRow(array(
'id' => $mail->getID(),
+ 'encrypt' => ($mail->getMustEncrypt() ? '#' : ' '),
'status' => PhabricatorMailOutboundStatus::getStatusName($status),
'subject' => $mail->getSubject(),
));
diff --git a/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php b/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php
--- a/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php
+++ b/src/applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php
@@ -79,7 +79,7 @@
$info = array();
- $info[] = pht('PROPERTIES');
+ $info[] = $this->newSectionHeader(pht('PROPERTIES'));
$info[] = pht('ID: %d', $message->getID());
$info[] = pht('Status: %s', $message->getStatus());
$info[] = pht('Related PHID: %s', $message->getRelatedPHID());
@@ -87,15 +87,17 @@
$ignore = array(
'body' => true,
+ 'body.sent' => true,
'html-body' => true,
'headers' => true,
'attachments' => true,
'headers.sent' => true,
+ 'headers.unfiltered' => true,
'authors.sent' => true,
);
$info[] = null;
- $info[] = pht('PARAMETERS');
+ $info[] = $this->newSectionHeader(pht('PARAMETERS'));
$parameters = $message->getParameters();
foreach ($parameters as $key => $value) {
if (isset($ignore[$key])) {
@@ -110,22 +112,40 @@
}
$info[] = null;
- $info[] = pht('HEADERS');
+ $info[] = $this->newSectionHeader(pht('HEADERS'));
$headers = $message->getDeliveredHeaders();
- if (!$headers) {
+ $unfiltered = $message->getUnfilteredHeaders();
+ if (!$unfiltered) {
$headers = $message->generateHeaders();
+ $unfiltered = $headers;
}
+ $header_map = array();
foreach ($headers as $header) {
list($name, $value) = $header;
- $info[] = "{$name}: {$value}";
+ $header_map[$name.':'.$value] = true;
+ }
+
+ foreach ($unfiltered as $header) {
+ list($name, $value) = $header;
+ $was_sent = isset($header_map[$name.':'.$value]);
+
+ if ($was_sent) {
+ $marker = ' ';
+ } else {
+ $marker = '#';
+ }
+
+ $info[] = "{$marker} {$name}: {$value}";
}
$attachments = idx($parameters, 'attachments');
if ($attachments) {
$info[] = null;
- $info[] = pht('ATTACHMENTS');
+
+ $info[] = $this->newSectionHeader(pht('ATTACHMENTS'));
+
foreach ($attachments as $attachment) {
$info[] = idx($attachment, 'filename', pht('Unnamed File'));
}
@@ -136,7 +156,9 @@
$actors = $message->getDeliveredActors();
if ($actors) {
$info[] = null;
- $info[] = pht('RECIPIENTS');
+
+ $info[] = $this->newSectionHeader(pht('RECIPIENTS'));
+
foreach ($actors as $actor_phid => $actor_info) {
$actor = idx($all_actors, $actor_phid);
if ($actor) {
@@ -162,15 +184,22 @@
}
$info[] = null;
- $info[] = pht('TEXT BODY');
+ $info[] = $this->newSectionHeader(pht('TEXT BODY'));
if (strlen($message->getBody())) {
- $info[] = $message->getBody();
+ $info[] = tsprintf('%B', $message->getBody());
} else {
$info[] = pht('(This message has no text body.)');
}
+ $delivered_body = $message->getDeliveredBody();
+ if ($delivered_body !== null) {
+ $info[] = null;
+ $info[] = $this->newSectionHeader(pht('BODY AS DELIVERED'), true);
+ $info[] = tsprintf('%B', $delivered_body);
+ }
+
$info[] = null;
- $info[] = pht('HTML BODY');
+ $info[] = $this->newSectionHeader(pht('HTML BODY'));
if (strlen($message->getHTMLBody())) {
$info[] = $message->getHTMLBody();
$info[] = null;
@@ -186,4 +215,12 @@
}
}
+ private function newSectionHeader($label, $emphasize = false) {
+ if ($emphasize) {
+ return tsprintf('**<bg:yellow> %s </bg>**', $label);
+ } else {
+ return tsprintf('**<bg:blue> %s </bg>**', $label);
+ }
+ }
+
}
diff --git a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
--- a/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
+++ b/src/applications/metamta/storage/PhabricatorMetaMTAMail.php
@@ -21,7 +21,10 @@
public function __construct() {
$this->status = PhabricatorMailOutboundStatus::STATUS_QUEUE;
- $this->parameters = array('sensitive' => true);
+ $this->parameters = array(
+ 'sensitive' => true,
+ 'mustEncrypt' => false,
+ );
parent::__construct();
}
@@ -247,6 +250,15 @@
return $this->getParam('sensitive', true);
}
+ public function setMustEncrypt($bool) {
+ $this->setParam('mustEncrypt', $bool);
+ return $this;
+ }
+
+ public function getMustEncrypt() {
+ return $this->getParam('mustEncrypt', false);
+ }
+
public function setHTMLBody($html) {
$this->setParam('html-body', $html);
return $this;
@@ -431,6 +443,7 @@
unset($params['is-first-message']);
$is_threaded = (bool)idx($params, 'thread-id');
+ $must_encrypt = $this->getMustEncrypt();
$reply_to_name = idx($params, 'reply-to-name', '');
unset($params['reply-to-name']);
@@ -502,6 +515,11 @@
mpull($cc_actors, 'getEmailAddress'));
break;
case 'attachments':
+ // If the mail content must be encrypted, don't add attachments.
+ if ($must_encrypt) {
+ break;
+ }
+
$value = $this->getAttachments();
foreach ($value as $attachment) {
$mailer->addAttachment(
@@ -521,14 +539,20 @@
$subject[] = trim(idx($params, 'subject-prefix'));
- $vary_prefix = idx($params, 'vary-subject-prefix');
- if ($vary_prefix != '') {
- if ($this->shouldVarySubject($preferences)) {
- $subject[] = $vary_prefix;
+ // If mail content must be encrypted, we replace the subject with
+ // a generic one.
+ if ($must_encrypt) {
+ $subject[] = pht('Object Updated');
+ } else {
+ $vary_prefix = idx($params, 'vary-subject-prefix');
+ if ($vary_prefix != '') {
+ if ($this->shouldVarySubject($preferences)) {
+ $subject[] = $vary_prefix;
+ }
}
- }
- $subject[] = $value;
+ $subject[] = $value;
+ }
$mailer->setSubject(implode(' ', array_filter($subject)));
break;
@@ -567,7 +591,22 @@
}
}
- $body = idx($params, 'body', '');
+ $raw_body = idx($params, 'body', '');
+ $body = $raw_body;
+ if ($must_encrypt) {
+ $parts = array();
+ $parts[] = pht(
+ 'The content for this message can only be transmitted over a '.
+ 'ustsecure channel. To view the message content, follow this '.
+ 'link:');
+
+ $parts[] = PhabricatorEnv::getProductionURI($this->getURI());
+
+ $body = implode("\n\n", $parts);
+ } else {
+ $body = $raw_body;
+ }
+
$max = PhabricatorEnv::getEnvConfig('metamta.email-body-limit');
if (strlen($body) > $max) {
$body = id(new PhutilUTF8StringTruncator())
@@ -578,18 +617,32 @@
}
$mailer->setBody($body);
- $html_emails = $this->shouldSendHTML($preferences);
- if ($html_emails && isset($params['html-body'])) {
+ // If we sent a different message body than we were asked to, record
+ // what we actually sent to make debugging and diagnostics easier.
+ if ($body !== $raw_body) {
+ $this->setParam('body.sent', $body);
+ }
+
+ if ($must_encrypt) {
+ $send_html = false;
+ } else {
+ $send_html = $this->shouldSendHTML($preferences);
+ }
+
+ if ($send_html && isset($params['html-body'])) {
$mailer->setHTMLBody($params['html-body']);
}
// Pass the headers to the mailer, then save the state so we can show
- // them in the web UI.
- foreach ($headers as $header) {
+ // them in the web UI. If the mail must be encrypted, we remove headers
+ // which are not on a strict whitelist to avoid disclosing information.
+ $filtered_headers = $this->filterHeaders($headers, $must_encrypt);
+ foreach ($filtered_headers as $header) {
list($header_key, $header_value) = $header;
$mailer->addHeader($header_key, $header_value);
}
- $this->setParam('headers.sent', $headers);
+ $this->setParam('headers.unfiltered', $headers);
+ $this->setParam('headers.sent', $filtered_headers);
// Save the final deliverability outcomes and reasoning so we can
// explain why things happened the way they did.
@@ -1002,8 +1055,6 @@
// Some clients respect this to suppress OOF and other auto-responses.
$headers[] = array('X-Auto-Response-Suppress', 'All');
- // If the message has mailtags, filter out any recipients who don't want
- // to receive this type of mail.
$mailtags = $this->getParam('mailtags');
if ($mailtags) {
$tag_header = array();
@@ -1028,6 +1079,10 @@
$headers[] = array('Precedence', 'bulk');
}
+ if ($this->getMustEncrypt()) {
+ $headers[] = array('X-Phabricator-Must-Encrypt', 'Yes');
+ }
+
return $headers;
}
@@ -1035,6 +1090,19 @@
return $this->getParam('headers.sent');
}
+ public function getUnfilteredHeaders() {
+ $unfiltered = $this->getParam('headers.unfiltered');
+
+ if ($unfiltered === null) {
+ // Older versions of Phabricator did not filter headers, and thus did
+ // not record unfiltered headers. If we don't have unfiltered header
+ // data just return the delivered headers for compatibility.
+ return $this->getDeliveredHeaders();
+ }
+
+ return $unfiltered;
+ }
+
public function getDeliveredActors() {
return $this->getParam('actors.sent');
}
@@ -1047,6 +1115,54 @@
return $this->getParam('routingmap.sent');
}
+ public function getDeliveredBody() {
+ return $this->getParam('body.sent');
+ }
+
+ private function filterHeaders(array $headers, $must_encrypt) {
+ if (!$must_encrypt) {
+ return $headers;
+ }
+
+ $whitelist = array(
+ 'In-Reply-To',
+ 'Message-ID',
+ 'Precedence',
+ 'References',
+ 'Thread-Index',
+
+ 'X-Mail-Transport-Agent',
+ 'X-Auto-Response-Suppress',
+
+ 'X-Phabricator-Sent-This-Message',
+ 'X-Phabricator-Must-Encrypt',
+ );
+
+ // NOTE: The major header we want to drop is "X-Phabricator-Mail-Tags".
+ // This header contains a significant amount of meaningful information
+ // about the object.
+
+ $whitelist_map = array();
+ foreach ($whitelist as $term) {
+ $whitelist_map[phutil_utf8_strtolower($term)] = true;
+ }
+
+ foreach ($headers as $key => $header) {
+ list($name, $value) = $header;
+ $name = phutil_utf8_strtolower($name);
+
+ if (!isset($whitelist_map[$name])) {
+ unset($headers[$key]);
+ }
+ }
+
+ return $headers;
+ }
+
+ public function getURI() {
+ return '/mail/detail/'.$this->getID().'/';
+ }
+
/* -( Routing )------------------------------------------------------------ */
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Mar 6, 4:50 PM (2 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7290381
Default Alt Text
D18983.id45526.diff (13 KB)
Attached To
Mode
D18983: Add basic support for a "Must Encrypt" mail flag which prevents unsecured content transmission
Attached
Detach File
Event Timeline
Log In to Comment