1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-21 11:09:02 +01:00
phorge-phorge/src/applications/metamta/receiver/PhabricatorMailReceiver.php

217 lines
7.1 KiB
PHP
Raw Normal View History

2013-05-14 10:57:41 -07:00
<?php
abstract class PhabricatorMailReceiver {
abstract public function isEnabled();
abstract public function canAcceptMail(PhabricatorMetaMTAReceivedMail $mail);
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) {
2014-04-03 18:43:18 -07:00
$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,
2014-04-03 18:43:18 -07:00
$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(
2014-04-03 18:43:18 -07:00
'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.
2014-04-03 18:43:18 -07:00
$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(
2014-04-03 18:43:18 -07:00
'(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()))
Introduce CAN_EDIT for ExternalAccount, and make CAN_VIEW more liberal Summary: Fixes T3732. Ref T1205. Ref T3116. External accounts (like emails used as identities, Facebook accounts, LDAP accounts, etc.) are stored in "ExternalAccount" objects. Currently, we have a very restrictive `CAN_VIEW` policy for ExternalAccounts, to add an extra layer of protection to make sure users can't use them in unintended ways. For example, it would be bad if a user could link their Phabricator account to a Facebook account without proper authentication. All of the controllers which do sensitive things have checks anyway, but a restrictive CAN_VIEW provided an extra layer of protection. Se T3116 for some discussion. However, this means that when grey/external users take actions (via email, or via applications like Legalpad) other users can't load the account handles and can't see anything about the actor (they just see "Restricted External Account" or similar). Balancing these concerns is mostly about not making a huge mess while doing it. This seems like a reasonable approach: - Add `CAN_EDIT` on these objects. - Make that very restricted, but open up `CAN_VIEW`. - Require `CAN_EDIT` any time we're going to do something authentication/identity related. This is slightly easier to get wrong (forget CAN_EDIT) than other approaches, but pretty simple, and we always have extra checks in place anyway -- this is just a safety net. I'm not quite sure how we should identify external accounts, so for now we're just rendering "Email User" or similar -- clearly not a bug, but not identifying. We can figure out what to render in the long term elsewhere. Test Plan: - Viewed external accounts. - Linked an external account. - Refreshed an external account. - Edited profile picture. - Viewed sessions panel. - Published a bunch of stuff to Asana/JIRA. - Legalpad signature page now shows external accounts. {F171595} Reviewers: chad, btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T3732, T1205, T3116 Differential Revision: https://secure.phabricator.com/D9767
2014-07-10 10:18:10 -07:00
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->loadOneOrCreate();
return $xuser->getPhabricatorUser();
} else {
$reasons[] = pht(
2014-04-03 18:43:18 -07:00
'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);
}
2014-04-03 18:43:18 -07:00
$reasons = implode("\n\n", $reasons);
throw new PhabricatorMetaMTAReceivedMailProcessingException(
MetaMTAReceivedMailStatus::STATUS_UNKNOWN_SENDER,
2014-04-03 18:43:18 -07:00
$reasons);
}
2013-05-14 10:57:41 -07:00
/**
* 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);
2013-05-14 10:57:41 -07:00
$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));
}
2013-05-14 10:57:41 -07:00
}