• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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