Home | History | Annotate | Download | only in accounts
      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 
     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 
     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
     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
     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 
     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                  */
    128                 Evictor evictor = mTokenEvictors.remove(oldVal.token);
    129                 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.
    137             Evictor tokenEvictor = mTokenEvictors.get(v.token);
    138             if (tokenEvictor == null) {
    139                 tokenEvictor = new Evictor();
    140             }
    141             tokenEvictor.add(k);
    142             mTokenEvictors.put(new Pair<>(k.account.type, v.token), 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, tokenEvictor);
    151 
    152             // Only cache the token once we can remove it directly or by account.
    153             put(k, v);
    154         }
    155 
    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 
    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      */
    188     public void put(
    189             Account account,
    190             String token,
    191             String tokenType,
    192             String packageName,
    193             byte[] sigDigest,
    194             long expiryMillis) {
    195         Preconditions.checkNotNull(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      */
    208     public void remove(String accountType, String token) {
    209         mCachedTokens.evict(accountType, token);
    210     }
    211 
    212     public void remove(Account account) {
    213         mCachedTokens.evict(account);
    214     }
    215 
    216     /**
    217      * Gets a token from the cache if possible.
    218      */
    219     public String 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.token;
    225         } else if (v != null) {
    226             remove(account.type, v.token);
    227         }
    228         return null;
    229     }
    230 }
    231