diff --git a/src/applications/auth/provider/PhabricatorAuthProviderOAuth2.php b/src/applications/auth/provider/PhabricatorAuthProviderOAuth2.php index 0388e475f2..a2d9ecc831 100644 --- a/src/applications/auth/provider/PhabricatorAuthProviderOAuth2.php +++ b/src/applications/auth/provider/PhabricatorAuthProviderOAuth2.php @@ -1,266 +1,277 @@ getProviderConfig(); $adapter->setClientID($config->getProperty(self::PROPERTY_APP_ID)); $adapter->setClientSecret( new PhutilOpaqueEnvelope( $config->getProperty(self::PROPERTY_APP_SECRET))); $adapter->setRedirectURI(PhabricatorEnv::getURI($this->getLoginURI())); return $adapter; } protected function renderLoginForm(AphrontRequest $request, $mode) { $adapter = $this->getAdapter(); $adapter->setState($this->getAuthCSRFCode($request)); $scope = $request->getStr('scope'); if ($scope) { $adapter->setScope($scope); } $attributes = array( 'method' => 'GET', 'uri' => $adapter->getAuthenticateURI(), ); return $this->renderStandardLoginButton($request, $mode, $attributes); } public function processLoginRequest( PhabricatorAuthLoginController $controller) { $request = $controller->getRequest(); $adapter = $this->getAdapter(); $account = null; $response = null; $error = $request->getStr('error'); if ($error) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider returned an error: %s', $error)); return array($account, $response); } $this->verifyAuthCSRFCode($request, $request->getStr('state')); $code = $request->getStr('code'); if (!strlen($code)) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider did not return a "code" parameter in its '. 'response.')); return array($account, $response); } $adapter->setCode($code); // NOTE: As a side effect, this will cause the OAuth adapter to request // an access token. try { $account_id = $adapter->getAccountID(); } catch (Exception $ex) { // TODO: Handle this in a more user-friendly way. throw $ex; } if (!strlen($account_id)) { $response = $controller->buildProviderErrorResponse( $this, pht( 'The OAuth provider failed to retrieve an account ID.')); return array($account, $response); } return array($this->loadOrCreateAccount($account_id), $response); } public function processEditForm( AphrontRequest $request, array $values) { return $this->processOAuthEditForm( $request, $values, pht('Application ID is required.'), pht('Application secret is required.')); } public function extendEditForm( AphrontRequest $request, AphrontFormView $form, array $values, array $issues) { return $this->extendOAuthEditForm( $request, $form, $values, $issues, pht('OAuth App ID'), pht('OAuth App Secret')); } public function renderConfigPropertyTransactionTitle( PhabricatorAuthProviderConfigTransaction $xaction) { $author_phid = $xaction->getAuthorPHID(); $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $key = $xaction->getMetadataValue( PhabricatorAuthProviderConfigTransaction::PROPERTY_KEY); switch ($key) { case self::PROPERTY_APP_ID: if (strlen($old)) { return pht( '%s updated the OAuth application ID for this provider from '. '"%s" to "%s".', $xaction->renderHandleLink($author_phid), $old, $new); } else { return pht( '%s set the OAuth application ID for this provider to '. '"%s".', $xaction->renderHandleLink($author_phid), $new); } case self::PROPERTY_APP_SECRET: if (strlen($old)) { return pht( '%s updated the OAuth application secret for this provider.', $xaction->renderHandleLink($author_phid)); } else { return pht( '%s set the OAuth application secret for this provider.', $xaction->renderHandleLink($author_phid)); } case self::PROPERTY_APP_NOTE: if (strlen($old)) { return pht( '%s updated the OAuth application notes for this provider.', $xaction->renderHandleLink($author_phid)); } else { return pht( '%s set the OAuth application notes for this provider.', $xaction->renderHandleLink($author_phid)); } } return parent::renderConfigPropertyTransactionTitle($xaction); } protected function synchronizeOAuthAccount( PhabricatorExternalAccount $account) { $adapter = $this->getAdapter(); $oauth_token = $adapter->getAccessToken(); $account->setProperty('oauth.token.access', $oauth_token); if ($adapter->supportsTokenRefresh()) { $refresh_token = $adapter->getRefreshToken(); $account->setProperty('oauth.token.refresh', $refresh_token); } else { $account->setProperty('oauth.token.refresh', null); } $expires = $adapter->getAccessTokenExpires(); $account->setProperty('oauth.token.access.expires', $expires); } public function getOAuthAccessToken( PhabricatorExternalAccount $account, $force_refresh = false) { if ($account->getProviderKey() !== $this->getProviderKey()) { throw new Exception('Account does not match provider!'); } if (!$force_refresh) { $access_expires = $account->getProperty('oauth.token.access.expires'); $access_token = $account->getProperty('oauth.token.access'); // Don't return a token with fewer than this many seconds remaining until // it expires. $shortest_token = 60; if ($access_token) { if ($access_expires === null || $access_expires > (time() + $shortest_token)) { return $access_token; } } } $refresh_token = $account->getProperty('oauth.token.refresh'); if ($refresh_token) { $adapter = $this->getAdapter(); if ($adapter->supportsTokenRefresh()) { $adapter->refreshAccessToken($refresh_token); $this->synchronizeOAuthAccount($account); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $account->save(); unset($unguarded); return $account->getProperty('oauth.token.access'); } } return null; } public function willRenderLinkedAccount( PhabricatorUser $viewer, PHUIObjectItemView $item, PhabricatorExternalAccount $account) { - // Get a valid token, possibly refreshing it. - $oauth_token = $this->getOAuthAccessToken($account); + // Get a valid token, possibly refreshing it. If we're unable to refresh + // it, render a message to that effect. The user may be able to repair the + // link by manually reconnecting. + + $is_invalid = false; + try { + $oauth_token = $this->getOAuthAccessToken($account); + } catch (Exception $ex) { + $oauth_token = null; + $is_invalid = true; + } $item->addAttribute(pht('OAuth2 Account')); if ($oauth_token) { $oauth_expires = $account->getProperty('oauth.token.access.expires'); if ($oauth_expires) { $item->addAttribute( pht( 'Active OAuth Token (Expires: %s)', phabricator_datetime($oauth_expires, $viewer))); } else { $item->addAttribute( pht( 'Active OAuth Token')); } + } else if ($is_invalid) { + $item->addAttribute(pht('Invalid OAuth Access Token')); } else { $item->addAttribute(pht('No OAuth Access Token')); } parent::willRenderLinkedAccount($viewer, $item, $account); } }