@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
at upstream/main 286 lines 9.6 kB view raw
1<?php 2 3final class PhabricatorAuthLoginController 4 extends PhabricatorAuthController { 5 6 private $providerKey; 7 private $extraURIData; 8 private $provider; 9 10 public function shouldRequireLogin() { 11 return false; 12 } 13 14 public function shouldAllowRestrictedParameter($parameter_name) { 15 // Whitelist the OAuth 'code' parameter. 16 17 if ($parameter_name == 'code') { 18 return true; 19 } 20 21 return parent::shouldAllowRestrictedParameter($parameter_name); 22 } 23 24 public function getExtraURIData() { 25 return $this->extraURIData; 26 } 27 28 public function handleRequest(AphrontRequest $request) { 29 $viewer = $this->getViewer(); 30 $this->providerKey = $request->getURIData('pkey'); 31 $this->extraURIData = $request->getURIData('extra'); 32 33 $response = $this->loadProvider(); 34 if ($response) { 35 return $response; 36 } 37 38 $invite = $this->loadInvite(); 39 $provider = $this->provider; 40 41 try { 42 list($account, $response) = $provider->processLoginRequest($this); 43 } catch (PhutilAuthUserAbortedException $ex) { 44 if ($viewer->isLoggedIn()) { 45 // If a logged-in user cancels, take them back to the external accounts 46 // panel. 47 $next_uri = '/settings/panel/external/'; 48 } else { 49 // If a logged-out user cancels, take them back to the auth start page. 50 $next_uri = '/'; 51 } 52 53 // User explicitly hit "Cancel". 54 $dialog = id(new AphrontDialogView()) 55 ->setUser($viewer) 56 ->setTitle(pht('Authentication Canceled')) 57 ->appendChild( 58 pht('You canceled authentication.')) 59 ->addCancelButton($next_uri, pht('Continue')); 60 return id(new AphrontDialogResponse())->setDialog($dialog); 61 } 62 63 if ($response) { 64 return $response; 65 } 66 67 if (!$account) { 68 throw new Exception( 69 pht( 70 'Auth provider failed to load an account from %s!', 71 'processLoginRequest()')); 72 } 73 74 if ($account->getUserPHID()) { 75 // The account is already attached to a Phabricator user, so this is 76 // either a login or a bad account link request. 77 if (!$viewer->isLoggedIn()) { 78 if ($provider->shouldAllowLogin()) { 79 return $this->processLoginUser($account); 80 } else { 81 return $this->renderError( 82 pht( 83 'The external service ("%s") you just authenticated with is '. 84 'not configured to allow logins on this server. An '. 85 'administrator may have recently disabled it.', 86 $provider->getProviderName())); 87 } 88 } else if ($viewer->getPHID() == $account->getUserPHID()) { 89 // This is either an attempt to re-link an existing and already 90 // linked account (which is silly) or a refresh of an external account 91 // (e.g., an OAuth account). 92 return id(new AphrontRedirectResponse()) 93 ->setURI('/settings/panel/external/'); 94 } else { 95 return $this->renderError( 96 pht( 97 'The external service ("%s") you just used to log in is already '. 98 'associated with another %s user account. Log in to the '. 99 'other %s account and unlink the external account before '. 100 'linking it to a new %s account.', 101 $provider->getProviderName(), 102 PlatformSymbols::getPlatformServerName(), 103 PlatformSymbols::getPlatformServerName(), 104 PlatformSymbols::getPlatformServerName())); 105 } 106 } else { 107 // The account is not yet attached to a Phabricator user, so this is 108 // either a registration or an account link request. 109 if (!$viewer->isLoggedIn()) { 110 if ($provider->shouldAllowRegistration() || $invite) { 111 return $this->processRegisterUser($account); 112 } else { 113 return $this->renderError( 114 pht( 115 'The external service ("%s") you just authenticated with is '. 116 'not configured to allow registration on this server. An '. 117 'administrator may have recently disabled it.', 118 $provider->getProviderName())); 119 } 120 } else { 121 122 // If the user already has a linked account on this provider, prevent 123 // them from linking a second account. This can happen if they swap 124 // logins and then refresh the account link. 125 126 // There's no technical reason we can't allow you to link multiple 127 // accounts from a single provider; disallowing this is currently a 128 // product deciison. See T2549. 129 130 $existing_accounts = id(new PhabricatorExternalAccountQuery()) 131 ->setViewer($viewer) 132 ->withUserPHIDs(array($viewer->getPHID())) 133 ->withProviderConfigPHIDs( 134 array( 135 $provider->getProviderConfigPHID(), 136 )) 137 ->execute(); 138 if ($existing_accounts) { 139 return $this->renderError( 140 pht( 141 'Your %s account is already connected to an external '. 142 'account on this service ("%s"), but you are currently logged '. 143 'in to the service with a different account. Log out of the '. 144 'external service, then log back in with the correct account '. 145 'before refreshing the account link.', 146 PlatformSymbols::getPlatformServerName(), 147 $provider->getProviderName())); 148 } 149 150 if ($provider->shouldAllowAccountLink()) { 151 return $this->processLinkUser($account); 152 } else { 153 return $this->renderError( 154 pht( 155 'The external service ("%s") you just authenticated with is '. 156 'not configured to allow account linking on this server. An '. 157 'administrator may have recently disabled it.', 158 $provider->getProviderName())); 159 } 160 } 161 } 162 163 // This should be unreachable, but fail explicitly if we get here somehow. 164 return new Aphront400Response(); 165 } 166 167 private function processLoginUser(PhabricatorExternalAccount $account) { 168 $user = id(new PhabricatorUser())->loadOneWhere( 169 'phid = %s', 170 $account->getUserPHID()); 171 172 if (!$user) { 173 return $this->renderError( 174 pht( 175 'The external account you just logged in with is not associated '. 176 'with a valid %s user account.', 177 PlatformSymbols::getPlatformServerName())); 178 } 179 180 return $this->loginUser($user); 181 } 182 183 private function processRegisterUser(PhabricatorExternalAccount $account) { 184 $account_secret = $account->getAccountSecret(); 185 $register_uri = $this->getApplicationURI('register/'.$account_secret.'/'); 186 return $this->setAccountKeyAndContinue($account, $register_uri); 187 } 188 189 private function processLinkUser(PhabricatorExternalAccount $account) { 190 $account_secret = $account->getAccountSecret(); 191 $confirm_uri = $this->getApplicationURI('confirmlink/'.$account_secret.'/'); 192 return $this->setAccountKeyAndContinue($account, $confirm_uri); 193 } 194 195 private function setAccountKeyAndContinue( 196 PhabricatorExternalAccount $account, 197 $next_uri) { 198 199 if ($account->getUserPHID()) { 200 throw new Exception(pht('Account is already registered or linked.')); 201 } 202 203 // Regenerate the registration secret key, set it on the external account, 204 // set a cookie on the user's machine, and redirect them to registration. 205 // See PhabricatorAuthRegisterController for discussion of the registration 206 // key. 207 208 $registration_key = Filesystem::readRandomCharacters(32); 209 $account->setProperty( 210 'registrationKey', 211 PhabricatorHash::weakDigest($registration_key)); 212 213 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 214 $account->save(); 215 unset($unguarded); 216 217 $this->getRequest()->setTemporaryCookie( 218 PhabricatorCookies::COOKIE_REGISTRATION, 219 $registration_key); 220 221 return id(new AphrontRedirectResponse())->setURI($next_uri); 222 } 223 224 private function loadProvider() { 225 $provider = PhabricatorAuthProvider::getEnabledProviderByKey( 226 $this->providerKey); 227 228 if (!$provider) { 229 return $this->renderError( 230 pht( 231 'The account you are attempting to log in with uses a nonexistent '. 232 'or disabled authentication provider (with key "%s"). An '. 233 'administrator may have recently disabled this provider.', 234 $this->providerKey)); 235 } 236 237 $this->provider = $provider; 238 239 return null; 240 } 241 242 protected function renderError($message) { 243 return $this->renderErrorPage( 244 pht('Login Failed'), 245 array($message)); 246 } 247 248 public function buildProviderPageResponse( 249 PhabricatorAuthProvider $provider, 250 $content) { 251 252 $crumbs = $this->buildApplicationCrumbs(); 253 $viewer = $this->getViewer(); 254 255 if ($viewer->isLoggedIn()) { 256 $crumbs->addTextCrumb(pht('Link Account'), $provider->getSettingsURI()); 257 } else { 258 $crumbs->addTextCrumb(pht('Login'), $this->getApplicationURI('start/')); 259 260 $content = array( 261 $this->newCustomStartMessage(), 262 $content, 263 ); 264 } 265 266 $crumbs->addTextCrumb($provider->getProviderName()); 267 $crumbs->setBorder(true); 268 269 return $this->newPage() 270 ->setTitle(pht('Login')) 271 ->setCrumbs($crumbs) 272 ->appendChild($content); 273 } 274 275 public function buildProviderErrorResponse( 276 PhabricatorAuthProvider $provider, 277 $message) { 278 279 $message = pht( 280 'Authentication provider ("%s") encountered an error while attempting '. 281 'to log in. %s', $provider->getProviderName(), $message); 282 283 return $this->renderError($message); 284 } 285 286}