blob: e38cf5f3ece506a9e6ca9ebbb0c744bb9eb9a10e [file] [log] [blame]
Carlos Valdivia91979be2015-05-22 14:11:35 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.accounts;
18
Carlos Valdiviac37ee222015-06-17 20:17:37 -070019import android.accounts.Account;
20import android.util.LruCache;
21import android.util.Pair;
22
23import com.android.internal.util.Preconditions;
24
Carlos Valdivia91979be2015-05-22 14:11:35 -070025import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.HashMap;
28import java.util.List;
29import java.util.Objects;
30
31/**
Carlos Valdiviac37ee222015-06-17 20:17:37 -070032 * TokenCaches manage time limited authentication tokens in memory.
Carlos Valdivia91979be2015-05-22 14:11:35 -070033 */
34/* default */ class TokenCache {
35
Carlos Valdiviac37ee222015-06-17 20:17:37 -070036 private static final int MAX_CACHE_CHARS = 64000;
37
Carlos Valdivia91979be2015-05-22 14:11:35 -070038 private static class Value {
39 public final String token;
40 public final long expiryEpochMillis;
41
42 public Value(String token, long expiryEpochMillis) {
43 this.token = token;
44 this.expiryEpochMillis = expiryEpochMillis;
45 }
46 }
47
48 private static class Key {
Carlos Valdiviac37ee222015-06-17 20:17:37 -070049 public final Account account;
Carlos Valdivia91979be2015-05-22 14:11:35 -070050 public final String packageName;
51 public final String tokenType;
52 public final byte[] sigDigest;
53
Carlos Valdiviac37ee222015-06-17 20:17:37 -070054 public Key(Account account, String tokenType, String packageName, byte[] sigDigest) {
55 this.account = account;
Carlos Valdivia91979be2015-05-22 14:11:35 -070056 this.tokenType = tokenType;
57 this.packageName = packageName;
58 this.sigDigest = sigDigest;
59 }
60
61 @Override
62 public boolean equals(Object o) {
63 if (o != null && o instanceof Key) {
64 Key cacheKey = (Key) o;
Carlos Valdiviac37ee222015-06-17 20:17:37 -070065 return Objects.equals(account, cacheKey.account)
66 && Objects.equals(packageName, cacheKey.packageName)
Carlos Valdivia91979be2015-05-22 14:11:35 -070067 && Objects.equals(tokenType, cacheKey.tokenType)
68 && Arrays.equals(sigDigest, cacheKey.sigDigest);
69 } else {
70 return false;
71 }
72 }
73
74 @Override
75 public int hashCode() {
Carlos Valdiviac37ee222015-06-17 20:17:37 -070076 return account.hashCode()
77 ^ packageName.hashCode()
78 ^ tokenType.hashCode()
79 ^ Arrays.hashCode(sigDigest);
80 }
81 }
82
83 private static class TokenLruCache extends LruCache<Key, Value> {
84
85 private class Evictor {
86 private final List<Key> mKeys;
87
88 public Evictor() {
89 mKeys = new ArrayList<>();
90 }
91
92 public void add(Key k) {
93 mKeys.add(k);
94 }
95
96 public void evict() {
97 for (Key k : mKeys) {
98 TokenLruCache.this.remove(k);
99 }
100 }
101 }
102
103 /**
104 * Map associated tokens with an Evictor that will manage evicting the token from the
105 * cache. This reverse lookup is needed because very little information is given at token
106 * invalidation time.
107 */
108 private HashMap<Pair<String, String>, Evictor> mTokenEvictors = new HashMap<>();
109 private HashMap<Account, Evictor> mAccountEvictors = new HashMap<>();
110
111 public TokenLruCache() {
112 super(MAX_CACHE_CHARS);
113 }
114
115 @Override
116 protected int sizeOf(Key k, Value v) {
117 return v.token.length();
118 }
119
120 @Override
121 protected void entryRemoved(boolean evicted, Key k, Value oldVal, Value newVal) {
122 // When a token has been removed, clean up the associated Evictor.
123 if (oldVal != null && newVal == null) {
124 /*
125 * This is recursive, but it won't spiral out of control because LruCache is
126 * thread safe and the Evictor can only be removed once.
127 */
Andreas Gampecbac1e42018-03-05 18:25:18 -0800128 Evictor evictor = mTokenEvictors.remove(new Pair<>(k.account.type, oldVal.token));
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700129 if (evictor != null) {
130 evictor.evict();
131 }
132 }
133 }
134
135 public void putToken(Key k, Value v) {
136 // Prepare for removal by token string.
Andreas Gampecbac1e42018-03-05 18:25:18 -0800137 Pair<String, String> mapKey = new Pair<>(k.account.type, v.token);
138 Evictor tokenEvictor = mTokenEvictors.get(mapKey);
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700139 if (tokenEvictor == null) {
140 tokenEvictor = new Evictor();
141 }
142 tokenEvictor.add(k);
Andreas Gampecbac1e42018-03-05 18:25:18 -0800143 mTokenEvictors.put(mapKey, tokenEvictor);
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700144
145 // Prepare for removal by associated account.
146 Evictor accountEvictor = mAccountEvictors.get(k.account);
147 if (accountEvictor == null) {
148 accountEvictor = new Evictor();
149 }
150 accountEvictor.add(k);
151 mAccountEvictors.put(k.account, tokenEvictor);
152
153 // Only cache the token once we can remove it directly or by account.
154 put(k, v);
155 }
156
157 public void evict(String accountType, String token) {
158 Evictor evictor = mTokenEvictors.get(new Pair<>(accountType, token));
159 if (evictor != null) {
160 evictor.evict();
161 }
162
163 }
164
165 public void evict(Account account) {
166 Evictor evictor = mAccountEvictors.get(account);
167 if (evictor != null) {
168 evictor.evict();
169 }
Carlos Valdivia91979be2015-05-22 14:11:35 -0700170 }
171 }
172
173 /**
174 * Map associating basic token lookup information with with actual tokens (and optionally their
175 * expiration times).
176 */
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700177 private TokenLruCache mCachedTokens = new TokenLruCache();
Carlos Valdivia91979be2015-05-22 14:11:35 -0700178
179 /**
180 * Caches the specified token until the specified expiryMillis. The token will be associated
181 * with the given token type, package name, and digest of signatures.
182 *
183 * @param token
184 * @param tokenType
185 * @param packageName
186 * @param sigDigest
187 * @param expiryMillis
188 */
189 public void put(
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700190 Account account,
Carlos Valdivia91979be2015-05-22 14:11:35 -0700191 String token,
192 String tokenType,
193 String packageName,
194 byte[] sigDigest,
195 long expiryMillis) {
Daulet Zhanguzina3e1f212020-01-03 09:45:16 +0000196 Objects.requireNonNull(account);
Carlos Valdivia91979be2015-05-22 14:11:35 -0700197 if (token == null || System.currentTimeMillis() > expiryMillis) {
198 return;
199 }
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700200 Key k = new Key(account, tokenType, packageName, sigDigest);
Carlos Valdivia91979be2015-05-22 14:11:35 -0700201 Value v = new Value(token, expiryMillis);
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700202 mCachedTokens.putToken(k, v);
Carlos Valdivia91979be2015-05-22 14:11:35 -0700203 }
204
205 /**
206 * Evicts the specified token from the cache. This should be called as part of a token
207 * invalidation workflow.
208 */
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700209 public void remove(String accountType, String token) {
210 mCachedTokens.evict(accountType, token);
211 }
212
213 public void remove(Account account) {
214 mCachedTokens.evict(account);
Carlos Valdivia91979be2015-05-22 14:11:35 -0700215 }
216
217 /**
218 * Gets a token from the cache if possible.
219 */
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700220 public String get(Account account, String tokenType, String packageName, byte[] sigDigest) {
221 Key k = new Key(account, tokenType, packageName, sigDigest);
Carlos Valdivia91979be2015-05-22 14:11:35 -0700222 Value v = mCachedTokens.get(k);
223 long currentTime = System.currentTimeMillis();
224 if (v != null && currentTime < v.expiryEpochMillis) {
225 return v.token;
226 } else if (v != null) {
Carlos Valdiviac37ee222015-06-17 20:17:37 -0700227 remove(account.type, v.token);
Carlos Valdivia91979be2015-05-22 14:11:35 -0700228 }
229 return null;
230 }
231}