Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / applications / people / storage / PhabricatorUserEmail.php
blobd9866f2c432adfe6ba68e780e8b8752b4ebd9183
1 <?php
3 /**
4 * @task restrictions Domain Restrictions
5 * @task email Email About Email
6 */
7 final class PhabricatorUserEmail
8 extends PhabricatorUserDAO
9 implements
10 PhabricatorDestructibleInterface,
11 PhabricatorPolicyInterface {
13 protected $userPHID;
14 protected $address;
15 protected $isVerified;
16 protected $isPrimary;
17 protected $verificationCode;
19 private $user = self::ATTACHABLE;
21 const MAX_ADDRESS_LENGTH = 128;
23 protected function getConfiguration() {
24 return array(
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(
33 'address' => array(
34 'columns' => array('address'),
35 'unique' => true,
37 'userPHID' => array(
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) {
60 $this->user = $user;
61 return $this;
64 public function getUser() {
65 return $this->assertAttached($this->user);
69 /* -( Domain Restrictions )------------------------------------------------ */
72 /**
73 * @task restrictions
75 public static function isValidAddress($address) {
76 if (strlen($address) > self::MAX_ADDRESS_LENGTH) {
77 return false;
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
85 // from it.
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)) {
94 return false;
97 return true;
102 * @task restrictions
104 public static function describeValidAddresses() {
105 return pht(
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));
113 * @task restrictions
115 public static function isAllowedAddress($address) {
116 if (!self::isValidAddress($address)) {
117 return false;
120 $allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
121 if (!$allowed_domains) {
122 return true;
125 $addr_obj = new PhutilEmailAddress($address);
127 $domain = $addr_obj->getDomainName();
128 if (!$domain) {
129 return false;
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) {
136 return true;
140 return false;
145 * @task restrictions
147 public static function describeAllowedAddresses() {
148 $domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
149 if (!$domains) {
150 return null;
153 if (count($domains) == 1) {
154 return pht('Email address must be @%s', head($domains));
155 } else {
156 return pht(
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.
168 * @task restrictions
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.
184 * @return this
185 * @task email
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');
196 $signature = null;
197 if (!$is_serious) {
198 $signature = pht(
199 "Get Well Soon,\n%s",
200 PlatformSymbols::getPlatformServerName());
203 $body = sprintf(
204 "%s\n\n%s\n\n %s\n\n%s",
205 pht('Hi %s', $username),
206 pht(
207 'Please verify that you own this email address (%s) by '.
208 'clicking this link:',
209 $address),
210 $link,
211 $signature);
213 id(new PhabricatorMetaMTAMail())
214 ->addRawTos(array($address))
215 ->setForceDelivery(true)
216 ->setSubject(
217 pht(
218 '[%s] Email Verification',
219 PlatformSymbols::getPlatformServerName()))
220 ->setBody($body)
221 ->setRelatedPHID($user->getPHID())
222 ->saveAndSend();
224 return $this;
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.
234 * @return this
235 * @task email
237 public function sendOldPrimaryEmail(
238 PhabricatorUser $user,
239 PhabricatorUserEmail $new) {
240 $username = $user->getUsername();
242 $old_address = $this->getAddress();
243 $new_address = $new->getAddress();
245 $body = sprintf(
246 "%s\n\n%s\n",
247 pht('Hi %s', $username),
248 pht(
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 '.
251 'address (%s).',
252 $old_address,
253 $new_address));
255 id(new PhabricatorMetaMTAMail())
256 ->addRawTos(array($old_address))
257 ->setForceDelivery(true)
258 ->setSubject(
259 pht(
260 '[%s] Primary Address Changed',
261 PlatformSymbols::getPlatformServerName()))
262 ->setBody($body)
263 ->setFrom($user->getPHID())
264 ->setRelatedPHID($user->getPHID())
265 ->saveAndSend();
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.
274 * @return this
275 * @task email
277 public function sendNewPrimaryEmail(PhabricatorUser $user) {
278 $username = $user->getUsername();
280 $new_address = $this->getAddress();
282 $body = sprintf(
283 "%s\n\n%s\n",
284 pht('Hi %s', $username),
285 pht(
286 'This is now your primary email address (%s). Going forward, '.
287 'all email will be sent here.',
288 $new_address));
290 id(new PhabricatorMetaMTAMail())
291 ->addRawTos(array($new_address))
292 ->setForceDelivery(true)
293 ->setSubject(
294 pht(
295 '[%s] Primary Address Changed',
296 PlatformSymbols::getPlatformServerName()))
297 ->setBody($body)
298 ->setFrom($user->getPHID())
299 ->setRelatedPHID($user->getPHID())
300 ->saveAndSend();
302 return $this;
306 /* -( PhabricatorDestructibleInterface )----------------------------------- */
309 public function destroyObjectPermanently(
310 PhabricatorDestructionEngine $engine) {
311 $this->delete();
315 /* -( PhabricatorPolicyInterface )----------------------------------------- */
317 public function getCapabilities() {
318 return array(
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) {
335 return false;