Changeset View
Changeset View
Standalone View
Standalone View
src/applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php
Show First 20 Lines • Show All 119 Lines • ▼ Show 20 Lines | if (empty($addresses)) { | ||||
return array(); | return array(); | ||||
} | } | ||||
$users = id(new PhabricatorUserEmail()) | $users = id(new PhabricatorUserEmail()) | ||||
->loadAllWhere('address IN (%Ls)', $addresses); | ->loadAllWhere('address IN (%Ls)', $addresses); | ||||
return mpull($users, 'getUserPHID'); | return mpull($users, 'getUserPHID'); | ||||
} | } | ||||
public function processReceivedMail() { | public function processReceivedMail() { | ||||
$viewer = $this->getViewer(); | |||||
$sender = null; | $sender = null; | ||||
try { | try { | ||||
$this->dropMailFromPhabricator(); | $this->dropMailFromPhabricator(); | ||||
$this->dropMailAlreadyReceived(); | $this->dropMailAlreadyReceived(); | ||||
$this->dropEmptyMail(); | $this->dropEmptyMail(); | ||||
$receiver = $this->loadReceiver(); | $sender = $this->loadSender(); | ||||
$sender = $receiver->loadSender($this); | if ($sender) { | ||||
$receiver->validateSender($this, $sender); | |||||
$this->setAuthorPHID($sender->getPHID()); | $this->setAuthorPHID($sender->getPHID()); | ||||
// Now that we've identified the sender, mark them as the author of | // If we've identified the sender, mark them as the author of any | ||||
// any attached files. | // attached files. We do this before we validate them (below), since | ||||
// they still authored these files even if their account is not allowed | |||||
// to interact via email. | |||||
$attachments = $this->getAttachments(); | $attachments = $this->getAttachments(); | ||||
if ($attachments) { | if ($attachments) { | ||||
$files = id(new PhabricatorFileQuery()) | $files = id(new PhabricatorFileQuery()) | ||||
->setViewer(PhabricatorUser::getOmnipotentUser()) | ->setViewer($viewer) | ||||
->withPHIDs($attachments) | ->withPHIDs($attachments) | ||||
->execute(); | ->execute(); | ||||
foreach ($files as $file) { | foreach ($files as $file) { | ||||
$file->setAuthorPHID($sender->getPHID())->save(); | $file->setAuthorPHID($sender->getPHID())->save(); | ||||
} | } | ||||
} | } | ||||
$receiver->receiveMail($this, $sender); | $this->validateSender($sender); | ||||
} | |||||
$receivers = id(new PhutilClassMapQuery()) | |||||
->setAncestorClass('PhabricatorMailReceiver') | |||||
->setFilterMethod('isEnabled') | |||||
->execute(); | |||||
$any_accepted = false; | |||||
$receiver_exception = null; | |||||
$targets = $this->newTargetAddresses(); | |||||
foreach ($receivers as $receiver) { | |||||
$receiver = id(clone $receiver) | |||||
->setViewer($viewer); | |||||
if ($sender) { | |||||
$receiver->setSender($sender); | |||||
} | |||||
foreach ($targets as $target) { | |||||
try { | |||||
if (!$receiver->canAcceptMail($this, $target)) { | |||||
continue; | |||||
} | |||||
$any_accepted = true; | |||||
$receiver->receiveMail($this, $target); | |||||
amckinley: Shouldn't these happen in the opposite order? | |||||
Done Inline ActionsIf receiveMail() throws, we shouldn't raise a "nothing received this" exception. We don't anyway, but the theory here is "once something has said it's going to accept it, the 'nothing accept this' exception is off the table". epriestley: If `receiveMail()` throws, we shouldn't raise a "nothing received this" exception. We don't… | |||||
} catch (Exception $ex) { | |||||
// If receivers raise exceptions, we'll keep the first one in hope | |||||
// that it points at a root cause. | |||||
if (!$receiver_exception) { | |||||
$receiver_exception = $ex; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
if ($receiver_exception) { | |||||
throw $receiver_exception; | |||||
} | |||||
if (!$any_accepted) { | |||||
if (!$sender) { | |||||
// NOTE: Currently, we'll always drop this mail (since it's headed to | |||||
// an unverified recipient). See T12237. These details are still | |||||
// useful because they'll appear in the mail logs and Mail web UI. | |||||
throw new PhabricatorMetaMTAReceivedMailProcessingException( | |||||
MetaMTAReceivedMailStatus::STATUS_UNKNOWN_SENDER, | |||||
pht( | |||||
'This email was sent from an email address ("%s") that is not '. | |||||
'associated with a Phabricator account. To interact with '. | |||||
'Phabricator via email, add this address to your account.', | |||||
Not Done Inline Actions"This email was sent from an email address not associated with any active Phabricator users"? amckinley: "This email was sent from an email address not associated with any active Phabricator users"? | |||||
Done Inline ActionsThat's probably more clear. epriestley: That's probably more clear. | |||||
(string)$this->newFromAddress())); | |||||
} else { | |||||
throw new PhabricatorMetaMTAReceivedMailProcessingException( | |||||
MetaMTAReceivedMailStatus::STATUS_NO_RECEIVERS, | |||||
pht( | |||||
'Phabricator can not process this mail because no application '. | |||||
'knows how to handle it. Check that the address you sent it to '. | |||||
'is correct.'. | |||||
"\n\n". | |||||
'(No concrete, enabled subclass of PhabricatorMailReceiver can '. | |||||
'accept this mail.)')); | |||||
} | |||||
} | |||||
} catch (PhabricatorMetaMTAReceivedMailProcessingException $ex) { | } catch (PhabricatorMetaMTAReceivedMailProcessingException $ex) { | ||||
switch ($ex->getStatusCode()) { | switch ($ex->getStatusCode()) { | ||||
case MetaMTAReceivedMailStatus::STATUS_DUPLICATE: | case MetaMTAReceivedMailStatus::STATUS_DUPLICATE: | ||||
case MetaMTAReceivedMailStatus::STATUS_FROM_PHABRICATOR: | case MetaMTAReceivedMailStatus::STATUS_FROM_PHABRICATOR: | ||||
// Don't send an error email back in these cases, since they're | // Don't send an error email back in these cases, since they're | ||||
// very unlikely to be the sender's fault. | // very unlikely to be the sender's fault. | ||||
break; | break; | ||||
case MetaMTAReceivedMailStatus::STATUS_EMPTY_IGNORED: | case MetaMTAReceivedMailStatus::STATUS_EMPTY_IGNORED: | ||||
▲ Show 20 Lines • Show All 143 Lines • ▼ Show 20 Lines | throw new PhabricatorMetaMTAReceivedMailProcessingException( | ||||
$status_code, | $status_code, | ||||
pht( | pht( | ||||
'Your message does not contain any body text or attachments, so '. | 'Your message does not contain any body text or attachments, so '. | ||||
'Phabricator can not do anything useful with it. Make sure comment '. | 'Phabricator can not do anything useful with it. Make sure comment '. | ||||
'text appears at the top of your message: quoted replies, inline '. | 'text appears at the top of your message: quoted replies, inline '. | ||||
'text, and signatures are discarded and ignored.')); | 'text, and signatures are discarded and ignored.')); | ||||
} | } | ||||
/** | |||||
* Load a concrete instance of the @{class:PhabricatorMailReceiver} which | |||||
* accepts this mail, if one exists. | |||||
*/ | |||||
private function loadReceiver() { | |||||
$receivers = id(new PhutilClassMapQuery()) | |||||
->setAncestorClass('PhabricatorMailReceiver') | |||||
->setFilterMethod('isEnabled') | |||||
->execute(); | |||||
$accept = array(); | |||||
foreach ($receivers as $key => $receiver) { | |||||
if ($receiver->canAcceptMail($this)) { | |||||
$accept[$key] = $receiver; | |||||
} | |||||
} | |||||
if (!$accept) { | |||||
throw new PhabricatorMetaMTAReceivedMailProcessingException( | |||||
MetaMTAReceivedMailStatus::STATUS_NO_RECEIVERS, | |||||
pht( | |||||
'Phabricator can not process this mail because no application '. | |||||
'knows how to handle it. Check that the address you sent it to is '. | |||||
'correct.'. | |||||
"\n\n". | |||||
'(No concrete, enabled subclass of PhabricatorMailReceiver can '. | |||||
'accept this mail.)')); | |||||
} | |||||
if (count($accept) > 1) { | |||||
$names = implode(', ', array_keys($accept)); | |||||
throw new PhabricatorMetaMTAReceivedMailProcessingException( | |||||
MetaMTAReceivedMailStatus::STATUS_ABUNDANT_RECEIVERS, | |||||
pht( | |||||
'Phabricator is not able to process this mail because more than '. | |||||
'one application is willing to accept it, creating ambiguity. '. | |||||
'Mail needs to be accepted by exactly one receiving application.'. | |||||
"\n\n". | |||||
'Accepting receivers: %s.', | |||||
$names)); | |||||
} | |||||
return head($accept); | |||||
} | |||||
private function sendExceptionMail( | private function sendExceptionMail( | ||||
Exception $ex, | Exception $ex, | ||||
PhabricatorUser $viewer = null) { | PhabricatorUser $viewer = null) { | ||||
// If we've failed to identify a legitimate sender, we don't send them | // If we've failed to identify a legitimate sender, we don't send them | ||||
// an error message back. We want to avoid sending mail to unverified | // an error message back. We want to avoid sending mail to unverified | ||||
// addresses. See T12491. | // addresses. See T12491. | ||||
if (!$viewer) { | if (!$viewer) { | ||||
▲ Show 20 Lines • Show All 62 Lines • ▼ Show 20 Lines | , | ||||
public function newContentSource() { | public function newContentSource() { | ||||
return PhabricatorContentSource::newForSource( | return PhabricatorContentSource::newForSource( | ||||
PhabricatorEmailContentSource::SOURCECONST, | PhabricatorEmailContentSource::SOURCECONST, | ||||
array( | array( | ||||
'id' => $this->getID(), | 'id' => $this->getID(), | ||||
)); | )); | ||||
} | } | ||||
public function newFromAddress() { | |||||
$raw_from = $this->getHeader('From'); | |||||
if (strlen($raw_from)) { | |||||
return new PhutilEmailAddress($raw_from); | |||||
} | |||||
return null; | |||||
} | |||||
private function getViewer() { | |||||
return PhabricatorUser::getOmnipotentUser(); | |||||
} | |||||
/** | |||||
* Identify the sender's user account for a piece of received mail. | |||||
* | |||||
* Note that this method does not validate that the sender is who they say | |||||
* they are, just that they've presented some credential which corresponds | |||||
* to a recognizable user. | |||||
*/ | |||||
private function loadSender() { | |||||
$viewer = $this->getViewer(); | |||||
// Try to identify the user based on their "From" address. | |||||
$from_address = $this->newFromAddress(); | |||||
if ($from_address) { | |||||
$user = id(new PhabricatorPeopleQuery()) | |||||
->setViewer($viewer) | |||||
->withEmails(array($from_address->getAddress())) | |||||
->executeOne(); | |||||
if ($user) { | |||||
return $user; | |||||
} | |||||
} | |||||
return null; | |||||
} | |||||
private function validateSender(PhabricatorUser $sender) { | |||||
$failure_reason = null; | |||||
if ($sender->getIsDisabled()) { | |||||
$failure_reason = pht( | |||||
'Your account ("%s") is disabled, so you can not interact with '. | |||||
'Phabricator over email.', | |||||
$sender->getUsername()); | |||||
} else if ($sender->getIsStandardUser()) { | |||||
if (!$sender->getIsApproved()) { | |||||
$failure_reason = pht( | |||||
'Your account ("%s") has not been approved yet. You can not '. | |||||
'interact with Phabricator over email until your account is '. | |||||
'approved.', | |||||
$sender->getUsername()); | |||||
} else if (PhabricatorUserEmail::isEmailVerificationRequired() && | |||||
!$sender->getIsEmailVerified()) { | |||||
$failure_reason = pht( | |||||
'You have not verified the email address for your account ("%s"). '. | |||||
'You must verify your email address before you can interact '. | |||||
'with Phabricator over email.', | |||||
$sender->getUsername()); | |||||
} | |||||
} | |||||
if ($failure_reason) { | |||||
throw new PhabricatorMetaMTAReceivedMailProcessingException( | |||||
MetaMTAReceivedMailStatus::STATUS_DISABLED_SENDER, | |||||
$failure_reason); | |||||
} | |||||
} | |||||
} | } |
Shouldn't these happen in the opposite order?