1 /* 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 17 package com.android.server.accounts; 18 19 import android.accounts.Account; 20 import android.util.LruCache; 21 import android.util.Pair; 22 23 import com.android.internal.util.Preconditions; 24 25 import java.util.ArrayList; 26 import java.util.Arrays; 27 import java.util.HashMap; 28 import java.util.List; 29 import java.util.Objects; 30 31 /** 32 * TokenCaches manage time limited authentication tokens in memory. 33 */ 34 /* default */ class TokenCache { 35 36 private static final int MAX_CACHE_CHARS = 64000; 37 38 private static class Value { 39 public final String token; 40 public final long expiryEpochMillis; 41 Value(String token, long expiryEpochMillis)42 public Value(String token, long expiryEpochMillis) { 43 this.token = token; 44 this.expiryEpochMillis = expiryEpochMillis; 45 } 46 } 47 48 private static class Key { 49 public final Account account; 50 public final String packageName; 51 public final String tokenType; 52 public final byte[] sigDigest; 53 Key(Account account, String tokenType, String packageName, byte[] sigDigest)54 public Key(Account account, String tokenType, String packageName, byte[] sigDigest) { 55 this.account = account; 56 this.tokenType = tokenType; 57 this.packageName = packageName; 58 this.sigDigest = sigDigest; 59 } 60 61 @Override equals(Object o)62 public boolean equals(Object o) { 63 if (o != null && o instanceof Key) { 64 Key cacheKey = (Key) o; 65 return Objects.equals(account, cacheKey.account) 66 && Objects.equals(packageName, cacheKey.packageName) 67 && Objects.equals(tokenType, cacheKey.tokenType) 68 && Arrays.equals(sigDigest, cacheKey.sigDigest); 69 } else { 70 return false; 71 } 72 } 73 74 @Override hashCode()75 public int hashCode() { 76 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 Evictor()88 public Evictor() { 89 mKeys = new ArrayList<>(); 90 } 91 add(Key k)92 public void add(Key k) { 93 mKeys.add(k); 94 } 95 evict()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 TokenLruCache()111 public TokenLruCache() { 112 super(MAX_CACHE_CHARS); 113 } 114 115 @Override sizeOf(Key k, Value v)116 protected int sizeOf(Key k, Value v) { 117 return v.token.length(); 118 } 119 120 @Override entryRemoved(boolean evicted, Key k, Value oldVal, Value newVal)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 */ 128 Evictor evictor = mTokenEvictors.remove(new Pair<>(k.account.type, oldVal.token)); 129 if (evictor != null) { 130 evictor.evict(); 131 } 132 } 133 } 134 putToken(Key k, Value v)135 public void putToken(Key k, Value v) { 136 // Prepare for removal by token string. 137 Pair<String, String> mapKey = new Pair<>(k.account.type, v.token); 138 Evictor tokenEvictor = mTokenEvictors.get(mapKey); 139 if (tokenEvictor == null) { 140 tokenEvictor = new Evictor(); 141 } 142 tokenEvictor.add(k); 143 mTokenEvictors.put(mapKey, tokenEvictor); 144 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, accountEvictor); 152 153 // Only cache the token once we can remove it directly or by account. 154 put(k, v); 155 } 156 evict(String accountType, String token)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 evict(Account account)165 public void evict(Account account) { 166 Evictor evictor = mAccountEvictors.get(account); 167 if (evictor != null) { 168 evictor.evict(); 169 } 170 } 171 } 172 173 /** 174 * Map associating basic token lookup information with with actual tokens (and optionally their 175 * expiration times). 176 */ 177 private TokenLruCache mCachedTokens = new TokenLruCache(); 178 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 */ put( Account account, String token, String tokenType, String packageName, byte[] sigDigest, long expiryMillis)189 public void put( 190 Account account, 191 String token, 192 String tokenType, 193 String packageName, 194 byte[] sigDigest, 195 long expiryMillis) { 196 Objects.requireNonNull(account); 197 if (token == null || System.currentTimeMillis() > expiryMillis) { 198 return; 199 } 200 Key k = new Key(account, tokenType, packageName, sigDigest); 201 Value v = new Value(token, expiryMillis); 202 mCachedTokens.putToken(k, v); 203 } 204 205 /** 206 * Evicts the specified token from the cache. This should be called as part of a token 207 * invalidation workflow. 208 */ remove(String accountType, String token)209 public void remove(String accountType, String token) { 210 mCachedTokens.evict(accountType, token); 211 } 212 remove(Account account)213 public void remove(Account account) { 214 mCachedTokens.evict(account); 215 } 216 217 /** 218 * Gets a token from the cache if possible. 219 */ get(Account account, String tokenType, String packageName, byte[] sigDigest)220 public String get(Account account, String tokenType, String packageName, byte[] sigDigest) { 221 Key k = new Key(account, tokenType, packageName, sigDigest); 222 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) { 227 remove(account.type, v.token); 228 } 229 return null; 230 } 231 } 232