Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/people/editor/PhabricatorUserEditor.php
12256 views
1
<?php
2
3
/**
4
* Editor class for creating and adjusting users. This class guarantees data
5
* integrity and writes logs when user information changes.
6
*
7
* @task config Configuration
8
* @task edit Creating and Editing Users
9
* @task role Editing Roles
10
* @task email Adding, Removing and Changing Email
11
* @task internal Internals
12
*/
13
final class PhabricatorUserEditor extends PhabricatorEditor {
14
15
private $logs = array();
16
17
18
/* -( Creating and Editing Users )----------------------------------------- */
19
20
21
/**
22
* @task edit
23
*/
24
public function createNewUser(
25
PhabricatorUser $user,
26
PhabricatorUserEmail $email,
27
$allow_reassign = false) {
28
29
if ($user->getID()) {
30
throw new Exception(pht('User has already been created!'));
31
}
32
33
$is_reassign = false;
34
if ($email->getID()) {
35
if ($allow_reassign) {
36
if ($email->getIsPrimary()) {
37
throw new Exception(
38
pht('Primary email addresses can not be reassigned.'));
39
}
40
$is_reassign = true;
41
} else {
42
throw new Exception(pht('Email has already been created!'));
43
}
44
}
45
46
if (!PhabricatorUser::validateUsername($user->getUsername())) {
47
$valid = PhabricatorUser::describeValidUsername();
48
throw new Exception(pht('Username is invalid! %s', $valid));
49
}
50
51
// Always set a new user's email address to primary.
52
$email->setIsPrimary(1);
53
54
// If the primary address is already verified, also set the verified flag
55
// on the user themselves.
56
if ($email->getIsVerified()) {
57
$user->setIsEmailVerified(1);
58
}
59
60
$this->willAddEmail($email);
61
62
$user->openTransaction();
63
try {
64
$user->save();
65
$email->setUserPHID($user->getPHID());
66
$email->save();
67
} catch (AphrontDuplicateKeyQueryException $ex) {
68
// We might have written the user but failed to write the email; if
69
// so, erase the IDs we attached.
70
$user->setID(null);
71
$user->setPHID(null);
72
73
$user->killTransaction();
74
throw $ex;
75
}
76
77
if ($is_reassign) {
78
$log = PhabricatorUserLog::initializeNewLog(
79
$this->requireActor(),
80
$user->getPHID(),
81
PhabricatorReassignEmailUserLogType::LOGTYPE);
82
$log->setNewValue($email->getAddress());
83
$log->save();
84
}
85
86
$user->saveTransaction();
87
88
if ($email->getIsVerified()) {
89
$this->didVerifyEmail($user, $email);
90
}
91
92
id(new DiffusionRepositoryIdentityEngine())
93
->didUpdateEmailAddress($email->getAddress());
94
95
return $this;
96
}
97
98
99
/* -( Editing Roles )------------------------------------------------------ */
100
101
/**
102
* @task role
103
*/
104
public function makeSystemAgentUser(PhabricatorUser $user, $system_agent) {
105
$actor = $this->requireActor();
106
107
if (!$user->getID()) {
108
throw new Exception(pht('User has not been created yet!'));
109
}
110
111
$user->openTransaction();
112
$user->beginWriteLocking();
113
114
$user->reload();
115
if ($user->getIsSystemAgent() == $system_agent) {
116
$user->endWriteLocking();
117
$user->killTransaction();
118
return $this;
119
}
120
121
$user->setIsSystemAgent((int)$system_agent);
122
$user->save();
123
124
$user->endWriteLocking();
125
$user->saveTransaction();
126
127
return $this;
128
}
129
130
/**
131
* @task role
132
*/
133
public function makeMailingListUser(PhabricatorUser $user, $mailing_list) {
134
$actor = $this->requireActor();
135
136
if (!$user->getID()) {
137
throw new Exception(pht('User has not been created yet!'));
138
}
139
140
$user->openTransaction();
141
$user->beginWriteLocking();
142
143
$user->reload();
144
if ($user->getIsMailingList() == $mailing_list) {
145
$user->endWriteLocking();
146
$user->killTransaction();
147
return $this;
148
}
149
150
$user->setIsMailingList((int)$mailing_list);
151
$user->save();
152
153
$user->endWriteLocking();
154
$user->saveTransaction();
155
156
return $this;
157
}
158
159
/* -( Adding, Removing and Changing Email )-------------------------------- */
160
161
162
/**
163
* @task email
164
*/
165
public function addEmail(
166
PhabricatorUser $user,
167
PhabricatorUserEmail $email) {
168
169
$actor = $this->requireActor();
170
171
if (!$user->getID()) {
172
throw new Exception(pht('User has not been created yet!'));
173
}
174
if ($email->getID()) {
175
throw new Exception(pht('Email has already been created!'));
176
}
177
178
// Use changePrimaryEmail() to change primary email.
179
$email->setIsPrimary(0);
180
$email->setUserPHID($user->getPHID());
181
182
$this->willAddEmail($email);
183
184
$user->openTransaction();
185
$user->beginWriteLocking();
186
187
$user->reload();
188
189
try {
190
$email->save();
191
} catch (AphrontDuplicateKeyQueryException $ex) {
192
$user->endWriteLocking();
193
$user->killTransaction();
194
195
throw $ex;
196
}
197
198
$log = PhabricatorUserLog::initializeNewLog(
199
$actor,
200
$user->getPHID(),
201
PhabricatorAddEmailUserLogType::LOGTYPE);
202
$log->setNewValue($email->getAddress());
203
$log->save();
204
205
$user->endWriteLocking();
206
$user->saveTransaction();
207
208
id(new DiffusionRepositoryIdentityEngine())
209
->didUpdateEmailAddress($email->getAddress());
210
211
return $this;
212
}
213
214
215
/**
216
* @task email
217
*/
218
public function removeEmail(
219
PhabricatorUser $user,
220
PhabricatorUserEmail $email) {
221
222
$actor = $this->requireActor();
223
224
if (!$user->getID()) {
225
throw new Exception(pht('User has not been created yet!'));
226
}
227
if (!$email->getID()) {
228
throw new Exception(pht('Email has not been created yet!'));
229
}
230
231
$user->openTransaction();
232
$user->beginWriteLocking();
233
234
$user->reload();
235
$email->reload();
236
237
if ($email->getIsPrimary()) {
238
throw new Exception(pht("Can't remove primary email!"));
239
}
240
if ($email->getUserPHID() != $user->getPHID()) {
241
throw new Exception(pht('Email not owned by user!'));
242
}
243
244
$destruction_engine = id(new PhabricatorDestructionEngine())
245
->setWaitToFinalizeDestruction(true)
246
->destroyObject($email);
247
248
$log = PhabricatorUserLog::initializeNewLog(
249
$actor,
250
$user->getPHID(),
251
PhabricatorRemoveEmailUserLogType::LOGTYPE);
252
$log->setOldValue($email->getAddress());
253
$log->save();
254
255
$user->endWriteLocking();
256
$user->saveTransaction();
257
258
$this->revokePasswordResetLinks($user);
259
$destruction_engine->finalizeDestruction();
260
261
return $this;
262
}
263
264
265
/**
266
* @task email
267
*/
268
public function changePrimaryEmail(
269
PhabricatorUser $user,
270
PhabricatorUserEmail $email) {
271
$actor = $this->requireActor();
272
273
if (!$user->getID()) {
274
throw new Exception(pht('User has not been created yet!'));
275
}
276
if (!$email->getID()) {
277
throw new Exception(pht('Email has not been created yet!'));
278
}
279
280
$user->openTransaction();
281
$user->beginWriteLocking();
282
283
$user->reload();
284
$email->reload();
285
286
if ($email->getUserPHID() != $user->getPHID()) {
287
throw new Exception(pht('User does not own email!'));
288
}
289
290
if ($email->getIsPrimary()) {
291
throw new Exception(pht('Email is already primary!'));
292
}
293
294
if (!$email->getIsVerified()) {
295
throw new Exception(pht('Email is not verified!'));
296
}
297
298
$old_primary = $user->loadPrimaryEmail();
299
if ($old_primary) {
300
$old_primary->setIsPrimary(0);
301
$old_primary->save();
302
}
303
304
$email->setIsPrimary(1);
305
$email->save();
306
307
// If the user doesn't have the verified flag set on their account
308
// yet, set it. We've made sure the email is verified above. See
309
// T12635 for discussion.
310
if (!$user->getIsEmailVerified()) {
311
$user->setIsEmailVerified(1);
312
$user->save();
313
}
314
315
$log = PhabricatorUserLog::initializeNewLog(
316
$actor,
317
$user->getPHID(),
318
PhabricatorPrimaryEmailUserLogType::LOGTYPE);
319
$log->setOldValue($old_primary ? $old_primary->getAddress() : null);
320
$log->setNewValue($email->getAddress());
321
322
$log->save();
323
324
$user->endWriteLocking();
325
$user->saveTransaction();
326
327
if ($old_primary) {
328
$old_primary->sendOldPrimaryEmail($user, $email);
329
}
330
$email->sendNewPrimaryEmail($user);
331
332
$this->revokePasswordResetLinks($user);
333
334
return $this;
335
}
336
337
338
/**
339
* Verify a user's email address.
340
*
341
* This verifies an individual email address. If the address is the user's
342
* primary address and their account was not previously verified, their
343
* account is marked as email verified.
344
*
345
* @task email
346
*/
347
public function verifyEmail(
348
PhabricatorUser $user,
349
PhabricatorUserEmail $email) {
350
$actor = $this->requireActor();
351
352
if (!$user->getID()) {
353
throw new Exception(pht('User has not been created yet!'));
354
}
355
if (!$email->getID()) {
356
throw new Exception(pht('Email has not been created yet!'));
357
}
358
359
$user->openTransaction();
360
$user->beginWriteLocking();
361
362
$user->reload();
363
$email->reload();
364
365
if ($email->getUserPHID() != $user->getPHID()) {
366
throw new Exception(pht('User does not own email!'));
367
}
368
369
if (!$email->getIsVerified()) {
370
$email->setIsVerified(1);
371
$email->save();
372
373
$log = PhabricatorUserLog::initializeNewLog(
374
$actor,
375
$user->getPHID(),
376
PhabricatorVerifyEmailUserLogType::LOGTYPE);
377
$log->setNewValue($email->getAddress());
378
$log->save();
379
}
380
381
if (!$user->getIsEmailVerified()) {
382
// If the user just verified their primary email address, mark their
383
// account as email verified.
384
$user_primary = $user->loadPrimaryEmail();
385
if ($user_primary->getID() == $email->getID()) {
386
$user->setIsEmailVerified(1);
387
$user->save();
388
}
389
}
390
391
$user->endWriteLocking();
392
$user->saveTransaction();
393
394
$this->didVerifyEmail($user, $email);
395
}
396
397
398
/**
399
* Reassign an unverified email address.
400
*/
401
public function reassignEmail(
402
PhabricatorUser $user,
403
PhabricatorUserEmail $email) {
404
$actor = $this->requireActor();
405
406
if (!$user->getID()) {
407
throw new Exception(pht('User has not been created yet!'));
408
}
409
410
if (!$email->getID()) {
411
throw new Exception(pht('Email has not been created yet!'));
412
}
413
414
$user->openTransaction();
415
$user->beginWriteLocking();
416
417
$user->reload();
418
$email->reload();
419
420
$old_user = $email->getUserPHID();
421
422
if ($old_user != $user->getPHID()) {
423
if ($email->getIsVerified()) {
424
throw new Exception(
425
pht('Verified email addresses can not be reassigned.'));
426
}
427
if ($email->getIsPrimary()) {
428
throw new Exception(
429
pht('Primary email addresses can not be reassigned.'));
430
}
431
432
$email->setUserPHID($user->getPHID());
433
$email->save();
434
435
$log = PhabricatorUserLog::initializeNewLog(
436
$actor,
437
$user->getPHID(),
438
PhabricatorReassignEmailUserLogType::LOGTYPE);
439
$log->setNewValue($email->getAddress());
440
$log->save();
441
}
442
443
$user->endWriteLocking();
444
$user->saveTransaction();
445
446
id(new DiffusionRepositoryIdentityEngine())
447
->didUpdateEmailAddress($email->getAddress());
448
}
449
450
451
/* -( Internals )---------------------------------------------------------- */
452
453
454
/**
455
* @task internal
456
*/
457
private function willAddEmail(PhabricatorUserEmail $email) {
458
459
// Hard check before write to prevent creation of disallowed email
460
// addresses. Normally, the application does checks and raises more
461
// user friendly errors for us, but we omit the courtesy checks on some
462
// pathways like administrative scripts for simplicity.
463
464
if (!PhabricatorUserEmail::isValidAddress($email->getAddress())) {
465
throw new Exception(PhabricatorUserEmail::describeValidAddresses());
466
}
467
468
if (!PhabricatorUserEmail::isAllowedAddress($email->getAddress())) {
469
throw new Exception(PhabricatorUserEmail::describeAllowedAddresses());
470
}
471
472
$application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())
473
->setViewer(PhabricatorUser::getOmnipotentUser())
474
->withAddresses(array($email->getAddress()))
475
->executeOne();
476
if ($application_email) {
477
throw new Exception($application_email->getInUseMessage());
478
}
479
}
480
481
public function revokePasswordResetLinks(PhabricatorUser $user) {
482
// Revoke any outstanding password reset links. If an attacker compromises
483
// an account, changes the email address, and sends themselves a password
484
// reset link, it could otherwise remain live for a short period of time
485
// and allow them to compromise the account again later.
486
487
PhabricatorAuthTemporaryToken::revokeTokens(
488
$user,
489
array($user->getPHID()),
490
array(
491
PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE,
492
PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE,
493
));
494
}
495
496
private function didVerifyEmail(
497
PhabricatorUser $user,
498
PhabricatorUserEmail $email) {
499
500
$event_type = PhabricatorEventType::TYPE_AUTH_DIDVERIFYEMAIL;
501
$event_data = array(
502
'user' => $user,
503
'email' => $email,
504
);
505
506
$event = id(new PhabricatorEvent($event_type, $event_data))
507
->setUser($user);
508
PhutilEventEngine::dispatchEvent($event);
509
}
510
511
512
}
513
514