extraURIData; } public function handleRequest(AphrontRequest $request) { $viewer = $this->getViewer(); $this->providerKey = $request->getURIData('pkey'); $this->extraURIData = $request->getURIData('extra'); $response = $this->loadProvider(); if ($response) { return $response; } $invite = $this->loadInvite(); $provider = $this->provider; try { list($account, $response) = $provider->processLoginRequest($this); } catch (PhutilAuthUserAbortedException $ex) { if ($viewer->isLoggedIn()) { // If a logged-in user cancels, take them back to the external accounts // panel. $next_uri = '/settings/panel/external/'; } else { // If a logged-out user cancels, take them back to the auth start page. $next_uri = '/'; } // User explicitly hit "Cancel". $dialog = id(new AphrontDialogView()) ->setUser($viewer) ->setTitle(pht('Authentication Canceled')) ->appendChild( pht('You canceled authentication.')) ->addCancelButton($next_uri, pht('Continue')); return id(new AphrontDialogResponse())->setDialog($dialog); } if ($response) { return $response; } if (!$account) { throw new Exception( pht( 'Auth provider failed to load an account from %s!', 'processLoginRequest()')); } if ($account->getUserPHID()) { // The account is already attached to a Phabricator user, so this is // either a login or a bad account link request. if (!$viewer->isLoggedIn()) { if ($provider->shouldAllowLogin()) { return $this->processLoginUser($account); } else { return $this->renderError( pht( 'The external service ("%s") you just authenticated with is '. 'not configured to allow logins on this server. An '. 'administrator may have recently disabled it.', $provider->getProviderName())); } } else if ($viewer->getPHID() == $account->getUserPHID()) { // This is either an attempt to re-link an existing and already // linked account (which is silly) or a refresh of an external account // (e.g., an OAuth account). return id(new AphrontRedirectResponse()) ->setURI('/settings/panel/external/'); } else { return $this->renderError( pht( 'The external service ("%s") you just used to log in is already '. 'associated with another %s user account. Log in to the '. 'other %s account and unlink the external account before '. 'linking it to a new %s account.', $provider->getProviderName(), PlatformSymbols::getPlatformServerName(), PlatformSymbols::getPlatformServerName(), PlatformSymbols::getPlatformServerName())); } } else { // The account is not yet attached to a Phabricator user, so this is // either a registration or an account link request. if (!$viewer->isLoggedIn()) { if ($provider->shouldAllowRegistration() || $invite) { return $this->processRegisterUser($account); } else { return $this->renderError( pht( 'The external service ("%s") you just authenticated with is '. 'not configured to allow registration on this server. An '. 'administrator may have recently disabled it.', $provider->getProviderName())); } } else { // If the user already has a linked account on this provider, prevent // them from linking a second account. This can happen if they swap // logins and then refresh the account link. // There's no technical reason we can't allow you to link multiple // accounts from a single provider; disallowing this is currently a // product deciison. See T2549. $existing_accounts = id(new PhabricatorExternalAccountQuery()) ->setViewer($viewer) ->withUserPHIDs(array($viewer->getPHID())) ->withProviderConfigPHIDs( array( $provider->getProviderConfigPHID(), )) ->execute(); if ($existing_accounts) { return $this->renderError( pht( 'Your %s account is already connected to an external '. 'account on this service ("%s"), but you are currently logged '. 'in to the service with a different account. Log out of the '. 'external service, then log back in with the correct account '. 'before refreshing the account link.', PlatformSymbols::getPlatformServerName(), $provider->getProviderName())); } if ($provider->shouldAllowAccountLink()) { return $this->processLinkUser($account); } else { return $this->renderError( pht( 'The external service ("%s") you just authenticated with is '. 'not configured to allow account linking on this server. An '. 'administrator may have recently disabled it.', $provider->getProviderName())); } } } // This should be unreachable, but fail explicitly if we get here somehow. return new Aphront400Response(); } private function processLoginUser(PhabricatorExternalAccount $account) { $user = id(new PhabricatorUser())->loadOneWhere( 'phid = %s', $account->getUserPHID()); if (!$user) { return $this->renderError( pht( 'The external account you just logged in with is not associated '. 'with a valid %s user account.', PlatformSymbols::getPlatformServerName())); } return $this->loginUser($user); } private function processRegisterUser(PhabricatorExternalAccount $account) { $account_secret = $account->getAccountSecret(); $register_uri = $this->getApplicationURI('register/'.$account_secret.'/'); return $this->setAccountKeyAndContinue($account, $register_uri); } private function processLinkUser(PhabricatorExternalAccount $account) { $account_secret = $account->getAccountSecret(); $confirm_uri = $this->getApplicationURI('confirmlink/'.$account_secret.'/'); return $this->setAccountKeyAndContinue($account, $confirm_uri); } private function setAccountKeyAndContinue( PhabricatorExternalAccount $account, $next_uri) { if ($account->getUserPHID()) { throw new Exception(pht('Account is already registered or linked.')); } // Regenerate the registration secret key, set it on the external account, // set a cookie on the user's machine, and redirect them to registration. // See PhabricatorAuthRegisterController for discussion of the registration // key. $registration_key = Filesystem::readRandomCharacters(32); $account->setProperty( 'registrationKey', PhabricatorHash::weakDigest($registration_key)); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $account->save(); unset($unguarded); $this->getRequest()->setTemporaryCookie( PhabricatorCookies::COOKIE_REGISTRATION, $registration_key); return id(new AphrontRedirectResponse())->setURI($next_uri); } private function loadProvider() { $provider = PhabricatorAuthProvider::getEnabledProviderByKey( $this->providerKey); if (!$provider) { return $this->renderError( pht( 'The account you are attempting to log in with uses a nonexistent '. 'or disabled authentication provider (with key "%s"). An '. 'administrator may have recently disabled this provider.', $this->providerKey)); } $this->provider = $provider; return null; } protected function renderError($message) { return $this->renderErrorPage( pht('Login Failed'), array($message)); } public function buildProviderPageResponse( PhabricatorAuthProvider $provider, $content) { $crumbs = $this->buildApplicationCrumbs(); $viewer = $this->getViewer(); if ($viewer->isLoggedIn()) { $crumbs->addTextCrumb(pht('Link Account'), $provider->getSettingsURI()); } else { $crumbs->addTextCrumb(pht('Login'), $this->getApplicationURI('start/')); $content = array( $this->newCustomStartMessage(), $content, ); } $crumbs->addTextCrumb($provider->getProviderName()); $crumbs->setBorder(true); return $this->newPage() ->setTitle(pht('Login')) ->setCrumbs($crumbs) ->appendChild($content); } public function buildProviderErrorResponse( PhabricatorAuthProvider $provider, $message) { $message = pht( 'Authentication provider ("%s") encountered an error while attempting '. 'to log in. %s', $provider->getProviderName(), $message); return $this->renderError($message); } }