Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/controller/PhabricatorAuthRegisterController.php
13401 views
1
<?php
2
3
final class PhabricatorAuthRegisterController
4
extends PhabricatorAuthController {
5
6
public function shouldRequireLogin() {
7
return false;
8
}
9
10
private function emailToUsername($email) {
11
$mangled_email = str_ireplace('@freebsd.org', '', $email);
12
$mangled_email = str_replace('@', '_', $mangled_email);
13
$mangled_email = str_replace('+', '_', $mangled_email);
14
return $mangled_email;
15
}
16
17
public function handleRequest(AphrontRequest $request) {
18
$viewer = $this->getViewer();
19
$account_key = $request->getURIData('akey');
20
21
if ($viewer->isLoggedIn()) {
22
return id(new AphrontRedirectResponse())->setURI('/');
23
}
24
25
$invite = $this->loadInvite();
26
27
$is_setup = false;
28
if ($account_key !== null && strlen($account_key)) {
29
$result = $this->loadAccountForRegistrationOrLinking($account_key);
30
list($account, $provider, $response) = $result;
31
$is_default = false;
32
} else if ($this->isFirstTimeSetup()) {
33
$account = null;
34
$provider = null;
35
$response = null;
36
$is_default = true;
37
$is_setup = true;
38
} else {
39
list($account, $provider, $response) = $this->loadDefaultAccount($invite);
40
$is_default = true;
41
}
42
43
if ($response) {
44
return $response;
45
}
46
47
if (!$is_setup) {
48
if (!$provider->shouldAllowRegistration()) {
49
if ($invite) {
50
// If the user has an invite, we allow them to register with any
51
// provider, even a login-only provider.
52
} else {
53
// TODO: This is a routine error if you click "Login" on an external
54
// auth source which doesn't allow registration. The error should be
55
// more tailored.
56
57
return $this->renderError(
58
pht(
59
'The account you are attempting to register with uses an '.
60
'authentication provider ("%s") which does not allow '.
61
'registration. An administrator may have recently disabled '.
62
'registration with this provider.',
63
$provider->getProviderName()));
64
}
65
}
66
}
67
68
$errors = array();
69
70
$user = new PhabricatorUser();
71
72
if ($is_setup) {
73
$default_username = null;
74
$default_realname = null;
75
$default_email = null;
76
} else {
77
$default_realname = $account->getRealName();
78
$default_email = $account->getEmail();
79
$fbsd_username = $this->emailToUsername($default_email);
80
}
81
82
$account_type = PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT;
83
$content_source = PhabricatorContentSource::newFromRequest($request);
84
85
if ($invite) {
86
$default_email = $invite->getEmailAddress();
87
}
88
89
if ($default_email !== null) {
90
if (!PhabricatorUserEmail::isValidAddress($default_email)) {
91
$errors[] = pht(
92
'The email address associated with this external account ("%s") is '.
93
'not a valid email address and can not be used to register an '.
94
'account. Choose a different, valid address.',
95
phutil_tag('strong', array(), $default_email));
96
$default_email = null;
97
}
98
}
99
100
if ($default_email !== null) {
101
// We should bypass policy here because e.g. limiting an application use
102
// to a subset of users should not allow the others to overwrite
103
// configured application emails.
104
$application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())
105
->setViewer(PhabricatorUser::getOmnipotentUser())
106
->withAddresses(array($default_email))
107
->executeOne();
108
if ($application_email) {
109
$errors[] = pht(
110
'The email address associated with this account ("%s") is '.
111
'already in use by an application and can not be used to '.
112
'register a new account. Choose a different, valid address.',
113
phutil_tag('strong', array(), $default_email));
114
$default_email = null;
115
}
116
}
117
118
$show_existing = null;
119
if ($default_email !== null) {
120
// If the account source provided an email, but it's not allowed by
121
// the configuration, roadblock the user. Previously, we let the user
122
// pick a valid email address instead, but this does not align well with
123
// user expectation and it's not clear the cases it enables are valuable.
124
// See discussion in T3472.
125
if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {
126
$debug_email = new PHUIInvisibleCharacterView($default_email);
127
return $this->renderError(
128
array(
129
pht(
130
'The account you are attempting to register with has an invalid '.
131
'email address (%s). This server only allows registration with '.
132
'specific email addresses:',
133
$debug_email),
134
phutil_tag('br'),
135
phutil_tag('br'),
136
PhabricatorUserEmail::describeAllowedAddresses(),
137
));
138
}
139
140
// If the account source provided an email, but another account already
141
// has that email, just pretend we didn't get an email.
142
if ($default_email !== null) {
143
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
144
'address = %s',
145
$default_email);
146
if ($same_email) {
147
if ($invite) {
148
// We're allowing this to continue. The fact that we loaded the
149
// invite means that the address is nonprimary and unverified and
150
// we're OK to steal it.
151
} else {
152
$show_existing = $default_email;
153
$default_email = null;
154
}
155
}
156
}
157
}
158
159
if ($show_existing !== null) {
160
if (!$request->getInt('phase')) {
161
return $this->newDialog()
162
->setTitle(pht('Email Address Already in Use'))
163
->addHiddenInput('phase', 1)
164
->appendParagraph(
165
pht(
166
'You are creating a new account linked to an existing '.
167
'external account.'))
168
->appendParagraph(
169
pht(
170
'The email address ("%s") associated with the external account '.
171
'is already in use by an existing %s account. Multiple '.
172
'%s accounts may not have the same email address, so '.
173
'you can not use this email address to register a new account.',
174
phutil_tag('strong', array(), $show_existing),
175
PlatformSymbols::getPlatformServerName(),
176
PlatformSymbols::getPlatformServerName()))
177
->appendParagraph(
178
pht(
179
'If you want to register a new account, continue with this '.
180
'registration workflow and choose a new, unique email address '.
181
'for the new account.'))
182
->appendParagraph(
183
pht(
184
'If you want to link an existing %s account to this '.
185
'external account, do not continue. Instead: log in to your '.
186
'existing account, then go to "Settings" and link the account '.
187
'in the "External Accounts" panel.',
188
PlatformSymbols::getPlatformServerName()))
189
->appendParagraph(
190
pht(
191
'If you continue, you will create a new account. You will not '.
192
'be able to link this external account to an existing account.'))
193
->addCancelButton('/auth/login/', pht('Cancel'))
194
->addSubmitButton(pht('Create New Account'));
195
} else {
196
$errors[] = pht(
197
'The external account you are registering with has an email address '.
198
'that is already in use ("%s") by an existing %s account. '.
199
'Choose a new, valid email address to register a new account.',
200
phutil_tag('strong', array(), $show_existing),
201
PlatformSymbols::getPlatformServerName());
202
}
203
}
204
205
$profile = id(new PhabricatorRegistrationProfile())
206
->setDefaultUsername($fbsd_username)
207
->setDefaultEmail($default_email)
208
->setDefaultRealName($default_realname)
209
->setCanEditUsername(true)
210
->setCanEditEmail(($default_email === null))
211
->setCanEditRealName(true)
212
->setShouldVerifyEmail(false);
213
214
$event_type = PhabricatorEventType::TYPE_AUTH_WILLREGISTERUSER;
215
$event_data = array(
216
'account' => $account,
217
'profile' => $profile,
218
);
219
220
$event = id(new PhabricatorEvent($event_type, $event_data))
221
->setUser($user);
222
PhutilEventEngine::dispatchEvent($event);
223
224
$fbsd_username = $profile->getDefaultUsername();
225
$default_email = $profile->getDefaultEmail();
226
$default_realname = $profile->getDefaultRealName();
227
228
$can_edit_username = false;
229
$can_edit_email = $profile->getCanEditEmail();
230
$can_edit_realname = $profile->getCanEditRealName();
231
232
if ($is_setup) {
233
$must_set_password = false;
234
} else {
235
$must_set_password = $provider->shouldRequireRegistrationPassword();
236
}
237
238
$can_edit_anything = $profile->getCanEditAnything() || $must_set_password;
239
$force_verify = $profile->getShouldVerifyEmail();
240
241
// Automatically verify the administrator's email address during first-time
242
// setup.
243
if ($is_setup) {
244
$force_verify = true;
245
}
246
247
$value_username = $fbsd_username;
248
$value_realname = $default_realname;
249
$value_email = $default_email;
250
$value_password = null;
251
252
$require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name');
253
254
$e_username = phutil_nonempty_string($value_username) ? null : true;
255
$e_realname = $require_real_name ? true : null;
256
$e_email = phutil_nonempty_string($value_email) ? null : true;
257
$e_password = true;
258
$e_captcha = true;
259
260
$skip_captcha = false;
261
if ($invite) {
262
// If the user is accepting an invite, assume they're trustworthy enough
263
// that we don't need to CAPTCHA them.
264
$skip_captcha = true;
265
}
266
267
$min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
268
$min_len = (int)$min_len;
269
270
$from_invite = $request->getStr('invite');
271
if ($from_invite && $can_edit_username) {
272
$value_username = $request->getStr('username');
273
$e_username = null;
274
}
275
276
$try_register =
277
($request->isFormPost() || !$can_edit_anything) &&
278
!$from_invite &&
279
($request->getInt('phase') != 1);
280
281
if ($try_register) {
282
$errors = array();
283
284
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
285
286
if ($must_set_password && !$skip_captcha) {
287
$e_captcha = pht('Again');
288
289
$captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
290
if (!$captcha_ok) {
291
$errors[] = pht('Captcha response is incorrect, try again.');
292
$e_captcha = pht('Invalid');
293
}
294
}
295
296
if ($can_edit_username) {
297
$value_username = $request->getStr('username');
298
if (!phutil_nonempty_string($value_username)) {
299
$e_username = pht('Required');
300
$errors[] = pht('Username is required.');
301
} else if (!PhabricatorUser::validateUsername($value_username)) {
302
$e_username = pht('Invalid');
303
$errors[] = PhabricatorUser::describeValidUsername();
304
} else {
305
$e_username = null;
306
}
307
}
308
309
if ($must_set_password) {
310
$value_password = $request->getStr('password');
311
$value_confirm = $request->getStr('confirm');
312
313
$password_envelope = new PhutilOpaqueEnvelope($value_password);
314
$confirm_envelope = new PhutilOpaqueEnvelope($value_confirm);
315
316
$engine = id(new PhabricatorAuthPasswordEngine())
317
->setViewer($user)
318
->setContentSource($content_source)
319
->setPasswordType($account_type)
320
->setObject($user);
321
322
try {
323
$engine->checkNewPassword($password_envelope, $confirm_envelope);
324
$e_password = null;
325
} catch (PhabricatorAuthPasswordException $ex) {
326
$errors[] = $ex->getMessage();
327
$e_password = $ex->getPasswordError();
328
}
329
}
330
331
if ($can_edit_email) {
332
$value_email = $request->getStr('email');
333
if (!phutil_nonempty_string($value_email)) {
334
$e_email = pht('Required');
335
$errors[] = pht('Email is required.');
336
} else if (!PhabricatorUserEmail::isValidAddress($value_email)) {
337
$e_email = pht('Invalid');
338
$errors[] = PhabricatorUserEmail::describeValidAddresses();
339
} else if (!PhabricatorUserEmail::isAllowedAddress($value_email)) {
340
$e_email = pht('Disallowed');
341
$errors[] = PhabricatorUserEmail::describeAllowedAddresses();
342
} else {
343
$e_email = null;
344
}
345
}
346
347
if ($account->getAccountType() != 'ldap') {
348
$value_username = $this->emailToUsername($value_email);
349
}
350
351
if ($can_edit_realname) {
352
$value_realname = $request->getStr('realName');
353
if (!phutil_nonempty_string($value_realname) && $require_real_name) {
354
$e_realname = pht('Required');
355
$errors[] = pht('Real name is required.');
356
} else {
357
$e_realname = null;
358
}
359
}
360
361
if (!$errors) {
362
if (!$is_setup) {
363
$image = $this->loadProfilePicture($account);
364
if ($image) {
365
$user->setProfileImagePHID($image->getPHID());
366
}
367
}
368
369
try {
370
$verify_email = false;
371
372
if ($force_verify) {
373
$verify_email = true;
374
}
375
376
if (!$is_setup) {
377
if ($value_email === $default_email) {
378
if ($account->getEmailVerified()) {
379
$verify_email = true;
380
}
381
382
if ($provider->shouldTrustEmails()) {
383
$verify_email = true;
384
}
385
386
if ($invite) {
387
$verify_email = true;
388
}
389
}
390
}
391
392
$email_obj = null;
393
if ($invite) {
394
// If we have a valid invite, this email may exist but be
395
// nonprimary and unverified, so we'll reassign it.
396
$email_obj = id(new PhabricatorUserEmail())->loadOneWhere(
397
'address = %s',
398
$value_email);
399
}
400
if (!$email_obj) {
401
$email_obj = id(new PhabricatorUserEmail())
402
->setAddress($value_email);
403
}
404
405
$email_obj->setIsVerified((int)$verify_email);
406
407
$user->setUsername($value_username);
408
$user->setRealname($value_realname);
409
410
if ($is_setup) {
411
$must_approve = false;
412
} else if ($invite) {
413
$must_approve = false;
414
} else {
415
$must_approve = PhabricatorEnv::getEnvConfig(
416
'auth.require-approval');
417
}
418
419
if ($must_approve) {
420
$user->setIsApproved(0);
421
} else {
422
$user->setIsApproved(1);
423
}
424
425
if ($invite) {
426
$allow_reassign_email = true;
427
} else {
428
$allow_reassign_email = false;
429
}
430
431
$user->openTransaction();
432
433
$editor = id(new PhabricatorUserEditor())
434
->setActor($user);
435
436
$editor->createNewUser($user, $email_obj, $allow_reassign_email);
437
if ($must_set_password) {
438
$password_object = PhabricatorAuthPassword::initializeNewPassword(
439
$user,
440
$account_type);
441
442
$password_object
443
->setPassword($password_envelope, $user)
444
->save();
445
}
446
447
if ($is_setup) {
448
$xactions = array();
449
$xactions[] = id(new PhabricatorUserTransaction())
450
->setTransactionType(
451
PhabricatorUserEmpowerTransaction::TRANSACTIONTYPE)
452
->setNewValue(true);
453
454
$actor = PhabricatorUser::getOmnipotentUser();
455
$content_source = PhabricatorContentSource::newFromRequest(
456
$request);
457
458
$people_application_phid = id(new PhabricatorPeopleApplication())
459
->getPHID();
460
461
$transaction_editor = id(new PhabricatorUserTransactionEditor())
462
->setActor($actor)
463
->setActingAsPHID($people_application_phid)
464
->setContentSource($content_source)
465
->setContinueOnMissingFields(true);
466
467
$transaction_editor->applyTransactions($user, $xactions);
468
}
469
470
if (!$is_setup) {
471
$account->setUserPHID($user->getPHID());
472
$account->save();
473
}
474
475
$user->saveTransaction();
476
477
if (!$email_obj->getIsVerified()) {
478
$email_obj->sendVerificationEmail($user);
479
}
480
481
if ($must_approve) {
482
$this->sendWaitingForApprovalEmail($user);
483
}
484
485
if ($invite) {
486
$invite->setAcceptedByPHID($user->getPHID())->save();
487
}
488
489
return $this->loginUser($user);
490
} catch (AphrontDuplicateKeyQueryException $exception) {
491
$same_username = id(new PhabricatorUser())->loadOneWhere(
492
'userName = %s',
493
$user->getUserName());
494
495
$same_email = id(new PhabricatorUserEmail())->loadOneWhere(
496
'address = %s',
497
$value_email);
498
499
if ($same_username) {
500
$e_username = pht('Duplicate');
501
$errors[] = pht('Another user already has that username.');
502
}
503
504
if ($same_email) {
505
// TODO: See T3340.
506
$e_email = pht('Duplicate');
507
$errors[] = pht('Another user already has that email.');
508
}
509
510
if (!$same_username && !$same_email) {
511
throw $exception;
512
}
513
}
514
}
515
516
unset($unguarded);
517
}
518
519
$form = id(new AphrontFormView())
520
->setUser($request->getUser())
521
->addHiddenInput('phase', 2);
522
523
if (!$is_default) {
524
$form->appendChild(
525
id(new AphrontFormMarkupControl())
526
->setLabel(pht('External Account'))
527
->setValue(
528
id(new PhabricatorAuthAccountView())
529
->setUser($request->getUser())
530
->setExternalAccount($account)
531
->setAuthProvider($provider)));
532
}
533
534
if ($can_edit_username) {
535
$form->appendChild(
536
id(new AphrontFormTextControl())
537
->setLabel(pht('Username'))
538
->setName('username')
539
->setValue($value_username));
540
} else {
541
$form->appendChild(
542
id(new AphrontFormMarkupControl())
543
->setLabel(pht('Username'))
544
->setValue('automatically set based on email address'));
545
}
546
547
if ($can_edit_realname) {
548
$form->appendChild(
549
id(new AphrontFormTextControl())
550
->setLabel(pht('Real Name'))
551
->setName('realName')
552
->setValue($value_realname)
553
->setError($e_realname));
554
}
555
556
if ($must_set_password) {
557
$form->appendChild(
558
id(new AphrontFormPasswordControl())
559
->setLabel(pht('Password'))
560
->setName('password')
561
->setError($e_password));
562
$form->appendChild(
563
id(new AphrontFormPasswordControl())
564
->setLabel(pht('Confirm Password'))
565
->setName('confirm')
566
->setError($e_password)
567
->setCaption(
568
$min_len
569
? pht('Minimum length of %d characters.', $min_len)
570
: null));
571
}
572
573
if ($can_edit_email) {
574
$form->appendChild(
575
id(new AphrontFormTextControl())
576
->setLabel(pht('Email'))
577
->setName('email')
578
->setValue($value_email)
579
->setCaption(pht('public information'))
580
->setError($e_email));
581
}
582
583
if ($must_set_password && !$skip_captcha) {
584
$form->appendChild(
585
id(new AphrontFormRecaptchaControl())
586
->setLabel(pht('Captcha'))
587
->setError($e_captcha));
588
}
589
590
$submit = id(new AphrontFormSubmitControl());
591
592
if ($is_setup) {
593
$submit
594
->setValue(pht('Create Admin Account'));
595
} else {
596
$submit
597
->addCancelButton($this->getApplicationURI('start/'))
598
->setValue(pht('Register Account'));
599
}
600
601
602
$form->appendChild($submit);
603
604
$crumbs = $this->buildApplicationCrumbs();
605
606
if ($is_setup) {
607
$crumbs->addTextCrumb(pht('Setup Admin Account'));
608
$title = pht(
609
'Welcome to %s',
610
PlatformSymbols::getPlatformServerName());
611
} else {
612
$crumbs->addTextCrumb(pht('Register'));
613
$crumbs->addTextCrumb($provider->getProviderName());
614
$title = pht('Create a New Account');
615
}
616
$crumbs->setBorder(true);
617
618
$welcome_view = null;
619
if ($is_setup) {
620
$welcome_view = id(new PHUIInfoView())
621
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
622
->setTitle(
623
pht(
624
'Welcome to %s',
625
PlatformSymbols::getPlatformServerName()))
626
->appendChild(
627
pht(
628
'Installation is complete. Register your administrator account '.
629
'below to log in. You will be able to configure options and add '.
630
'authentication mechanisms later on.'));
631
}
632
633
$freebsd_view = id(new PHUIInfoView())
634
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
635
->setTitle(pht('Note to new registration'))
636
->appendChild(
637
pht(
638
'Due to spam, after creating new account, please send an email to '.
639
'<phabric-admin AT FreeBSD.org> from the registered email address '.
640
'and briefly describe your plan for using your account, '.
641
'in order to have it activated.'));
642
643
$object_box = id(new PHUIObjectBoxView())
644
->setForm($form)
645
->setFormErrors($errors);
646
647
$invite_header = null;
648
if ($invite) {
649
$invite_header = $this->renderInviteHeader($invite);
650
}
651
652
$header = id(new PHUIHeaderView())
653
->setHeader($title);
654
655
$view = id(new PHUITwoColumnView())
656
->setHeader($header)
657
->setFooter(
658
array(
659
$welcome_view,
660
$freebsd_view,
661
$invite_header,
662
$object_box,
663
));
664
665
return $this->newPage()
666
->setTitle($title)
667
->setCrumbs($crumbs)
668
->appendChild($view);
669
}
670
671
private function loadDefaultAccount($invite) {
672
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
673
$account = null;
674
$provider = null;
675
$response = null;
676
677
foreach ($providers as $key => $candidate_provider) {
678
if (!$invite) {
679
if (!$candidate_provider->shouldAllowRegistration()) {
680
unset($providers[$key]);
681
continue;
682
}
683
}
684
685
if (!$candidate_provider->isDefaultRegistrationProvider()) {
686
unset($providers[$key]);
687
}
688
}
689
690
if (!$providers) {
691
$response = $this->renderError(
692
pht(
693
'There are no configured default registration providers.'));
694
return array($account, $provider, $response);
695
} else if (count($providers) > 1) {
696
$response = $this->renderError(
697
pht('There are too many configured default registration providers.'));
698
return array($account, $provider, $response);
699
}
700
701
$provider = head($providers);
702
$account = $provider->newDefaultExternalAccount();
703
704
return array($account, $provider, $response);
705
}
706
707
private function loadProfilePicture(PhabricatorExternalAccount $account) {
708
$phid = $account->getProfileImagePHID();
709
if (!$phid) {
710
return null;
711
}
712
713
// NOTE: Use of omnipotent user is okay here because the registering user
714
// can not control the field value, and we can't use their user object to
715
// do meaningful policy checks anyway since they have not registered yet.
716
// Reaching this means the user holds the account secret key and the
717
// registration secret key, and thus has permission to view the image.
718
719
$file = id(new PhabricatorFileQuery())
720
->setViewer(PhabricatorUser::getOmnipotentUser())
721
->withPHIDs(array($phid))
722
->executeOne();
723
if (!$file) {
724
return null;
725
}
726
727
$xform = PhabricatorFileTransform::getTransformByKey(
728
PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
729
return $xform->executeTransform($file);
730
}
731
732
protected function renderError($message) {
733
return $this->renderErrorPage(
734
pht('Registration Failed'),
735
array($message));
736
}
737
738
private function sendWaitingForApprovalEmail(PhabricatorUser $user) {
739
$title = pht(
740
'[%s] New User "%s" Awaiting Approval',
741
PlatformSymbols::getPlatformServerName(),
742
$user->getUsername());
743
744
$body = new PhabricatorMetaMTAMailBody();
745
746
$body->addRawSection(
747
pht(
748
'Newly registered user "%s" is awaiting account approval by an '.
749
'administrator.',
750
$user->getUsername()));
751
752
$body->addLinkSection(
753
pht('APPROVAL QUEUE'),
754
PhabricatorEnv::getProductionURI(
755
'/people/query/approval/'));
756
757
$body->addLinkSection(
758
pht('DISABLE APPROVAL QUEUE'),
759
PhabricatorEnv::getProductionURI(
760
'/config/edit/auth.require-approval/'));
761
762
$admins = id(new PhabricatorPeopleQuery())
763
->setViewer(PhabricatorUser::getOmnipotentUser())
764
->withIsAdmin(true)
765
->execute();
766
767
if (!$admins) {
768
return;
769
}
770
771
$mail = id(new PhabricatorMetaMTAMail())
772
->addCCs(mpull($admins, 'getPHID'))
773
->setSubject($title)
774
->setBody($body->render())
775
->saveAndSend();
776
}
777
778
}
779
780