REST API for retrieving OAuth access tokens

As preparation for an UI to retrieve OAuth tokens, a new endpoint
for the account REST API is added that returns a previously
obtained OAuth token:

GET /a/accounts/self/oauthtoken

The response will be 200 OK in case a token is available and the
response will contain a JSON body of the form

)]}'
{
  "username": "johndow",
  "resource_host": "git.example.org",
  "access_token": "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOi...",
  "providerId": "oauth-plugin:oauth-provider",
  "expires_at": "922337203775",
  "type": "bearer"
}

If there is no token available, or the token has already expired,
404 is returned. Attempts to retrieve a token of another user are
rejected with 403 Forbidden.

Change-Id: I6ddb825890e88c49bd8c5e66b8c5508cef7df347
Signed-off-by: Michael Ochmann <michael.ochmann@sap.com>
This commit is contained in:
Michael Ochmann
2015-12-15 15:59:42 +01:00
parent 524faceb97
commit e56acd6cca
4 changed files with 158 additions and 2 deletions

View File

@@ -420,6 +420,43 @@ Deletes the HTTP password of an account.
HTTP/1.1 204 No Content
----
[[get-oauth-token]]
=== Get OAuth Access Token
--
'GET /accounts/link:#account-id[\{account-id\}]/oauthtoken'
--
Returns a previously obtained OAuth access token.
.Request
----
GET /accounts/self/oauthtoken HTTP/1.1
----
As a response, an link:#oauth-token-info[OAuthTokenInfo] entity is returned
that describes the OAuth access token.
.Response
----
HTTP/1.1 200 OK
Content-Disposition: attachment
Content-Type: application/json; charset=UTF-8
)]}'
{
"username": "johndow",
"resource_host": "gerrit.example.org",
"access_token": "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOi",
"provider_id": "oauth-plugin:oauth-provider",
"expires_at": "922337203775807",
"type": "bearer"
}
----
If there is no token available, or the token has already expired,
"`404 Not Found`" is returned as response. Requests to obtain an access
token of another user are rejected with "`403 Forbidden`".
[[list-account-emails]]
=== List Account Emails
--
@@ -2075,6 +2112,22 @@ If empty or not set and `generate` is false or not set, the HTTP
password is deleted.
|============================
[[oauth-token-info]]
=== OAuthTokenInfo
The `OAuthTokenInfo` entity contains information about an OAuth access token.
[options="header",cols="1,^1,5"]
|========================
|Field Name ||Description
|`username` ||The owner of the OAuth access token.
|`resource_host` ||The host of the Gerrit instance.
|`access_token` ||The actual token value.
|`provider_id` |optional|
The identifier of the OAuth provider in the form `plugin-name:provider-name`.
|`expires_at` |optional|Time of expiration of this token in milliseconds.
|`type` ||The type of the OAuth access token, always `bearer`.
|========================
[[preferences-info]]
=== PreferencesInfo
The `PreferencesInfo` entity contains information about a user's preferences.

View File

@@ -31,16 +31,23 @@ public class OAuthToken implements Serializable {
*/
private final long expiresAt;
/**
* The identifier of the OAuth provider that issued this token
* in the form <tt>"plugin-name:provider-name"</tt>, or {@code null}.
*/
private final String providerId;
public OAuthToken(String token, String secret, String raw) {
this(token, secret, raw, Long.MAX_VALUE);
this(token, secret, raw, Long.MAX_VALUE, null);
}
public OAuthToken(String token, String secret, String raw,
long expiresAt) {
long expiresAt, String providerId) {
this.token = token;
this.secret = secret;
this.raw = raw;
this.expiresAt = expiresAt;
this.providerId = providerId;
}
public String getToken() {
@@ -62,4 +69,8 @@ public class OAuthToken implements Serializable {
public boolean isExpired() {
return System.currentTimeMillis() > expiresAt;
}
public String getProviderId() {
return providerId;
}
}

View File

@@ -0,0 +1,90 @@
// Copyright (C) 2016 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
import com.google.gerrit.server.config.CanonicalWebUrl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.net.URI;
import java.net.URISyntaxException;
@Singleton
class GetOAuthToken implements RestReadView<AccountResource>{
private static final String BEARER_TYPE = "bearer";
private final Provider<CurrentUser> self;
private final OAuthTokenCache tokenCache;
private final String hostName;
@Inject
GetOAuthToken(Provider<CurrentUser> self,
OAuthTokenCache tokenCache,
@CanonicalWebUrl Provider<String> urlProvider) {
this.self = self;
this.tokenCache = tokenCache;
this.hostName = getHostName(urlProvider.get());
}
@Override
public OAuthTokenInfo apply(AccountResource rsrc) throws AuthException,
ResourceNotFoundException {
if (self.get() != rsrc.getUser()) {
throw new AuthException("not allowed to get access token");
}
String username = rsrc.getUser().getAccount().getUserName();
if (username == null) {
throw new ResourceNotFoundException();
}
OAuthToken accessToken = tokenCache.get(username);
if (accessToken == null) {
throw new ResourceNotFoundException();
}
OAuthTokenInfo accessTokenInfo = new OAuthTokenInfo();
accessTokenInfo.username = username;
accessTokenInfo.resourceHost = hostName;
accessTokenInfo.accessToken = accessToken.getToken();
accessTokenInfo.providerId = accessToken.getProviderId();
accessTokenInfo.expiresAt = Long.toString(accessToken.getExpiresAt());
accessTokenInfo.type = BEARER_TYPE;
return accessTokenInfo;
}
private static String getHostName(String canonicalWebUrl) {
try {
return new URI(canonicalWebUrl).getHost();
} catch (URISyntaxException e) {
return null;
}
}
public static class OAuthTokenInfo {
public String username;
public String resourceHost;
public String accessToken;
public String providerId;
public String expiresAt;
public String type;
}
}

View File

@@ -64,6 +64,8 @@ public class Module extends RestApiModule {
get(SSH_KEY_KIND).to(GetSshKey.class);
delete(SSH_KEY_KIND).to(DeleteSshKey.class);
get(ACCOUNT_KIND, "oauthtoken").to(GetOAuthToken.class);
get(ACCOUNT_KIND, "avatar").to(GetAvatar.class);
get(ACCOUNT_KIND, "avatar.change.url").to(GetAvatarChangeUrl.class);