am 2ed6369c: Merge "System Health: Support expiring tokens" into mnc-dev
* commit '2ed6369c5d856aa2e1f9972d52f0f3c2526c713a': System Health: Support expiring tokens
This commit is contained in:
@@ -2716,6 +2716,7 @@ package android.accounts {
|
|||||||
method public final android.os.IBinder getIBinder();
|
method public final android.os.IBinder getIBinder();
|
||||||
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
|
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
|
||||||
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
|
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
|
||||||
|
field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Account implements android.os.Parcelable {
|
public class Account implements android.os.Parcelable {
|
||||||
|
|||||||
@@ -2797,6 +2797,7 @@ package android.accounts {
|
|||||||
method public final android.os.IBinder getIBinder();
|
method public final android.os.IBinder getIBinder();
|
||||||
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
|
method public abstract android.os.Bundle hasFeatures(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String[]) throws android.accounts.NetworkErrorException;
|
||||||
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
|
method public abstract android.os.Bundle updateCredentials(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, java.lang.String, android.os.Bundle) throws android.accounts.NetworkErrorException;
|
||||||
|
field public static final java.lang.String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Account implements android.os.Parcelable {
|
public class Account implements android.os.Parcelable {
|
||||||
|
|||||||
@@ -108,6 +108,14 @@ import java.util.Arrays;
|
|||||||
public abstract class AbstractAccountAuthenticator {
|
public abstract class AbstractAccountAuthenticator {
|
||||||
private static final String TAG = "AccountAuthenticator";
|
private static final String TAG = "AccountAuthenticator";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle key used for the {@code long} expiration time (in millis from the unix epoch) of the
|
||||||
|
* associated auth token.
|
||||||
|
*
|
||||||
|
* @see #getAuthToken
|
||||||
|
*/
|
||||||
|
public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry";
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
|
||||||
public AbstractAccountAuthenticator(Context context) {
|
public AbstractAccountAuthenticator(Context context) {
|
||||||
@@ -115,6 +123,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private class Transport extends IAccountAuthenticator.Stub {
|
private class Transport extends IAccountAuthenticator.Stub {
|
||||||
|
@Override
|
||||||
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
|
public void addAccount(IAccountAuthenticatorResponse response, String accountType,
|
||||||
String authTokenType, String[] features, Bundle options)
|
String authTokenType, String[] features, Bundle options)
|
||||||
throws RemoteException {
|
throws RemoteException {
|
||||||
@@ -140,6 +149,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void confirmCredentials(IAccountAuthenticatorResponse response,
|
public void confirmCredentials(IAccountAuthenticatorResponse response,
|
||||||
Account account, Bundle options) throws RemoteException {
|
Account account, Bundle options) throws RemoteException {
|
||||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||||
@@ -162,6 +172,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
|
public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
|
||||||
String authTokenType)
|
String authTokenType)
|
||||||
throws RemoteException {
|
throws RemoteException {
|
||||||
@@ -184,6 +195,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void getAuthToken(IAccountAuthenticatorResponse response,
|
public void getAuthToken(IAccountAuthenticatorResponse response,
|
||||||
Account account, String authTokenType, Bundle loginOptions)
|
Account account, String authTokenType, Bundle loginOptions)
|
||||||
throws RemoteException {
|
throws RemoteException {
|
||||||
@@ -209,6 +221,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
|
public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
|
||||||
String authTokenType, Bundle loginOptions) throws RemoteException {
|
String authTokenType, Bundle loginOptions) throws RemoteException {
|
||||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||||
@@ -234,6 +247,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void editProperties(IAccountAuthenticatorResponse response,
|
public void editProperties(IAccountAuthenticatorResponse response,
|
||||||
String accountType) throws RemoteException {
|
String accountType) throws RemoteException {
|
||||||
checkBinderPermission();
|
checkBinderPermission();
|
||||||
@@ -248,6 +262,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void hasFeatures(IAccountAuthenticatorResponse response,
|
public void hasFeatures(IAccountAuthenticatorResponse response,
|
||||||
Account account, String[] features) throws RemoteException {
|
Account account, String[] features) throws RemoteException {
|
||||||
checkBinderPermission();
|
checkBinderPermission();
|
||||||
@@ -262,6 +277,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
|
public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
|
||||||
Account account) throws RemoteException {
|
Account account) throws RemoteException {
|
||||||
checkBinderPermission();
|
checkBinderPermission();
|
||||||
@@ -276,6 +292,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
|
public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
|
||||||
Account account) throws RemoteException {
|
Account account) throws RemoteException {
|
||||||
checkBinderPermission();
|
checkBinderPermission();
|
||||||
@@ -291,6 +308,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
|
public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
|
||||||
Account account,
|
Account account,
|
||||||
Bundle accountCredentials) throws RemoteException {
|
Bundle accountCredentials) throws RemoteException {
|
||||||
@@ -410,21 +428,42 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response,
|
public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response,
|
||||||
Account account, Bundle options)
|
Account account, Bundle options)
|
||||||
throws NetworkErrorException;
|
throws NetworkErrorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the authtoken for an account.
|
* Gets an authtoken for an account.
|
||||||
|
*
|
||||||
|
* If not {@code null}, the resultant {@link Bundle} will contain different sets of keys
|
||||||
|
* depending on whether a token was successfully issued and, if not, whether one
|
||||||
|
* could be issued via some {@link android.app.Activity}.
|
||||||
|
* <p>
|
||||||
|
* If a token cannot be provided without some additional activity, the Bundle should contain
|
||||||
|
* {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if
|
||||||
|
* there is no such activity, then a Bundle containing
|
||||||
|
* {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be
|
||||||
|
* returned.
|
||||||
|
* <p>
|
||||||
|
* If a token can be successfully issued, the implementation should return the
|
||||||
|
* {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the
|
||||||
|
* account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In
|
||||||
|
* addition {@link AbstractAccountAuthenticator} implementations that declare themselves
|
||||||
|
* {@code android:customTokens=true} may also provide a non-negative {@link
|
||||||
|
* #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration
|
||||||
|
* time (in millis since the unix epoch).
|
||||||
|
* <p>
|
||||||
|
* Implementers should assume that tokens will be cached on the basis of account and
|
||||||
|
* authTokenType. The system may ignore the contents of the supplied options Bundle when
|
||||||
|
* determining to re-use a cached token. Furthermore, implementers should assume a supplied
|
||||||
|
* expiration time will be treated as non-binding advice.
|
||||||
|
* <p>
|
||||||
|
* Finally, note that for android:customTokens=false authenticators, tokens are cached
|
||||||
|
* indefinitely until some client calls {@link
|
||||||
|
* AccountManager#invalidateAuthToken(String,String)}.
|
||||||
|
*
|
||||||
* @param response to send the result back to the AccountManager, will never be null
|
* @param response to send the result back to the AccountManager, will never be null
|
||||||
* @param account the account whose credentials are to be retrieved, will never be null
|
* @param account the account whose credentials are to be retrieved, will never be null
|
||||||
* @param authTokenType the type of auth token to retrieve, will never be null
|
* @param authTokenType the type of auth token to retrieve, will never be null
|
||||||
* @param options a Bundle of authenticator-specific options, may be null
|
* @param options a Bundle of authenticator-specific options, may be null
|
||||||
* @return a Bundle result or null if the result is to be returned via the response. The result
|
* @return a Bundle result or null if the result is to be returned via the response.
|
||||||
* will contain either:
|
|
||||||
* <ul>
|
|
||||||
* <li> {@link AccountManager#KEY_INTENT}, or
|
|
||||||
* <li> {@link AccountManager#KEY_ACCOUNT_NAME}, {@link AccountManager#KEY_ACCOUNT_TYPE},
|
|
||||||
* and {@link AccountManager#KEY_AUTHTOKEN}, or
|
|
||||||
* <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to
|
|
||||||
* indicate an error
|
|
||||||
* </ul>
|
|
||||||
* @throws NetworkErrorException if the authenticator could not honor the request due to a
|
* @throws NetworkErrorException if the authenticator could not honor the request due to a
|
||||||
* network error
|
* network error
|
||||||
*/
|
*/
|
||||||
@@ -518,6 +557,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
|
public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
|
||||||
final Account account) throws NetworkErrorException {
|
final Account account) throws NetworkErrorException {
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Bundle result = new Bundle();
|
Bundle result = new Bundle();
|
||||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
||||||
@@ -543,6 +583,7 @@ public abstract class AbstractAccountAuthenticator {
|
|||||||
Account account,
|
Account account,
|
||||||
Bundle accountCredentials) throws NetworkErrorException {
|
Bundle accountCredentials) throws NetworkErrorException {
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Bundle result = new Bundle();
|
Bundle result = new Bundle();
|
||||||
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.server.accounts;
|
package com.android.server.accounts;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.accounts.AbstractAccountAuthenticator;
|
||||||
import android.accounts.Account;
|
import android.accounts.Account;
|
||||||
import android.accounts.AccountAndUser;
|
import android.accounts.AccountAndUser;
|
||||||
import android.accounts.AccountAuthenticatorResponse;
|
import android.accounts.AccountAuthenticatorResponse;
|
||||||
@@ -49,6 +50,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
|||||||
import android.content.pm.RegisteredServicesCache;
|
import android.content.pm.RegisteredServicesCache;
|
||||||
import android.content.pm.RegisteredServicesCacheListener;
|
import android.content.pm.RegisteredServicesCacheListener;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.content.pm.Signature;
|
||||||
import android.content.pm.UserInfo;
|
import android.content.pm.UserInfo;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.DatabaseUtils;
|
import android.database.DatabaseUtils;
|
||||||
@@ -84,6 +86,11 @@ import com.google.android.collect.Sets;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -93,6 +100,7 @@ import java.util.Date;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
@@ -166,6 +174,10 @@ public class AccountManagerService
|
|||||||
private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
|
private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
|
||||||
new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
|
new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
|
||||||
private static final Intent ACCOUNTS_CHANGED_INTENT;
|
private static final Intent ACCOUNTS_CHANGED_INTENT;
|
||||||
|
static {
|
||||||
|
ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
|
||||||
|
ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
||||||
|
}
|
||||||
|
|
||||||
private static final String COUNT_OF_MATCHING_GRANTS = ""
|
private static final String COUNT_OF_MATCHING_GRANTS = ""
|
||||||
+ "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
|
+ "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
|
||||||
@@ -177,6 +189,7 @@ public class AccountManagerService
|
|||||||
|
|
||||||
private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
|
private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
|
||||||
AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
|
AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
|
||||||
|
|
||||||
private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
|
private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
|
||||||
AUTHTOKENS_AUTHTOKEN};
|
AUTHTOKENS_AUTHTOKEN};
|
||||||
|
|
||||||
@@ -205,6 +218,10 @@ public class AccountManagerService
|
|||||||
/** protected by the {@link #cacheLock} */
|
/** protected by the {@link #cacheLock} */
|
||||||
private final HashMap<Account, HashMap<String, String>> authTokenCache =
|
private final HashMap<Account, HashMap<String, String>> authTokenCache =
|
||||||
new HashMap<Account, HashMap<String, String>>();
|
new HashMap<Account, HashMap<String, String>>();
|
||||||
|
|
||||||
|
/** protected by the {@link #cacheLock} */
|
||||||
|
private final HashMap<Account, WeakReference<TokenCache>> accountTokenCaches = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* protected by the {@link #cacheLock}
|
* protected by the {@link #cacheLock}
|
||||||
*
|
*
|
||||||
@@ -237,12 +254,6 @@ public class AccountManagerService
|
|||||||
new AtomicReference<AccountManagerService>();
|
new AtomicReference<AccountManagerService>();
|
||||||
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
|
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
|
||||||
|
|
||||||
static {
|
|
||||||
ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
|
|
||||||
ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should only be called by system code. One should only call this after the service
|
* This should only be called by system code. One should only call this after the service
|
||||||
* has started.
|
* has started.
|
||||||
@@ -425,6 +436,7 @@ public class AccountManagerService
|
|||||||
final Account account = new Account(accountName, accountType);
|
final Account account = new Account(accountName, accountType);
|
||||||
accounts.userDataCache.remove(account);
|
accounts.userDataCache.remove(account);
|
||||||
accounts.authTokenCache.remove(account);
|
accounts.authTokenCache.remove(account);
|
||||||
|
accounts.accountTokenCaches.remove(account);
|
||||||
} else {
|
} else {
|
||||||
ArrayList<String> accountNames = accountNamesByType.get(accountType);
|
ArrayList<String> accountNames = accountNamesByType.get(accountType);
|
||||||
if (accountNames == null) {
|
if (accountNames == null) {
|
||||||
@@ -1337,9 +1349,10 @@ public class AccountManagerService
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invalidateAuthToken(String accountType, String authToken) {
|
public void invalidateAuthToken(String accountType, String authToken) {
|
||||||
|
int callerUid = Binder.getCallingUid();
|
||||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||||
Log.v(TAG, "invalidateAuthToken: accountType " + accountType
|
Log.v(TAG, "invalidateAuthToken: accountType " + accountType
|
||||||
+ ", caller's uid " + Binder.getCallingUid()
|
+ ", caller's uid " + callerUid
|
||||||
+ ", pid " + Binder.getCallingPid());
|
+ ", pid " + Binder.getCallingPid());
|
||||||
}
|
}
|
||||||
if (accountType == null) throw new IllegalArgumentException("accountType is null");
|
if (accountType == null) throw new IllegalArgumentException("accountType is null");
|
||||||
@@ -1353,6 +1366,7 @@ public class AccountManagerService
|
|||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
try {
|
try {
|
||||||
invalidateAuthTokenLocked(accounts, db, accountType, authToken);
|
invalidateAuthTokenLocked(accounts, db, accountType, authToken);
|
||||||
|
invalidateCustomTokenLocked(accounts, accountType, authToken);
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
@@ -1363,6 +1377,26 @@ public class AccountManagerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void invalidateCustomTokenLocked(
|
||||||
|
UserAccounts accounts,
|
||||||
|
String accountType,
|
||||||
|
String authToken) {
|
||||||
|
if (authToken == null || accountType == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Also wipe out cached token in memory.
|
||||||
|
for (Account a : accounts.accountTokenCaches.keySet()) {
|
||||||
|
if (a.type.equals(accountType)) {
|
||||||
|
WeakReference<TokenCache> tokenCacheRef =
|
||||||
|
accounts.accountTokenCaches.get(a);
|
||||||
|
TokenCache cache = null;
|
||||||
|
if (tokenCacheRef != null && (cache = tokenCacheRef.get()) != null) {
|
||||||
|
cache.remove(authToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
|
private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
|
||||||
String accountType, String authToken) {
|
String accountType, String authToken) {
|
||||||
if (authToken == null || accountType == null) {
|
if (authToken == null || accountType == null) {
|
||||||
@@ -1385,14 +1419,41 @@ public class AccountManagerService
|
|||||||
String accountName = cursor.getString(1);
|
String accountName = cursor.getString(1);
|
||||||
String authTokenType = cursor.getString(2);
|
String authTokenType = cursor.getString(2);
|
||||||
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
|
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
|
||||||
writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType),
|
writeAuthTokenIntoCacheLocked(
|
||||||
authTokenType, null);
|
accounts,
|
||||||
|
db,
|
||||||
|
new Account(accountName, accountType),
|
||||||
|
authTokenType,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void saveCachedToken(
|
||||||
|
UserAccounts accounts,
|
||||||
|
Account account,
|
||||||
|
String callerPkg,
|
||||||
|
byte[] callerSigDigest,
|
||||||
|
String tokenType,
|
||||||
|
String token,
|
||||||
|
long expiryMillis) {
|
||||||
|
|
||||||
|
if (account == null || tokenType == null || callerPkg == null || callerSigDigest == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cancelNotification(getSigninRequiredNotificationId(accounts, account),
|
||||||
|
new UserHandle(accounts.userId));
|
||||||
|
synchronized (accounts.cacheLock) {
|
||||||
|
TokenCache cache = getTokenCacheForAccountLocked(accounts, account);
|
||||||
|
if (cache != null) {
|
||||||
|
cache.put(token, tokenType, callerPkg, callerSigDigest, expiryMillis);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type,
|
private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type,
|
||||||
String authToken) {
|
String authToken) {
|
||||||
if (account == null || type == null) {
|
if (account == null || type == null) {
|
||||||
@@ -1510,6 +1571,7 @@ public class AccountManagerService
|
|||||||
db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
|
db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
|
||||||
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
|
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
|
||||||
accounts.authTokenCache.remove(account);
|
accounts.authTokenCache.remove(account);
|
||||||
|
accounts.accountTokenCaches.remove(account);
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
|
|
||||||
String action = (password == null || password.length() == 0) ?
|
String action = (password == null || password.length() == 0) ?
|
||||||
@@ -1673,9 +1735,14 @@ public class AccountManagerService
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getAuthToken(IAccountManagerResponse response, final Account account,
|
public void getAuthToken(
|
||||||
final String authTokenType, final boolean notifyOnAuthFailure,
|
IAccountManagerResponse response,
|
||||||
final boolean expectActivityLaunch, Bundle loginOptionsIn) {
|
final Account account,
|
||||||
|
final String authTokenType,
|
||||||
|
final boolean notifyOnAuthFailure,
|
||||||
|
final boolean expectActivityLaunch,
|
||||||
|
final Bundle loginOptions) {
|
||||||
|
|
||||||
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
if (Log.isLoggable(TAG, Log.VERBOSE)) {
|
||||||
Log.v(TAG, "getAuthToken: " + account
|
Log.v(TAG, "getAuthToken: " + account
|
||||||
+ ", response " + response
|
+ ", response " + response
|
||||||
@@ -1707,19 +1774,33 @@ public class AccountManagerService
|
|||||||
final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
|
final RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo;
|
||||||
authenticatorInfo = mAuthenticatorCache.getServiceInfo(
|
authenticatorInfo = mAuthenticatorCache.getServiceInfo(
|
||||||
AuthenticatorDescription.newKey(account.type), accounts.userId);
|
AuthenticatorDescription.newKey(account.type), accounts.userId);
|
||||||
|
|
||||||
final boolean customTokens =
|
final boolean customTokens =
|
||||||
authenticatorInfo != null && authenticatorInfo.type.customTokens;
|
authenticatorInfo != null && authenticatorInfo.type.customTokens;
|
||||||
|
|
||||||
// skip the check if customTokens
|
// skip the check if customTokens
|
||||||
final int callerUid = Binder.getCallingUid();
|
final int callerUid = Binder.getCallingUid();
|
||||||
final boolean permissionGranted = customTokens ||
|
final boolean permissionGranted = customTokens ||
|
||||||
permissionIsGranted(account, authTokenType, callerUid);
|
permissionIsGranted(account, authTokenType, callerUid);
|
||||||
|
|
||||||
final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() :
|
// Get the calling package. We will use it for the purpose of caching.
|
||||||
loginOptionsIn;
|
final String callerPkg = loginOptions.getString(AccountManager.KEY_ANDROID_PACKAGE_NAME);
|
||||||
|
List<String> callerOwnedPackageNames = Arrays.asList(mPackageManager.getPackagesForUid(callerUid));
|
||||||
|
if (callerPkg == null || !callerOwnedPackageNames.contains(callerPkg)) {
|
||||||
|
String msg = String.format(
|
||||||
|
"Uid %s is attempting to illegally masquerade as package %s!",
|
||||||
|
callerUid,
|
||||||
|
callerPkg);
|
||||||
|
throw new SecurityException(msg);
|
||||||
|
}
|
||||||
|
|
||||||
// let authenticator know the identity of the caller
|
// let authenticator know the identity of the caller
|
||||||
loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
|
loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
|
||||||
loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
|
loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
|
||||||
|
|
||||||
|
// Distill the caller's package signatures into a single digest.
|
||||||
|
final byte[] callerPkgSigDigest = calculatePackageSignatureDigest(callerPkg);
|
||||||
|
|
||||||
if (notifyOnAuthFailure) {
|
if (notifyOnAuthFailure) {
|
||||||
loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
|
loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
|
||||||
}
|
}
|
||||||
@@ -1740,6 +1821,28 @@ public class AccountManagerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (customTokens) {
|
||||||
|
/*
|
||||||
|
* Look up tokens in the new cache only if the loginOptions don't have parameters
|
||||||
|
* outside of those expected to be injected by the AccountManager, e.g.
|
||||||
|
* ANDORID_PACKAGE_NAME.
|
||||||
|
*/
|
||||||
|
String token = readCachedTokenInternal(
|
||||||
|
accounts,
|
||||||
|
account,
|
||||||
|
authTokenType,
|
||||||
|
callerPkg,
|
||||||
|
callerPkgSigDigest);
|
||||||
|
if (token != null) {
|
||||||
|
Bundle result = new Bundle();
|
||||||
|
result.putString(AccountManager.KEY_AUTHTOKEN, token);
|
||||||
|
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
|
||||||
|
result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
|
||||||
|
onResult(response, result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
new Session(accounts, response, account.type, expectActivityLaunch,
|
new Session(accounts, response, account.type, expectActivityLaunch,
|
||||||
false /* stripAuthTokenFromResult */, account.name,
|
false /* stripAuthTokenFromResult */, account.name,
|
||||||
false /* authDetailsRequired */) {
|
false /* authDetailsRequired */) {
|
||||||
@@ -1786,9 +1889,26 @@ public class AccountManagerService
|
|||||||
"the type and name should not be empty");
|
"the type and name should not be empty");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Account resultAccount = new Account(name, type);
|
||||||
if (!customTokens) {
|
if (!customTokens) {
|
||||||
saveAuthTokenToDatabase(mAccounts, new Account(name, type),
|
saveAuthTokenToDatabase(
|
||||||
authTokenType, authToken);
|
mAccounts,
|
||||||
|
resultAccount,
|
||||||
|
authTokenType,
|
||||||
|
authToken);
|
||||||
|
}
|
||||||
|
long expiryMillis = result.getLong(
|
||||||
|
AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, 0L);
|
||||||
|
if (customTokens
|
||||||
|
&& expiryMillis > System.currentTimeMillis()) {
|
||||||
|
saveCachedToken(
|
||||||
|
mAccounts,
|
||||||
|
account,
|
||||||
|
callerPkg,
|
||||||
|
callerPkgSigDigest,
|
||||||
|
authTokenType,
|
||||||
|
authToken,
|
||||||
|
expiryMillis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1807,6 +1927,25 @@ public class AccountManagerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] calculatePackageSignatureDigest(String callerPkg) {
|
||||||
|
MessageDigest digester;
|
||||||
|
try {
|
||||||
|
digester = MessageDigest.getInstance("SHA-256");
|
||||||
|
PackageInfo pkgInfo = mPackageManager.getPackageInfo(
|
||||||
|
callerPkg, PackageManager.GET_SIGNATURES);
|
||||||
|
for (Signature sig : pkgInfo.signatures) {
|
||||||
|
digester.update(sig.toByteArray());
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException x) {
|
||||||
|
Log.wtf(TAG, "SHA-256 should be available", x);
|
||||||
|
digester = null;
|
||||||
|
} catch (NameNotFoundException e) {
|
||||||
|
Log.w(TAG, "Could not find packageinfo for: " + callerPkg);
|
||||||
|
digester = null;
|
||||||
|
}
|
||||||
|
return (digester == null) ? null : digester.digest();
|
||||||
|
}
|
||||||
|
|
||||||
private void createNoCredentialsPermissionNotification(Account account, Intent intent,
|
private void createNoCredentialsPermissionNotification(Account account, Intent intent,
|
||||||
int userId) {
|
int userId) {
|
||||||
int uid = intent.getIntExtra(
|
int uid = intent.getIntExtra(
|
||||||
@@ -2745,13 +2884,13 @@ public class AccountManagerService
|
|||||||
if (result != null) {
|
if (result != null) {
|
||||||
boolean isSuccessfulConfirmCreds = result.getBoolean(
|
boolean isSuccessfulConfirmCreds = result.getBoolean(
|
||||||
AccountManager.KEY_BOOLEAN_RESULT, false);
|
AccountManager.KEY_BOOLEAN_RESULT, false);
|
||||||
boolean isSuccessfulUpdateCreds =
|
boolean isSuccessfulUpdateCreds =
|
||||||
result.containsKey(AccountManager.KEY_ACCOUNT_NAME)
|
result.containsKey(AccountManager.KEY_ACCOUNT_NAME)
|
||||||
&& result.containsKey(AccountManager.KEY_ACCOUNT_TYPE);
|
&& result.containsKey(AccountManager.KEY_ACCOUNT_TYPE);
|
||||||
// We should only update lastAuthenticated time, if
|
// We should only update lastAuthenticated time, if
|
||||||
// mUpdateLastAuthenticatedTime is true and the confirmRequest
|
// mUpdateLastAuthenticatedTime is true and the confirmRequest
|
||||||
// or updateRequest was successful
|
// or updateRequest was successful
|
||||||
boolean needUpdate = mUpdateLastAuthenticatedTime
|
boolean needUpdate = mUpdateLastAuthenticatedTime
|
||||||
&& (isSuccessfulConfirmCreds || isSuccessfulUpdateCreds);
|
&& (isSuccessfulConfirmCreds || isSuccessfulUpdateCreds);
|
||||||
if (needUpdate || mAuthDetailsRequired) {
|
if (needUpdate || mAuthDetailsRequired) {
|
||||||
boolean accountPresent = isAccountPresentForCaller(mAccountName, mAccountType);
|
boolean accountPresent = isAccountPresentForCaller(mAccountName, mAccountType);
|
||||||
@@ -3398,7 +3537,6 @@ public class AccountManagerService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
|
String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
|
||||||
Log.w(TAG, " " + msg);
|
Log.w(TAG, " " + msg);
|
||||||
throw new SecurityException(msg);
|
throw new SecurityException(msg);
|
||||||
@@ -3796,6 +3934,18 @@ public class AccountManagerService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String readCachedTokenInternal(
|
||||||
|
UserAccounts accounts,
|
||||||
|
Account account,
|
||||||
|
String tokenType,
|
||||||
|
String callingPackage,
|
||||||
|
byte[] pkgSigDigest) {
|
||||||
|
synchronized (accounts.cacheLock) {
|
||||||
|
TokenCache cache = getTokenCacheForAccountLocked(accounts, account);
|
||||||
|
return cache.get(tokenType, callingPackage, pkgSigDigest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
|
protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
|
||||||
Account account, String key, String value) {
|
Account account, String key, String value) {
|
||||||
HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
|
HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
|
||||||
@@ -3877,6 +4027,17 @@ public class AccountManagerService
|
|||||||
return authTokensForAccount;
|
return authTokensForAccount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected TokenCache getTokenCacheForAccountLocked(UserAccounts accounts, Account account) {
|
||||||
|
WeakReference<TokenCache> cacheRef = accounts.accountTokenCaches.get(account);
|
||||||
|
TokenCache cache;
|
||||||
|
if (cacheRef == null || (cache = cacheRef.get()) == null) {
|
||||||
|
cache = new TokenCache();
|
||||||
|
cacheRef = new WeakReference<>(cache);
|
||||||
|
accounts.accountTokenCaches.put(account, cacheRef);
|
||||||
|
}
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
private Context getContextForUser(UserHandle user) {
|
private Context getContextForUser(UserHandle user) {
|
||||||
try {
|
try {
|
||||||
return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
|
return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
|
||||||
|
|||||||
163
services/core/java/com/android/server/accounts/TokenCache.java
Normal file
163
services/core/java/com/android/server/accounts/TokenCache.java
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TokenCaches manage tokens associated with an account in memory.
|
||||||
|
*/
|
||||||
|
/* default */ class TokenCache {
|
||||||
|
|
||||||
|
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 String packageName;
|
||||||
|
public final String tokenType;
|
||||||
|
public final byte[] sigDigest;
|
||||||
|
|
||||||
|
public Key(String tokenType, String packageName, byte[] sigDigest) {
|
||||||
|
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(packageName, cacheKey.packageName)
|
||||||
|
&& Objects.equals(tokenType, cacheKey.tokenType)
|
||||||
|
&& Arrays.equals(sigDigest, cacheKey.sigDigest);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return packageName.hashCode() ^ tokenType.hashCode() ^ Arrays.hashCode(sigDigest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map associating basic token lookup information with with actual tokens (and optionally their
|
||||||
|
* expiration times).
|
||||||
|
*/
|
||||||
|
private HashMap<Key, Value> mCachedTokens = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<String, Evictor> mTokenEvictors = new HashMap<>();
|
||||||
|
|
||||||
|
private class Evictor {
|
||||||
|
private final String mToken;
|
||||||
|
private final List<Key> mKeys;
|
||||||
|
|
||||||
|
public Evictor(String token) {
|
||||||
|
mKeys = new ArrayList<>();
|
||||||
|
mToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(Key k) {
|
||||||
|
mKeys.add(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void evict() {
|
||||||
|
for (Key k : mKeys) {
|
||||||
|
mCachedTokens.remove(k);
|
||||||
|
}
|
||||||
|
// Clear out the evictor reference.
|
||||||
|
mTokenEvictors.remove(mToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(
|
||||||
|
String token,
|
||||||
|
String tokenType,
|
||||||
|
String packageName,
|
||||||
|
byte[] sigDigest,
|
||||||
|
long expiryMillis) {
|
||||||
|
if (token == null || System.currentTimeMillis() > expiryMillis) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Key k = new Key(tokenType, packageName, sigDigest);
|
||||||
|
// Prep evictor. No token should be cached without a corresponding evictor.
|
||||||
|
Evictor evictor = mTokenEvictors.get(token);
|
||||||
|
if (evictor == null) {
|
||||||
|
evictor = new Evictor(token);
|
||||||
|
}
|
||||||
|
evictor.add(k);
|
||||||
|
mTokenEvictors.put(token, evictor);
|
||||||
|
// Then cache values.
|
||||||
|
Value v = new Value(token, expiryMillis);
|
||||||
|
mCachedTokens.put(k, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evicts the specified token from the cache. This should be called as part of a token
|
||||||
|
* invalidation workflow.
|
||||||
|
*/
|
||||||
|
public void remove(String token) {
|
||||||
|
Evictor evictor = mTokenEvictors.get(token);
|
||||||
|
if (evictor == null) {
|
||||||
|
// This condition is expected if the token isn't cached.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
evictor.evict();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a token from the cache if possible.
|
||||||
|
*/
|
||||||
|
public String get(String tokenType, String packageName, byte[] sigDigest) {
|
||||||
|
Key k = new Key(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(v.token);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user