@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add password authentication and registration to new registration

Summary:
Ref T1536. Ref T1930. Code is not reachable.

This provides password authentication and registration on the new provider/adapter framework.

I sort of cheated a little bit and don't really route any password logic through the adapter (instead, this provider uses an empty adapter and just sets the type/domain on it). I think the right way to do this //conceptually// is to treat username/passwords as an external black box which the adapter communicates with. However, this creates a lot of practical implementation and UX problems:

- There would basically be two steps -- in the first one, you interact with the "password black box", which behaves like an OAuth provider. This produces some ExternalAccount associated with the username/password pair, then we go into normal registration.
- In normal registration, we'd proceed normally.

This means:

- The registration flow would be split into two parts, one where you select a username/password (interacting with the black box) and one where you actually register (interacting with the generic flow). This is unusual and probably confusing for users.
- We would need to do a lot of re-hashing of passwords, since passwords currently depend on the username and user PHID, which won't exist yet during registration or the "black box" phase. This is a big mess I don't want to deal with.
- We hit a weird condition where two users complete step 1 with the same username but don't complete step 2 yet. The box knows about two different copies of the username, with two different passwords. When we arrive at step 2 the second time we have a lot of bad choices about how to reoslve it, most of which create security problems. The most stragihtforward and "pure" way to resolve the issues is to put password-auth usernames in a separate space, but this would be incredibly confusuing to users (your login name might not be the same as your username, which is bizarre).
- If we change this, we need to update all the other password-related code, which I don't want to bother with (at least for now).

Instead, let registration know about a "default" registration controller (which is always password, if enabled), and let it require a password. This gives us a much simpler (albeit slightly less pure) implementation:

- All the fields are on one form.
- Password adapter is just a shell.
- Password provider does the heavy lifting.

We might make this more pure at some point, but I'm generally pretty satisfied with this.

This doesn't implement the brute-force CAPTCHA protection, that will be coming soon.

Test Plan: Registered with password only and logged in with a password. Hit various error conditions.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran, chad

Maniphest Tasks: T1536, T1930

Differential Revision: https://secure.phabricator.com/D6164

