| /* |
| * Copyright (C) 2015 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.android.server.accounts; |
| |
| import android.accounts.Account; |
| import android.util.LruCache; |
| import android.util.Pair; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Objects; |
| |
| /** |
| * TokenCaches manage time limited authentication tokens in memory. |
| */ |
| /* default */ class TokenCache { |
| |
| private static final int MAX_CACHE_CHARS = 64000; |
| |
| private static class Value { |
| public final String token; |
| public final long expiryEpochMillis; |
| |
| public Value(String token, long expiryEpochMillis) { |
| this.token = token; |
| this.expiryEpochMillis = expiryEpochMillis; |
| } |
| } |
| |
| private static class Key { |
| public final Account account; |
| public final String packageName; |
| public final String tokenType; |
| public final byte[] sigDigest; |
| |
| public Key(Account account, String tokenType, String packageName, byte[] sigDigest) { |
| this.account = account; |
| this.tokenType = tokenType; |
| this.packageName = packageName; |
| this.sigDigest = sigDigest; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (o != null && o instanceof Key) { |
| Key cacheKey = (Key) o; |
| return Objects.equals(account, cacheKey.account) |
| && Objects.equals(packageName, cacheKey.packageName) |
| && Objects.equals(tokenType, cacheKey.tokenType) |
| && Arrays.equals(sigDigest, cacheKey.sigDigest); |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| public int hashCode() { |
| return account.hashCode() |
| ^ packageName.hashCode() |
| ^ tokenType.hashCode() |
| ^ Arrays.hashCode(sigDigest); |
| } |
| } |
| |
| private static class TokenLruCache extends LruCache<Key, Value> { |
| |
| private class Evictor { |
| private final List<Key> mKeys; |
| |
| public Evictor() { |
| mKeys = new ArrayList<>(); |
| } |
| |
| public void add(Key k) { |
| mKeys.add(k); |
| } |
| |
| public void evict() { |
| for (Key k : mKeys) { |
| TokenLruCache.this.remove(k); |
| } |
| } |
| } |
| |
| /** |
| * Map associated tokens with an Evictor that will manage evicting the token from the |
| * cache. This reverse lookup is needed because very little information is given at token |
| * invalidation time. |
| */ |
| private HashMap<Pair<String, String>, Evictor> mTokenEvictors = new HashMap<>(); |
| private HashMap<Account, Evictor> mAccountEvictors = new HashMap<>(); |
| |
| public TokenLruCache() { |
| super(MAX_CACHE_CHARS); |
| } |
| |
| @Override |
| protected int sizeOf(Key k, Value v) { |
| return v.token.length(); |
| } |
| |
| @Override |
| protected void entryRemoved(boolean evicted, Key k, Value oldVal, Value newVal) { |
| // When a token has been removed, clean up the associated Evictor. |
| if (oldVal != null && newVal == null) { |
| /* |
| * This is recursive, but it won't spiral out of control because LruCache is |
| * thread safe and the Evictor can only be removed once. |
| */ |
| Evictor evictor = mTokenEvictors.remove(new Pair<>(k.account.type, oldVal.token)); |
| if (evictor != null) { |
| evictor.evict(); |
| } |
| } |
| } |
| |
| public void putToken(Key k, Value v) { |
| // Prepare for removal by token string. |
| Pair<String, String> mapKey = new Pair<>(k.account.type, v.token); |
| Evictor tokenEvictor = mTokenEvictors.get(mapKey); |
| if (tokenEvictor == null) { |
| tokenEvictor = new Evictor(); |
| } |
| tokenEvictor.add(k); |
| mTokenEvictors.put(mapKey, tokenEvictor); |
| |
| // Prepare for removal by associated account. |
| Evictor accountEvictor = mAccountEvictors.get(k.account); |
| if (accountEvictor == null) { |
| accountEvictor = new Evictor(); |
| } |
| accountEvictor.add(k); |
| mAccountEvictors.put(k.account, tokenEvictor); |
| |
| // Only cache the token once we can remove it directly or by account. |
| put(k, v); |
| } |
| |
| public void evict(String accountType, String token) { |
| Evictor evictor = mTokenEvictors.get(new Pair<>(accountType, token)); |
| if (evictor != null) { |
| evictor.evict(); |
| } |
| |
| } |
| |
| public void evict(Account account) { |
| Evictor evictor = mAccountEvictors.get(account); |
| if (evictor != null) { |
| evictor.evict(); |
| } |
| } |
| } |
| |
| /** |
| * Map associating basic token lookup information with with actual tokens (and optionally their |
| * expiration times). |
| */ |
| private TokenLruCache mCachedTokens = new TokenLruCache(); |
| |
| /** |
| * Caches the specified token until the specified expiryMillis. The token will be associated |
| * with the given token type, package name, and digest of signatures. |
| * |
| * @param token |
| * @param tokenType |
| * @param packageName |
| * @param sigDigest |
| * @param expiryMillis |
| */ |
| public void put( |
| Account account, |
| String token, |
| String tokenType, |
| String packageName, |
| byte[] sigDigest, |
| long expiryMillis) { |
| Preconditions.checkNotNull(account); |
| if (token == null || System.currentTimeMillis() > expiryMillis) { |
| return; |
| } |
| Key k = new Key(account, tokenType, packageName, sigDigest); |
| Value v = new Value(token, expiryMillis); |
| mCachedTokens.putToken(k, v); |
| } |
| |
| /** |
| * Evicts the specified token from the cache. This should be called as part of a token |
| * invalidation workflow. |
| */ |
| public void remove(String accountType, String token) { |
| mCachedTokens.evict(accountType, token); |
| } |
| |
| public void remove(Account account) { |
| mCachedTokens.evict(account); |
| } |
| |
| /** |
| * Gets a token from the cache if possible. |
| */ |
| public String get(Account account, String tokenType, String packageName, byte[] sigDigest) { |
| Key k = new Key(account, tokenType, packageName, sigDigest); |
| Value v = mCachedTokens.get(k); |
| long currentTime = System.currentTimeMillis(); |
| if (v != null && currentTime < v.expiryEpochMillis) { |
| return v.token; |
| } else if (v != null) { |
| remove(account.type, v.token); |
| } |
| return null; |
| } |
| } |