mirror of
https://we.phorge.it/source/phorge.git
synced 2025-03-02 23:49:19 +01:00
Summary: All classes should extend from some other class. See D13275 for some explanation. Test Plan: `arc unit` Reviewers: epriestley, #blessed_reviewers Reviewed By: epriestley, #blessed_reviewers Subscribers: epriestley, Korvin Differential Revision: https://secure.phabricator.com/D13283
271 lines
8.7 KiB
PHP
271 lines
8.7 KiB
PHP
<?php
|
|
|
|
abstract class PhabricatorMailReceiver extends Phobject {
|
|
|
|
private $applicationEmail;
|
|
|
|
public function setApplicationEmail(
|
|
PhabricatorMetaMTAApplicationEmail $email) {
|
|
$this->applicationEmail = $email;
|
|
return $this;
|
|
}
|
|
|
|
public function getApplicationEmail() {
|
|
return $this->applicationEmail;
|
|
}
|
|
|
|
abstract public function isEnabled();
|
|
abstract public function canAcceptMail(PhabricatorMetaMTAReceivedMail $mail);
|
|
final protected function canAcceptApplicationMail(
|
|
PhabricatorApplication $app,
|
|
PhabricatorMetaMTAReceivedMail $mail) {
|
|
|
|
$application_emails = id(new PhabricatorMetaMTAApplicationEmailQuery())
|
|
->setViewer($this->getViewer())
|
|
->withApplicationPHIDs(array($app->getPHID()))
|
|
->execute();
|
|
|
|
foreach ($mail->getToAddresses() as $to_address) {
|
|
foreach ($application_emails as $application_email) {
|
|
$create_address = $application_email->getAddress();
|
|
if ($this->matchAddresses($create_address, $to_address)) {
|
|
$this->setApplicationEmail($application_email);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
abstract protected function processReceivedMail(
|
|
PhabricatorMetaMTAReceivedMail $mail,
|
|
PhabricatorUser $sender);
|
|
|
|
final public function receiveMail(
|
|
PhabricatorMetaMTAReceivedMail $mail,
|
|
PhabricatorUser $sender) {
|
|
$this->processReceivedMail($mail, $sender);
|
|
}
|
|
|
|
public function getViewer() {
|
|
return PhabricatorUser::getOmnipotentUser();
|
|
}
|
|
|
|
public function validateSender(
|
|
PhabricatorMetaMTAReceivedMail $mail,
|
|
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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Identifies 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.
|
|
*/
|
|
public function loadSender(PhabricatorMetaMTAReceivedMail $mail) {
|
|
$raw_from = $mail->getHeader('From');
|
|
$from = self::getRawAddress($raw_from);
|
|
|
|
$reasons = array();
|
|
|
|
// Try to find a user with this email address.
|
|
$user = PhabricatorUser::loadOneWithEmailAddress($from);
|
|
if ($user) {
|
|
return $user;
|
|
} else {
|
|
$reasons[] = pht(
|
|
'This email was sent from "%s", but that address is not recognized by '.
|
|
'Phabricator and does not correspond to any known user account.',
|
|
$raw_from);
|
|
}
|
|
|
|
// If we missed on "From", try "Reply-To" if we're configured for it.
|
|
$raw_reply_to = $mail->getHeader('Reply-To');
|
|
if (strlen($raw_reply_to)) {
|
|
$reply_to_key = 'metamta.insecure-auth-with-reply-to';
|
|
$allow_reply_to = PhabricatorEnv::getEnvConfig($reply_to_key);
|
|
if ($allow_reply_to) {
|
|
$reply_to = self::getRawAddress($raw_reply_to);
|
|
|
|
$user = PhabricatorUser::loadOneWithEmailAddress($reply_to);
|
|
if ($user) {
|
|
return $user;
|
|
} else {
|
|
$reasons[] = pht(
|
|
'Phabricator is configured to authenticate users using the '.
|
|
'"Reply-To" header, but the reply address ("%s") on this '.
|
|
'message does not correspond to any known user account.',
|
|
$raw_reply_to);
|
|
}
|
|
} else {
|
|
$reasons[] = pht(
|
|
'(Phabricator is not configured to authenticate users using the '.
|
|
'"Reply-To" header, so it was ignored.)');
|
|
}
|
|
}
|
|
|
|
// If we don't know who this user is, load or create an external user
|
|
// account for them if we're configured for it.
|
|
$email_key = 'phabricator.allow-email-users';
|
|
$allow_email_users = PhabricatorEnv::getEnvConfig($email_key);
|
|
if ($allow_email_users) {
|
|
$from_obj = new PhutilEmailAddress($from);
|
|
$xuser = id(new PhabricatorExternalAccountQuery())
|
|
->setViewer($this->getViewer())
|
|
->withAccountTypes(array('email'))
|
|
->withAccountDomains(array($from_obj->getDomainName(), 'self'))
|
|
->withAccountIDs(array($from_obj->getAddress()))
|
|
->requireCapabilities(
|
|
array(
|
|
PhabricatorPolicyCapability::CAN_VIEW,
|
|
PhabricatorPolicyCapability::CAN_EDIT,
|
|
))
|
|
->loadOneOrCreate();
|
|
return $xuser->getPhabricatorUser();
|
|
} else {
|
|
$reasons[] = pht(
|
|
'Phabricator is also not configured to allow unknown external users '.
|
|
'to send mail to the system using just an email address.');
|
|
$reasons[] = pht(
|
|
'To interact with Phabricator, add this address ("%s") to your '.
|
|
'account.',
|
|
$raw_from);
|
|
}
|
|
|
|
if ($this->getApplicationEmail()) {
|
|
$application_email = $this->getApplicationEmail();
|
|
$default_user_phid = $application_email->getConfigValue(
|
|
PhabricatorMetaMTAApplicationEmail::CONFIG_DEFAULT_AUTHOR);
|
|
|
|
if ($default_user_phid) {
|
|
$user = id(new PhabricatorUser())->loadOneWhere(
|
|
'phid = %s',
|
|
$default_user_phid);
|
|
if ($user) {
|
|
return $user;
|
|
}
|
|
}
|
|
|
|
$reasons[] = pht(
|
|
"Phabricator is misconfigured, the application email ".
|
|
"'%s' is set to user '%s' but that user does not exist.",
|
|
$application_email->getAddress(),
|
|
$default_user_phid);
|
|
}
|
|
|
|
$reasons = implode("\n\n", $reasons);
|
|
|
|
throw new PhabricatorMetaMTAReceivedMailProcessingException(
|
|
MetaMTAReceivedMailStatus::STATUS_UNKNOWN_SENDER,
|
|
$reasons);
|
|
}
|
|
|
|
/**
|
|
* Determine if two inbound email addresses are effectively identical. This
|
|
* method strips and normalizes addresses so that equivalent variations are
|
|
* correctly detected as identical. For example, these addresses are all
|
|
* considered to match one another:
|
|
*
|
|
* "Abraham Lincoln" <alincoln@example.com>
|
|
* alincoln@example.com
|
|
* <ALincoln@example.com>
|
|
* "Abraham" <phabricator+ALINCOLN@EXAMPLE.COM> # With configured prefix.
|
|
*
|
|
* @param string Email address.
|
|
* @param string Another email address.
|
|
* @return bool True if addresses match.
|
|
*/
|
|
public static function matchAddresses($u, $v) {
|
|
$u = self::getRawAddress($u);
|
|
$v = self::getRawAddress($v);
|
|
|
|
$u = self::stripMailboxPrefix($u);
|
|
$v = self::stripMailboxPrefix($v);
|
|
|
|
return ($u === $v);
|
|
}
|
|
|
|
|
|
/**
|
|
* Strip a global mailbox prefix from an address if it is present. Phabricator
|
|
* can be configured to prepend a prefix to all reply addresses, which can
|
|
* make forwarding rules easier to write. A prefix looks like:
|
|
*
|
|
* example@phabricator.example.com # No Prefix
|
|
* phabricator+example@phabricator.example.com # Prefix "phabricator"
|
|
*
|
|
* @param string Email address, possibly with a mailbox prefix.
|
|
* @return string Email address with any prefix stripped.
|
|
*/
|
|
public static function stripMailboxPrefix($address) {
|
|
$address = id(new PhutilEmailAddress($address))->getAddress();
|
|
|
|
$prefix_key = 'metamta.single-reply-handler-prefix';
|
|
$prefix = PhabricatorEnv::getEnvConfig($prefix_key);
|
|
|
|
$len = strlen($prefix);
|
|
|
|
if ($len) {
|
|
$prefix = $prefix.'+';
|
|
$len = $len + 1;
|
|
}
|
|
|
|
if ($len) {
|
|
if (!strncasecmp($address, $prefix, $len)) {
|
|
$address = substr($address, strlen($prefix));
|
|
}
|
|
}
|
|
|
|
return $address;
|
|
}
|
|
|
|
|
|
/**
|
|
* Reduce an email address to its canonical form. For example, an adddress
|
|
* like:
|
|
*
|
|
* "Abraham Lincoln" < ALincoln@example.com >
|
|
*
|
|
* ...will be reduced to:
|
|
*
|
|
* alincoln@example.com
|
|
*
|
|
* @param string Email address in noncanonical form.
|
|
* @return string Canonical email address.
|
|
*/
|
|
public static function getRawAddress($address) {
|
|
$address = id(new PhutilEmailAddress($address))->getAddress();
|
|
return trim(phutil_utf8_strtolower($address));
|
|
}
|
|
|
|
}
|