Path: blob/master/src/applications/people/storage/PhabricatorUserEmail.php
12256 views
<?php12/**3* @task restrictions Domain Restrictions4* @task email Email About Email5*/6final class PhabricatorUserEmail7extends PhabricatorUserDAO8implements9PhabricatorDestructibleInterface,10PhabricatorPolicyInterface {1112protected $userPHID;13protected $address;14protected $isVerified;15protected $isPrimary;16protected $verificationCode;1718private $user = self::ATTACHABLE;1920const MAX_ADDRESS_LENGTH = 128;2122protected function getConfiguration() {23return array(24self::CONFIG_AUX_PHID => true,25self::CONFIG_COLUMN_SCHEMA => array(26'address' => 'sort128',27'isVerified' => 'bool',28'isPrimary' => 'bool',29'verificationCode' => 'text64?',30),31self::CONFIG_KEY_SCHEMA => array(32'address' => array(33'columns' => array('address'),34'unique' => true,35),36'userPHID' => array(37'columns' => array('userPHID', 'isPrimary'),38),39),40) + parent::getConfiguration();41}4243public function getPHIDType() {44return PhabricatorPeopleUserEmailPHIDType::TYPECONST;45}4647public function getVerificationURI() {48return '/emailverify/'.$this->getVerificationCode().'/';49}5051public function save() {52if (!$this->verificationCode) {53$this->setVerificationCode(Filesystem::readRandomCharacters(24));54}55return parent::save();56}5758public function attachUser(PhabricatorUser $user) {59$this->user = $user;60return $this;61}6263public function getUser() {64return $this->assertAttached($this->user);65}666768/* -( Domain Restrictions )------------------------------------------------ */697071/**72* @task restrictions73*/74public static function isValidAddress($address) {75if (strlen($address) > self::MAX_ADDRESS_LENGTH) {76return false;77}7879// Very roughly validate that this address isn't so mangled that a80// reasonable piece of code might completely misparse it. In particular,81// the major risks are:82//83// - `PhutilEmailAddress` needs to be able to extract the domain portion84// from it.85// - Reasonable mail adapters should be hard-pressed to interpret one86// address as several addresses.87//88// To this end, we're roughly verifying that there's some normal text, an89// "@" symbol, and then some more normal text.9091$email_regex = '(^[a-z0-9_+.!-]+@[a-z0-9_+:.-]+\z)i';92if (!preg_match($email_regex, $address)) {93return false;94}9596return true;97}9899100/**101* @task restrictions102*/103public static function describeValidAddresses() {104return pht(105'Email addresses should be in the form "[email protected]". The maximum '.106'length of an email address is %s characters.',107new PhutilNumber(self::MAX_ADDRESS_LENGTH));108}109110111/**112* @task restrictions113*/114public static function isAllowedAddress($address) {115if (!self::isValidAddress($address)) {116return false;117}118119$allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains');120if (!$allowed_domains) {121return true;122}123124$addr_obj = new PhutilEmailAddress($address);125126$domain = $addr_obj->getDomainName();127if (!$domain) {128return false;129}130131$lower_domain = phutil_utf8_strtolower($domain);132foreach ($allowed_domains as $allowed_domain) {133$lower_allowed = phutil_utf8_strtolower($allowed_domain);134if ($lower_allowed === $lower_domain) {135return true;136}137}138139return false;140}141142143/**144* @task restrictions145*/146public static function describeAllowedAddresses() {147$domains = PhabricatorEnv::getEnvConfig('auth.email-domains');148if (!$domains) {149return null;150}151152if (count($domains) == 1) {153return pht('Email address must be @%s', head($domains));154} else {155return pht(156'Email address must be at one of: %s',157implode(', ', $domains));158}159}160161162/**163* Check if this install requires email verification.164*165* @return bool True if email addresses must be verified.166*167* @task restrictions168*/169public static function isEmailVerificationRequired() {170// NOTE: Configuring required email domains implies required verification.171return PhabricatorEnv::getEnvConfig('auth.require-email-verification') ||172PhabricatorEnv::getEnvConfig('auth.email-domains');173}174175176/* -( Email About Email )-------------------------------------------------- */177178179/**180* Send a verification email from $user to this address.181*182* @param PhabricatorUser The user sending the verification.183* @return this184* @task email185*/186public function sendVerificationEmail(PhabricatorUser $user) {187$username = $user->getUsername();188189$address = $this->getAddress();190$link = PhabricatorEnv::getProductionURI($this->getVerificationURI());191192193$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');194195$signature = null;196if (!$is_serious) {197$signature = pht(198"Get Well Soon,\n%s",199PlatformSymbols::getPlatformServerName());200}201202$body = sprintf(203"%s\n\n%s\n\n %s\n\n%s",204pht('Hi %s', $username),205pht(206'Please verify that you own this email address (%s) by '.207'clicking this link:',208$address),209$link,210$signature);211212id(new PhabricatorMetaMTAMail())213->addRawTos(array($address))214->setForceDelivery(true)215->setSubject(216pht(217'[%s] Email Verification',218PlatformSymbols::getPlatformServerName()))219->setBody($body)220->setRelatedPHID($user->getPHID())221->saveAndSend();222223return $this;224}225226227/**228* Send a notification email from $user to this address, informing the229* recipient that this is no longer their account's primary address.230*231* @param PhabricatorUser The user sending the notification.232* @param PhabricatorUserEmail New primary email address.233* @return this234* @task email235*/236public function sendOldPrimaryEmail(237PhabricatorUser $user,238PhabricatorUserEmail $new) {239$username = $user->getUsername();240241$old_address = $this->getAddress();242$new_address = $new->getAddress();243244$body = sprintf(245"%s\n\n%s\n",246pht('Hi %s', $username),247pht(248'This email address (%s) is no longer your primary email address. '.249'Going forward, all email will be sent to your new primary email '.250'address (%s).',251$old_address,252$new_address));253254id(new PhabricatorMetaMTAMail())255->addRawTos(array($old_address))256->setForceDelivery(true)257->setSubject(258pht(259'[%s] Primary Address Changed',260PlatformSymbols::getPlatformServerName()))261->setBody($body)262->setFrom($user->getPHID())263->setRelatedPHID($user->getPHID())264->saveAndSend();265}266267268/**269* Send a notification email from $user to this address, informing the270* recipient that this is now their account's new primary email address.271*272* @param PhabricatorUser The user sending the verification.273* @return this274* @task email275*/276public function sendNewPrimaryEmail(PhabricatorUser $user) {277$username = $user->getUsername();278279$new_address = $this->getAddress();280281$body = sprintf(282"%s\n\n%s\n",283pht('Hi %s', $username),284pht(285'This is now your primary email address (%s). Going forward, '.286'all email will be sent here.',287$new_address));288289id(new PhabricatorMetaMTAMail())290->addRawTos(array($new_address))291->setForceDelivery(true)292->setSubject(293pht(294'[%s] Primary Address Changed',295PlatformSymbols::getPlatformServerName()))296->setBody($body)297->setFrom($user->getPHID())298->setRelatedPHID($user->getPHID())299->saveAndSend();300301return $this;302}303304305/* -( PhabricatorDestructibleInterface )----------------------------------- */306307308public function destroyObjectPermanently(309PhabricatorDestructionEngine $engine) {310$this->delete();311}312313314/* -( PhabricatorPolicyInterface )----------------------------------------- */315316public function getCapabilities() {317return array(318PhabricatorPolicyCapability::CAN_VIEW,319PhabricatorPolicyCapability::CAN_EDIT,320);321}322323public function getPolicy($capability) {324$user = $this->getUser();325326if ($this->getIsSystemAgent() || $this->getIsMailingList()) {327return PhabricatorPolicies::POLICY_ADMIN;328}329330return $user->getPHID();331}332333public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {334return false;335}336337}338339340