Page MenuHomePhabricator

New account e-mail invites do not allow registration on auth providers that do not allow registration
Open, Needs TriagePublic

Description

According to D11737

All providers allow registration if you have an invite.

however this does not appear to be the case.

To reproduce:

  • Create a new phabricator install
  • Enable Username/Password authentication provider
  • Ensure Allow Login is ticked/enabled and Allow Registration is unticked/disabled
  • Create a new e-mail invitiation in People > Invites > Invite Users
  • Attempt to register a new account with the URL provided in the e-mail

Expected: After entering the desired username and clicking 'Continue' the remainder of the account details should be prompted for the user to enter

Actual: Error message Registration Failed: There are no configured default registration providers

Detail:

In src/applications/auth/controller/PhabricatorAuthRegisterController.php at lines 36-55, inside processRequest(), there is a conditional to still allow registration if $provider->shouldAllowRegistration() is false but $invite is true; however this appears to be too late.

The authentication providers are obtained on line 28, where $this->loadDefaultAccount() is called.

loadDefaultAccion() is at lines 535-566, where the authentication providers are iterated and removed if $candidate_provider->shouldAllowRegistration() is false. As part of loadDefaultAction() if there are no providers left once done iterating as above, it produces the error message that I am seeing.

1<?php
2
3final class PhabricatorAuthRegisterController
4 extends PhabricatorAuthController {
5
6 public function shouldRequireLogin() {
7 return false;
8 }
9
10 public function handleRequest(AphrontRequest $request) {
11 $viewer = $this->getViewer();
12 $account_key = $request->getURIData('akey');
13
14 if ($request->getUser()->isLoggedIn()) {
15 return $this->renderError(pht('You are already logged in.'));
16 }
17
18 $is_setup = false;
19 if (strlen($account_key)) {
20 $result = $this->loadAccountForRegistrationOrLinking($account_key);
21 list($account, $provider, $response) = $result;
22 $is_default = false;
23 } else if ($this->isFirstTimeSetup()) {
24 list($account, $provider, $response) = $this->loadSetupAccount();
25 $is_default = true;
26 $is_setup = true;
27 } else {
28 list($account, $provider, $response) = $this->loadDefaultAccount();
29 $is_default = true;
30 }
31
32 if ($response) {
33 return $response;
34 }
35
36 $invite = $this->loadInvite();
37
38 if (!$provider->shouldAllowRegistration()) {
39 if ($invite) {
40 // If the user has an invite, we allow them to register with any
41 // provider, even a login-only provider.
42 } else {
43 // TODO: This is a routine error if you click "Login" on an external
44 // auth source which doesn't allow registration. The error should be
45 // more tailored.
46
47 return $this->renderError(
48 pht(
49 'The account you are attempting to register with uses an '.
50 'authentication provider ("%s") which does not allow '.
51 'registration. An administrator may have recently disabled '.
52 'registration with this provider.',
53 $provider->getProviderName()));
54 }
55 }
56
57 $user = new PhabricatorUser();
58
59 $default_username = $account->getUsername();
60 $default_realname = $account->getRealName();
61
62 $default_email = $account->getEmail();
63
64 if ($invite) {
65 $default_email = $invite->getEmailAddress();
66 }
67
68 if (!PhabricatorUserEmail::isValidAddress($default_email)) {
69 $default_email = null;
70 }
71
72 if ($default_email !== null) {
73 // We should bypass policy here becase e.g. limiting an application use
74 // to a subset of users should not allow the others to overwrite
75 // configured application emails
76 $application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())
77 ->setViewer(PhabricatorUser::getOmnipotentUser())
78 ->withAddresses(array($default_email))
79 ->executeOne();
80 if ($application_email) {
81 $default_email = null;
82 }
83 }
84
85 if ($default_email !== null) {
86 // If the account source provided an email, but it's not allowed by
87 // the configuration, roadblock the user. Previously, we let the user
88 // pick a valid email address instead, but this does not align well with
89 // user expectation and it's not clear the cases it enables are valuable.
90 // See discussion in T3472.
91 if (!PhabricatorUserEmail::isAllowedAddress($default_email)) {
92 return $this->renderError(
93 array(
94 pht(
95 'The account you are attempting to register with has an invalid '.
96 'email address (%s). This Phabricator install only allows '.
97 'registration with specific email addresses:',
98 $default_email),
99 phutil_tag('br'),
100 phutil_tag('br'),
101 PhabricatorUserEmail::describeAllowedAddresses(),
102 ));
103 }
104
105 // If the account source provided an email, but another account already
106 // has that email, just pretend we didn't get an email.
107
108 // TODO: See T3472.
109
110 if ($default_email !== null) {
111 $same_email = id(new PhabricatorUserEmail())->loadOneWhere(
112 'address = %s',
113 $default_email);
114 if ($same_email) {
115 if ($invite) {
116 // We're allowing this to continue. The fact that we loaded the
117 // invite means that the address is nonprimary and unverified and
118 // we're OK to steal it.
119 } else {
120 $default_email = null;
121 }
122 }
123 }
124 }
125
126 $profile = id(new PhabricatorRegistrationProfile())
127 ->setDefaultUsername($default_username)
128 ->setDefaultEmail($default_email)
129 ->setDefaultRealName($default_realname)
130 ->setCanEditUsername(true)
131 ->setCanEditEmail(($default_email === null))
132 ->setCanEditRealName(true)
133 ->setShouldVerifyEmail(false);
134
135 $event_type = PhabricatorEventType::TYPE_AUTH_WILLREGISTERUSER;
136 $event_data = array(
137 'account' => $account,
138 'profile' => $profile,
139 );
140
141 $event = id(new PhabricatorEvent($event_type, $event_data))
142 ->setUser($user);
143 PhutilEventEngine::dispatchEvent($event);
144
145 $default_username = $profile->getDefaultUsername();
146 $default_email = $profile->getDefaultEmail();
147 $default_realname = $profile->getDefaultRealName();
148
149 $can_edit_username = $profile->getCanEditUsername();
150 $can_edit_email = $profile->getCanEditEmail();
151 $can_edit_realname = $profile->getCanEditRealName();
152
153 $must_set_password = $provider->shouldRequireRegistrationPassword();
154
155 $can_edit_anything = $profile->getCanEditAnything() || $must_set_password;
156 $force_verify = $profile->getShouldVerifyEmail();
157
158 // Automatically verify the administrator's email address during first-time
159 // setup.
160 if ($is_setup) {
161 $force_verify = true;
162 }
163
164 $value_username = $default_username;
165 $value_realname = $default_realname;
166 $value_email = $default_email;
167 $value_password = null;
168
169 $errors = array();
170
171 $require_real_name = PhabricatorEnv::getEnvConfig('user.require-real-name');
172
173 $e_username = strlen($value_username) ? null : true;
174 $e_realname = $require_real_name ? true : null;
175 $e_email = strlen($value_email) ? null : true;
176 $e_password = true;
177 $e_captcha = true;
178
179 $skip_captcha = false;
180 if ($invite) {
181 // If the user is accepting an invite, assume they're trustworthy enough
182 // that we don't need to CAPTCHA them.
183 $skip_captcha = true;
184 }
185
186 $min_len = PhabricatorEnv::getEnvConfig('account.minimum-password-length');
187 $min_len = (int)$min_len;
188
189 $from_invite = $request->getStr('invite');
190 if ($from_invite && $can_edit_username) {
191 $value_username = $request->getStr('username');
192 $e_username = null;
193 }
194
195 if (($request->isFormPost() || !$can_edit_anything) && !$from_invite) {
196 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
197
198 if ($must_set_password && !$skip_captcha) {
199 $e_captcha = pht('Again');
200
201 $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
202 if (!$captcha_ok) {
203 $errors[] = pht('Captcha response is incorrect, try again.');
204 $e_captcha = pht('Invalid');
205 }
206 }
207
208 if ($can_edit_username) {
209 $value_username = $request->getStr('username');
210 if (!strlen($value_username)) {
211 $e_username = pht('Required');
212 $errors[] = pht('Username is required.');
213 } else if (!PhabricatorUser::validateUsername($value_username)) {
214 $e_username = pht('Invalid');
215 $errors[] = PhabricatorUser::describeValidUsername();
216 } else {
217 $e_username = null;
218 }
219 }
220
221 if ($must_set_password) {
222 $value_password = $request->getStr('password');
223 $value_confirm = $request->getStr('confirm');
224 if (!strlen($value_password)) {
225 $e_password = pht('Required');
226 $errors[] = pht('You must choose a password.');
227 } else if ($value_password !== $value_confirm) {
228 $e_password = pht('No Match');
229 $errors[] = pht('Password and confirmation must match.');
230 } else if (strlen($value_password) < $min_len) {
231 $e_password = pht('Too Short');
232 $errors[] = pht(
233 'Password is too short (must be at least %d characters long).',
234 $min_len);
235 } else if (
236 PhabricatorCommonPasswords::isCommonPassword($value_password)) {
237
238 $e_password = pht('Very Weak');
239 $errors[] = pht(
240 'Password is pathologically weak. This password is one of the '.
241 'most common passwords in use, and is extremely easy for '.
242 'attackers to guess. You must choose a stronger password.');
243 } else {
244 $e_password = null;
245 }
246 }
247
248 if ($can_edit_email) {
249 $value_email = $request->getStr('email');
250 if (!strlen($value_email)) {
251 $e_email = pht('Required');
252 $errors[] = pht('Email is required.');
253 } else if (!PhabricatorUserEmail::isValidAddress($value_email)) {
254 $e_email = pht('Invalid');
255 $errors[] = PhabricatorUserEmail::describeValidAddresses();
256 } else if (!PhabricatorUserEmail::isAllowedAddress($value_email)) {
257 $e_email = pht('Disallowed');
258 $errors[] = PhabricatorUserEmail::describeAllowedAddresses();
259 } else {
260 $e_email = null;
261 }
262 }
263
264 if ($can_edit_realname) {
265 $value_realname = $request->getStr('realName');
266 if (!strlen($value_realname) && $require_real_name) {
267 $e_realname = pht('Required');
268 $errors[] = pht('Real name is required.');
269 } else {
270 $e_realname = null;
271 }
272 }
273
274 if (!$errors) {
275 $image = $this->loadProfilePicture($account);
276 if ($image) {
277 $user->setProfileImagePHID($image->getPHID());
278 }
279
280 try {
281 $verify_email = false;
282
283 if ($force_verify) {
284 $verify_email = true;
285 }
286
287 if ($value_email === $default_email) {
288 if ($account->getEmailVerified()) {
289 $verify_email = true;
290 }
291
292 if ($provider->shouldTrustEmails()) {
293 $verify_email = true;
294 }
295
296 if ($invite) {
297 $verify_email = true;
298 }
299 }
300
301 $email_obj = null;
302 if ($invite) {
303 // If we have a valid invite, this email may exist but be
304 // nonprimary and unverified, so we'll reassign it.
305 $email_obj = id(new PhabricatorUserEmail())->loadOneWhere(
306 'address = %s',
307 $value_email);
308 }
309 if (!$email_obj) {
310 $email_obj = id(new PhabricatorUserEmail())
311 ->setAddress($value_email);
312 }
313
314 $email_obj->setIsVerified((int)$verify_email);
315
316 $user->setUsername($value_username);
317 $user->setRealname($value_realname);
318
319 if ($is_setup) {
320 $must_approve = false;
321 } else if ($invite) {
322 $must_approve = false;
323 } else {
324 $must_approve = PhabricatorEnv::getEnvConfig(
325 'auth.require-approval');
326 }
327
328 if ($must_approve) {
329 $user->setIsApproved(0);
330 } else {
331 $user->setIsApproved(1);
332 }
333
334 if ($invite) {
335 $allow_reassign_email = true;
336 } else {
337 $allow_reassign_email = false;
338 }
339
340 $user->openTransaction();
341
342 $editor = id(new PhabricatorUserEditor())
343 ->setActor($user);
344
345 $editor->createNewUser($user, $email_obj, $allow_reassign_email);
346 if ($must_set_password) {
347 $envelope = new PhutilOpaqueEnvelope($value_password);
348 $editor->changePassword($user, $envelope);
349 }
350
351 if ($is_setup) {
352 $editor->makeAdminUser($user, true);
353 }
354
355 $account->setUserPHID($user->getPHID());
356 $provider->willRegisterAccount($account);
357 $account->save();
358
359 $user->saveTransaction();
360
361 if (!$email_obj->getIsVerified()) {
362 $email_obj->sendVerificationEmail($user);
363 }
364
365 if ($must_approve) {
366 $this->sendWaitingForApprovalEmail($user);
367 }
368
369 if ($invite) {
370 $invite->setAcceptedByPHID($user->getPHID())->save();
371 }
372
373 return $this->loginUser($user);
374 } catch (AphrontDuplicateKeyQueryException $exception) {
375 $same_username = id(new PhabricatorUser())->loadOneWhere(
376 'userName = %s',
377 $user->getUserName());
378
379 $same_email = id(new PhabricatorUserEmail())->loadOneWhere(
380 'address = %s',
381 $value_email);
382
383 if ($same_username) {
384 $e_username = pht('Duplicate');
385 $errors[] = pht('Another user already has that username.');
386 }
387
388 if ($same_email) {
389 // TODO: See T3340.
390 $e_email = pht('Duplicate');
391 $errors[] = pht('Another user already has that email.');
392 }
393
394 if (!$same_username && !$same_email) {
395 throw $exception;
396 }
397 }
398 }
399
400 unset($unguarded);
401 }
402
403 $form = id(new AphrontFormView())
404 ->setUser($request->getUser());
405
406 if (!$is_default) {
407 $form->appendChild(
408 id(new AphrontFormMarkupControl())
409 ->setLabel(pht('External Account'))
410 ->setValue(
411 id(new PhabricatorAuthAccountView())
412 ->setUser($request->getUser())
413 ->setExternalAccount($account)
414 ->setAuthProvider($provider)));
415 }
416
417
418 if ($can_edit_username) {
419 $form->appendChild(
420 id(new AphrontFormTextControl())
421 ->setLabel(pht('Phabricator Username'))
422 ->setName('username')
423 ->setValue($value_username)
424 ->setError($e_username));
425 } else {
426 $form->appendChild(
427 id(new AphrontFormMarkupControl())
428 ->setLabel(pht('Phabricator Username'))
429 ->setValue($value_username)
430 ->setError($e_username));
431 }
432
433 if ($can_edit_realname) {
434 $form->appendChild(
435 id(new AphrontFormTextControl())
436 ->setLabel(pht('Real Name'))
437 ->setName('realName')
438 ->setValue($value_realname)
439 ->setError($e_realname));
440 }
441
442 if ($must_set_password) {
443 $form->appendChild(
444 id(new AphrontFormPasswordControl())
445 ->setLabel(pht('Password'))
446 ->setName('password')
447 ->setError($e_password));
448 $form->appendChild(
449 id(new AphrontFormPasswordControl())
450 ->setLabel(pht('Confirm Password'))
451 ->setName('confirm')
452 ->setError($e_password)
453 ->setCaption(
454 $min_len
455 ? pht('Minimum length of %d characters.', $min_len)
456 : null));
457 }
458
459 if ($can_edit_email) {
460 $form->appendChild(
461 id(new AphrontFormTextControl())
462 ->setLabel(pht('Email'))
463 ->setName('email')
464 ->setValue($value_email)
465 ->setCaption(PhabricatorUserEmail::describeAllowedAddresses())
466 ->setError($e_email));
467 }
468
469 if ($must_set_password && !$skip_captcha) {
470 $form->appendChild(
471 id(new AphrontFormRecaptchaControl())
472 ->setLabel(pht('Captcha'))
473 ->setError($e_captcha));
474 }
475
476 $submit = id(new AphrontFormSubmitControl());
477
478 if ($is_setup) {
479 $submit
480 ->setValue(pht('Create Admin Account'));
481 } else {
482 $submit
483 ->addCancelButton($this->getApplicationURI('start/'))
484 ->setValue(pht('Register Phabricator Account'));
485 }
486
487
488 $form->appendChild($submit);
489
490 $crumbs = $this->buildApplicationCrumbs();
491
492 if ($is_setup) {
493 $crumbs->addTextCrumb(pht('Setup Admin Account'));
494 $title = pht('Welcome to Phabricator');
495 } else {
496 $crumbs->addTextCrumb(pht('Register'));
497 $crumbs->addTextCrumb($provider->getProviderName());
498 $title = pht('Phabricator Registration');
499 }
500
501 $welcome_view = null;
502 if ($is_setup) {
503 $welcome_view = id(new PHUIInfoView())
504 ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
505 ->setTitle(pht('Welcome to Phabricator'))
506 ->appendChild(
507 pht(
508 'Installation is complete. Register your administrator account '.
509 'below to log in. You will be able to configure options and add '.
510 'other authentication mechanisms (like LDAP or OAuth) later on.'));
511 }
512
513 $object_box = id(new PHUIObjectBoxView())
514 ->setHeaderText($title)
515 ->setForm($form)
516 ->setFormErrors($errors);
517
518 $invite_header = null;
519 if ($invite) {
520 $invite_header = $this->renderInviteHeader($invite);
521 }
522
523 return $this->buildApplicationPage(
524 array(
525 $crumbs,
526 $welcome_view,
527 $invite_header,
528 $object_box,
529 ),
530 array(
531 'title' => $title,
532 ));
533 }
534
535 private function loadDefaultAccount() {
536 $providers = PhabricatorAuthProvider::getAllEnabledProviders();
537 $account = null;
538 $provider = null;
539 $response = null;
540
541 foreach ($providers as $key => $candidate_provider) {
542 if (!$candidate_provider->shouldAllowRegistration()) {
543 unset($providers[$key]);
544 continue;
545 }
546 if (!$candidate_provider->isDefaultRegistrationProvider()) {
547 unset($providers[$key]);
548 }
549 }
550
551 if (!$providers) {
552 $response = $this->renderError(
553 pht(
554 'There are no configured default registration providers.'));
555 return array($account, $provider, $response);
556 } else if (count($providers) > 1) {
557 $response = $this->renderError(
558 pht('There are too many configured default registration providers.'));
559 return array($account, $provider, $response);
560 }
561
562 $provider = head($providers);
563 $account = $provider->getDefaultExternalAccount();
564
565 return array($account, $provider, $response);
566 }
567
568 private function loadSetupAccount() {
569 $provider = new PhabricatorPasswordAuthProvider();
570 $provider->attachProviderConfig(
571 id(new PhabricatorAuthProviderConfig())
572 ->setShouldAllowRegistration(1)
573 ->setShouldAllowLogin(1)
574 ->setIsEnabled(true));
575
576 $account = $provider->getDefaultExternalAccount();
577 $response = null;
578 return array($account, $provider, $response);
579 }
580
581 private function loadProfilePicture(PhabricatorExternalAccount $account) {
582 $phid = $account->getProfileImagePHID();
583 if (!$phid) {
584 return null;
585 }
586
587 // NOTE: Use of omnipotent user is okay here because the registering user
588 // can not control the field value, and we can't use their user object to
589 // do meaningful policy checks anyway since they have not registered yet.
590 // Reaching this means the user holds the account secret key and the
591 // registration secret key, and thus has permission to view the image.
592
593 $file = id(new PhabricatorFileQuery())
594 ->setViewer(PhabricatorUser::getOmnipotentUser())
595 ->withPHIDs(array($phid))
596 ->executeOne();
597 if (!$file) {
598 return null;
599 }
600
601 $xform = PhabricatorFileTransform::getTransformByKey(
602 PhabricatorFileThumbnailTransform::TRANSFORM_PROFILE);
603 return $xform->executeTransform($file);
604 }
605
606 protected function renderError($message) {
607 return $this->renderErrorPage(
608 pht('Registration Failed'),
609 array($message));
610 }
611
612 private function sendWaitingForApprovalEmail(PhabricatorUser $user) {
613 $title = '[Phabricator] '.pht(
614 'New User "%s" Awaiting Approval',
615 $user->getUsername());
616
617 $body = new PhabricatorMetaMTAMailBody();
618
619 $body->addRawSection(
620 pht(
621 'Newly registered user "%s" is awaiting account approval by an '.
622 'administrator.',
623 $user->getUsername()));
624
625 $body->addLinkSection(
626 pht('APPROVAL QUEUE'),
627 PhabricatorEnv::getProductionURI(
628 '/people/query/approval/'));
629
630 $body->addLinkSection(
631 pht('DISABLE APPROVAL QUEUE'),
632 PhabricatorEnv::getProductionURI(
633 '/config/edit/auth.require-approval/'));
634
635 $admins = id(new PhabricatorPeopleQuery())
636 ->setViewer(PhabricatorUser::getOmnipotentUser())
637 ->withIsAdmin(true)
638 ->execute();
639
640 if (!$admins) {
641 return;
642 }
643
644 $mail = id(new PhabricatorMetaMTAMail())
645 ->addTos(mpull($admins, 'getPHID'))
646 ->setSubject($title)
647 ->setBody($body->render())
648 ->saveAndSend();
649 }
650
651}