+332 -50
+48 -48
src/__celerity_resource_map__.php
··· 814 814 ), 815 815 'aphront-form-view-css' => 816 816 array( 817 - 'uri' => '/res/4fe4c174/rsrc/css/aphront/form-view.css', 817 + 'uri' => '/res/a668dc36/rsrc/css/aphront/form-view.css', 818 818 'type' => 'css', 819 819 'requires' => 820 820 array( ··· 941 941 ), 942 942 'conpherence-message-pane-css' => 943 943 array( 944 - 'uri' => '/res/a3cb0efc/rsrc/css/application/conpherence/message-pane.css', 944 + 'uri' => '/res/80cde760/rsrc/css/application/conpherence/message-pane.css', 945 945 'type' => 'css', 946 946 'requires' => 947 947 array( ··· 959 959 ), 960 960 'conpherence-widget-pane-css' => 961 961 array( 962 - 'uri' => '/res/d1db8220/rsrc/css/application/conpherence/widget-pane.css', 962 + 'uri' => '/res/ee9d5004/rsrc/css/application/conpherence/widget-pane.css', 963 963 'type' => 'css', 964 964 'requires' => 965 965 array( ··· 3601 3601 ), 3602 3602 'phabricator-zindex-css' => 3603 3603 array( 3604 - 'uri' => '/res/f919f9e1/rsrc/css/core/z-index.css', 3604 + 'uri' => '/res/99a9447b/rsrc/css/core/z-index.css', 3605 3605 'type' => 'css', 3606 3606 'requires' => 3607 3607 array( ··· 4048 4048 ), array( 4049 4049 'packages' => 4050 4050 array( 4051 - '0a101340' => 4051 + 'ab82f2c0' => 4052 4052 array( 4053 4053 'name' => 'core.pkg.css', 4054 4054 'symbols' => ··· 4096 4096 40 => 'phabricator-property-list-view-css', 4097 4097 41 => 'phabricator-tag-view-css', 4098 4098 ), 4099 - 'uri' => '/res/pkg/0a101340/core.pkg.css', 4099 + 'uri' => '/res/pkg/ab82f2c0/core.pkg.css', 4100 4100 'type' => 'css', 4101 4101 ), 4102 4102 'f2ad0683' => ··· 4290 4290 'reverse' => 4291 4291 array( 4292 4292 'aphront-attached-file-view-css' => 'a7ca34a9', 4293 - 'aphront-dialog-view-css' => '0a101340', 4294 - 'aphront-error-view-css' => '0a101340', 4295 - 'aphront-form-view-css' => '0a101340', 4296 - 'aphront-list-filter-view-css' => '0a101340', 4297 - 'aphront-pager-view-css' => '0a101340', 4298 - 'aphront-panel-view-css' => '0a101340', 4299 - 'aphront-table-view-css' => '0a101340', 4300 - 'aphront-tokenizer-control-css' => '0a101340', 4301 - 'aphront-tooltip-css' => '0a101340', 4302 - 'aphront-typeahead-control-css' => '0a101340', 4293 + 'aphront-dialog-view-css' => 'ab82f2c0', 4294 + 'aphront-error-view-css' => 'ab82f2c0', 4295 + 'aphront-form-view-css' => 'ab82f2c0', 4296 + 'aphront-list-filter-view-css' => 'ab82f2c0', 4297 + 'aphront-pager-view-css' => 'ab82f2c0', 4298 + 'aphront-panel-view-css' => 'ab82f2c0', 4299 + 'aphront-table-view-css' => 'ab82f2c0', 4300 + 'aphront-tokenizer-control-css' => 'ab82f2c0', 4301 + 'aphront-tooltip-css' => 'ab82f2c0', 4302 + 'aphront-typeahead-control-css' => 'ab82f2c0', 4303 4303 'differential-changeset-view-css' => 'dd27a69b', 4304 4304 'differential-core-view-css' => 'dd27a69b', 4305 4305 'differential-inline-comment-editor' => '9488bb69', ··· 4313 4313 'differential-table-of-contents-css' => 'dd27a69b', 4314 4314 'diffusion-commit-view-css' => 'c8ce2d88', 4315 4315 'diffusion-icons-css' => 'c8ce2d88', 4316 - 'global-drag-and-drop-css' => '0a101340', 4316 + 'global-drag-and-drop-css' => 'ab82f2c0', 4317 4317 'inline-comment-summary-css' => 'dd27a69b', 4318 4318 'javelin-aphlict' => 'f2ad0683', 4319 4319 'javelin-behavior' => 'a9f14d76', ··· 4387 4387 'javelin-util' => 'a9f14d76', 4388 4388 'javelin-vector' => 'a9f14d76', 4389 4389 'javelin-workflow' => 'a9f14d76', 4390 - 'lightbox-attachment-css' => '0a101340', 4390 + 'lightbox-attachment-css' => 'ab82f2c0', 4391 4391 'maniphest-task-summary-css' => 'a7ca34a9', 4392 4392 'maniphest-transaction-detail-css' => 'a7ca34a9', 4393 - 'phabricator-action-list-view-css' => '0a101340', 4394 - 'phabricator-application-launch-view-css' => '0a101340', 4393 + 'phabricator-action-list-view-css' => 'ab82f2c0', 4394 + 'phabricator-application-launch-view-css' => 'ab82f2c0', 4395 4395 'phabricator-busy' => 'f2ad0683', 4396 4396 'phabricator-content-source-view-css' => 'dd27a69b', 4397 - 'phabricator-core-css' => '0a101340', 4398 - 'phabricator-crumbs-view-css' => '0a101340', 4397 + 'phabricator-core-css' => 'ab82f2c0', 4398 + 'phabricator-crumbs-view-css' => 'ab82f2c0', 4399 4399 'phabricator-drag-and-drop-file-upload' => '9488bb69', 4400 4400 'phabricator-dropdown-menu' => 'f2ad0683', 4401 4401 'phabricator-file-upload' => 'f2ad0683', 4402 - 'phabricator-filetree-view-css' => '0a101340', 4403 - 'phabricator-flag-css' => '0a101340', 4404 - 'phabricator-form-view-css' => '0a101340', 4405 - 'phabricator-header-view-css' => '0a101340', 4402 + 'phabricator-filetree-view-css' => 'ab82f2c0', 4403 + 'phabricator-flag-css' => 'ab82f2c0', 4404 + 'phabricator-form-view-css' => 'ab82f2c0', 4405 + 'phabricator-header-view-css' => 'ab82f2c0', 4406 4406 'phabricator-hovercard' => 'f2ad0683', 4407 - 'phabricator-jump-nav' => '0a101340', 4407 + 'phabricator-jump-nav' => 'ab82f2c0', 4408 4408 'phabricator-keyboard-shortcut' => 'f2ad0683', 4409 4409 'phabricator-keyboard-shortcut-manager' => 'f2ad0683', 4410 - 'phabricator-main-menu-view' => '0a101340', 4410 + 'phabricator-main-menu-view' => 'ab82f2c0', 4411 4411 'phabricator-menu-item' => 'f2ad0683', 4412 - 'phabricator-nav-view-css' => '0a101340', 4412 + 'phabricator-nav-view-css' => 'ab82f2c0', 4413 4413 'phabricator-notification' => 'f2ad0683', 4414 - 'phabricator-notification-css' => '0a101340', 4415 - 'phabricator-notification-menu-css' => '0a101340', 4416 - 'phabricator-object-item-list-view-css' => '0a101340', 4414 + 'phabricator-notification-css' => 'ab82f2c0', 4415 + 'phabricator-notification-menu-css' => 'ab82f2c0', 4416 + 'phabricator-object-item-list-view-css' => 'ab82f2c0', 4417 4417 'phabricator-object-selector-css' => 'dd27a69b', 4418 4418 'phabricator-phtize' => 'f2ad0683', 4419 4419 'phabricator-prefab' => 'f2ad0683', 4420 4420 'phabricator-project-tag-css' => 'a7ca34a9', 4421 - 'phabricator-property-list-view-css' => '0a101340', 4422 - 'phabricator-remarkup-css' => '0a101340', 4421 + 'phabricator-property-list-view-css' => 'ab82f2c0', 4422 + 'phabricator-remarkup-css' => 'ab82f2c0', 4423 4423 'phabricator-shaped-request' => '9488bb69', 4424 - 'phabricator-side-menu-view-css' => '0a101340', 4425 - 'phabricator-standard-page-view' => '0a101340', 4426 - 'phabricator-tag-view-css' => '0a101340', 4424 + 'phabricator-side-menu-view-css' => 'ab82f2c0', 4425 + 'phabricator-standard-page-view' => 'ab82f2c0', 4426 + 'phabricator-tag-view-css' => 'ab82f2c0', 4427 4427 'phabricator-textareautils' => 'f2ad0683', 4428 4428 'phabricator-tooltip' => 'f2ad0683', 4429 - 'phabricator-transaction-view-css' => '0a101340', 4430 - 'phabricator-zindex-css' => '0a101340', 4431 - 'phui-button-css' => '0a101340', 4432 - 'phui-form-css' => '0a101340', 4433 - 'phui-icon-view-css' => '0a101340', 4434 - 'phui-spacing-css' => '0a101340', 4435 - 'sprite-apps-large-css' => '0a101340', 4436 - 'sprite-gradient-css' => '0a101340', 4437 - 'sprite-icons-css' => '0a101340', 4438 - 'sprite-menu-css' => '0a101340', 4439 - 'syntax-highlighting-css' => '0a101340', 4429 + 'phabricator-transaction-view-css' => 'ab82f2c0', 4430 + 'phabricator-zindex-css' => 'ab82f2c0', 4431 + 'phui-button-css' => 'ab82f2c0', 4432 + 'phui-form-css' => 'ab82f2c0', 4433 + 'phui-icon-view-css' => 'ab82f2c0', 4434 + 'phui-spacing-css' => 'ab82f2c0', 4435 + 'sprite-apps-large-css' => 'ab82f2c0', 4436 + 'sprite-gradient-css' => 'ab82f2c0', 4437 + 'sprite-icons-css' => 'ab82f2c0', 4438 + 'sprite-menu-css' => 'ab82f2c0', 4439 + 'syntax-highlighting-css' => 'ab82f2c0', 4440 4440 ), 4441 4441 ));
+2
src/__phutil_library_map__.php
··· 819 819 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 820 820 'PhabricatorAuthProviderOAuth' => 'applications/auth/provider/PhabricatorAuthProviderOAuth.php', 821 821 'PhabricatorAuthProviderOAuthFacebook' => 'applications/auth/provider/PhabricatorAuthProviderOAuthFacebook.php', 822 + 'PhabricatorAuthProviderPassword' => 'applications/auth/provider/PhabricatorAuthProviderPassword.php', 822 823 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 823 824 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 824 825 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', ··· 2678 2679 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 2679 2680 'PhabricatorAuthProviderOAuth' => 'PhabricatorAuthProvider', 2680 2681 'PhabricatorAuthProviderOAuthFacebook' => 'PhabricatorAuthProviderOAuth', 2682 + 'PhabricatorAuthProviderPassword' => 'PhabricatorAuthProvider', 2681 2683 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', 2682 2684 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 2683 2685 'PhabricatorAuthValidateController' => 'PhabricatorAuthController',
+1 -1
src/applications/auth/application/PhabricatorApplicationAuth.php
··· 37 37 return array( 38 38 '/auth/' => array( 39 39 'login/(?P<pkey>[^/]+)/' => 'PhabricatorAuthLoginController', 40 - 'register/(?P<akey>[^/]+)/' => 'PhabricatorAuthRegisterController', 40 + 'register/(?:(?P<akey>[^/]+)/)?' => 'PhabricatorAuthRegisterController', 41 41 'start/' => 'PhabricatorAuthStartController', 42 42 'validate/' => 'PhabricatorAuthValidateController', 43 43 ),
+30
src/applications/auth/controller/PhabricatorAuthLoginController.php
··· 30 30 return $response; 31 31 } 32 32 33 + if (!$account) { 34 + throw new Exception( 35 + "Auth provider failed to load an account from processLoginRequest()!"); 36 + } 37 + 33 38 if ($account->getUserPHID()) { 34 39 // The account is already attached to a Phabricator user, so this is 35 40 // either a login or a bad account link request. ··· 165 170 $view, 166 171 array( 167 172 'title' => $title, 173 + 'device' => true, 174 + 'dust' => true, 175 + )); 176 + } 177 + 178 + public function buildProviderPageResponse( 179 + PhabricatorAuthProvider $provider, 180 + $content) { 181 + 182 + $crumbs = $this->buildApplicationCrumbs(); 183 + $crumbs->addCrumb( 184 + id(new PhabricatorCrumbView()) 185 + ->setName(pht('Login')) 186 + ->setHref($this->getApplicationURI('start/'))); 187 + $crumbs->addCrumb( 188 + id(new PhabricatorCrumbView()) 189 + ->setName($provider->getProviderName())); 190 + 191 + return $this->buildApplicationPage( 192 + array( 193 + $crumbs, 194 + $content, 195 + ), 196 + array( 197 + 'title' => pht('Login'), 168 198 'device' => true, 169 199 'dust' => true, 170 200 ));
+16 -1
src/applications/auth/provider/PhabricatorAuthProvider.php
··· 74 74 return; 75 75 } 76 76 77 + protected function willRegisterAccount(PhabricatorExternalAccount $account) { 78 + return; 79 + } 80 + 77 81 protected function loadOrCreateAccount($account_id) { 78 82 if (!strlen($account_id)) { 79 83 throw new Exception( ··· 107 111 ->setAccountID($account_id); 108 112 } 109 113 110 - $account->setDisplayName(''); 111 114 $account->setUsername($adapter->getAccountName()); 112 115 $account->setRealName($adapter->getAccountRealName()); 113 116 $account->setEmail($adapter->getAccountEmail()); ··· 145 148 $app = PhabricatorApplication::getByClass('PhabricatorApplicationAuth'); 146 149 $uri = $app->getApplicationURI('/login/'.$this->getProviderKey().'/'); 147 150 return PhabricatorEnv::getURI($uri); 151 + } 152 + 153 + public function isDefaultRegistrationProvider() { 154 + return false; 155 + } 156 + 157 + public function shouldRequireRegistrationPassword() { 158 + return false; 159 + } 160 + 161 + public function getDefaultExternalAccount() { 162 + throw new Exception("Not implemented!"); 148 163 } 149 164 150 165 }
+231
src/applications/auth/provider/PhabricatorAuthProviderPassword.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthProviderPassword 4 + extends PhabricatorAuthProvider { 5 + 6 + private $adapter; 7 + 8 + public function getProviderName() { 9 + return pht('Username/Password'); 10 + } 11 + 12 + public function isEnabled() { 13 + // TODO: Remove this once we switch to the new auth mechanism. 14 + return false && 15 + PhabricatorEnv::getEnvConfig('auth.password-auth-enabled'); 16 + } 17 + 18 + public function getAdapter() { 19 + if (!$this->adapter) { 20 + $adapter = new PhutilAuthAdapterEmpty(); 21 + $adapter->setAdapterType('password'); 22 + $adapter->setAdapterDomain('self'); 23 + $this->adapter = $adapter; 24 + } 25 + return $this->adapter; 26 + } 27 + 28 + public function shouldAllowLogin() { 29 + return true; 30 + } 31 + 32 + public function shouldAllowRegistration() { 33 + return true; 34 + } 35 + 36 + public function shouldAllowAccountLink() { 37 + return false; 38 + } 39 + 40 + public function shouldAllowAccountUnlink() { 41 + return false; 42 + } 43 + 44 + public function isDefaultRegistrationProvider() { 45 + return true; 46 + } 47 + 48 + public function buildLoginForm( 49 + PhabricatorAuthStartController $controller) { 50 + 51 + $request = $controller->getRequest(); 52 + 53 + return $this->renderLoginForm($request); 54 + } 55 + 56 + private function renderLoginForm( 57 + AphrontRequest $request, 58 + $require_captcha = false, 59 + $captcha_valid = false) { 60 + 61 + $viewer = $request->getUser(); 62 + 63 + $submit = id(new AphrontFormSubmitControl()) 64 + ->setValue(pht('Login')); 65 + 66 + if ($this->shouldAllowRegistration()) { 67 + $submit->addCancelButton( 68 + '/auth/register/', 69 + pht('Register New Account')); 70 + } 71 + 72 + $header = id(new PhabricatorHeaderView()) 73 + ->setHeader(pht('Login to Phabricator')); 74 + 75 + $v_user = nonempty( 76 + $request->getStr('username'), 77 + $request->getCookie('phusr')); 78 + 79 + $e_user = null; 80 + $e_pass = null; 81 + $e_captcha = null; 82 + 83 + $errors = array(); 84 + if ($require_captcha && !$captcha_valid) { 85 + if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) { 86 + $e_captcha = pht('Invalid'); 87 + $errors[] = pht('CAPTCHA was not entered correctly.'); 88 + } else { 89 + $e_captcha = pht('Required'); 90 + $errors[] = pht('Too many login failures recently. You must '. 91 + 'submit a CAPTCHA with your login request.'); 92 + } 93 + } else if ($request->isHTTPPost()) { 94 + // NOTE: This is intentionally vague so as not to disclose whether a 95 + // given username or email is registered. 96 + $e_user = pht('Invalid'); 97 + $e_pass = pht('Invalid'); 98 + $errors[] = pht('Username or password are incorrect.'); 99 + } 100 + 101 + $form = id(new AphrontFormView()) 102 + ->setAction($this->getLoginURI()) 103 + ->setUser($viewer) 104 + ->setFlexible(true) 105 + ->appendChild( 106 + id(new AphrontFormTextControl()) 107 + ->setLabel('Username/Email') 108 + ->setName('username') 109 + ->setValue($v_user) 110 + ->setError($e_user)) 111 + ->appendChild( 112 + id(new AphrontFormPasswordControl()) 113 + ->setLabel('Password') 114 + ->setName('password') 115 + ->setError($e_pass) 116 + ->setCaption( 117 + phutil_tag( 118 + 'a', 119 + array( 120 + 'href' => '/login/email/', 121 + ), 122 + pht('Forgot your password?')))); 123 + 124 + if ($require_captcha) { 125 + $form->appendChild( 126 + id(new AphrontFormRecaptchaControl()) 127 + ->setError($e_captcha)); 128 + } 129 + 130 + $form 131 + ->appendChild($submit); 132 + 133 + if ($errors) { 134 + $errors = id(new AphrontErrorView())->setErrors($errors); 135 + } 136 + 137 + return array( 138 + $errors, 139 + $header, 140 + $form, 141 + ); 142 + } 143 + 144 + public function processLoginRequest( 145 + PhabricatorAuthLoginController $controller) { 146 + 147 + $request = $controller->getRequest(); 148 + $viewer = $request->getUser(); 149 + 150 + $require_captcha = false; 151 + $captcha_valid = false; 152 + if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) { 153 + $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( 154 + PhabricatorUserLog::ACTION_LOGIN_FAILURE, 155 + 60 * 15); 156 + if (count($failed_attempts) > 5) { 157 + $require_captcha = true; 158 + $captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request); 159 + } 160 + } 161 + 162 + $response = null; 163 + $account = null; 164 + $log_user = null; 165 + 166 + if (!$require_captcha || $captcha_valid) { 167 + $username_or_email = $request->getStr('username'); 168 + 169 + if (strlen($username_or_email)) { 170 + $user = id(new PhabricatorUser())->loadOneWhere( 171 + 'username = %s', 172 + $username_or_email); 173 + 174 + if (!$user) { 175 + $user = PhabricatorUser::loadOneWithEmailAddress($username_or_email); 176 + } 177 + } 178 + 179 + if ($user) { 180 + $envelope = new PhutilOpaqueEnvelope($request->getStr('password')); 181 + if ($user->comparePassword($envelope)) { 182 + $account = $this->loadOrCreateAccount($user->getPHID()); 183 + $log_user = $user; 184 + } 185 + } 186 + } 187 + 188 + if (!$account) { 189 + $log = PhabricatorUserLog::newLog( 190 + null, 191 + $log_user, 192 + PhabricatorUserLog::ACTION_LOGIN_FAILURE); 193 + $log->save(); 194 + 195 + $request->clearCookie('phusr'); 196 + $request->clearCookie('phsid'); 197 + 198 + $response = $controller->buildProviderPageResponse( 199 + $this, 200 + $this->renderLoginForm( 201 + $request, 202 + $require_captcha, 203 + $captcha_valid)); 204 + } 205 + 206 + return array($account, $response); 207 + } 208 + 209 + public function shouldRequireRegistrationPassword() { 210 + return true; 211 + } 212 + 213 + public function getDefaultExternalAccount() { 214 + $adapter = $this->getAdapter(); 215 + 216 + return id(new PhabricatorExternalAccount()) 217 + ->setAccountType($adapter->getAdapterType()) 218 + ->setAccountDomain($adapter->getAdapterDomain()); 219 + } 220 + 221 + protected function willSaveAccount(PhabricatorExternalAccount $account) { 222 + parent::willSaveAccount($account); 223 + $account->setUserPHID($account->getAccountID()); 224 + } 225 + 226 + public function willRegisterAccount(PhabricatorExternalAccount $account) { 227 + parent::willRegisterAccount($account); 228 + $account->setAccountID($account->getUserPHID()); 229 + } 230 + 231 + }
+4
webroot/rsrc/css/aphront/form-view.css
··· 437 437 width: 85%; 438 438 margin: 15px auto; 439 439 } 440 + 441 + .recaptcha_only_if_privacy { 442 + display: none; 443 + }