| /* |
| * 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 android.accounts.cts; |
| |
| import android.accounts.Account; |
| import android.accounts.AccountManager; |
| import android.accounts.AccountManagerFuture; |
| import android.accounts.AuthenticatorException; |
| import android.accounts.OperationCanceledException; |
| import android.accounts.cts.common.AuthenticatorContentProvider; |
| import android.accounts.cts.common.Fixtures; |
| import android.accounts.cts.common.tx.StartAddAccountSessionTx; |
| import android.accounts.cts.common.tx.StartUpdateCredentialsSessionTx; |
| import android.content.ContentProviderClient; |
| import android.content.ContentResolver; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.platform.test.annotations.AppModeFull; |
| import android.test.AndroidTestCase; |
| |
| import java.io.IOException; |
| import java.util.HashMap; |
| |
| /** |
| * Tests for AccountManager and AbstractAccountAuthenticator related behavior using {@link |
| * android.accounts.cts.common.TestAccountAuthenticator} instances signed with different keys than |
| * the caller. This is important to test that portion of the {@link AccountManager} API intended |
| * for {@link android.accounts.AbstractAccountAuthenticator} implementers. |
| * <p> |
| * You can run those unit tests with the following command line: |
| * <p> |
| * adb shell am instrument |
| * -e debug false -w |
| * -e class android.accounts.cts.AccountManagerUnaffiliatedAuthenticatorTests |
| * android.accounts.cts/android.support.test.runner.AndroidJUnitRunner |
| */ |
| public class AccountManagerUnaffiliatedAuthenticatorTests extends AndroidTestCase { |
| |
| public static final Bundle SESSION_BUNDLE = new Bundle(); |
| public static final String SESSION_DATA_NAME_1 = "session.data.name.1"; |
| public static final String SESSION_DATA_VALUE_1 = "session.data.value.1"; |
| |
| private AccountManager mAccountManager; |
| private ContentProviderClient mProviderClient; |
| |
| @Override |
| public void setUp() { |
| SESSION_BUNDLE.putString(SESSION_DATA_NAME_1, SESSION_DATA_VALUE_1); |
| |
| // bind to the diagnostic service and set it up. |
| mAccountManager = AccountManager.get(getContext()); |
| } |
| |
| public void testNotifyAccountAuthenticated() { |
| try { |
| mAccountManager.notifyAccountAuthenticated( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS); |
| fail("Expected to just barf if the caller doesn't share a signature."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testEditProperties() { |
| try { |
| mAccountManager.editProperties( |
| Fixtures.TYPE_STANDARD_UNAFFILIATED, |
| null, // activity |
| null, // callback |
| null); // handler |
| fail("Expecting a OperationCanceledException."); |
| } catch (SecurityException expected) { |
| |
| } |
| } |
| |
| public void testAddAccountExplicitly() { |
| try { |
| mAccountManager.addAccountExplicitly( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "shouldn't matter", // password |
| null); // bundle |
| fail("addAccountExplicitly should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testRemoveAccount_withBooleanResult() { |
| try { |
| mAccountManager.removeAccount( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| null, |
| null); |
| fail("removeAccount should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testRemoveAccount_withBundleResult() { |
| try { |
| mAccountManager.removeAccount( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| null, // Activity |
| null, |
| null); |
| fail("removeAccount should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testRemoveAccountExplicitly() { |
| try { |
| mAccountManager.removeAccountExplicitly( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS); |
| fail("removeAccountExplicitly should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testGetPassword() { |
| try { |
| mAccountManager.getPassword( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS); |
| fail("getPassword should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testSetPassword() { |
| try { |
| mAccountManager.setPassword( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "Doesn't matter"); |
| fail("setPassword should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testClearPassword() { |
| try { |
| mAccountManager.clearPassword( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS); |
| fail("clearPassword should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testGetUserData() { |
| try { |
| mAccountManager.getUserData( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "key"); |
| fail("getUserData should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void testSetUserData() { |
| try { |
| mAccountManager.setUserData( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "key", |
| "value"); |
| fail("setUserData should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) {} |
| } |
| |
| public void setAuthToken() { |
| try { |
| mAccountManager.setAuthToken(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, "tokenType", |
| "token"); |
| fail("setAuthToken should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| public void testPeekAuthToken() { |
| try { |
| mAccountManager.peekAuthToken(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "tokenType"); |
| fail("peekAuthToken should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| public void testSetAccountVisibility() |
| throws IOException, AuthenticatorException, OperationCanceledException { |
| try { |
| mAccountManager.setAccountVisibility(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "some", AccountManager.VISIBILITY_VISIBLE); |
| fail("setAccountVisibility should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| public void testGetAccountVisibility() |
| throws IOException, AuthenticatorException, OperationCanceledException { |
| try { |
| mAccountManager.getAccountVisibility(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "some.example"); |
| fail("getAccountVisibility should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| public void testGetAccountsAndVisibilityForPackage() |
| throws IOException, AuthenticatorException, OperationCanceledException { |
| try { |
| mAccountManager.getAccountsAndVisibilityForPackage("some.package", |
| Fixtures.TYPE_STANDARD_UNAFFILIATED); |
| fail("getAccountsAndVisibilityForPackage should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| public void testGetPackagesAndVisibilityForAccount() |
| throws IOException, AuthenticatorException, OperationCanceledException { |
| try { |
| mAccountManager.getPackagesAndVisibilityForAccount( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS); |
| fail("getRequestingUidsForType should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| public void testAddAccountExplicitlyVisthVisibilityMap() |
| throws IOException, AuthenticatorException, OperationCanceledException { |
| try { |
| mAccountManager.addAccountExplicitly(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| "shouldn't matter", // password |
| null, // bundle |
| new HashMap<String, Integer>()); // visibility; |
| fail("addAccountExplicitly should just barf if the caller isn't permitted."); |
| } catch (SecurityException expected) { |
| } |
| } |
| |
| public void testGetAccounts() { |
| Account[] accounts = mAccountManager.getAccounts(); |
| assertEquals(0, accounts.length); |
| } |
| |
| public void testGetAccountsByType() { |
| Account[] accounts = mAccountManager.getAccountsByType(Fixtures.TYPE_STANDARD_UNAFFILIATED); |
| assertEquals(0, accounts.length); |
| } |
| |
| public void testGetAccountsByTypeAndFeatures() |
| throws OperationCanceledException, AuthenticatorException, IOException { |
| AccountManagerFuture<Account[]> future = mAccountManager.getAccountsByTypeAndFeatures( |
| Fixtures.TYPE_STANDARD_UNAFFILIATED, |
| new String[] { "doesn't matter" }, |
| null, // Callback |
| null); // Handler |
| Account[] accounts = future.getResult(); |
| assertEquals(0, accounts.length); |
| } |
| |
| public void testGetAccountsByTypeForPackage() { |
| Account[] accounts = mAccountManager.getAccountsByTypeForPackage( |
| Fixtures.TYPE_STANDARD_UNAFFILIATED, |
| getContext().getPackageName()); |
| assertEquals(0, accounts.length); |
| } |
| |
| /** |
| * Tests startAddAccountSession when calling package doesn't have the same sig as the |
| * authenticator. |
| * An encrypted session bundle should always be returned without password. |
| */ |
| // TODO: Either allow instant app to expose content provider, or move the content provider |
| // out of the test app. |
| @AppModeFull |
| public void testStartAddAccountSession() throws |
| OperationCanceledException, AuthenticatorException, IOException, RemoteException { |
| setupAccounts(); |
| |
| String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE; |
| Bundle options = createOptionsWithAccountName(accountName); |
| |
| AccountManagerFuture<Bundle> future = mAccountManager.startAddAccountSession( |
| Fixtures.TYPE_STANDARD_UNAFFILIATED, |
| null /* authTokenType */, |
| null /* requiredFeatures */, |
| options, |
| null /* activity */, |
| null /* callback */, |
| null /* handler */); |
| |
| Bundle result = future.getResult(); |
| assertTrue(future.isDone()); |
| assertNotNull(result); |
| |
| validateStartAddAccountSessionParameters(options); |
| |
| // Validate that auth token was stripped from result. |
| assertNull(result.get(AccountManager.KEY_AUTHTOKEN)); |
| |
| // Validate returned data |
| validateSessionBundleAndPasswordAndStatusTokenResult(result); |
| resetAccounts(); |
| } |
| |
| /** |
| * Tests startUpdateCredentialsSession when calling package doesn't have the same sig as |
| * the authenticator. |
| * An encrypted session bundle should always be returned without password. |
| */ |
| // TODO: Either allow instant app to expose content provider, or move the content provider |
| // out of the test app. |
| @AppModeFull |
| public void testStartUpdateCredentialsSession() throws |
| OperationCanceledException, AuthenticatorException, IOException, RemoteException { |
| setupAccounts(); |
| |
| String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE; |
| Bundle options = createOptionsWithAccountName(accountName); |
| |
| AccountManagerFuture<Bundle> future = mAccountManager.startUpdateCredentialsSession( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| null /* authTokenType */, |
| options, |
| null /* activity */, |
| null /* callback */, |
| null /* handler */); |
| |
| Bundle result = future.getResult(); |
| assertTrue(future.isDone()); |
| assertNotNull(result); |
| |
| validateStartUpdateCredentialsSessionParameters(options); |
| |
| // Validate no auth token in result. |
| assertNull(result.get(AccountManager.KEY_AUTHTOKEN)); |
| |
| // Validate returned data |
| validateSessionBundleAndPasswordAndStatusTokenResult(result); |
| resetAccounts(); |
| } |
| |
| /** |
| * Tests finishSession default implementation with overridden startAddAccountSession |
| * implementation. AuthenticatorException is expected because default AbstractAuthenticator |
| * implementation cannot understand customized session bundle. |
| */ |
| public void testDefaultFinishSessiontWithStartAddAccountSessionImpl() |
| throws OperationCanceledException, AuthenticatorException, IOException { |
| String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE; |
| // Creates session bundle to be returned by custom implementation of |
| // startAddAccountSession of authenticator. |
| Bundle sessionBundle = new Bundle(); |
| sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountName); |
| sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, |
| Fixtures.TYPE_STANDARD_UNAFFILIATED); |
| Bundle options = new Bundle(); |
| options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName); |
| options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); |
| |
| // First get an encrypted session bundle from custom startAddAccountSession implementation. |
| AccountManagerFuture<Bundle> future = mAccountManager.startAddAccountSession( |
| Fixtures.TYPE_STANDARD_UNAFFILIATED, |
| null /* authTokenType */, |
| null /* requiredFeatures */, |
| options, |
| null /* activity */, |
| null /* callback */, |
| null /* handler */); |
| |
| Bundle result = future.getResult(); |
| assertTrue(future.isDone()); |
| assertNotNull(result); |
| |
| Bundle decryptedBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE); |
| assertNotNull(decryptedBundle); |
| |
| try { |
| // Call default implementation of finishSession of authenticator |
| // with encrypted session bundle. |
| future = mAccountManager.finishSession( |
| decryptedBundle, |
| null /* activity */, |
| null /* callback */, |
| null /* handler */); |
| future.getResult(); |
| |
| fail("Should have thrown AuthenticatorException if finishSession is not overridden."); |
| } catch (AuthenticatorException e) { |
| } |
| } |
| |
| /** |
| * Tests finishSession default implementation with overridden startUpdateCredentialsSession |
| * implementation. AuthenticatorException is expected because default implementation cannot |
| * understand custom session bundle. |
| */ |
| public void testDefaultFinishSessionWithCustomStartUpdateCredentialsSessionImpl() |
| throws OperationCanceledException, AuthenticatorException, IOException { |
| String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE; |
| // Creates session bundle to be returned by custom implementation of |
| // startUpdateCredentialsSession of authenticator. |
| Bundle sessionBundle = new Bundle(); |
| sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountName); |
| sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE, |
| Fixtures.TYPE_STANDARD_UNAFFILIATED); |
| Bundle options = new Bundle(); |
| options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName); |
| options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); |
| |
| // First get an encrypted session bundle from custom |
| // startUpdateCredentialsSession implementation. |
| AccountManagerFuture<Bundle> future = mAccountManager.startUpdateCredentialsSession( |
| Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, |
| null /* authTokenType */, |
| options, |
| null /* activity */, |
| null /* callback */, |
| null /* handler */); |
| |
| Bundle result = future.getResult(); |
| assertTrue(future.isDone()); |
| assertNotNull(result); |
| |
| Bundle decryptedBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE); |
| assertNotNull(decryptedBundle); |
| |
| try { |
| // Call default implementation of finishSession of authenticator |
| // with encrypted session bundle. |
| future = mAccountManager.finishSession( |
| decryptedBundle, |
| null /* activity */, |
| null /* callback */, |
| null /* handler */); |
| future.getResult(); |
| |
| fail("Should have thrown AuthenticatorException if finishSession is not overridden."); |
| } catch (AuthenticatorException e) { |
| } |
| } |
| |
| private void setupAccounts() throws RemoteException { |
| ContentResolver resolver = getContext().getContentResolver(); |
| mProviderClient = resolver.acquireContentProviderClient( |
| AuthenticatorContentProvider.AUTHORITY); |
| /* |
| * This will install a bunch of accounts on the device |
| * (see Fixtures.getFixtureAccountNames()). |
| */ |
| mProviderClient.call(AuthenticatorContentProvider.METHOD_SETUP, null, null); |
| } |
| |
| private void resetAccounts() throws RemoteException { |
| try { |
| mProviderClient.call(AuthenticatorContentProvider.METHOD_TEARDOWN, null, null); |
| } finally { |
| mProviderClient.release(); |
| } |
| } |
| |
| private void validateStartAddAccountSessionParameters(Bundle inOpt) |
| throws RemoteException { |
| Bundle params = mProviderClient.call(AuthenticatorContentProvider.METHOD_GET, null, null); |
| params.setClassLoader(StartAddAccountSessionTx.class.getClassLoader()); |
| StartAddAccountSessionTx tx = params.<StartAddAccountSessionTx>getParcelable( |
| AuthenticatorContentProvider.KEY_TX); |
| assertEquals(tx.accountType, Fixtures.TYPE_STANDARD_UNAFFILIATED); |
| assertEquals(tx.options.getString(Fixtures.KEY_ACCOUNT_NAME), |
| inOpt.getString(Fixtures.KEY_ACCOUNT_NAME)); |
| } |
| |
| private void validateStartUpdateCredentialsSessionParameters(Bundle inOpt) |
| throws RemoteException { |
| Bundle params = mProviderClient.call(AuthenticatorContentProvider.METHOD_GET, null, null); |
| params.setClassLoader(StartUpdateCredentialsSessionTx.class.getClassLoader()); |
| StartUpdateCredentialsSessionTx tx = |
| params.<StartUpdateCredentialsSessionTx>getParcelable( |
| AuthenticatorContentProvider.KEY_TX); |
| assertEquals(tx.account, Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS); |
| assertEquals(tx.options.getString(Fixtures.KEY_ACCOUNT_NAME), |
| inOpt.getString(Fixtures.KEY_ACCOUNT_NAME)); |
| } |
| |
| private Bundle createOptionsWithAccountName(final String accountName) { |
| Bundle options = new Bundle(); |
| options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName); |
| options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, SESSION_BUNDLE); |
| return options; |
| } |
| |
| private void validateSessionBundleAndPasswordAndStatusTokenResult(Bundle result) |
| throws RemoteException { |
| Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE); |
| assertNotNull(sessionBundle); |
| // Assert that session bundle is encrypted and hence data not visible. |
| assertNull(sessionBundle.getString(SESSION_DATA_NAME_1)); |
| // Validate that no password is returned in the result for unaffiliated package. |
| assertNull(result.getString(AccountManager.KEY_PASSWORD)); |
| assertEquals(Fixtures.ACCOUNT_STATUS_TOKEN_UNAFFILIATED, |
| result.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN)); |
| } |
| } |