Line data Source code
1 : // Copyright (C) 2016 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.auth.oauth; 16 : 17 : import static java.util.Objects.requireNonNull; 18 : 19 : import com.google.common.annotations.VisibleForTesting; 20 : import com.google.common.base.Converter; 21 : import com.google.common.base.Strings; 22 : import com.google.common.cache.Cache; 23 : import com.google.gerrit.common.Nullable; 24 : import com.google.gerrit.entities.Account; 25 : import com.google.gerrit.extensions.auth.oauth.OAuthToken; 26 : import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter; 27 : import com.google.gerrit.extensions.registration.DynamicItem; 28 : import com.google.gerrit.proto.Protos; 29 : import com.google.gerrit.server.cache.CacheModule; 30 : import com.google.gerrit.server.cache.proto.Cache.OAuthTokenProto; 31 : import com.google.gerrit.server.cache.serialize.CacheSerializer; 32 : import com.google.gerrit.server.cache.serialize.IntegerCacheSerializer; 33 : import com.google.inject.Inject; 34 : import com.google.inject.Module; 35 : import com.google.inject.Singleton; 36 : import com.google.inject.name.Named; 37 : 38 : @Singleton 39 : public class OAuthTokenCache { 40 : public static final String OAUTH_TOKENS = "oauth_tokens"; 41 : 42 : private final DynamicItem<OAuthTokenEncrypter> encrypter; 43 : 44 152 : public enum AccountIdSerializer implements CacheSerializer<Account.Id> { 45 152 : INSTANCE; 46 : 47 152 : private final Converter<Account.Id, Integer> converter = 48 152 : Converter.from(Account.Id::get, Account::id); 49 : 50 152 : private final Converter<Integer, Account.Id> reverse = converter.reverse(); 51 : 52 : @Override 53 : public byte[] serialize(Account.Id object) { 54 1 : return IntegerCacheSerializer.INSTANCE.serialize(converter.convert(object)); 55 : } 56 : 57 : @Override 58 : public Account.Id deserialize(byte[] in) { 59 1 : return reverse.convert(IntegerCacheSerializer.INSTANCE.deserialize(in)); 60 : } 61 : } 62 : 63 : public static Module module() { 64 152 : return new CacheModule() { 65 : @Override 66 : protected void configure() { 67 152 : persist(OAUTH_TOKENS, Account.Id.class, OAuthToken.class) 68 152 : .version(1) 69 152 : .keySerializer(AccountIdSerializer.INSTANCE) 70 152 : .valueSerializer(new Serializer()); 71 152 : } 72 : }; 73 : } 74 : 75 : // Defined outside of OAuthToken class, since that is in the extensions package which doesn't have 76 : // access to the serializer code. 77 : @VisibleForTesting 78 152 : static class Serializer implements CacheSerializer<OAuthToken> { 79 : @Override 80 : public byte[] serialize(OAuthToken object) { 81 1 : return Protos.toByteArray( 82 1 : OAuthTokenProto.newBuilder() 83 1 : .setToken(object.getToken()) 84 1 : .setSecret(object.getSecret()) 85 1 : .setRaw(object.getRaw()) 86 1 : .setExpiresAtMillis(object.getExpiresAt()) 87 1 : .setProviderId(Strings.nullToEmpty(object.getProviderId())) 88 1 : .build()); 89 : } 90 : 91 : @Override 92 : public OAuthToken deserialize(byte[] in) { 93 1 : OAuthTokenProto proto = Protos.parseUnchecked(OAuthTokenProto.parser(), in); 94 1 : return new OAuthToken( 95 1 : proto.getToken(), 96 1 : proto.getSecret(), 97 1 : proto.getRaw(), 98 1 : proto.getExpiresAtMillis(), 99 1 : Strings.emptyToNull(proto.getProviderId())); 100 : } 101 : } 102 : 103 : private final Cache<Account.Id, OAuthToken> cache; 104 : 105 : @Inject 106 : OAuthTokenCache( 107 : @Named(OAUTH_TOKENS) Cache<Account.Id, OAuthToken> cache, 108 138 : DynamicItem<OAuthTokenEncrypter> encrypter) { 109 138 : this.cache = cache; 110 138 : this.encrypter = encrypter; 111 138 : } 112 : 113 : @Nullable 114 : public OAuthToken get(Account.Id id) { 115 0 : OAuthToken accessToken = cache.getIfPresent(id); 116 0 : if (accessToken == null) { 117 0 : return null; 118 : } 119 0 : accessToken = decrypt(accessToken); 120 0 : if (accessToken.isExpired()) { 121 0 : cache.invalidate(id); 122 0 : return null; 123 : } 124 0 : return accessToken; 125 : } 126 : 127 : public void put(Account.Id id, OAuthToken accessToken) { 128 0 : cache.put(id, encrypt(requireNonNull(accessToken))); 129 0 : } 130 : 131 : public void remove(Account.Id id) { 132 0 : cache.invalidate(id); 133 0 : } 134 : 135 : private OAuthToken encrypt(OAuthToken token) { 136 0 : OAuthTokenEncrypter enc = encrypter.get(); 137 0 : if (enc == null) { 138 0 : return token; 139 : } 140 0 : return enc.encrypt(token); 141 : } 142 : 143 : private OAuthToken decrypt(OAuthToken token) { 144 0 : OAuthTokenEncrypter enc = encrypter.get(); 145 0 : if (enc == null) { 146 0 : return token; 147 : } 148 0 : return enc.decrypt(token); 149 : } 150 : }