array( 'address' => 'sort128', 'isVerified' => 'bool', 'isPrimary' => 'bool', 'verificationCode' => 'text64?', ), self::CONFIG_KEY_SCHEMA => array( 'address' => array( 'columns' => array('address'), 'unique' => true, ), 'userPHID' => array( 'columns' => array('userPHID', 'isPrimary'), ), ), ) + parent::getConfiguration(); } public function getVerificationURI() { return '/emailverify/'.$this->getVerificationCode().'/'; } public function save() { if (!$this->verificationCode) { $this->setVerificationCode(Filesystem::readRandomCharacters(24)); } return parent::save(); } /* -( Domain Restrictions )------------------------------------------------ */ /** * @task restrictions */ public static function isValidAddress($address) { if (strlen($address) > self::MAX_ADDRESS_LENGTH) { return false; } // Very roughly validate that this address isn't so mangled that a // reasonable piece of code might completely misparse it. In particular, // the major risks are: // // - `PhutilEmailAddress` needs to be able to extract the domain portion // from it. // - Reasonable mail adapters should be hard-pressed to interpret one // address as several addresses. // // To this end, we're roughly verifying that there's some normal text, an // "@" symbol, and then some more normal text. $email_regex = '(^[a-z0-9_+.!-]+@[a-z0-9_+:.-]+\z)i'; if (!preg_match($email_regex, $address)) { return false; } return true; } /** * @task restrictions */ public static function describeValidAddresses() { return pht( "Email addresses should be in the form 'user@domain.com'. The maximum ". "length of an email address is %d character(s).", new PhutilNumber(self::MAX_ADDRESS_LENGTH)); } /** * @task restrictions */ public static function isAllowedAddress($address) { if (!self::isValidAddress($address)) { return false; } $allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains'); if (!$allowed_domains) { return true; } $addr_obj = new PhutilEmailAddress($address); $domain = $addr_obj->getDomainName(); if (!$domain) { return false; } $lower_domain = phutil_utf8_strtolower($domain); foreach ($allowed_domains as $allowed_domain) { $lower_allowed = phutil_utf8_strtolower($allowed_domain); if ($lower_allowed === $lower_domain) { return true; } } return false; } /** * @task restrictions */ public static function describeAllowedAddresses() { $domains = PhabricatorEnv::getEnvConfig('auth.email-domains'); if (!$domains) { return null; } if (count($domains) == 1) { return 'Email address must be @'.head($domains); } else { return 'Email address must be at one of: '. implode(', ', $domains); } } /** * Check if this install requires email verification. * * @return bool True if email addresses must be verified. * * @task restrictions */ public static function isEmailVerificationRequired() { // NOTE: Configuring required email domains implies required verification. return PhabricatorEnv::getEnvConfig('auth.require-email-verification') || PhabricatorEnv::getEnvConfig('auth.email-domains'); } /* -( Email About Email )-------------------------------------------------- */ /** * Send a verification email from $user to this address. * * @param PhabricatorUser The user sending the verification. * @return this * @task email */ public function sendVerificationEmail(PhabricatorUser $user) { $username = $user->getUsername(); $address = $this->getAddress(); $link = PhabricatorEnv::getProductionURI($this->getVerificationURI()); $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business'); $signature = null; if (!$is_serious) { $signature = <<addRawTos(array($address)) ->setForceDelivery(true) ->setSubject('[Phabricator] Email Verification') ->setBody($body) ->setRelatedPHID($user->getPHID()) ->saveAndSend(); return $this; } /** * Send a notification email from $user to this address, informing the * recipient that this is no longer their account's primary address. * * @param PhabricatorUser The user sending the notification. * @param PhabricatorUserEmail New primary email address. * @return this * @task email */ public function sendOldPrimaryEmail( PhabricatorUser $user, PhabricatorUserEmail $new) { $username = $user->getUsername(); $old_address = $this->getAddress(); $new_address = $new->getAddress(); $body = <<addRawTos(array($old_address)) ->setForceDelivery(true) ->setSubject('[Phabricator] Primary Address Changed') ->setBody($body) ->setFrom($user->getPHID()) ->setRelatedPHID($user->getPHID()) ->saveAndSend(); } /** * Send a notification email from $user to this address, informing the * recipient that this is now their account's new primary email address. * * @param PhabricatorUser The user sending the verification. * @return this * @task email */ public function sendNewPrimaryEmail(PhabricatorUser $user) { $username = $user->getUsername(); $new_address = $this->getAddress(); $body = <<addRawTos(array($new_address)) ->setForceDelivery(true) ->setSubject('[Phabricator] Primary Address Changed') ->setBody($body) ->setFrom($user->getPHID()) ->setRelatedPHID($user->getPHID()) ->saveAndSend(); return $this; } }