Merge "Cache for OAuth access tokens"

This commit is contained in:
Saša Živkov 2016-05-11 07:44:36 +00:00 committed by Gerrit Code Review
commit a6d646b658
5 changed files with 183 additions and 8 deletions

View File

@ -14,17 +14,33 @@
package com.google.gerrit.extensions.auth.oauth;
import java.io.Serializable;
/* OAuth token */
public class OAuthToken {
public class OAuthToken implements Serializable {
private static final long serialVersionUID = 1L;
private final String token;
private final String secret;
private final String raw;
/**
* Time of expiration of this token, or {@code Long#MAX_VALUE} if this
* token never expires, or time of expiration is unknown.
*/
private final long expiresAt;
public OAuthToken(String token, String secret, String raw) {
this(token, secret, raw, Long.MAX_VALUE);
}
public OAuthToken(String token, String secret, String raw,
long expiresAt) {
this.token = token;
this.secret = secret;
this.raw = raw;
this.expiresAt = expiresAt;
}
public String getToken() {
@ -38,4 +54,12 @@ public class OAuthToken {
public String getRaw() {
return raw;
}
public long getExpiresAt() {
return expiresAt;
}
public boolean isExpired() {
return System.currentTimeMillis() > expiresAt;
}
}

View File

@ -0,0 +1,35 @@
// 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.extensions.auth.oauth;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
@ExtensionPoint
public interface OAuthTokenEncrypter {
/**
* Encrypts the secret parts of the given OAuth access token.
*
* @param unencrypted a raw OAuth access token.
*/
OAuthToken encrypt(OAuthToken unencrypted);
/**
* Decrypts the secret parts of the given OAuth access token.
*
* @param encrypted an encryppted OAuth access token.
*/
OAuthToken decrypt(OAuthToken encrypted);
}

View File

@ -31,6 +31,7 @@ import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.AuthResult;
import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@ -58,8 +59,8 @@ class OAuthSession {
private final Provider<IdentifiedUser> identifiedUser;
private final AccountManager accountManager;
private final CanonicalWebUrl urlProvider;
private final OAuthTokenCache tokenCache;
private OAuthServiceProvider serviceProvider;
private OAuthToken token;
private OAuthUserInfo user;
private String redirectToken;
private boolean linkMode;
@ -68,16 +69,18 @@ class OAuthSession {
OAuthSession(DynamicItem<WebSession> webSession,
Provider<IdentifiedUser> identifiedUser,
AccountManager accountManager,
CanonicalWebUrl urlProvider) {
CanonicalWebUrl urlProvider,
OAuthTokenCache tokenCache) {
this.state = generateRandomState();
this.identifiedUser = identifiedUser;
this.webSession = webSession;
this.accountManager = accountManager;
this.urlProvider = urlProvider;
this.tokenCache = tokenCache;
}
boolean isLoggedIn() {
return token != null && user != null;
return tokenCache.has(user);
}
boolean isOAuthFinal(HttpServletRequest request) {
@ -95,9 +98,12 @@ class OAuthSession {
}
log.debug("Login-Retrieve-User " + this);
token = oauth.getAccessToken(new OAuthVerifier(request.getParameter("code")));
OAuthToken token = oauth.getAccessToken(
new OAuthVerifier(request.getParameter("code")));
user = oauth.getUserInfo(token);
if (user != null && token != null) {
tokenCache.put(user, token);
}
if (isLoggedIn()) {
log.debug("Login-SUCCESS " + this);
@ -211,7 +217,7 @@ class OAuthSession {
}
void logout() {
token = null;
tokenCache.remove(user);
user = null;
redirectToken = null;
serviceProvider = null;
@ -243,7 +249,8 @@ class OAuthSession {
@Override
public String toString() {
return "OAuthSession [token=" + token + ", user=" + user + "]";
return "OAuthSession [token=" + tokenCache.get(user) + ", user=" + user
+ "]";
}
public void setServiceProvider(OAuthServiceProvider provider) {

View File

@ -0,0 +1,105 @@
// 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.auth.oauth;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.cache.Cache;
import com.google.gerrit.extensions.auth.oauth.OAuthToken;
import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
import com.google.gerrit.extensions.auth.oauth.OAuthUserInfo;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.server.cache.CacheModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.Singleton;
import com.google.inject.name.Named;
@Singleton
public class OAuthTokenCache {
public static final String OAUTH_TOKENS = "oauth_tokens";
private final DynamicItem<OAuthTokenEncrypter> encrypter;
public static Module module() {
return new CacheModule() {
@Override
protected void configure() {
persist(OAUTH_TOKENS, String.class, OAuthToken.class);
}
};
}
private final Cache<String, OAuthToken> cache;
@Inject
OAuthTokenCache(@Named(OAUTH_TOKENS) Cache<String, OAuthToken> cache,
DynamicItem<OAuthTokenEncrypter> encrypter) {
this.cache = cache;
this.encrypter = encrypter;
}
public boolean has(OAuthUserInfo user) {
return user != null
? cache.getIfPresent(user.getUserName()) != null
: false;
}
public OAuthToken get(OAuthUserInfo user) {
return user != null
? get(user.getUserName())
: null;
}
public OAuthToken get(String userName) {
OAuthToken accessToken = cache.getIfPresent(userName);
if (accessToken == null) {
return null;
}
accessToken = decrypt(accessToken);
if (accessToken.isExpired()) {
cache.invalidate(userName);
return null;
}
return accessToken;
}
public void put(OAuthUserInfo user, OAuthToken accessToken) {
cache.put(checkNotNull(user.getUserName()),
encrypt(checkNotNull(accessToken)));
}
public void remove(OAuthUserInfo user) {
if (user != null) {
cache.invalidate(user.getUserName());
}
}
private OAuthToken encrypt(OAuthToken token) {
OAuthTokenEncrypter enc = encrypter.get();
if (enc == null) {
return token;
}
return enc.encrypt(token);
}
private OAuthToken decrypt(OAuthToken token) {
OAuthTokenEncrypter enc = encrypter.get();
if (enc == null) {
return token;
}
return enc.decrypt(token);
}
}

View File

@ -21,6 +21,7 @@ import com.google.gerrit.audit.AuditModule;
import com.google.gerrit.common.EventListener;
import com.google.gerrit.common.UserScopedEventListener;
import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
import com.google.gerrit.extensions.config.CapabilityDefinition;
import com.google.gerrit.extensions.config.CloneCommand;
import com.google.gerrit.extensions.config.DownloadCommand;
@ -74,6 +75,7 @@ import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.api.accounts.AccountExternalIdCreator;
import com.google.gerrit.server.auth.AuthBackend;
import com.google.gerrit.server.auth.UniversalAuthBackend;
import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
import com.google.gerrit.server.avatar.AvatarProvider;
import com.google.gerrit.server.cache.CacheRemovalListener;
import com.google.gerrit.server.change.ChangeJson;
@ -191,6 +193,7 @@ public class GerritGlobalModule extends FactoryModule {
install(SectionSortCache.module());
install(SubmitStrategy.module());
install(TagCache.module());
install(OAuthTokenCache.module());
install(new AccessControlModule());
install(new CmdLineParserModule());
@ -315,6 +318,7 @@ public class GerritGlobalModule extends FactoryModule {
DynamicSet.setOf(binder(), ProjectWebLink.class);
DynamicSet.setOf(binder(), BranchWebLink.class);
DynamicMap.mapOf(binder(), OAuthLoginProvider.class);
DynamicItem.itemOf(binder(), OAuthTokenEncrypter.class);
DynamicSet.setOf(binder(), AccountExternalIdCreator.class);
DynamicSet.setOf(binder(), WebUiPlugin.class);