4 * @task restrictions Domain Restrictions
5 * @task email Email About Email
7 final class PhabricatorUserEmail
8 extends PhabricatorUserDAO
10 PhabricatorDestructibleInterface
,
11 PhabricatorPolicyInterface
{
15 protected $isVerified;
17 protected $verificationCode;
19 private $user = self
::ATTACHABLE
;
21 const MAX_ADDRESS_LENGTH
= 128;
23 protected function getConfiguration() {
25 self
::CONFIG_AUX_PHID
=> true,
26 self
::CONFIG_COLUMN_SCHEMA
=> array(
27 'address' => 'sort128',
28 'isVerified' => 'bool',
29 'isPrimary' => 'bool',
30 'verificationCode' => 'text64?',
32 self
::CONFIG_KEY_SCHEMA
=> array(
34 'columns' => array('address'),
38 'columns' => array('userPHID', 'isPrimary'),
41 ) + parent
::getConfiguration();
44 public function getPHIDType() {
45 return PhabricatorPeopleUserEmailPHIDType
::TYPECONST
;
48 public function getVerificationURI() {
49 return '/emailverify/'.$this->getVerificationCode().'/';
52 public function save() {
53 if (!$this->verificationCode
) {
54 $this->setVerificationCode(Filesystem
::readRandomCharacters(24));
56 return parent
::save();
59 public function attachUser(PhabricatorUser
$user) {
64 public function getUser() {
65 return $this->assertAttached($this->user
);
69 /* -( Domain Restrictions )------------------------------------------------ */
75 public static function isValidAddress($address) {
76 if (strlen($address) > self
::MAX_ADDRESS_LENGTH
) {
80 // Very roughly validate that this address isn't so mangled that a
81 // reasonable piece of code might completely misparse it. In particular,
82 // the major risks are:
84 // - `PhutilEmailAddress` needs to be able to extract the domain portion
86 // - Reasonable mail adapters should be hard-pressed to interpret one
87 // address as several addresses.
89 // To this end, we're roughly verifying that there's some normal text, an
90 // "@" symbol, and then some more normal text.
92 $email_regex = '(^[a-z0-9_+.!-]+@[a-z0-9_+:.-]+\z)i';
93 if (!preg_match($email_regex, $address)) {
104 public static function describeValidAddresses() {
106 'Email addresses should be in the form "user@domain.com". The maximum '.
107 'length of an email address is %s characters.',
108 new PhutilNumber(self
::MAX_ADDRESS_LENGTH
));
115 public static function isAllowedAddress($address) {
116 if (!self
::isValidAddress($address)) {
120 $allowed_domains = PhabricatorEnv
::getEnvConfig('auth.email-domains');
121 if (!$allowed_domains) {
125 $addr_obj = new PhutilEmailAddress($address);
127 $domain = $addr_obj->getDomainName();
132 $lower_domain = phutil_utf8_strtolower($domain);
133 foreach ($allowed_domains as $allowed_domain) {
134 $lower_allowed = phutil_utf8_strtolower($allowed_domain);
135 if ($lower_allowed === $lower_domain) {
147 public static function describeAllowedAddresses() {
148 $domains = PhabricatorEnv
::getEnvConfig('auth.email-domains');
153 if (count($domains) == 1) {
154 return pht('Email address must be @%s', head($domains));
157 'Email address must be at one of: %s',
158 implode(', ', $domains));
164 * Check if this install requires email verification.
166 * @return bool True if email addresses must be verified.
170 public static function isEmailVerificationRequired() {
171 // NOTE: Configuring required email domains implies required verification.
172 return PhabricatorEnv
::getEnvConfig('auth.require-email-verification') ||
173 PhabricatorEnv
::getEnvConfig('auth.email-domains');
177 /* -( Email About Email )-------------------------------------------------- */
181 * Send a verification email from $user to this address.
183 * @param PhabricatorUser The user sending the verification.
187 public function sendVerificationEmail(PhabricatorUser
$user) {
188 $username = $user->getUsername();
190 $address = $this->getAddress();
191 $link = PhabricatorEnv
::getProductionURI($this->getVerificationURI());
194 $is_serious = PhabricatorEnv
::getEnvConfig('phabricator.serious-business');
199 "Get Well Soon,\n%s",
200 PlatformSymbols
::getPlatformServerName());
204 "%s\n\n%s\n\n %s\n\n%s",
205 pht('Hi %s', $username),
207 'Please verify that you own this email address (%s) by '.
208 'clicking this link:',
213 id(new PhabricatorMetaMTAMail())
214 ->addRawTos(array($address))
215 ->setForceDelivery(true)
218 '[%s] Email Verification',
219 PlatformSymbols
::getPlatformServerName()))
221 ->setRelatedPHID($user->getPHID())
229 * Send a notification email from $user to this address, informing the
230 * recipient that this is no longer their account's primary address.
232 * @param PhabricatorUser The user sending the notification.
233 * @param PhabricatorUserEmail New primary email address.
237 public function sendOldPrimaryEmail(
238 PhabricatorUser
$user,
239 PhabricatorUserEmail
$new) {
240 $username = $user->getUsername();
242 $old_address = $this->getAddress();
243 $new_address = $new->getAddress();
247 pht('Hi %s', $username),
249 'This email address (%s) is no longer your primary email address. '.
250 'Going forward, all email will be sent to your new primary email '.
255 id(new PhabricatorMetaMTAMail())
256 ->addRawTos(array($old_address))
257 ->setForceDelivery(true)
260 '[%s] Primary Address Changed',
261 PlatformSymbols
::getPlatformServerName()))
263 ->setFrom($user->getPHID())
264 ->setRelatedPHID($user->getPHID())
270 * Send a notification email from $user to this address, informing the
271 * recipient that this is now their account's new primary email address.
273 * @param PhabricatorUser The user sending the verification.
277 public function sendNewPrimaryEmail(PhabricatorUser
$user) {
278 $username = $user->getUsername();
280 $new_address = $this->getAddress();
284 pht('Hi %s', $username),
286 'This is now your primary email address (%s). Going forward, '.
287 'all email will be sent here.',
290 id(new PhabricatorMetaMTAMail())
291 ->addRawTos(array($new_address))
292 ->setForceDelivery(true)
295 '[%s] Primary Address Changed',
296 PlatformSymbols
::getPlatformServerName()))
298 ->setFrom($user->getPHID())
299 ->setRelatedPHID($user->getPHID())
306 /* -( PhabricatorDestructibleInterface )----------------------------------- */
309 public function destroyObjectPermanently(
310 PhabricatorDestructionEngine
$engine) {
315 /* -( PhabricatorPolicyInterface )----------------------------------------- */
317 public function getCapabilities() {
319 PhabricatorPolicyCapability
::CAN_VIEW
,
320 PhabricatorPolicyCapability
::CAN_EDIT
,
324 public function getPolicy($capability) {
325 $user = $this->getUser();
327 if ($this->getIsSystemAgent() ||
$this->getIsMailingList()) {
328 return PhabricatorPolicies
::POLICY_ADMIN
;
331 return $user->getPHID();
334 public function hasAutomaticCapability($capability, PhabricatorUser
$viewer) {