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 | |
3 | final 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 | } |