diff options
-rw-r--r-- | api/current.txt | 2 | ||||
-rw-r--r-- | api/system-current.txt | 2 | ||||
-rw-r--r-- | core/java/android/accounts/AccountManager.java | 50 | ||||
-rw-r--r-- | core/java/android/accounts/IAccountManager.aidl | 1 | ||||
-rw-r--r-- | services/core/java/com/android/server/accounts/AccountManagerService.java | 110 |
5 files changed, 144 insertions, 21 deletions
diff --git a/api/current.txt b/api/current.txt index c7898e4..afa2137 100644 --- a/api/current.txt +++ b/api/current.txt @@ -2719,6 +2719,7 @@ package android.accounts { } public class AccountManager { + method public boolean accountAuthenticated(android.accounts.Account); method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle); method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean); @@ -2779,6 +2780,7 @@ package android.accounts { field public static final java.lang.String KEY_ERROR_CODE = "errorCode"; field public static final java.lang.String KEY_ERROR_MESSAGE = "errorMessage"; field public static final java.lang.String KEY_INTENT = "intent"; + field public static final java.lang.String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = "lastAuthenticatedTimeMillisEpoch"; field public static final java.lang.String KEY_PASSWORD = "password"; field public static final java.lang.String KEY_USERDATA = "userdata"; field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED"; diff --git a/api/system-current.txt b/api/system-current.txt index f274a0d..bf3a5eb 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -2798,6 +2798,7 @@ package android.accounts { } public class AccountManager { + method public boolean accountAuthenticated(android.accounts.Account); method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler); method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle); method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean); @@ -2858,6 +2859,7 @@ package android.accounts { field public static final java.lang.String KEY_ERROR_CODE = "errorCode"; field public static final java.lang.String KEY_ERROR_MESSAGE = "errorMessage"; field public static final java.lang.String KEY_INTENT = "intent"; + field public static final java.lang.String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = "lastAuthenticatedTimeMillisEpoch"; field public static final java.lang.String KEY_PASSWORD = "password"; field public static final java.lang.String KEY_USERDATA = "userdata"; field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED"; diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 6957435..480d171 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -203,6 +203,14 @@ public class AccountManager { public static final String KEY_USERDATA = "userdata"; /** + * Bundle key used to supply the last time the credentials of the account + * were authenticated successfully. Time is specified in milliseconds since + * epoch. + */ + public static final String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = + "lastAuthenticatedTimeMillisEpoch"; + + /** * Authenticators using 'customTokens' option will also get the UID of the * caller */ @@ -663,6 +671,31 @@ public class AccountManager { } /** + * Informs the system that the account has been authenticated recently. This + * recency may be used by other applications to verify the account. This + * should be called only when the user has entered correct credentials for + * the account. + * <p> + * It is not safe to call this method from the main thread. As such, call it + * from another thread. + * <p> + * This method requires the caller to hold the permission + * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and should be + * called from the account's authenticator. + * + * @param account The {@link Account} to be updated. + */ + public boolean accountAuthenticated(Account account) { + if (account == null) + throw new IllegalArgumentException("account is null"); + try { + return mService.accountAuthenticated(account); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + /** * Rename the specified {@link Account}. This is equivalent to removing * the existing account and adding a new renamed account with the old * account's user data. @@ -1544,15 +1577,20 @@ public class AccountManager { * with these fields if activity or password was supplied and * the account was successfully verified: * <ul> - * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created + * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success * </ul> * * If no activity or password was specified, the returned Bundle contains - * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the - * password prompt. If an error occurred, - * {@link AccountManagerFuture#getResult()} throws: + * {@link #KEY_INTENT} with the {@link Intent} needed to launch the + * password prompt. + * + * <p>Also the returning Bundle may contain {@link + * #KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH} indicating the last time the + * credential was validated/created. + * + * If an error occurred,{@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if the authenticator failed to respond * <li> {@link OperationCanceledException} if the operation was canceled for @@ -1625,9 +1663,9 @@ public class AccountManager { * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account * </ul> * - * If no activity was specified, the returned Bundle contains only + * If no activity was specified, the returned Bundle contains * {@link #KEY_INTENT} with the {@link Intent} needed to launch the - * password prompt. If an error occurred, + * password prompt. If an error occurred, * {@link AccountManagerFuture#getResult()} throws: * <ul> * <li> {@link AuthenticatorException} if the authenticator failed to respond diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index aa41161..04b3c88 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -67,6 +67,7 @@ interface IAccountManager { boolean expectActivityLaunch); void confirmCredentialsAsUser(in IAccountManagerResponse response, in Account account, in Bundle options, boolean expectActivityLaunch, int userId); + boolean accountAuthenticated(in Account account); void getAuthTokenLabel(in IAccountManagerResponse response, String accountType, String authTokenType); diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 9339b35..90ef0a7 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -109,7 +109,7 @@ public class AccountManagerService private static final int TIMEOUT_DELAY_MS = 1000 * 60; private static final String DATABASE_NAME = "accounts.db"; - private static final int DATABASE_VERSION = 6; + private static final int DATABASE_VERSION = 7; private final Context mContext; @@ -131,6 +131,8 @@ public class AccountManagerService private static final String ACCOUNTS_TYPE_COUNT = "count(type)"; private static final String ACCOUNTS_PASSWORD = "password"; private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name"; + private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS = + "last_password_entry_time_millis_epoch"; private static final String TABLE_AUTHTOKENS = "authtokens"; private static final String AUTHTOKENS_ID = "_id"; @@ -697,7 +699,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(fromAccounts, response, account.type, false, - false /* stripAuthTokenFromResult */) { + false /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAccountCredentialsForClone" @@ -725,12 +728,43 @@ public class AccountManagerService } } + @Override + public boolean accountAuthenticated(final Account account) { + if (account == null) { + throw new IllegalArgumentException("account is null"); + } + checkAuthenticateAccountsPermission(account); + + final UserAccounts accounts = getUserAccountsForCaller(); + int userId = Binder.getCallingUserHandle().getIdentifier(); + if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) { + return false; + } + synchronized (accounts.cacheLock) { + final ContentValues values = new ContentValues(); + values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis()); + final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); + int i = db.update( + TABLE_ACCOUNTS, + values, + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", + new String[] { + account.name, account.type + }); + if (i > 0) { + return true; + } + } + return false; + } + private void completeCloningAccount(IAccountManagerResponse response, final Bundle accountCredentials, final Account account, final UserAccounts targetUser) { long id = clearCallingIdentity(); try { new Session(targetUser, response, account.type, false, - false /* stripAuthTokenFromResult */) { + false /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAccountCredentialsForClone" @@ -795,6 +829,7 @@ public class AccountManagerService values.put(ACCOUNTS_NAME, account.name); values.put(ACCOUNTS_TYPE, account.type); values.put(ACCOUNTS_PASSWORD, password); + values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis()); long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); if (accountId < 0) { Log.w(TAG, "insertAccountIntoDatabase: " + account @@ -885,7 +920,8 @@ public class AccountManagerService public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response, Account account, String[] features) { super(accounts, response, account.type, false /* expectActivityLaunch */, - true /* stripAuthTokenFromResult */); + true /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */); mFeatures = features; mAccount = account; } @@ -1184,7 +1220,8 @@ public class AccountManagerService public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, Account account, boolean expectActivityLaunch) { super(accounts, response, account.type, expectActivityLaunch, - true /* stripAuthTokenFromResult */); + true /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */); mAccount = account; } @@ -1419,6 +1456,13 @@ public class AccountManagerService try { final ContentValues values = new ContentValues(); values.put(ACCOUNTS_PASSWORD, password); + long time = 0; + // Only set current time, if it is a valid password. For clear password case, it + // should not be set. + if (password != null) { + time = System.currentTimeMillis(); + } + values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, time); final long accountId = getAccountIdLocked(db, account); if (accountId >= 0) { final String[] argsAccountId = {String.valueOf(accountId)}; @@ -1547,8 +1591,9 @@ public class AccountManagerService UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid)); long identityToken = clearCallingIdentity(); try { - new Session(accounts, response, accountType, false, - false /* stripAuthTokenFromResult */) { + new Session(accounts, response, accountType, false /* expectActivityLaunch */, + false /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAuthTokenLabel" @@ -1648,7 +1693,8 @@ public class AccountManagerService } new Session(accounts, response, account.type, expectActivityLaunch, - false /* stripAuthTokenFromResult */) { + false /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override protected String toDebugString(long now) { if (loginOptions != null) loginOptions.keySet(); @@ -1842,7 +1888,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, accountType, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, @@ -1917,7 +1964,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, accountType, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, @@ -1973,7 +2021,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, account.type, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, account.name, + true /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.confirmCredentials(this, account, options); @@ -2009,7 +2058,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, account.type, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, account.name, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); @@ -2045,7 +2095,8 @@ public class AccountManagerService long identityToken = clearCallingIdentity(); try { new Session(accounts, response, accountType, expectActivityLaunch, - true /* stripAuthTokenFromResult */) { + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */) { @Override public void run() throws RemoteException { mAuthenticator.editProperties(this, mAccountType); @@ -2071,7 +2122,8 @@ public class AccountManagerService public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, IAccountManagerResponse response, String type, String[] features, int callingUid) { super(accounts, response, type, false /* expectActivityLaunch */, - true /* stripAuthTokenFromResult */); + true /* stripAuthTokenFromResult */, null /* accountName */, + false /* authDetailsRequired */); mCallingUid = callingUid; mFeatures = features; } @@ -2437,6 +2489,9 @@ public class AccountManagerService final String mAccountType; final boolean mExpectActivityLaunch; final long mCreationTime; + final String mAccountName; + // Indicates if we need to add auth details(like last credential time) + final boolean mAuthDetailsRequired; public int mNumResults = 0; private int mNumRequestContinued = 0; @@ -2448,7 +2503,8 @@ public class AccountManagerService protected final UserAccounts mAccounts; public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType, - boolean expectActivityLaunch, boolean stripAuthTokenFromResult) { + boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName, + boolean authDetailsRequired) { super(); //if (response == null) throw new IllegalArgumentException("response is null"); if (accountType == null) throw new IllegalArgumentException("accountType is null"); @@ -2458,6 +2514,9 @@ public class AccountManagerService mAccountType = accountType; mExpectActivityLaunch = expectActivityLaunch; mCreationTime = SystemClock.elapsedRealtime(); + mAccountName = accountName; + mAuthDetailsRequired = authDetailsRequired; + synchronized (mSessions) { mSessions.put(toString(), this); } @@ -2592,6 +2651,16 @@ public class AccountManagerService public void onResult(Bundle result) { mNumResults++; Intent intent = null; + if (result != null && mAuthDetailsRequired) { + long lastAuthenticatedTime = DatabaseUtils.longForQuery( + mAccounts.openHelper.getReadableDatabase(), + "select " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " from " + + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND " + + ACCOUNTS_TYPE + "=?", + new String[]{mAccountName, mAccountType}); + result.putLong(AccountManager.KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH, + lastAuthenticatedTime); + } if (result != null && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { /* @@ -2798,6 +2867,7 @@ public class AccountManagerService + ACCOUNTS_TYPE + " TEXT NOT NULL, " + ACCOUNTS_PASSWORD + " TEXT, " + ACCOUNTS_PREVIOUS_NAME + " TEXT, " + + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, " + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " @@ -2833,6 +2903,11 @@ public class AccountManagerService + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); } + private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0"); + } + private void addOldAccountNameColumn(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME); } @@ -2892,6 +2967,11 @@ public class AccountManagerService oldVersion++; } + if (oldVersion == 6) { + addLastSuccessfullAuthenticatedTimeColumn(db); + oldVersion++; + } + if (oldVersion != newVersion) { Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion); } |