@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.

Move all account link / unlink to new registration flow

Summary:
Ref T1536. Currently, we have separate panels for each link/unlink and separate controllers for OAuth vs LDAP.

Instead, provide a single "External Accounts" panel which shows all linked accounts and allows you to link/unlink more easily.

Move link/unlink over to a full externalaccount-based workflow.

Test Plan:
- Linked and unlinked OAuth accounts.
- Linked and unlinked LDAP accounts.
- Registered new accounts.
- Exercised most/all of the error cases.

Reviewers: btrahan, chad

Reviewed By: btrahan

CC: aran, mbishopim3

Maniphest Tasks: T1536

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

+690 -529
+1 -1
src/__celerity_resource_map__.php
··· 923 923 ), 924 924 'auth-css' => 925 925 array( 926 - 'uri' => '/res/8a95bad7/rsrc/css/application/auth/auth.css', 926 + 'uri' => '/res/81f72bfa/rsrc/css/application/auth/auth.css', 927 927 'type' => 'css', 928 928 'requires' => 929 929 array(
+8 -8
src/__phutil_library_map__.php
··· 814 814 'PhabricatorAuditQuery' => 'applications/audit/query/PhabricatorAuditQuery.php', 815 815 'PhabricatorAuditReplyHandler' => 'applications/audit/mail/PhabricatorAuditReplyHandler.php', 816 816 'PhabricatorAuditStatusConstants' => 'applications/audit/constants/PhabricatorAuditStatusConstants.php', 817 + 'PhabricatorAuthConfirmLinkController' => 'applications/auth/controller/PhabricatorAuthConfirmLinkController.php', 817 818 'PhabricatorAuthController' => 'applications/auth/controller/PhabricatorAuthController.php', 819 + 'PhabricatorAuthLinkController' => 'applications/auth/controller/PhabricatorAuthLinkController.php', 818 820 'PhabricatorAuthLoginController' => 'applications/auth/controller/PhabricatorAuthLoginController.php', 819 821 'PhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorAuthProvider.php', 820 822 'PhabricatorAuthProviderLDAP' => 'applications/auth/provider/PhabricatorAuthProviderLDAP.php', ··· 826 828 'PhabricatorAuthProviderPassword' => 'applications/auth/provider/PhabricatorAuthProviderPassword.php', 827 829 'PhabricatorAuthRegisterController' => 'applications/auth/controller/PhabricatorAuthRegisterController.php', 828 830 'PhabricatorAuthStartController' => 'applications/auth/controller/PhabricatorAuthStartController.php', 831 + 'PhabricatorAuthUnlinkController' => 'applications/auth/controller/PhabricatorAuthUnlinkController.php', 829 832 'PhabricatorAuthValidateController' => 'applications/auth/controller/PhabricatorAuthValidateController.php', 830 833 'PhabricatorAuthenticationConfigOptions' => 'applications/config/option/PhabricatorAuthenticationConfigOptions.php', 831 834 'PhabricatorBarePageExample' => 'applications/uiexample/examples/PhabricatorBarePageExample.php', ··· 1111 1114 'PhabricatorLDAPProvider' => 'applications/auth/ldap/PhabricatorLDAPProvider.php', 1112 1115 'PhabricatorLDAPRegistrationController' => 'applications/auth/controller/PhabricatorLDAPRegistrationController.php', 1113 1116 'PhabricatorLDAPUnknownUserException' => 'applications/auth/ldap/PhabricatorLDAPUnknownUserException.php', 1114 - 'PhabricatorLDAPUnlinkController' => 'applications/auth/controller/PhabricatorLDAPUnlinkController.php', 1115 1117 'PhabricatorLintEngine' => 'infrastructure/lint/PhabricatorLintEngine.php', 1116 1118 'PhabricatorLipsumArtist' => 'applications/lipsum/image/PhabricatorLipsumArtist.php', 1117 1119 'PhabricatorLipsumGenerateWorkflow' => 'applications/lipsum/management/PhabricatorLipsumGenerateWorkflow.php', ··· 1241 1243 'PhabricatorOAuthServerTestCase' => 'applications/oauthserver/__tests__/PhabricatorOAuthServerTestCase.php', 1242 1244 'PhabricatorOAuthServerTestController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTestController.php', 1243 1245 'PhabricatorOAuthServerTokenController' => 'applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php', 1244 - 'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/PhabricatorOAuthUnlinkController.php', 1245 1246 'PhabricatorObjectHandle' => 'applications/phid/PhabricatorObjectHandle.php', 1246 1247 'PhabricatorObjectHandleConstants' => 'applications/phid/handle/const/PhabricatorObjectHandleConstants.php', 1247 1248 'PhabricatorObjectHandleData' => 'applications/phid/handle/PhabricatorObjectHandleData.php', ··· 1444 1445 'PhabricatorSettingsPanelDisplayPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelDisplayPreferences.php', 1445 1446 'PhabricatorSettingsPanelEmailAddresses' => 'applications/settings/panel/PhabricatorSettingsPanelEmailAddresses.php', 1446 1447 'PhabricatorSettingsPanelEmailPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelEmailPreferences.php', 1448 + 'PhabricatorSettingsPanelExternalAccounts' => 'applications/settings/panel/PhabricatorSettingsPanelExternalAccounts.php', 1447 1449 'PhabricatorSettingsPanelHomePreferences' => 'applications/settings/panel/PhabricatorSettingsPanelHomePreferences.php', 1448 - 'PhabricatorSettingsPanelLDAP' => 'applications/settings/panel/PhabricatorSettingsPanelLDAP.php', 1449 - 'PhabricatorSettingsPanelOAuth' => 'applications/settings/panel/PhabricatorSettingsPanelOAuth.php', 1450 1450 'PhabricatorSettingsPanelPassword' => 'applications/settings/panel/PhabricatorSettingsPanelPassword.php', 1451 1451 'PhabricatorSettingsPanelProfile' => 'applications/settings/panel/PhabricatorSettingsPanelProfile.php', 1452 1452 'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php', ··· 2678 2678 'PhabricatorAuditMailReceiver' => 'PhabricatorObjectMailReceiver', 2679 2679 'PhabricatorAuditPreviewController' => 'PhabricatorAuditController', 2680 2680 'PhabricatorAuditReplyHandler' => 'PhabricatorMailReplyHandler', 2681 + 'PhabricatorAuthConfirmLinkController' => 'PhabricatorAuthController', 2681 2682 'PhabricatorAuthController' => 'PhabricatorController', 2683 + 'PhabricatorAuthLinkController' => 'PhabricatorAuthController', 2682 2684 'PhabricatorAuthLoginController' => 'PhabricatorAuthController', 2683 2685 'PhabricatorAuthProviderLDAP' => 'PhabricatorAuthProvider', 2684 2686 'PhabricatorAuthProviderOAuth' => 'PhabricatorAuthProvider', ··· 2689 2691 'PhabricatorAuthProviderPassword' => 'PhabricatorAuthProvider', 2690 2692 'PhabricatorAuthRegisterController' => 'PhabricatorAuthController', 2691 2693 'PhabricatorAuthStartController' => 'PhabricatorAuthController', 2694 + 'PhabricatorAuthUnlinkController' => 'PhabricatorAuthController', 2692 2695 'PhabricatorAuthValidateController' => 'PhabricatorAuthController', 2693 2696 'PhabricatorAuthenticationConfigOptions' => 'PhabricatorApplicationConfigOptions', 2694 2697 'PhabricatorBarePageExample' => 'PhabricatorUIExample', ··· 2971 2974 'PhabricatorLDAPLoginController' => 'PhabricatorAuthController', 2972 2975 'PhabricatorLDAPRegistrationController' => 'PhabricatorAuthController', 2973 2976 'PhabricatorLDAPUnknownUserException' => 'Exception', 2974 - 'PhabricatorLDAPUnlinkController' => 'PhabricatorAuthController', 2975 2977 'PhabricatorLintEngine' => 'PhutilLintEngine', 2976 2978 'PhabricatorLipsumGenerateWorkflow' => 'PhabricatorLipsumManagementWorkflow', 2977 2979 'PhabricatorLipsumManagementWorkflow' => 'PhutilArgumentWorkflow', ··· 3093 3095 'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase', 3094 3096 'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController', 3095 3097 'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController', 3096 - 'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController', 3097 3098 'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants', 3098 3099 'PhabricatorObjectItemListExample' => 'PhabricatorUIExample', 3099 3100 'PhabricatorObjectItemListView' => 'AphrontTagView', ··· 3302 3303 'PhabricatorSettingsPanelDisplayPreferences' => 'PhabricatorSettingsPanel', 3303 3304 'PhabricatorSettingsPanelEmailAddresses' => 'PhabricatorSettingsPanel', 3304 3305 'PhabricatorSettingsPanelEmailPreferences' => 'PhabricatorSettingsPanel', 3306 + 'PhabricatorSettingsPanelExternalAccounts' => 'PhabricatorSettingsPanel', 3305 3307 'PhabricatorSettingsPanelHomePreferences' => 'PhabricatorSettingsPanel', 3306 - 'PhabricatorSettingsPanelLDAP' => 'PhabricatorSettingsPanel', 3307 - 'PhabricatorSettingsPanelOAuth' => 'PhabricatorSettingsPanel', 3308 3308 'PhabricatorSettingsPanelPassword' => 'PhabricatorSettingsPanel', 3309 3309 'PhabricatorSettingsPanelProfile' => 'PhabricatorSettingsPanel', 3310 3310 'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel',
+4 -2
src/applications/auth/application/PhabricatorApplicationAuth.php
··· 40 40 'register/(?:(?P<akey>[^/]+)/)?' => 'PhabricatorAuthRegisterController', 41 41 'start/' => 'PhabricatorAuthStartController', 42 42 'validate/' => 'PhabricatorAuthValidateController', 43 + 'unlink/(?P<pkey>[^/]+)/' => 'PhabricatorAuthUnlinkController', 44 + 'link/(?P<pkey>[^/]+)/' => 'PhabricatorAuthLinkController', 45 + 'confirmlink/(?P<akey>[^/]+)/' 46 + => 'PhabricatorAuthConfirmLinkController', 43 47 ), 44 48 45 49 '/login/' => array( ··· 56 60 '(?P<provider>\w+)/' => array( 57 61 'login/' => 'PhabricatorOAuthLoginController', 58 62 'diagnose/' => 'PhabricatorOAuthDiagnosticsController', 59 - 'unlink/' => 'PhabricatorOAuthUnlinkController', 60 63 ), 61 64 ), 62 65 63 66 '/ldap/' => array( 64 67 'login/' => 'PhabricatorLDAPLoginController', 65 - 'unlink/' => 'PhabricatorLDAPUnlinkController', 66 68 ), 67 69 ); 68 70 }
+89
src/applications/auth/controller/PhabricatorAuthConfirmLinkController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthConfirmLinkController 4 + extends PhabricatorAuthController { 5 + 6 + private $accountKey; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->accountKey = idx($data, 'akey'); 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $result = $this->loadAccountForRegistrationOrLinking($this->accountKey); 17 + list($account, $provider, $response) = $result; 18 + 19 + if ($response) { 20 + return $response; 21 + } 22 + 23 + if (!$provider->shouldAllowAccountLink()) { 24 + return $this->renderError(pht('This account is not linkable.')); 25 + } 26 + 27 + $panel_uri = '/settings/panel/external/'; 28 + 29 + if ($request->isFormPost()) { 30 + $account->setUserPHID($viewer->getPHID()); 31 + $account->save(); 32 + 33 + $this->clearRegistrationCookies(); 34 + 35 + // TODO: Send the user email about the new account link. 36 + 37 + return id(new AphrontRedirectResponse())->setURI($panel_uri); 38 + } 39 + 40 + // TODO: Provide more information about the external account. Clicking 41 + // through this form blindly is dangerous. 42 + 43 + // TODO: If the user has password authentication, require them to retype 44 + // their password here. 45 + 46 + $dialog = id(new AphrontDialogView()) 47 + ->setUser($viewer) 48 + ->setTitle(pht('Confirm %s Account Link', $provider->getProviderName())) 49 + ->addCancelButton($panel_uri) 50 + ->addSubmitButton(pht('Confirm Account Link')); 51 + 52 + $form = id(new AphrontFormLayoutView()) 53 + ->setFullWidth(true) 54 + ->appendChild( 55 + phutil_tag( 56 + 'div', 57 + array( 58 + 'class' => 'aphront-form-instructions', 59 + ), 60 + pht( 61 + "Confirm the link with this %s account. This account will be ". 62 + "able to log in to your Phabricator account.", 63 + $provider->getProviderName()))); 64 + 65 + $dialog->appendChild($form); 66 + 67 + $crumbs = $this->buildApplicationCrumbs(); 68 + $crumbs->addCrumb( 69 + id(new PhabricatorCrumbView()) 70 + ->setName(pht('Confirm Link')) 71 + ->setHref($panel_uri)); 72 + $crumbs->addCrumb( 73 + id(new PhabricatorCrumbView()) 74 + ->setName($provider->getProviderName())); 75 + 76 + return $this->buildApplicationPage( 77 + array( 78 + $crumbs, 79 + $dialog, 80 + ), 81 + array( 82 + 'title' => pht('Confirm External Account Link'), 83 + 'dust' => true, 84 + 'device' => true, 85 + )); 86 + } 87 + 88 + 89 + }
+105
src/applications/auth/controller/PhabricatorAuthController.php
··· 29 29 30 30 } 31 31 32 + 32 33 /** 33 34 * Log a user into a web session and return an @{class:AphrontResponse} which 34 35 * corresponds to continuing the login process. ··· 97 98 array( 98 99 $message, 99 100 )); 101 + } 102 + 103 + protected function loadAccountForRegistrationOrLinking($account_key) { 104 + $request = $this->getRequest(); 105 + $viewer = $request->getUser(); 106 + 107 + $account = null; 108 + $provider = null; 109 + $response = null; 110 + 111 + if (!$account_key) { 112 + $response = $this->renderError( 113 + pht('Request did not include account key.')); 114 + return array($account, $provider, $response); 115 + } 116 + 117 + $account = id(new PhabricatorExternalAccount())->loadOneWhere( 118 + 'accountSecret = %s', 119 + $account_key); 120 + if (!$account) { 121 + $response = $this->renderError(pht('No valid linkable account.')); 122 + return array($account, $provider, $response); 123 + } 124 + 125 + if ($account->getUserPHID()) { 126 + if ($account->getUserPHID() != $viewer->getUserPHID()) { 127 + $response = $this->renderError( 128 + pht( 129 + 'The account you are attempting to register or link is already '. 130 + 'linked to another user.')); 131 + } else { 132 + $response = $this->renderError( 133 + pht( 134 + 'The account you are attempting to link is already linked '. 135 + 'to your account.')); 136 + } 137 + return array($account, $provider, $response); 138 + } 139 + 140 + $registration_key = $request->getCookie('phreg'); 141 + 142 + // NOTE: This registration key check is not strictly necessary, because 143 + // we're only creating new accounts, not linking existing accounts. It 144 + // might be more hassle than it is worth, especially for email. 145 + // 146 + // The attack this prevents is getting to the registration screen, then 147 + // copy/pasting the URL and getting someone else to click it and complete 148 + // the process. They end up with an account bound to credentials you 149 + // control. This doesn't really let you do anything meaningful, though, 150 + // since you could have simply completed the process yourself. 151 + 152 + if (!$registration_key) { 153 + $response = $this->renderError( 154 + pht( 155 + 'Your browser did not submit a registration key with the request. '. 156 + 'You must use the same browser to begin and complete registration. '. 157 + 'Check that cookies are enabled and try again.')); 158 + return array($account, $provider, $response); 159 + } 160 + 161 + // We store the digest of the key rather than the key itself to prevent a 162 + // theoretical attacker with read-only access to the database from 163 + // hijacking registration sessions. 164 + 165 + $actual = $account->getProperty('registrationKey'); 166 + $expect = PhabricatorHash::digest($registration_key); 167 + if ($actual !== $expect) { 168 + $response = $this->renderError( 169 + pht( 170 + 'Your browser submitted a different registration key than the one '. 171 + 'associated with this account. You may need to clear your cookies.')); 172 + return array($account, $provider, $response); 173 + } 174 + 175 + $other_account = id(new PhabricatorExternalAccount())->loadAllWhere( 176 + 'accountType = %s AND accountDomain = %s AND accountID = %s 177 + AND id != %d', 178 + $account->getAccountType(), 179 + $account->getAccountDomain(), 180 + $account->getAccountID(), 181 + $account->getID()); 182 + 183 + if ($other_account) { 184 + $response = $this->renderError( 185 + pht( 186 + 'The account you are attempting to register with already belongs '. 187 + 'to another user.')); 188 + return array($account, $provider, $response); 189 + } 190 + 191 + $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 192 + $account->getProviderKey()); 193 + 194 + if (!$provider) { 195 + $response = $this->renderError( 196 + pht( 197 + 'The account you are attempting to register with uses a nonexistent '. 198 + 'or disabled authentication provider (with key "%s"). An '. 199 + 'administrator may have recently disabled this provider.', 200 + $account->getProviderKey())); 201 + return array($account, $provider, $response); 202 + } 203 + 204 + return array($account, $provider, null); 100 205 } 101 206 102 207 }
+81
src/applications/auth/controller/PhabricatorAuthLinkController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthLinkController 4 + extends PhabricatorAuthController { 5 + 6 + private $providerKey; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->providerKey = $data['pkey']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 17 + $this->providerKey); 18 + if (!$provider) { 19 + return new Aphront404Response(); 20 + } 21 + 22 + if (!$provider->shouldAllowAccountLink()) { 23 + return $this->renderErrorPage( 24 + pht('Account Not Linkable'), 25 + array( 26 + pht('This provider is not configured to allow linking.'), 27 + )); 28 + } 29 + 30 + $account = id(new PhabricatorExternalAccount())->loadOneWhere( 31 + 'accountType = %s AND accountDomain = %s AND userPHID = %s', 32 + $provider->getProviderType(), 33 + $provider->getProviderDomain(), 34 + $viewer->getPHID()); 35 + if ($account) { 36 + return $this->renderErrorPage( 37 + pht('Account Already Linked'), 38 + array( 39 + pht( 40 + 'Your Phabricator account is already linked to an external '. 41 + 'account for this provider.'), 42 + )); 43 + } 44 + 45 + $panel_uri = '/settings/panel/external/'; 46 + 47 + $request->setCookie('phcid', Filesystem::readRandomCharacters(16)); 48 + $form = $provider->buildLinkForm($this); 49 + 50 + if ($provider->isLoginFormAButton()) { 51 + require_celerity_resource('auth-css'); 52 + $form = phutil_tag( 53 + 'div', 54 + array( 55 + 'class' => 'phabricator-link-button pl', 56 + ), 57 + $form); 58 + } 59 + 60 + $crumbs = $this->buildApplicationCrumbs(); 61 + $crumbs->addCrumb( 62 + id(new PhabricatorCrumbView()) 63 + ->setName(pht('Link Account')) 64 + ->setHref($panel_uri)); 65 + $crumbs->addCrumb( 66 + id(new PhabricatorCrumbView()) 67 + ->setName($provider->getProviderName())); 68 + 69 + return $this->buildApplicationPage( 70 + array( 71 + $crumbs, 72 + $form, 73 + ), 74 + array( 75 + 'title' => pht('Link %s Account', $provider->getProviderName()), 76 + 'dust' => true, 77 + 'device' => true, 78 + )); 79 + } 80 + 81 + }
+20 -22
src/applications/auth/controller/PhabricatorAuthLoginController.php
··· 111 111 } 112 112 113 113 private function processRegisterUser(PhabricatorExternalAccount $account) { 114 + $account_secret = $account->getAccountSecret(); 115 + $register_uri = $this->getApplicationURI('register/'.$account_secret.'/'); 116 + return $this->setAccountKeyAndContinue($account, $register_uri); 117 + } 118 + 119 + private function processLinkUser(PhabricatorExternalAccount $account) { 120 + $account_secret = $account->getAccountSecret(); 121 + $confirm_uri = $this->getApplicationURI('confirmlink/'.$account_secret.'/'); 122 + return $this->setAccountKeyAndContinue($account, $confirm_uri); 123 + } 124 + 125 + private function setAccountKeyAndContinue( 126 + PhabricatorExternalAccount $account, 127 + $next_uri) { 128 + 114 129 if ($account->getUserPHID()) { 115 - throw new Exception("Account is already registered."); 130 + throw new Exception("Account is already registered or linked."); 116 131 } 117 132 118 133 // Regenerate the registration secret key, set it on the external account, ··· 131 146 132 147 $this->getRequest()->setCookie('phreg', $registration_key); 133 148 134 - $account_secret = $account->getAccountSecret(); 135 - $register_uri = $this->getApplicationURI('register/'.$account_secret.'/'); 136 - return id(new AphrontRedirectResponse())->setURI($register_uri); 137 - } 138 - 139 - private function processLinkUser(PhabricatorExternalAccount $account) { 140 - // TODO: Implement. 141 - return new Aphront404Response(); 149 + return id(new AphrontRedirectResponse())->setURI($next_uri); 142 150 } 143 151 144 152 private function loadProvider() { ··· 160 168 } 161 169 162 170 protected function renderError($message) { 163 - $title = pht('Login Failed'); 164 - 165 - $view = new AphrontErrorView(); 166 - $view->setTitle($title); 167 - $view->appendChild($message); 168 - 169 - return $this->buildApplicationPage( 170 - $view, 171 - array( 172 - 'title' => $title, 173 - 'device' => true, 174 - 'dust' => true, 175 - )); 171 + return $this->renderErrorPage( 172 + pht('Login Failed'), 173 + array($message)); 176 174 } 177 175 178 176 public function buildProviderPageResponse(
+34 -118
src/applications/auth/controller/PhabricatorAuthRegisterController.php
··· 4 4 extends PhabricatorAuthController { 5 5 6 6 private $accountKey; 7 - private $account; 8 - private $provider; 9 7 10 8 public function shouldRequireLogin() { 11 9 return false; ··· 23 21 } 24 22 25 23 if (strlen($this->accountKey)) { 26 - $response = $this->loadAccount(); 24 + $result = $this->loadAccountForRegistrationOrLinking($this->accountKey); 25 + list($account, $provider, $response) = $result; 27 26 } else { 28 - $response = $this->loadDefaultAccount(); 27 + list($account, $provider, $response) = $this->loadDefaultAccount(); 29 28 } 30 29 31 30 if ($response) { 32 31 return $response; 33 32 } 34 33 35 - $account = $this->account; 34 + if (!$provider->shouldAllowRegistration()) { 35 + 36 + // TODO: This is a routine error if you click "Login" on an external 37 + // auth source which doesn't allow registration. The error should be 38 + // more tailored. 39 + 40 + return $this->renderError( 41 + pht( 42 + 'The account you are attempting to register with uses an '. 43 + 'authentication provider ("%s") which does not allow registration. '. 44 + 'An administrator may have recently disabled registration with this '. 45 + 'provider.', 46 + $provider->getProviderName())); 47 + } 36 48 37 49 $user = new PhabricatorUser(); 38 50 ··· 88 100 $can_edit_email = $profile->getCanEditEmail(); 89 101 $can_edit_realname = $profile->getCanEditRealName(); 90 102 91 - $must_set_password = $this->provider->shouldRequireRegistrationPassword(); 103 + $must_set_password = $provider->shouldRequireRegistrationPassword(); 92 104 93 105 $can_edit_anything = $profile->getCanEditAnything() || $must_set_password; 94 106 $force_verify = $profile->getShouldVerifyEmail(); ··· 200 212 } 201 213 202 214 $account->setUserPHID($user->getPHID()); 203 - $this->provider->willRegisterAccount($account); 215 + $provider->willRegisterAccount($account); 204 216 $account->save(); 205 217 206 218 $user->saveTransaction(); ··· 315 327 )); 316 328 } 317 329 318 - private function loadAccount() { 319 - $request = $this->getRequest(); 320 - 321 - if (!$this->accountKey) { 322 - return $this->renderError(pht('Request did not include account key.')); 323 - } 324 - 325 - $account = id(new PhabricatorExternalAccount())->loadOneWhere( 326 - 'accountSecret = %s', 327 - $this->accountKey); 328 - 329 - if (!$account) { 330 - return $this->renderError(pht('No registration account.')); 331 - } 332 - 333 - if ($account->getUserPHID()) { 334 - return $this->renderError( 335 - pht( 336 - 'The account you are attempting to register with is already '. 337 - 'registered to another user.')); 338 - } 339 - 340 - $registration_key = $request->getCookie('phreg'); 341 - 342 - // NOTE: This registration key check is not strictly necessary, because 343 - // we're only creating new accounts, not linking existing accounts. It 344 - // might be more hassle than it is worth, especially for email. 345 - // 346 - // The attack this prevents is getting to the registration screen, then 347 - // copy/pasting the URL and getting someone else to click it and complete 348 - // the process. They end up with an account bound to credentials you 349 - // control. This doesn't really let you do anything meaningful, though, 350 - // since you could have simply completed the process yourself. 351 - 352 - if (!$registration_key) { 353 - return $this->renderError( 354 - pht( 355 - 'Your browser did not submit a registration key with the request. '. 356 - 'You must use the same browser to begin and complete registration. '. 357 - 'Check that cookies are enabled and try again.')); 358 - } 359 - 360 - // We store the digest of the key rather than the key itself to prevent a 361 - // theoretical attacker with read-only access to the database from 362 - // hijacking registration sessions. 363 - 364 - $actual = $account->getProperty('registrationKey'); 365 - $expect = PhabricatorHash::digest($registration_key); 366 - if ($actual !== $expect) { 367 - return $this->renderError( 368 - pht( 369 - 'Your browser submitted a different registration key than the one '. 370 - 'associated with this account. You may need to clear your cookies.')); 371 - } 372 - 373 - $other_account = id(new PhabricatorExternalAccount())->loadAllWhere( 374 - 'accountType = %s AND accountDomain = %s AND accountID = %s 375 - AND id != %d', 376 - $account->getAccountType(), 377 - $account->getAccountDomain(), 378 - $account->getAccountID(), 379 - $account->getID()); 380 - 381 - if ($other_account) { 382 - return $this->renderError( 383 - pht( 384 - 'The account you are attempting to register with already belongs '. 385 - 'to another user.')); 386 - } 387 - 388 - $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 389 - $account->getProviderKey()); 390 - 391 - if (!$provider) { 392 - return $this->renderError( 393 - pht( 394 - 'The account you are attempting to register with uses a nonexistent '. 395 - 'or disabled authentication provider (with key "%s"). An '. 396 - 'administrator may have recently disabled this provider.', 397 - $account->getProviderKey())); 398 - } 399 - 400 - if (!$provider->shouldAllowRegistration()) { 401 - 402 - // TODO: This is a routine error if you click "Login" on an external 403 - // auth source which doesn't allow registration. The error should be 404 - // more tailored. 405 - 406 - return $this->renderError( 407 - pht( 408 - 'The account you are attempting to register with uses an '. 409 - 'authentication provider ("%s") which does not allow registration. '. 410 - 'An administrator may have recently disabled registration with this '. 411 - 'provider.', 412 - $provider->getProviderName())); 413 - } 414 - 415 - $this->account = $account; 416 - $this->provider = $provider; 417 - 418 - return null; 419 - } 420 - 421 330 private function loadDefaultAccount() { 422 331 $providers = PhabricatorAuthProvider::getAllEnabledProviders(); 423 - foreach ($providers as $key => $provider) { 424 - if (!$provider->shouldAllowRegistration()) { 332 + $account = null; 333 + $provider = null; 334 + $response = null; 335 + 336 + foreach ($providers as $key => $candidate_provider) { 337 + if (!$candidate_provider->shouldAllowRegistration()) { 425 338 unset($providers[$key]); 426 339 continue; 427 340 } 428 - if (!$provider->isDefaultRegistrationProvider()) { 341 + if (!$candidate_provider->isDefaultRegistrationProvider()) { 429 342 unset($providers[$key]); 430 343 } 431 344 } 432 345 433 346 if (!$providers) { 434 - return $this->renderError( 347 + $response = $this->renderError( 435 348 pht( 436 349 "There are no configured default registration providers.")); 350 + return array($account, $provider, $response); 437 351 } else if (count($providers) > 1) { 438 - return $this->renderError( 352 + $response = $this->renderError( 439 353 pht( 440 354 "There are too many configured default registration providers.")); 355 + return array($account, $provider, $response); 441 356 } 442 357 443 - $this->account = $provider->getDefaultExternalAccount(); 444 - $this->provider = $provider; 445 - return null; 358 + $provider = head($providers); 359 + $account = $provider->getDefaultExternalAccount(); 360 + 361 + return array($account, $provider, $response); 446 362 } 447 363 448 364 private function loadProfilePicture(PhabricatorExternalAccount $account) {
+140
src/applications/auth/controller/PhabricatorAuthUnlinkController.php
··· 1 + <?php 2 + 3 + final class PhabricatorAuthUnlinkController 4 + extends PhabricatorAuthController { 5 + 6 + private $providerKey; 7 + 8 + public function willProcessRequest(array $data) { 9 + $this->providerKey = $data['pkey']; 10 + } 11 + 12 + public function processRequest() { 13 + $request = $this->getRequest(); 14 + $viewer = $request->getUser(); 15 + 16 + list($type, $domain) = explode(':', $this->providerKey, 2); 17 + 18 + // Check that this account link actually exists. We don't require the 19 + // provider to exist because we want users to be able to delete links to 20 + // dead accounts if they want. 21 + $account = id(new PhabricatorExternalAccount())->loadOneWhere( 22 + 'accountType = %s AND accountDomain = %s AND userPHID = %s', 23 + $type, 24 + $domain, 25 + $viewer->getPHID()); 26 + if (!$account) { 27 + return $this->renderNoAccountErrorDialog(); 28 + } 29 + 30 + // Check that the provider (if it exists) allows accounts to be unlinked. 31 + $provider_key = $this->providerKey; 32 + $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key); 33 + if ($provider) { 34 + if (!$provider->shouldAllowAccountUnlink()) { 35 + return $this->renderNotUnlinkableErrorDialog($provider); 36 + } 37 + } 38 + 39 + // Check that this account isn't the last account which can be used to 40 + // login. We prevent you from removing the last account. 41 + if ($account->isUsableForLogin()) { 42 + $other_accounts = id(new PhabricatorExternalAccount())->loadAllWhere( 43 + 'userPHID = %s', 44 + $viewer->getPHID()); 45 + 46 + $valid_accounts = 0; 47 + foreach ($other_accounts as $other_account) { 48 + if ($other_account->isUsableForLogin()) { 49 + $valid_accounts++; 50 + } 51 + } 52 + 53 + if ($valid_accounts < 2) { 54 + return $this->renderLastUsableAccountErrorDialog(); 55 + } 56 + } 57 + 58 + if ($request->isDialogFormPost()) { 59 + $account->delete(); 60 + return id(new AphrontRedirectResponse())->setURI($this->getDoneURI()); 61 + } 62 + 63 + return $this->renderConfirmDialog($account); 64 + } 65 + 66 + private function getDoneURI() { 67 + return '/settings/panel/external/'; 68 + } 69 + 70 + private function renderNoAccountErrorDialog() { 71 + $dialog = id(new AphrontDialogView()) 72 + ->setUser($this->getRequest()->getUser()) 73 + ->setTitle(pht('No Such Account')) 74 + ->appendChild( 75 + pht( 76 + "You can not unlink this account because it is not linked.")) 77 + ->addCancelButton($this->getDoneURI()); 78 + 79 + return id(new AphrontDialogResponse())->setDialog($dialog); 80 + } 81 + 82 + private function renderNotUnlinkableErrorDialog( 83 + PhabricatorAuthProvider $provider) { 84 + 85 + $dialog = id(new AphrontDialogView()) 86 + ->setUser($this->getRequest()->getUser()) 87 + ->setTitle(pht('Permanent Account Link')) 88 + ->appendChild( 89 + pht( 90 + "You can not unlink this account because the administrator has ". 91 + "configured Phabricator to make links to %s accounts permanent.", 92 + $provider->getProviderName())) 93 + ->addCancelButton($this->getDoneURI()); 94 + 95 + return id(new AphrontDialogResponse())->setDialog($dialog); 96 + } 97 + 98 + private function renderLastUsableAccountErrorDialog() { 99 + $dialog = id(new AphrontDialogView()) 100 + ->setUser($this->getRequest()->getUser()) 101 + ->setTitle(pht('Last Valid Account')) 102 + ->appendChild( 103 + pht( 104 + "You can not unlink this account because you have no other ". 105 + "valid login accounts. If you removed it, you would be unable ". 106 + "to login. Add another authentication method before removing ". 107 + "this one.")) 108 + ->addCancelButton($this->getDoneURI()); 109 + 110 + return id(new AphrontDialogResponse())->setDialog($dialog); 111 + } 112 + 113 + private function renderConfirmDialog() { 114 + $provider_key = $this->providerKey; 115 + $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key); 116 + 117 + if ($provider) { 118 + $title = pht('Unlink "%s" Account?', $provider->getProviderName()); 119 + $body = pht( 120 + 'You will no longer be able to use your %s account to '. 121 + 'log in to Phabricator.', 122 + $provider->getProviderName()); 123 + } else { 124 + $title = pht('Unlink Account?'); 125 + $body = pht( 126 + 'You will no longer be able to use this account to log in '. 127 + 'to Phabricator.'); 128 + } 129 + 130 + $dialog = id(new AphrontDialogView()) 131 + ->setUser($this->getRequest()->getUser()) 132 + ->setTitle($title) 133 + ->appendChild($body) 134 + ->addSubmitButton(pht('Unlink Account')) 135 + ->addCancelButton($this->getDoneURI()); 136 + 137 + return id(new AphrontDialogResponse())->setDialog($dialog); 138 + } 139 + 140 + }
+1 -7
src/applications/auth/controller/PhabricatorEmailTokenController.php
··· 75 75 76 76 $next = '/'; 77 77 if (!PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) { 78 - $panels = id(new PhabricatorSettingsPanelOAuth())->buildPanels(); 79 - foreach ($panels as $panel) { 80 - if ($panel->isEnabled()) { 81 - $next = $panel->getPanelURI(); 82 - break; 83 - } 84 - } 78 + $next = '/settings/panel/external/'; 85 79 } else if (PhabricatorEnv::getEnvConfig('account.editable')) { 86 80 $next = (string)id(new PhutilURI('/settings/panel/password/')) 87 81 ->setQueryParams(
-38
src/applications/auth/controller/PhabricatorLDAPUnlinkController.php
··· 1 - <?php 2 - 3 - final class PhabricatorLDAPUnlinkController extends PhabricatorAuthController { 4 - 5 - public function processRequest() { 6 - $request = $this->getRequest(); 7 - $user = $request->getUser(); 8 - 9 - $ldap_account = id(new PhabricatorExternalAccount())->loadOneWhere( 10 - 'userPHID = %s AND accountType = %s AND accountDomain = %s', 11 - $user->getPHID(), 12 - 'ldap', 13 - 'self'); 14 - 15 - if (!$ldap_account) { 16 - return new Aphront400Response(); 17 - } 18 - 19 - if (!$request->isDialogFormPost()) { 20 - $dialog = new AphrontDialogView(); 21 - $dialog->setUser($user); 22 - $dialog->setTitle(pht('Really unlink account?')); 23 - $dialog->appendChild(phutil_tag('p', array(), pht( 24 - 'You will not be able to login using this account '. 25 - 'once you unlink it. Continue?'))); 26 - $dialog->addSubmitButton(pht('Unlink Account')); 27 - $dialog->addCancelButton('/settings/panel/ldap/'); 28 - 29 - return id(new AphrontDialogResponse())->setDialog($dialog); 30 - } 31 - 32 - $ldap_account->delete(); 33 - 34 - return id(new AphrontRedirectResponse()) 35 - ->setURI('/settings/panel/ldap/'); 36 - } 37 - 38 - }
-51
src/applications/auth/controller/PhabricatorOAuthUnlinkController.php
··· 1 - <?php 2 - 3 - final class PhabricatorOAuthUnlinkController extends PhabricatorAuthController { 4 - 5 - private $provider; 6 - 7 - public function willProcessRequest(array $data) { 8 - $this->provider = PhabricatorOAuthProvider::newProvider($data['provider']); 9 - } 10 - 11 - public function processRequest() { 12 - $request = $this->getRequest(); 13 - $user = $request->getUser(); 14 - 15 - $provider = $this->provider; 16 - 17 - if ($provider->isProviderLinkPermanent()) { 18 - throw new Exception( 19 - pht("You may not unlink accounts from this OAuth provider.")); 20 - } 21 - 22 - $provider_key = $provider->getProviderKey(); 23 - 24 - $oauth_info = PhabricatorUserOAuthInfo::loadOneByUserAndProviderKey( 25 - $user, 26 - $provider_key); 27 - 28 - if (!$oauth_info) { 29 - return new Aphront400Response(); 30 - } 31 - 32 - if (!$request->isDialogFormPost()) { 33 - $dialog = new AphrontDialogView(); 34 - $dialog->setUser($user); 35 - $dialog->setTitle(pht('Really unlink account?')); 36 - $dialog->appendChild(phutil_tag('p', array(), pht( 37 - 'You will not be able to login using this account '. 38 - 'once you unlink it. Continue?'))); 39 - $dialog->addSubmitButton(pht('Unlink Account')); 40 - $dialog->addCancelButton($provider->getSettingsPanelURI()); 41 - 42 - return id(new AphrontDialogResponse())->setDialog($dialog); 43 - } 44 - 45 - $oauth_info->delete(); 46 - 47 - return id(new AphrontRedirectResponse()) 48 - ->setURI($provider->getSettingsPanelURI()); 49 - } 50 - 51 - }
+1 -3
src/applications/auth/oauth/provider/PhabricatorOAuthProvider.php
··· 23 23 abstract public function getTestURIs(); 24 24 25 25 public function getSettingsPanelURI() { 26 - $panel = new PhabricatorSettingsPanelOAuth(); 27 - $panel->setOAuthProvider($this); 28 - return $panel->getPanelURI(); 26 + return '/settings/panel/external/'; 29 27 } 30 28 31 29 /**
+31 -5
src/applications/auth/provider/PhabricatorAuthProvider.php
··· 6 6 return $this->getAdapter()->getAdapterKey(); 7 7 } 8 8 9 + public function getProviderType() { 10 + return $this->getAdapter()->getAdapterType(); 11 + } 12 + 13 + public function getProviderDomain() { 14 + return $this->getAdapter()->getAdapterDomain(); 15 + } 16 + 9 17 public static function getAllProviders() { 10 18 static $providers; 11 19 ··· 56 64 abstract public function getAdapter(); 57 65 58 66 public function isEnabled() { 59 - // TODO: Remove once we switch to the new auth stuff. 60 - return false; 67 + return true; 61 68 } 62 69 63 70 abstract public function shouldAllowLogin(); ··· 65 72 abstract public function shouldAllowAccountLink(); 66 73 abstract public function shouldAllowAccountUnlink(); 67 74 68 - abstract public function buildLoginForm( 69 - PhabricatorAuthStartController $controller); 75 + public function buildLoginForm( 76 + PhabricatorAuthStartController $controller) { 77 + return $this->renderLoginForm($controller->getRequest(), $is_link = false); 78 + } 70 79 71 80 abstract public function processLoginRequest( 72 81 PhabricatorAuthLoginController $controller); 73 82 83 + public function buildLinkForm( 84 + PhabricatorAuthLinkController $controller) { 85 + return $this->renderLoginForm($controller->getRequest(), $is_link = true); 86 + } 87 + 88 + protected function renderLoginForm( 89 + AphrontRequest $request, 90 + $is_link) { 91 + throw new Exception("Not implemented!"); 92 + } 93 + 74 94 public function createProviders() { 75 95 return array($this); 76 96 } ··· 144 164 145 165 $this->willSaveAccount($account); 146 166 147 - $account->save(); 167 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 168 + $account->save(); 169 + unset($unguarded); 148 170 149 171 return $account; 150 172 } ··· 153 175 $app = PhabricatorApplication::getByClass('PhabricatorApplicationAuth'); 154 176 $uri = $app->getApplicationURI('/login/'.$this->getProviderKey().'/'); 155 177 return PhabricatorEnv::getURI($uri); 178 + } 179 + 180 + protected function getCancelLinkURI() { 181 + return '/settings/panel/external/'; 156 182 } 157 183 158 184 public function isDefaultRegistrationProvider() {
+7 -12
src/applications/auth/provider/PhabricatorAuthProviderLDAP.php
··· 49 49 } 50 50 51 51 public function shouldAllowAccountLink() { 52 - return false; 52 + return true; 53 53 } 54 54 55 55 public function shouldAllowAccountUnlink() { 56 - return false; 56 + return true; 57 57 } 58 58 59 - public function buildLoginForm( 60 - PhabricatorAuthStartController $controller) { 61 - 62 - $request = $controller->getRequest(); 63 - return $this->renderLoginForm($request); 64 - } 65 - 66 - private function renderLoginForm(AphrontRequest $request) { 67 - 59 + protected function renderLoginForm(AphrontRequest $request, $is_link) { 68 60 $viewer = $request->getUser(); 69 61 70 62 $dialog = id(new AphrontDialogView()) 71 63 ->setSubmitURI($this->getLoginURI()) 72 64 ->setUser($viewer); 73 65 74 - if ($this->shouldAllowRegistration()) { 66 + if ($is_link) { 67 + $dialog->setTitle(pht('Link LDAP Account')); 68 + $dialog->addSubmitButton(pht('Link Accounts')); 69 + } else if ($this->shouldAllowRegistration()) { 75 70 $dialog->setTitle(pht('Login or Register with LDAP')); 76 71 $dialog->addSubmitButton(pht('Login or Register')); 77 72 } else {
+4 -7
src/applications/auth/provider/PhabricatorAuthProviderOAuth.php
··· 40 40 return true; 41 41 } 42 42 43 - public function buildLoginForm( 44 - PhabricatorAuthStartController $controller) { 45 - 46 - $request = $controller->getRequest(); 43 + protected function renderLoginForm(AphrontRequest $request, $is_link) { 47 44 $viewer = $request->getUser(); 48 45 49 - if ($this->shouldAllowRegistration()) { 46 + if ($is_link) { 47 + $button_text = pht('Link External Account'); 48 + } else if ($this->shouldAllowRegistration()) { 50 49 $button_text = pht('Login or Register'); 51 50 } else { 52 51 $button_text = pht('Login'); ··· 57 56 ->setSpriteIcon($this->getLoginIcon()); 58 57 59 58 $button = id(new PHUIButtonView()) 60 - ->setTag('a') 61 59 ->setSize(PHUIButtonView::BIG) 62 60 ->setColor(PHUIButtonView::GREY) 63 61 ->setIcon($icon) ··· 91 89 ), 92 90 $content); 93 91 } 94 - 95 92 public function processLoginRequest( 96 93 PhabricatorAuthLoginController $controller) { 97 94
+7 -4
src/applications/auth/provider/PhabricatorAuthProviderPassword.php
··· 51 51 52 52 public function buildLoginForm( 53 53 PhabricatorAuthStartController $controller) { 54 - 55 54 $request = $controller->getRequest(); 55 + return $this->renderPasswordLoginForm($request); 56 + } 56 57 57 - return $this->renderLoginForm($request); 58 + public function buildLinkForm( 59 + PhabricatorAuthLinkController $controller) { 60 + throw new Exception("Password providers can't be linked."); 58 61 } 59 62 60 - private function renderLoginForm( 63 + private function renderPasswordLoginForm( 61 64 AphrontRequest $request, 62 65 $require_captcha = false, 63 66 $captcha_valid = false) { ··· 195 198 196 199 $response = $controller->buildProviderPageResponse( 197 200 $this, 198 - $this->renderLoginForm( 201 + $this->renderPasswordLoginForm( 199 202 $request, 200 203 $require_captcha, 201 204 $captcha_valid));
+15
src/applications/people/storage/PhabricatorExternalAccount.php
··· 57 57 return idx($this->properties, $key, $default); 58 58 } 59 59 60 + public function isUsableForLogin() { 61 + $key = $this->getProviderKey(); 62 + $provider = PhabricatorAuthProvider::getEnabledProviderByKey($key); 63 + 64 + if (!$provider) { 65 + return false; 66 + } 67 + 68 + if (!$provider->shouldAllowLogin()) { 69 + return false; 70 + } 71 + 72 + return true; 73 + } 74 + 60 75 }
+120
src/applications/settings/panel/PhabricatorSettingsPanelExternalAccounts.php
··· 1 + <?php 2 + 3 + final class PhabricatorSettingsPanelExternalAccounts 4 + extends PhabricatorSettingsPanel { 5 + 6 + public function getPanelKey() { 7 + return 'external'; 8 + } 9 + 10 + public function getPanelName() { 11 + return pht('External Accounts'); 12 + } 13 + 14 + public function getPanelGroup() { 15 + return pht('Authentication'); 16 + } 17 + 18 + public function isEnabled() { 19 + return true; 20 + } 21 + 22 + public function processRequest(AphrontRequest $request) { 23 + $viewer = $request->getUser(); 24 + 25 + $providers = PhabricatorAuthProvider::getAllProviders(); 26 + $accounts = id(new PhabricatorExternalAccount())->loadAllWhere( 27 + 'userPHID = %s', 28 + $viewer->getPHID()); 29 + 30 + $linked_head = id(new PhabricatorHeaderView()) 31 + ->setHeader(pht('Linked Accounts and Authentication')); 32 + 33 + $linked = id(new PhabricatorObjectItemListView()) 34 + ->setUser($viewer) 35 + ->setNoDataString(pht('You have no linked accounts.')); 36 + 37 + $login_accounts = 0; 38 + foreach ($accounts as $account) { 39 + if ($account->isUsableForLogin()) { 40 + $login_accounts++; 41 + } 42 + } 43 + 44 + foreach ($accounts as $account) { 45 + $item = id(new PhabricatorObjectItemView()); 46 + 47 + $provider = idx($providers, $account->getProviderKey()); 48 + if ($provider) { 49 + $item->setHeader($provider->getProviderName()); 50 + $can_unlink = $provider->shouldAllowAccountUnlink(); 51 + if (!$can_unlink) { 52 + $item->addAttribute(pht('Permanently Linked')); 53 + } 54 + } else { 55 + $item->setHeader( 56 + pht('Unknown Account ("%s")', $account->getProviderKey())); 57 + $can_unlink = true; 58 + } 59 + 60 + $can_login = $account->isUsableForLogin(); 61 + if (!$can_login) { 62 + $item->addAttribute( 63 + pht( 64 + 'Disabled (an administrator has disabled login for this '. 65 + 'account provider).')); 66 + } 67 + 68 + $can_unlink = $can_unlink && (!$can_login || ($login_accounts > 1)); 69 + 70 + $item->addAction( 71 + id(new PHUIListItemView()) 72 + ->setIcon('delete') 73 + ->setWorkflow(true) 74 + ->setDisabled(!$can_unlink) 75 + ->setHref('/auth/unlink/'.$account->getProviderKey().'/')); 76 + 77 + $linked->addItem($item); 78 + } 79 + 80 + $linkable_head = id(new PhabricatorHeaderView()) 81 + ->setHeader(pht('Add External Account')); 82 + 83 + $linkable = id(new PhabricatorObjectItemListView()) 84 + ->setUser($viewer) 85 + ->setNoDataString( 86 + pht('Your account is linked with all available providers.')); 87 + 88 + $accounts = mpull($accounts, null, 'getProviderKey'); 89 + $providers = msort($providers, 'getProviderName'); 90 + foreach ($providers as $key => $provider) { 91 + if (isset($accounts[$key])) { 92 + continue; 93 + } 94 + 95 + if (!$provider->shouldAllowAccountLink()) { 96 + continue; 97 + } 98 + 99 + $link_uri = '/auth/link/'.$provider->getProviderKey().'/'; 100 + 101 + $item = id(new PhabricatorObjectItemView()); 102 + $item->setHeader($provider->getProviderName()); 103 + $item->setHref($link_uri); 104 + $item->addAction( 105 + id(new PHUIListItemView()) 106 + ->setIcon('link') 107 + ->setHref($link_uri)); 108 + 109 + $linkable->addItem($item); 110 + } 111 + 112 + return array( 113 + $linked_head, 114 + $linked, 115 + $linkable_head, 116 + $linkable, 117 + ); 118 + } 119 + 120 + }
-92
src/applications/settings/panel/PhabricatorSettingsPanelLDAP.php
··· 1 - <?php 2 - 3 - final class PhabricatorSettingsPanelLDAP 4 - extends PhabricatorSettingsPanel { 5 - 6 - public function getPanelKey() { 7 - return 'ldap'; 8 - } 9 - 10 - public function getPanelName() { 11 - return pht('LDAP'); 12 - } 13 - 14 - public function getPanelGroup() { 15 - return pht('Linked Accounts'); 16 - } 17 - 18 - public function isEnabled() { 19 - $ldap_provider = new PhabricatorLDAPProvider(); 20 - return $ldap_provider->isProviderEnabled(); 21 - } 22 - 23 - public function processRequest(AphrontRequest $request) { 24 - $user = $request->getUser(); 25 - 26 - $ldap_account = id(new PhabricatorExternalAccount())->loadOneWhere( 27 - 'userPHID = %s AND accountType = %s AND accountDomain = %s', 28 - $user->getPHID(), 29 - 'ldap', 30 - 'self'); 31 - 32 - $forms = array(); 33 - 34 - if (!$ldap_account) { 35 - $unlink = pht('Link LDAP Account'); 36 - $unlink_form = new AphrontFormView(); 37 - $unlink_form 38 - ->setUser($user) 39 - ->setAction('/ldap/login/') 40 - ->appendChild(hsprintf( 41 - '<p class="aphront-form-instructions">%s</p>', 42 - pht('There is currently no LDAP account linked to your Phabricator '. 43 - 'account. You can link an account, which will allow you to use it '. 44 - 'to log into Phabricator.'))) 45 - ->appendChild( 46 - id(new AphrontFormTextControl()) 47 - ->setLabel(pht('LDAP username')) 48 - ->setName('username')) 49 - ->appendChild( 50 - id(new AphrontFormPasswordControl()) 51 - ->setLabel(pht('Password')) 52 - ->setName('password')) 53 - ->appendChild( 54 - id(new AphrontFormSubmitControl()) 55 - ->setValue(pht("Link LDAP Account \xC2\xBB"))); 56 - 57 - $forms['Link Account'] = $unlink_form; 58 - } else { 59 - $unlink = pht('Unlink LDAP Account'); 60 - $unlink_form = new AphrontFormView(); 61 - $unlink_form 62 - ->setUser($user) 63 - ->appendChild(hsprintf( 64 - '<p class="aphront-form-instructions">%s</p>', 65 - pht('You may unlink this account from your LDAP account. This will '. 66 - 'prevent you from logging in with your LDAP credentials.'))) 67 - ->appendChild( 68 - id(new AphrontFormSubmitControl()) 69 - ->addCancelButton('/ldap/unlink/', $unlink)); 70 - 71 - $forms['Unlink Account'] = $unlink_form; 72 - } 73 - 74 - $header = new PhabricatorHeaderView(); 75 - $header->setHeader(pht('LDAP Account Settings')); 76 - 77 - $formbox = new PHUIBoxView(); 78 - foreach ($forms as $name => $form) { 79 - if ($name) { 80 - $head = new PhabricatorHeaderView(); 81 - $head->setHeader($name); 82 - $formbox->appendChild($head); 83 - } 84 - $formbox->appendChild($form); 85 - } 86 - 87 - return array( 88 - $header, 89 - $formbox, 90 - ); 91 - } 92 - }
-156
src/applications/settings/panel/PhabricatorSettingsPanelOAuth.php
··· 1 - <?php 2 - 3 - final class PhabricatorSettingsPanelOAuth 4 - extends PhabricatorSettingsPanel { 5 - 6 - public function getPanelKey() { 7 - return 'oauth-'.$this->provider->getProviderKey(); 8 - } 9 - 10 - public function getPanelName() { 11 - return $this->provider->getProviderName(); 12 - } 13 - 14 - public function getPanelGroup() { 15 - return pht('Linked Accounts'); 16 - } 17 - 18 - public function buildPanels() { 19 - $panels = array(); 20 - 21 - $providers = PhabricatorOAuthProvider::getAllProviders(); 22 - foreach ($providers as $provider) { 23 - $panel = clone $this; 24 - $panel->setOAuthProvider($provider); 25 - $panels[] = $panel; 26 - } 27 - 28 - return $panels; 29 - } 30 - 31 - public function isEnabled() { 32 - return $this->provider->isProviderEnabled(); 33 - } 34 - 35 - private $provider; 36 - 37 - public function setOAuthProvider(PhabricatorOAuthProvider $oauth_provider) { 38 - $this->provider = $oauth_provider; 39 - return $this; 40 - } 41 - 42 - private function prepareAuthForm(AphrontFormView $form) { 43 - $provider = $this->provider; 44 - 45 - $auth_uri = $provider->getAuthURI(); 46 - $client_id = $provider->getClientID(); 47 - $redirect_uri = $provider->getRedirectURI(); 48 - $minimum_scope = $provider->getMinimumScope(); 49 - 50 - $form 51 - ->setAction($auth_uri) 52 - ->setMethod('GET') 53 - ->addHiddenInput('redirect_uri', $redirect_uri) 54 - ->addHiddenInput('client_id', $client_id) 55 - ->addHiddenInput('scope', $minimum_scope); 56 - 57 - foreach ($provider->getExtraAuthParameters() as $key => $value) { 58 - $form->addHiddenInput($key, $value); 59 - } 60 - 61 - return $form; 62 - } 63 - 64 - public function processRequest(AphrontRequest $request) { 65 - $user = $request->getUser(); 66 - $provider = $this->provider; 67 - $notice = null; 68 - $provider_name = $provider->getProviderName(); 69 - $provider_key = $provider->getProviderKey(); 70 - 71 - $oauth_info = PhabricatorUserOAuthInfo::loadOneByUserAndProviderKey( 72 - $user, 73 - $provider_key); 74 - 75 - $form = new AphrontFormView(); 76 - $form->setUser($user); 77 - 78 - $forms = array(); 79 - $forms[] = $form; 80 - if (!$oauth_info) { 81 - $form 82 - ->appendChild(hsprintf( 83 - '<p class="aphront-form-instructions">%s</p>', 84 - pht('There is currently no %s '. 85 - 'account linked to your Phabricator account. You can link an '. 86 - 'account, which will allow you to use it to log into Phabricator.', 87 - $provider_name))); 88 - 89 - $this->prepareAuthForm($form); 90 - 91 - $form 92 - ->appendChild( 93 - id(new AphrontFormSubmitControl()) 94 - ->setValue(pht("Link %s Account \xC2\xBB", $provider_name))); 95 - } else { 96 - $form 97 - ->appendChild(hsprintf( 98 - '<p class="aphront-form-instructions">%s</p>', 99 - pht('Your account is linked with '. 100 - 'a %s account. You may use your %s credentials to log into '. 101 - 'Phabricator.', 102 - $provider_name, 103 - $provider_name))) 104 - ->appendChild( 105 - id(new AphrontFormStaticControl()) 106 - ->setLabel(pht('%s ID', $provider_name)) 107 - ->setValue($oauth_info->getOAuthUID())) 108 - ->appendChild( 109 - id(new AphrontFormStaticControl()) 110 - ->setLabel(pht('%s Name', $provider_name)) 111 - ->setValue($oauth_info->getAccountName())) 112 - ->appendChild( 113 - id(new AphrontFormStaticControl()) 114 - ->setLabel(pht('%s URI', $provider_name)) 115 - ->setValue($oauth_info->getAccountURI())); 116 - 117 - if (!$provider->isProviderLinkPermanent()) { 118 - $unlink = pht('Unlink %s Account', $provider_name); 119 - $unlink_form = new AphrontFormView(); 120 - $unlink_form 121 - ->setUser($user) 122 - ->appendChild(hsprintf( 123 - '<p class="aphront-form-instructions">%s</p>', 124 - pht('You may unlink this account from your %s account. This will '. 125 - 'prevent you from logging in with your %s credentials.', 126 - $provider_name, 127 - $provider_name))) 128 - ->appendChild( 129 - id(new AphrontFormSubmitControl()) 130 - ->addCancelButton('/oauth/'.$provider_key.'/unlink/', $unlink)); 131 - $forms['Unlink Account'] = $unlink_form; 132 - } 133 - } 134 - 135 - $header = new PhabricatorHeaderView(); 136 - $header->setHeader(pht('%s Account Settings', $provider_name)); 137 - 138 - $formbox = new PHUIBoxView(); 139 - foreach ($forms as $name => $form) { 140 - if ($name) { 141 - $head = new PhabricatorHeaderView(); 142 - $head->setHeader($name); 143 - $formbox->appendChild($head); 144 - } 145 - $formbox->appendChild($form); 146 - } 147 - 148 - return id(new AphrontNullView()) 149 - ->appendChild( 150 - array( 151 - $notice, 152 - $header, 153 - $formbox, 154 - )); 155 - } 156 - }
+1 -1
src/view/phui/PHUIButtonView.php
··· 14 14 private $text; 15 15 private $subtext; 16 16 private $color; 17 - private $tag = 'a'; 17 + private $tag = 'button'; 18 18 private $dropdown; 19 19 private $icon; 20 20
+16 -1
src/view/phui/PHUIListItemView.php
··· 18 18 private $icon; 19 19 private $selected; 20 20 private $containerAttrs; 21 + private $disabled; 21 22 22 23 public function setSelected($selected) { 23 24 $this->selected = $selected; ··· 104 105 ); 105 106 } 106 107 108 + public function setDisabled($disabled) { 109 + $this->disabled = $disabled; 110 + return $this; 111 + } 112 + 113 + public function getDisabled() { 114 + return $this->disabled; 115 + } 116 + 107 117 protected function getTagContent() { 108 118 $name = null; 109 119 $icon = null; ··· 126 136 } 127 137 128 138 if ($this->icon) { 139 + $icon_name = $this->icon; 140 + if ($this->getDisabled()) { 141 + $icon_name .= '-grey'; 142 + } 143 + 129 144 $icon = id(new PHUIIconView()) 130 145 ->addClass('phui-list-item-icon') 131 146 ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) 132 - ->setSpriteIcon($this->icon); 147 + ->setSpriteIcon($icon_name); 133 148 } 134 149 135 150 return phutil_tag(
+5 -1
webroot/rsrc/css/application/auth/auth.css
··· 8 8 } 9 9 10 10 .phabricator-login-buttons .phabricator-login-button .button { 11 - width: 180px; 11 + width: 216px; 12 12 } 13 13 14 14 .device-desktop .phabricator-login-buttons .aphront-multi-column-column-last { ··· 18 18 .device .phabricator-login-buttons { 19 19 text-align: center; 20 20 } 21 + 22 + .phabricator-link-button { 23 + text-align: center; 24 + }