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 java.util.ArrayList; 24 import java.util.Arrays; 25 import java.util.HashMap; 26 import java.util.List; 27 import java.util.Objects; 28 29 /** 30 * TokenCaches manage time limited authentication tokens in memory. 31 */ 32 /* default */ class TokenCache { 33 34 private static final int MAX_CACHE_CHARS = 64000; 35 36 /** Package private*/ 37 static class Value { 38 public final String token; 39 public final long expiryEpochMillis; 40 Value(String token, long expiryEpochMillis)41 public Value(String token, long expiryEpochMillis) { 42 this.token = token; 43 this.expiryEpochMillis = expiryEpochMillis; 44 } 45 } 46 47 private static class Key { 48 public final Account account; 49 public final String packageName; 50 public final String tokenType; 51 public final byte[] sigDigest; 52 Key(Account account, String tokenType, String packageName, byte[] sigDigest)53 public Key(Account account, String tokenType, String packageName, byte[] sigDigest) { 54 this.account = account; 55 this.tokenType = tokenType; 56 this.packageName = packageName; 57 this.sigDigest = sigDigest; 58 } 59 60 @Override equals(Object o)61 public boolean equals(Object o) { 62 if (o != null && o instanceof Key) { 63 Key cacheKey = (Key) o; 64 return Objects.equals(account, cacheKey.account) 65 && Objects.equals(packageName, cacheKey.packageName) 66 && Objects.equals(tokenType, cacheKey.tokenType) 67 && Arrays.equals(sigDigest, cacheKey.sigDigest); 68 } else { 69 return false; 70 } 71 } 72 73 @Override hashCode()74 public int hashCode() { 75 return account.hashCode() 76 ^ packageName.hashCode() 77 ^ tokenType.hashCode() 78 ^ Arrays.hashCode(sigDigest); 79 } 80 } 81 82 private static class TokenLruCache extends LruCache<Key, Value> { 83 84 private class Evictor { 85 private final List<Key> mKeys; 86 Evictor()87 public Evictor() { 88 mKeys = new ArrayList<>(); 89 } 90 add(Key k)91 public void add(Key k) { 92 mKeys.add(k); 93 } 94 evict()95 public void evict() { 96 for (Key k : mKeys) { 97 TokenLruCache.this.remove(k); 98 } 99 } 100 } 101 102 /** 103 * Map associated tokens with an Evictor that will manage evicting the token from the 104 * cache. This reverse lookup is needed because very little information is given at token 105 * invalidation time. 106 */ 107 private HashMap<Pair<String, String>, Evictor> mTokenEvictors = new HashMap<>(); 108 private HashMap<Account, Evictor> mAccountEvictors = new HashMap<>(); 109 TokenLruCache()110 public TokenLruCache() { 111 super(MAX_CACHE_CHARS); 112 } 113 114 @Override sizeOf(Key k, Value v)115 protected int sizeOf(Key k, Value v) { 116 return v.token.length(); 117 } 118 119 @Override entryRemoved(boolean evicted, Key k, Value oldVal, Value newVal)120 protected void entryRemoved(boolean evicted, Key k, Value oldVal, Value newVal) { 121 // When a token has been removed, clean up the associated Evictor. 122 if (oldVal != null && newVal == null) { 123 /* 124 * This is recursive, but it won't spiral out of control because LruCache is 125 * thread safe and the Evictor can only be removed once. 126 */ 127 Evictor evictor = mTokenEvictors.remove(new Pair<>(k.account.type, oldVal.token)); 128 if (evictor != null) { 129 evictor.evict(); 130 } 131 } 132 } 133 putToken(Key k, Value v)134 public void putToken(Key k, Value v) { 135 // Prepare for removal by token string. 136 Pair<String, String> mapKey = new Pair<>(k.account.type, v.token); 137 Evictor tokenEvictor = mTokenEvictors.get(mapKey); 138 if (tokenEvictor == null) { 139 tokenEvictor = new Evictor(); 140 } 141 tokenEvictor.add(k); 142 mTokenEvictors.put(mapKey, tokenEvictor); 143 144 // Prepare for removal by associated account. 145 Evictor accountEvictor = mAccountEvictors.get(k.account); 146 if (accountEvictor == null) { 147 accountEvictor = new Evictor(); 148 } 149 accountEvictor.add(k); 150 mAccountEvictors.put(k.account, accountEvictor); 151 152 // Only cache the token once we can remove it directly or by account. 153 put(k, v); 154 } 155 evict(String accountType, String token)156 public void evict(String accountType, String token) { 157 Evictor evictor = mTokenEvictors.get(new Pair<>(accountType, token)); 158 if (evictor != null) { 159 evictor.evict(); 160 } 161 162 } 163 evict(Account account)164 public void evict(Account account) { 165 Evictor evictor = mAccountEvictors.get(account); 166 if (evictor != null) { 167 evictor.evict(); 168 } 169 } 170 } 171 172 /** 173 * Map associating basic token lookup information with with actual tokens (and optionally their 174 * expiration times). 175 */ 176 private TokenLruCache mCachedTokens = new TokenLruCache(); 177 178 /** 179 * Caches the specified token until the specified expiryMillis. The token will be associated 180 * with the given token type, package name, and digest of signatures. 181 * 182 * @param token 183 * @param tokenType 184 * @param packageName 185 * @param sigDigest 186 * @param expiryMillis 187 */ put( Account account, String token, String tokenType, String packageName, byte[] sigDigest, long expiryMillis)188 public void put( 189 Account account, 190 String token, 191 String tokenType, 192 String packageName, 193 byte[] sigDigest, 194 long expiryMillis) { 195 Objects.requireNonNull(account); 196 if (token == null || System.currentTimeMillis() > expiryMillis) { 197 return; 198 } 199 Key k = new Key(account, tokenType, packageName, sigDigest); 200 Value v = new Value(token, expiryMillis); 201 mCachedTokens.putToken(k, v); 202 } 203 204 /** 205 * Evicts the specified token from the cache. This should be called as part of a token 206 * invalidation workflow. 207 */ remove(String accountType, String token)208 public void remove(String accountType, String token) { 209 mCachedTokens.evict(accountType, token); 210 } 211 remove(Account account)212 public void remove(Account account) { 213 mCachedTokens.evict(account); 214 } 215 216 /** 217 * Gets a token from the cache if possible. 218 */ get(Account account, String tokenType, String packageName, byte[] sigDigest)219 public Value get(Account account, String tokenType, String packageName, byte[] sigDigest) { 220 Key k = new Key(account, tokenType, packageName, sigDigest); 221 Value v = mCachedTokens.get(k); 222 long currentTime = System.currentTimeMillis(); 223 if (v != null && currentTime < v.expiryEpochMillis) { 224 return v; 225 } else if (v != null) { 226 remove(account.type, v.token); 227 } 228 return null; 229 } 230 } 231