Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 91979be8 authored by Carlos Valdivia's avatar Carlos Valdivia
Browse files

System Health: Support expiring tokens

In the past android:customTokens=true authenticators were required to handle
their own token caching. This is detrimental for battery when high traffic
authenticators are constantly spinning up processes to start services to do
file io to check their own caches.  This change allows authenticator
implementers to optionally let the framework do some of the work for them by
providing the framework with a expiration time.

The AccountManagerService will make a best effort to re-use the cached
token if possible.

Bug: 21530782

Change-Id: I16a7edba36a220e3891e55cf61c725c2be863323
parent 8fa8b95c
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -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 {
+1 −0
Original line number Original line Diff line number Diff line
@@ -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 {
+51 −10
Original line number Original line Diff line number Diff line
@@ -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);
+182 −21
Original line number Original line Diff line number Diff line
@@ -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,6 +1774,7 @@ 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;


@@ -1715,11 +1783,24 @@ public class AccountManagerService
        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(
@@ -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 −0

File added.

Preview size limit exceeded, changes collapsed.