| // 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.auth.oauth; | |
| import static java.util.Objects.requireNonNull; | |
| import com.google.common.annotations.VisibleForTesting; | |
| import com.google.common.base.Converter; | |
| import com.google.common.base.Strings; | |
| import com.google.common.cache.Cache; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.entities.Account; | |
| import com.google.gerrit.extensions.auth.oauth.OAuthToken; | |
| import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter; | |
| import com.google.gerrit.extensions.registration.DynamicItem; | |
| import com.google.gerrit.proto.Protos; | |
| import com.google.gerrit.server.cache.CacheModule; | |
| import com.google.gerrit.server.cache.proto.Cache.OAuthTokenProto; | |
| import com.google.gerrit.server.cache.serialize.CacheSerializer; | |
| import com.google.gerrit.server.cache.serialize.IntegerCacheSerializer; | |
| 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 enum AccountIdSerializer implements CacheSerializer<Account.Id> { | |
| INSTANCE; | |
| private final Converter<Account.Id, Integer> converter = | |
| Converter.from(Account.Id::get, Account::id); | |
| private final Converter<Integer, Account.Id> reverse = converter.reverse(); | |
| @Override | |
| public byte[] serialize(Account.Id object) { | |
| return IntegerCacheSerializer.INSTANCE.serialize(converter.convert(object)); | |
| } | |
| @Override | |
| public Account.Id deserialize(byte[] in) { | |
| return reverse.convert(IntegerCacheSerializer.INSTANCE.deserialize(in)); | |
| } | |
| } | |
| public static Module module() { | |
| return new CacheModule() { | |
| @Override | |
| protected void configure() { | |
| persist(OAUTH_TOKENS, Account.Id.class, OAuthToken.class) | |
| .version(1) | |
| .keySerializer(AccountIdSerializer.INSTANCE) | |
| .valueSerializer(new Serializer()); | |
| } | |
| }; | |
| } | |
| // Defined outside of OAuthToken class, since that is in the extensions package which doesn't have | |
| // access to the serializer code. | |
| @VisibleForTesting | |
| static class Serializer implements CacheSerializer<OAuthToken> { | |
| @Override | |
| public byte[] serialize(OAuthToken object) { | |
| return Protos.toByteArray( | |
| OAuthTokenProto.newBuilder() | |
| .setToken(object.getToken()) | |
| .setSecret(object.getSecret()) | |
| .setRaw(object.getRaw()) | |
| .setExpiresAtMillis(object.getExpiresAt()) | |
| .setProviderId(Strings.nullToEmpty(object.getProviderId())) | |
| .build()); | |
| } | |
| @Override | |
| public OAuthToken deserialize(byte[] in) { | |
| OAuthTokenProto proto = Protos.parseUnchecked(OAuthTokenProto.parser(), in); | |
| return new OAuthToken( | |
| proto.getToken(), | |
| proto.getSecret(), | |
| proto.getRaw(), | |
| proto.getExpiresAtMillis(), | |
| Strings.emptyToNull(proto.getProviderId())); | |
| } | |
| } | |
| private final Cache<Account.Id, OAuthToken> cache; | |
| @Inject | |
| OAuthTokenCache( | |
| @Named(OAUTH_TOKENS) Cache<Account.Id, OAuthToken> cache, | |
| DynamicItem<OAuthTokenEncrypter> encrypter) { | |
| this.cache = cache; | |
| this.encrypter = encrypter; | |
| } | |
| @Nullable | |
| public OAuthToken get(Account.Id id) { | |
| OAuthToken accessToken = cache.getIfPresent(id); | |
| if (accessToken == null) { | |
| return null; | |
| } | |
| accessToken = decrypt(accessToken); | |
| if (accessToken.isExpired()) { | |
| cache.invalidate(id); | |
| return null; | |
| } | |
| return accessToken; | |
| } | |
| public void put(Account.Id id, OAuthToken accessToken) { | |
| cache.put(id, encrypt(requireNonNull(accessToken))); | |
| } | |
| public void remove(Account.Id id) { | |
| cache.invalidate(id); | |
| } | |
| 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); | |
| } | |
| } |