Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/people/storage/PhabricatorUserEmail.php
12256 views
1
<?php
2
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 {
12
13
protected $userPHID;
14
protected $address;
15
protected $isVerified;
16
protected $isPrimary;
17
protected $verificationCode;
18
19
private $user = self::ATTACHABLE;
20
21
const MAX_ADDRESS_LENGTH = 128;
22
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?',
31
),
32
self::CONFIG_KEY_SCHEMA => array(
33
'address' => array(
34
'columns' => array('address'),
35
'unique' => true,
36
),
37
'userPHID' => array(
38
'columns' => array('userPHID', 'isPrimary'),
39
),
40
),
41
) + parent::getConfiguration();
42
}
43
44
public function getPHIDType() {
45
return PhabricatorPeopleUserEmailPHIDType::TYPECONST;
46
}
47
48
public function getVerificationURI() {
49
return '/emailverify/'.$this->getVerificationCode().'/';
50
}
51
52
public function save() {
53
if (!$this->verificationCode) {
54
$this->setVerificationCode(Filesystem::readRandomCharacters(24));
55
}
56
return parent::save();
57
}
58
59
public function attachUser(PhabricatorUser $user) {
60
$this->user = $user;
61
return $this;
62
}
63
64
public function getUser() {
65
return $this->assertAttached($this->user);
66
}
67
68
69
/* -( Domain Restrictions )------------------------------------------------ */
70
71
72
/**
73
* @task restrictions
74
*/
75
public static function isValidAddress($address) {
76
if (strlen($address) > self::MAX_ADDRESS_LENGTH) {
77
return false;
78
}
79
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:
83
//
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.
88
//
89
// To this end, we're roughly verifying that there's some normal text, an
90
// "@" symbol, and then some more normal text.
91
92
$email_regex = '(^[a-z0-9_+.!-]+@[a-z0-9_+:.-]+\z)i';
93
if (!preg_match($email_regex, $address)) {
94
return false;
95
}
96
97
return true;
98
}
99
100
101
/**
102
* @task restrictions
103
*/
104
public static function describeValidAddresses() {
105
return pht(
106
'Email addresses should be in the form "[email protected]". The maximum '.
107
'length of an email address is %s characters.',
108
new PhutilNumber(self::MAX_ADDRESS_LENGTH));
109
}
110
111
112
/**
113
* @task restrictions
114
*/
115
public static function isAllowedAddress($address) {
116
if (!self::isValidAddress($address)) {
117
return false;
118
}
119
120
$allowed_domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
121
if (!$allowed_domains) {
122
return true;
123
}
124
125
$addr_obj = new PhutilEmailAddress($address);
126
127
$domain = $addr_obj->getDomainName();
128
if (!$domain) {
129
return false;
130
}
131
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;
137
}
138
}
139
140
return false;
141
}
142
143
144
/**
145
* @task restrictions
146
*/
147
public static function describeAllowedAddresses() {
148
$domains = PhabricatorEnv::getEnvConfig('auth.email-domains');
149
if (!$domains) {
150
return null;
151
}
152
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));
159
}
160
}
161
162
163
/**
164
* Check if this install requires email verification.
165
*
166
* @return bool True if email addresses must be verified.
167
*
168
* @task restrictions
169
*/
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');
174
}
175
176
177
/* -( Email About Email )-------------------------------------------------- */
178
179
180
/**
181
* Send a verification email from $user to this address.
182
*
183
* @param PhabricatorUser The user sending the verification.
184
* @return this
185
* @task email
186
*/
187
public function sendVerificationEmail(PhabricatorUser $user) {
188
$username = $user->getUsername();
189
190
$address = $this->getAddress();
191
$link = PhabricatorEnv::getProductionURI($this->getVerificationURI());
192
193
194
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
195
196
$signature = null;
197
if (!$is_serious) {
198
$signature = pht(
199
"Get Well Soon,\n%s",
200
PlatformSymbols::getPlatformServerName());
201
}
202
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);
212
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();
223
224
return $this;
225
}
226
227
228
/**
229
* Send a notification email from $user to this address, informing the
230
* recipient that this is no longer their account's primary address.
231
*
232
* @param PhabricatorUser The user sending the notification.
233
* @param PhabricatorUserEmail New primary email address.
234
* @return this
235
* @task email
236
*/
237
public function sendOldPrimaryEmail(
238
PhabricatorUser $user,
239
PhabricatorUserEmail $new) {
240
$username = $user->getUsername();
241
242
$old_address = $this->getAddress();
243
$new_address = $new->getAddress();
244
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));
254
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();
266
}
267
268
269
/**
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.
272
*
273
* @param PhabricatorUser The user sending the verification.
274
* @return this
275
* @task email
276
*/
277
public function sendNewPrimaryEmail(PhabricatorUser $user) {
278
$username = $user->getUsername();
279
280
$new_address = $this->getAddress();
281
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));
289
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();
301
302
return $this;
303
}
304
305
306
/* -( PhabricatorDestructibleInterface )----------------------------------- */
307
308
309
public function destroyObjectPermanently(
310
PhabricatorDestructionEngine $engine) {
311
$this->delete();
312
}
313
314
315
/* -( PhabricatorPolicyInterface )----------------------------------------- */
316
317
public function getCapabilities() {
318
return array(
319
PhabricatorPolicyCapability::CAN_VIEW,
320
PhabricatorPolicyCapability::CAN_EDIT,
321
);
322
}
323
324
public function getPolicy($capability) {
325
$user = $this->getUser();
326
327
if ($this->getIsSystemAgent() || $this->getIsMailingList()) {
328
return PhabricatorPolicies::POLICY_ADMIN;
329
}
330
331
return $user->getPHID();
332
}
333
334
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
335
return false;
336
}
337
338
}
339
340