am 46344f66: am 88817de8: Merge "Tweak GET_ACCOUNTS behavior and improve memory." into mnc-dev

* commit '46344f665e9aa7a48a458c9f9298a8f1f75f4a9b':
  Tweak GET_ACCOUNTS behavior and improve memory.
This commit is contained in:
Carlos Valdivia
2015-07-02 01:00:33 +00:00
committed by Android Git Automerger
3 changed files with 283 additions and 177 deletions

View File

@@ -489,7 +489,8 @@ public class AccountManager {
* <p>It is safe to call this method from the main thread.
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#GET_ACCOUNTS}.
* {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
* authenticator that owns the account type.
*
* @param type The type of accounts to return, null to retrieve all accounts
* @return An array of {@link Account}, one per matching account. Empty
@@ -615,7 +616,8 @@ public class AccountManager {
* {@link AccountManagerFuture} must not be used on the main thread.
*
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#GET_ACCOUNTS}.
* {@link android.Manifest.permission#GET_ACCOUNTS} or share a uid with the
* authenticator that owns the account type.
*
* @param type The type of accounts to return, must not be null
* @param features An array of the account features to require,

View File

@@ -86,7 +86,6 @@ import com.google.android.collect.Sets;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
@@ -115,7 +114,6 @@ public class AccountManagerService
implements RegisteredServicesCacheListener<AuthenticatorDescription> {
private static final String TAG = "AccountManagerService";
private static final int TIMEOUT_DELAY_MS = 1000 * 60;
private static final String DATABASE_NAME = "accounts.db";
private static final int DATABASE_VERSION = 8;
@@ -216,7 +214,7 @@ public class AccountManagerService
new HashMap<Account, HashMap<String, String>>();
/** protected by the {@link #cacheLock} */
private final HashMap<Account, WeakReference<TokenCache>> accountTokenCaches = new HashMap<>();
private final TokenCache accountTokenCaches = new TokenCache();
/**
* protected by the {@link #cacheLock}
@@ -529,7 +527,7 @@ public class AccountManagerService
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot get secrets for accounts of type: %s",
callingUid,
@@ -627,7 +625,7 @@ public class AccountManagerService
}
if (account == null) throw new IllegalArgumentException("account is null");
if (key == null) throw new IllegalArgumentException("key is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot get user data for accounts of type: %s",
callingUid,
@@ -645,15 +643,22 @@ public class AccountManagerService
@Override
public AuthenticatorDescription[] getAuthenticatorTypes(int userId) {
int callingUid = Binder.getCallingUid();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthenticatorTypes: "
+ "for user id " + userId
+ "caller's uid " + Binder.getCallingUid()
+ "caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
// Only allow the system process to read accounts of other users
enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
+ " trying get authenticator types for " + userId);
if (isCrossUser(callingUid, userId)) {
throw new SecurityException(
String.format(
"User %s tying to get authenticator types for %s" ,
UserHandle.getCallingUserId(),
userId));
}
final long identityToken = clearCallingIdentity();
try {
Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
@@ -672,14 +677,12 @@ public class AccountManagerService
}
}
private void enforceCrossUserPermission(int userId, String errorMessage) {
if (userId != UserHandle.getCallingUserId()
&& Binder.getCallingUid() != Process.myUid()
private boolean isCrossUser(int callingUid, int userId) {
return (userId != UserHandle.getCallingUserId()
&& callingUid != Process.myUid()
&& mContext.checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(errorMessage);
}
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED);
}
@Override
@@ -691,7 +694,7 @@ public class AccountManagerService
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot explicitly add accounts of type: %s",
callingUid,
@@ -720,8 +723,11 @@ public class AccountManagerService
@Override
public void copyAccountToUser(final IAccountManagerResponse response, final Account account,
int userFrom, int userTo) {
enforceCrossUserPermission(UserHandle.USER_ALL, "Calling copyAccountToUser requires "
int callingUid = Binder.getCallingUid();
if (isCrossUser(callingUid, UserHandle.USER_ALL)) {
throw new SecurityException("Calling copyAccountToUser requires "
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
}
final UserAccounts fromAccounts = getUserAccounts(userFrom);
final UserAccounts toAccounts = getUserAccounts(userTo);
if (fromAccounts == null || toAccounts == null) {
@@ -784,7 +790,7 @@ public class AccountManagerService
if (account == null) {
throw new IllegalArgumentException("account is null");
}
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot notify authentication for accounts of type: %s",
callingUid,
@@ -957,17 +963,18 @@ public class AccountManagerService
@Override
public void hasFeatures(IAccountManagerResponse response,
Account account, String[] features) {
int callingUid = Binder.getCallingUid();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "hasFeatures: " + account
+ ", response " + response
+ ", features " + stringArrayToString(features)
+ ", caller's uid " + Binder.getCallingUid()
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
if (response == null) throw new IllegalArgumentException("response is null");
if (account == null) throw new IllegalArgumentException("account is null");
if (features == null) throw new IllegalArgumentException("features is null");
checkReadAccountsPermission();
checkReadAccountsPermitted(callingUid, account.type);
UserAccounts accounts = getUserAccountsForCaller();
long identityToken = clearCallingIdentity();
try {
@@ -1043,7 +1050,7 @@ public class AccountManagerService
+ ", pid " + Binder.getCallingPid());
}
if (accountToRename == null) throw new IllegalArgumentException("account is null");
if (!isAccountOwnedByCallingUid(accountToRename.type, callingUid)) {
if (!isAccountManagedByCaller(accountToRename.type, callingUid)) {
String msg = String.format(
"uid %s cannot rename accounts of type: %s",
callingUid,
@@ -1053,8 +1060,7 @@ public class AccountManagerService
UserAccounts accounts = getUserAccountsForCaller();
long identityToken = clearCallingIdentity();
try {
Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName,
callingUid);
Account resultingAccount = renameAccountInternal(accounts, accountToRename, newName);
Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, resultingAccount.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE, resultingAccount.type);
@@ -1069,7 +1075,7 @@ public class AccountManagerService
}
private Account renameAccountInternal(
UserAccounts accounts, Account accountToRename, String newName, int callingUid) {
UserAccounts accounts, Account accountToRename, String newName) {
Account resultAccount = null;
/*
* Cancel existing notifications. Let authenticators
@@ -1179,16 +1185,20 @@ public class AccountManagerService
}
if (response == null) throw new IllegalArgumentException("response is null");
if (account == null) throw new IllegalArgumentException("account is null");
// Only allow the system process to modify accounts of other users
enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
+ " trying to remove account for " + userId);
if (isCrossUser(callingUid, userId)) {
throw new SecurityException(
String.format(
"User %s tying remove account for %s" ,
UserHandle.getCallingUserId(),
userId));
}
/*
* Only the system or authenticator should be allowed to remove accounts for that
* authenticator. This will let users remove accounts (via Settings in the system) but not
* arbitrary applications (like competing authenticators).
*/
if (!isAccountOwnedByCallingUid(account.type, callingUid) && !isSystemUid(callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid) && !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot remove accounts of type: %s",
callingUid,
@@ -1252,7 +1262,7 @@ public class AccountManagerService
*/
Log.e(TAG, "account is null");
return false;
} else if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
} else if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot explicitly add accounts of type: %s",
callingUid,
@@ -1398,16 +1408,7 @@ public class AccountManagerService
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);
}
}
}
accounts.accountTokenCaches.remove(accountType, authToken);
}
private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
@@ -1459,11 +1460,8 @@ public class AccountManagerService
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;
accounts.accountTokenCaches.put(
account, token, tokenType, callerPkg, callerSigDigest, expiryMillis);
}
}
@@ -1512,7 +1510,7 @@ public class AccountManagerService
}
if (account == null) throw new IllegalArgumentException("account is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot peek the authtokens associated with accounts of type: %s",
callingUid,
@@ -1539,7 +1537,7 @@ public class AccountManagerService
}
if (account == null) throw new IllegalArgumentException("account is null");
if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot set auth tokens associated with accounts of type: %s",
callingUid,
@@ -1564,7 +1562,7 @@ public class AccountManagerService
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot set secrets for accounts of type: %s",
callingUid,
@@ -1627,7 +1625,7 @@ public class AccountManagerService
+ ", pid " + Binder.getCallingPid());
}
if (account == null) throw new IllegalArgumentException("account is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot clear passwords for accounts of type: %s",
callingUid,
@@ -1654,7 +1652,7 @@ public class AccountManagerService
}
if (key == null) throw new IllegalArgumentException("key is null");
if (account == null) throw new IllegalArgumentException("account is null");
if (!isAccountOwnedByCallingUid(account.type, callingUid)) {
if (!isAccountManagedByCaller(account.type, callingUid)) {
String msg = String.format(
"uid %s cannot set user data for accounts of type: %s",
callingUid,
@@ -1780,7 +1778,6 @@ public class AccountManagerService
final boolean notifyOnAuthFailure,
final boolean expectActivityLaunch,
final Bundle loginOptions) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthToken: " + account
+ ", response " + response
@@ -1877,6 +1874,9 @@ public class AccountManagerService
callerPkg,
callerPkgSigDigest);
if (token != null) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthToken: cache hit ofr custom token authenticator.");
}
Bundle result = new Bundle();
result.putString(AccountManager.KEY_AUTHTOKEN, token);
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
@@ -1914,10 +1914,11 @@ public class AccountManagerService
public void onResult(Bundle result) {
if (result != null) {
if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
Intent intent = newGrantCredentialsPermissionIntent(
account,
callerUid,
new AccountAuthenticatorResponse(this),
authTokenType,
result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL));
authTokenType);
Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
onResult(bundle);
@@ -1995,9 +1996,6 @@ public class AccountManagerService
GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
String authTokenType = intent.getStringExtra(
GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
String authTokenLabel = intent.getStringExtra(
GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
final String titleAndSubtitle =
mContext.getString(R.string.permission_request_notification_with_subtitle,
account.name);
@@ -2025,7 +2023,7 @@ public class AccountManagerService
}
private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
AccountAuthenticatorResponse response, String authTokenType) {
Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
// See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
@@ -2149,6 +2147,7 @@ public class AccountManagerService
public void addAccountAsUser(final IAccountManagerResponse response, final String accountType,
final String authTokenType, final String[] requiredFeatures,
final boolean expectActivityLaunch, final Bundle optionsIn, int userId) {
int callingUid = Binder.getCallingUid();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "addAccount: accountType " + accountType
+ ", response " + response
@@ -2161,10 +2160,14 @@ public class AccountManagerService
}
if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
// Only allow the system process to add accounts of other users
enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
+ " trying to add account for " + userId);
if (isCrossUser(callingUid, userId)) {
throw new SecurityException(
String.format(
"User %s trying to add account for %s" ,
UserHandle.getCallingUserId(),
userId));
}
// Is user disallowed from modifying accounts?
if (!canUserModifyAccounts(userId)) {
@@ -2235,20 +2238,28 @@ public class AccountManagerService
}
@Override
public void confirmCredentialsAsUser(IAccountManagerResponse response,
final Account account, final Bundle options, final boolean expectActivityLaunch,
public void confirmCredentialsAsUser(
IAccountManagerResponse response,
final Account account,
final Bundle options,
final boolean expectActivityLaunch,
int userId) {
// Only allow the system process to read accounts of other users
enforceCrossUserPermission(userId, "User " + UserHandle.getCallingUserId()
+ " trying to confirm account credentials for " + userId);
int callingUid = Binder.getCallingUid();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "confirmCredentials: " + account
+ ", response " + response
+ ", expectActivityLaunch " + expectActivityLaunch
+ ", caller's uid " + Binder.getCallingUid()
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
// Only allow the system process to read accounts of other users
if (isCrossUser(callingUid, userId)) {
throw new SecurityException(
String.format(
"User %s trying to confirm account credentials for %s" ,
UserHandle.getCallingUserId(),
userId));
}
if (response == null) throw new IllegalArgumentException("response is null");
if (account == null) throw new IllegalArgumentException("account is null");
UserAccounts accounts = getUserAccounts(userId);
@@ -2324,7 +2335,7 @@ public class AccountManagerService
}
if (response == null) throw new IllegalArgumentException("response is null");
if (accountType == null) throw new IllegalArgumentException("accountType is null");
if (!isAccountOwnedByCallingUid(accountType, callingUid) && !isSystemUid(callingUid)) {
if (!isAccountManagedByCaller(accountType, callingUid) && !isSystemUid(callingUid)) {
String msg = String.format(
"uid %s cannot edit authenticator properites for account type: %s",
callingUid,
@@ -2457,9 +2468,11 @@ public class AccountManagerService
* @hide
*/
public Account[] getAccounts(int userId) {
checkReadAccountsPermission();
UserAccounts accounts = getUserAccounts(userId);
int callingUid = Binder.getCallingUid();
if (!isReadAccountsPermitted(callingUid, null)) {
return new Account[0];
}
long identityToken = clearCallingIdentity();
try {
synchronized (accounts.cacheLock) {
@@ -2519,7 +2532,10 @@ public class AccountManagerService
return getAccountsAsUser(type, userId, null, -1);
}
private Account[] getAccountsAsUser(String type, int userId, String callingPackage,
private Account[] getAccountsAsUser(
String type,
int userId,
String callingPackage,
int packageUid) {
int callingUid = Binder.getCallingUid();
// Only allow the system process to read accounts of other users
@@ -2542,7 +2558,12 @@ public class AccountManagerService
if (packageUid != -1 && UserHandle.isSameApp(callingUid, Process.myUid())) {
callingUid = packageUid;
}
checkReadAccountsPermission();
// Authenticators should be able to see their own accounts regardless of permissions.
if (TextUtils.isEmpty(type) && !isReadAccountsPermitted(callingUid, type)) {
return new Account[0];
}
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
@@ -2593,7 +2614,7 @@ public class AccountManagerService
logRecord(db, DebugDbHelper.ACTION_ACCOUNT_RENAME, TABLE_SHARED_ACCOUNTS,
sharedTableAccountId, accounts, callingUid);
// Recursively rename the account.
renameAccountInternal(accounts, account, newName, callingUid);
renameAccountInternal(accounts, account, newName);
}
return r > 0;
}
@@ -2663,7 +2684,6 @@ public class AccountManagerService
@Override
public Account[] getAccountsByTypeForPackage(String type, String packageName) {
checkBinderPermission(android.Manifest.permission.INTERACT_ACROSS_USERS);
int packageUid = -1;
try {
packageUid = AppGlobals.getPackageManager().getPackageUid(
@@ -2676,20 +2696,31 @@ public class AccountManagerService
}
@Override
public void getAccountsByFeatures(IAccountManagerResponse response,
String type, String[] features) {
public void getAccountsByFeatures(
IAccountManagerResponse response,
String type,
String[] features) {
int callingUid = Binder.getCallingUid();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAccounts: accountType " + type
+ ", response " + response
+ ", features " + stringArrayToString(features)
+ ", caller's uid " + Binder.getCallingUid()
+ ", caller's uid " + callingUid
+ ", pid " + Binder.getCallingPid());
}
if (response == null) throw new IllegalArgumentException("response is null");
if (type == null) throw new IllegalArgumentException("accountType is null");
checkReadAccountsPermission();
if (!isReadAccountsPermitted(callingUid, type)) {
Bundle result = new Bundle();
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, new Account[0]);
try {
response.onResult(result);
} catch (RemoteException e) {
Log.e(TAG, "Cannot respond to caller do to exception." , e);
}
return;
}
UserAccounts userAccounts = getUserAccountsForCaller();
int callingUid = Binder.getCallingUid();
long identityToken = clearCallingIdentity();
try {
if (features == null || features.length == 0) {
@@ -2872,11 +2903,6 @@ public class AccountManagerService
}
}
public void scheduleTimeout() {
mMessageHandler.sendMessageDelayed(
mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
}
public void cancelTimeout() {
mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
}
@@ -3181,12 +3207,9 @@ public class AccountManagerService
// who called.
private static String ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add";
private static String ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove";
private static String ACTION_CALLED_ACCOUNT_RENAME = "action_called_account_rename";
private static SimpleDateFormat dateFromat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static String UPDATE_WHERE_CLAUSE = KEY + "=?";
private static void createDebugTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
+ ACCOUNTS_ID + " INTEGER,"
@@ -3421,7 +3444,7 @@ public class AccountManagerService
}
}
public IBinder onBind(Intent intent) {
public IBinder onBind(@SuppressWarnings("unused") Intent intent) {
return asBinder();
}
@@ -3576,21 +3599,29 @@ public class AccountManagerService
}
}
/** Succeeds if any of the specified permissions are granted. */
private void checkBinderPermission(String... permissions) {
final int uid = Binder.getCallingUid();
private boolean isPermitted(int callingUid, String... permissions) {
for (String perm : permissions) {
if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, " caller uid " + uid + " has " + perm);
Log.v(TAG, " caller uid " + callingUid + " has " + perm);
}
return;
return true;
}
}
String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
Log.w(TAG, " " + msg);
throw new SecurityException(msg);
return false;
}
/** Succeeds if any of the specified permissions are granted. */
private void checkBinderPermission(String... permissions) {
final int callingUid = Binder.getCallingUid();
if (isPermitted(callingUid, permissions)) {
String msg = String.format(
"caller uid %s lacks any of %s",
callingUid,
TextUtils.join(",", permissions));
Log.w(TAG, " " + msg);
throw new SecurityException(msg);
}
}
private int handleIncomingUser(int userId) {
@@ -3646,6 +3677,9 @@ public class AccountManagerService
}
private boolean isAccountManagedByCaller(String accountType, int callingUid) {
if (accountType == null) {
return false;
}
final int callingUserId = UserHandle.getUserId(callingUid);
for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
mAuthenticatorCache.getAllServices(callingUserId)) {
@@ -3723,20 +3757,34 @@ public class AccountManagerService
return false;
}
private boolean isAccountOwnedByCallingUid(String accountType, int callingUid) {
if (!isAccountManagedByCaller(accountType, callingUid)) {
String msg = "caller uid " + callingUid + " is different than the authenticator's uid";
Log.w(TAG, msg);
return false;
}
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "caller uid " + callingUid + " is the same as the authenticator's uid");
}
return true;
private boolean isReadAccountsPermitted(int callingUid, String accountType) {
/*
* Settings app (which is in the same uid as AcocuntManagerService), apps with the
* GET_ACCOUNTS permission or authenticators that own the account type should be able to
* access accounts of the specified account.
*/
boolean isPermitted =
isPermitted(callingUid, Manifest.permission.GET_ACCOUNTS);
boolean isAccountManagedByCaller = isAccountManagedByCaller(accountType, callingUid);
Log.w(TAG, String.format(
"isReadAccountPermitted: isPermitted: %s, isAM: %s",
isPermitted,
isAccountManagedByCaller));
return isPermitted || isAccountManagedByCaller;
}
private void checkReadAccountsPermission() {
checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
/** Succeeds if any of the specified permissions are granted. */
private void checkReadAccountsPermitted(
int callingUid,
String accountType) {
if (!isReadAccountsPermitted(callingUid, accountType)) {
String msg = String.format(
"caller uid %s cannot access %s accounts",
callingUid,
accountType);
Log.w(TAG, " " + msg);
throw new SecurityException(msg);
}
}
private boolean canUserModifyAccounts(int userId) {
@@ -4008,8 +4056,8 @@ public class AccountManagerService
String callingPackage,
byte[] pkgSigDigest) {
synchronized (accounts.cacheLock) {
TokenCache cache = getTokenCacheForAccountLocked(accounts, account);
return cache.get(tokenType, callingPackage, pkgSigDigest);
return accounts.accountTokenCaches.get(
account, tokenType, callingPackage, pkgSigDigest);
}
}
@@ -4094,17 +4142,6 @@ public class AccountManagerService
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) {
try {
return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);

View File

@@ -16,6 +16,12 @@
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;
@@ -23,10 +29,12 @@ import java.util.List;
import java.util.Objects;
/**
* TokenCaches manage tokens associated with an account in memory.
* 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;
@@ -38,11 +46,13 @@ import java.util.Objects;
}
private static class Key {
public final Account account;
public final String packageName;
public final String tokenType;
public final byte[] sigDigest;
public Key(String tokenType, String packageName, byte[] sigDigest) {
public Key(Account account, String tokenType, String packageName, byte[] sigDigest) {
this.account = account;
this.tokenType = tokenType;
this.packageName = packageName;
this.sigDigest = sigDigest;
@@ -52,7 +62,8 @@ import java.util.Objects;
public boolean equals(Object o) {
if (o != null && o instanceof Key) {
Key cacheKey = (Key) o;
return Objects.equals(packageName, cacheKey.packageName)
return Objects.equals(account, cacheKey.account)
&& Objects.equals(packageName, cacheKey.packageName)
&& Objects.equals(tokenType, cacheKey.tokenType)
&& Arrays.equals(sigDigest, cacheKey.sigDigest);
} else {
@@ -62,7 +73,99 @@ import java.util.Objects;
@Override
public int hashCode() {
return packageName.hashCode() ^ tokenType.hashCode() ^ Arrays.hashCode(sigDigest);
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(oldVal.token);
if (evictor != null) {
evictor.evict();
}
}
}
public void putToken(Key k, Value v) {
// Prepare for removal by token string.
Evictor tokenEvictor = mTokenEvictors.get(v.token);
if (tokenEvictor == null) {
tokenEvictor = new Evictor();
}
tokenEvictor.add(k);
mTokenEvictors.put(new Pair<>(k.account.type, v.token), 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();
}
}
}
@@ -70,36 +173,7 @@ import java.util.Objects;
* 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);
}
}
private TokenLruCache mCachedTokens = new TokenLruCache();
/**
* Caches the specified token until the specified expiryMillis. The token will be associated
@@ -112,51 +186,44 @@ import java.util.Objects;
* @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(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.
Key k = new Key(account, tokenType, packageName, sigDigest);
Value v = new Value(token, expiryMillis);
mCachedTokens.put(k, v);
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 token) {
Evictor evictor = mTokenEvictors.get(token);
if (evictor == null) {
// This condition is expected if the token isn't cached.
return;
}
evictor.evict();
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(String tokenType, String packageName, byte[] sigDigest) {
Key k = new Key(tokenType, packageName, sigDigest);
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(v.token);
remove(account.type, v.token);
}
return null;
}