1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.accounts.cts;
18 
19 import android.accounts.Account;
20 import android.accounts.AccountManager;
21 import android.accounts.AccountManagerFuture;
22 import android.accounts.AuthenticatorException;
23 import android.accounts.OperationCanceledException;
24 import android.accounts.cts.common.AuthenticatorContentProvider;
25 import android.accounts.cts.common.Fixtures;
26 import android.content.ContentProviderClient;
27 import android.content.ContentResolver;
28 import android.os.Bundle;
29 import android.os.RemoteException;
30 import android.platform.test.annotations.AppModeFull;
31 import android.test.AndroidTestCase;
32 
33 import java.io.IOException;
34 import java.util.HashMap;
35 
36 /**
37  * Tests for AccountManager and AbstractAccountAuthenticator related behavior using {@link
38  * android.accounts.cts.common.TestAccountAuthenticator} instances signed with different keys than
39  * the caller. This is important to test that portion of the {@link AccountManager} API intended
40  * for {@link android.accounts.AbstractAccountAuthenticator} implementers.
41  * <p>
42  * You can run those unit tests with the following command line:
43  * <p>
44  *  adb shell am instrument
45  *   -e debug false -w
46  *   -e class android.accounts.cts.AccountManagerUnaffiliatedAuthenticatorTests
47  * android.accounts.cts/androidx.test.runner.AndroidJUnitRunner
48  */
49 public class AccountManagerUnaffiliatedAuthenticatorTests extends AndroidTestCase {
50 
51     public static final Bundle SESSION_BUNDLE = new Bundle();
52     public static final String SESSION_DATA_NAME_1 = "session.data.name.1";
53     public static final String SESSION_DATA_VALUE_1 = "session.data.value.1";
54 
55     private AccountManager mAccountManager;
56     private ContentProviderClient mProviderClient;
57 
58     @Override
setUp()59     public void setUp() {
60         SESSION_BUNDLE.putString(SESSION_DATA_NAME_1, SESSION_DATA_VALUE_1);
61 
62         // bind to the diagnostic service and set it up.
63         mAccountManager = AccountManager.get(getContext());
64     }
65 
testNotifyAccountAuthenticated()66     public void testNotifyAccountAuthenticated() {
67         try {
68             mAccountManager.notifyAccountAuthenticated(
69                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS);
70             fail("Expected to just barf if the caller doesn't share a signature.");
71         } catch (SecurityException expected) {}
72     }
73 
testEditProperties()74     public void testEditProperties()  {
75         try {
76             mAccountManager.editProperties(
77                     Fixtures.TYPE_STANDARD_UNAFFILIATED,
78                     null, // activity
79                     null, // callback
80                     null); // handler
81             fail("Expecting a OperationCanceledException.");
82         } catch (SecurityException expected) {
83 
84         }
85     }
86 
testAddAccountExplicitly()87     public void testAddAccountExplicitly() {
88         try {
89             mAccountManager.addAccountExplicitly(
90                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
91                     "shouldn't matter", // password
92                     null); // bundle
93             fail("addAccountExplicitly should just barf if the caller isn't permitted.");
94         } catch (SecurityException expected) {}
95     }
96 
testRemoveAccount_withBooleanResult()97     public void testRemoveAccount_withBooleanResult() {
98         try {
99             mAccountManager.removeAccount(
100                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
101                     null,
102                     null);
103             fail("removeAccount should just barf if the caller isn't permitted.");
104         } catch (SecurityException expected) {}
105     }
106 
testRemoveAccount_withBundleResult()107     public void testRemoveAccount_withBundleResult() {
108         try {
109             mAccountManager.removeAccount(
110                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
111                     null, // Activity
112                     null,
113                     null);
114             fail("removeAccount should just barf if the caller isn't permitted.");
115         } catch (SecurityException expected) {}
116     }
117 
testRemoveAccountExplicitly()118     public void testRemoveAccountExplicitly() {
119         try {
120             mAccountManager.removeAccountExplicitly(
121                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS);
122             fail("removeAccountExplicitly should just barf if the caller isn't permitted.");
123         } catch (SecurityException expected) {}
124     }
125 
testGetPassword()126     public void testGetPassword() {
127         try {
128             mAccountManager.getPassword(
129                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS);
130             fail("getPassword should just barf if the caller isn't permitted.");
131         } catch (SecurityException expected) {}
132     }
133 
testSetPassword()134     public void testSetPassword() {
135         try {
136             mAccountManager.setPassword(
137                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
138                     "Doesn't matter");
139             fail("setPassword should just barf if the caller isn't permitted.");
140         } catch (SecurityException expected) {}
141     }
142 
testClearPassword()143     public void testClearPassword() {
144         try {
145             mAccountManager.clearPassword(
146                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS);
147             fail("clearPassword should just barf if the caller isn't permitted.");
148         } catch (SecurityException expected) {}
149     }
150 
testGetUserData()151     public void testGetUserData() {
152         try {
153             mAccountManager.getUserData(
154                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
155                     "key");
156             fail("getUserData should just barf if the caller isn't permitted.");
157         } catch (SecurityException expected) {}
158     }
159 
testSetUserData()160     public void testSetUserData() {
161         try {
162             mAccountManager.setUserData(
163                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
164                     "key",
165                     "value");
166             fail("setUserData should just barf if the caller isn't permitted.");
167         } catch (SecurityException expected) {}
168     }
169 
setAuthToken()170     public void setAuthToken() {
171         try {
172             mAccountManager.setAuthToken(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS, "tokenType",
173                     "token");
174             fail("setAuthToken should just barf if the caller isn't permitted.");
175         } catch (SecurityException expected) {
176         }
177     }
178 
testPeekAuthToken()179     public void testPeekAuthToken() {
180         try {
181             mAccountManager.peekAuthToken(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
182                     "tokenType");
183             fail("peekAuthToken should just barf if the caller isn't permitted.");
184         } catch (SecurityException expected) {
185         }
186     }
187 
testSetAccountVisibility()188     public void testSetAccountVisibility()
189             throws IOException, AuthenticatorException, OperationCanceledException {
190         try {
191             mAccountManager.setAccountVisibility(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
192                     "some", AccountManager.VISIBILITY_VISIBLE);
193             fail("setAccountVisibility should just barf if the caller isn't permitted.");
194         } catch (SecurityException expected) {
195         }
196     }
197 
testGetAccountVisibility()198     public void testGetAccountVisibility()
199             throws IOException, AuthenticatorException, OperationCanceledException {
200         try {
201             mAccountManager.getAccountVisibility(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
202                     "some.example");
203             fail("getAccountVisibility should just barf if the caller isn't permitted.");
204         } catch (SecurityException expected) {
205         }
206     }
207 
testGetAccountsAndVisibilityForPackage()208     public void testGetAccountsAndVisibilityForPackage()
209             throws IOException, AuthenticatorException, OperationCanceledException {
210         try {
211             mAccountManager.getAccountsAndVisibilityForPackage("some.package",
212                     Fixtures.TYPE_STANDARD_UNAFFILIATED);
213             fail("getAccountsAndVisibilityForPackage should just barf if the caller isn't permitted.");
214         } catch (SecurityException expected) {
215         }
216     }
217 
testGetPackagesAndVisibilityForAccount()218     public void testGetPackagesAndVisibilityForAccount()
219             throws IOException, AuthenticatorException, OperationCanceledException {
220         try {
221             mAccountManager.getPackagesAndVisibilityForAccount(
222                     Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS);
223             fail("getRequestingUidsForType should just barf if the caller isn't permitted.");
224         } catch (SecurityException expected) {
225         }
226     }
227 
testAddAccountExplicitlyVisthVisibilityMap()228     public void testAddAccountExplicitlyVisthVisibilityMap()
229             throws IOException, AuthenticatorException, OperationCanceledException {
230         try {
231             mAccountManager.addAccountExplicitly(Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
232                     "shouldn't matter", // password
233                     null, // bundle
234                     new HashMap<String, Integer>()); // visibility;
235             fail("addAccountExplicitly should just barf if the caller isn't permitted.");
236         } catch (SecurityException expected) {
237         }
238     }
239 
testGetAccounts()240     public void testGetAccounts() {
241         Account[] accounts = mAccountManager.getAccounts();
242         assertEquals(0, accounts.length);
243     }
244 
testGetAccountsByType()245     public void testGetAccountsByType() {
246         Account[] accounts = mAccountManager.getAccountsByType(Fixtures.TYPE_STANDARD_UNAFFILIATED);
247         assertEquals(0, accounts.length);
248     }
249 
testGetAccountsByTypeAndFeatures()250     public void testGetAccountsByTypeAndFeatures()
251             throws OperationCanceledException, AuthenticatorException, IOException {
252         AccountManagerFuture<Account[]> future = mAccountManager.getAccountsByTypeAndFeatures(
253                 Fixtures.TYPE_STANDARD_UNAFFILIATED,
254                 new String[] { "doesn't matter" },
255                 null,  // Callback
256                 null);  // Handler
257         Account[] accounts = future.getResult();
258         assertEquals(0, accounts.length);
259     }
260 
testGetAccountsByTypeForPackage()261     public void testGetAccountsByTypeForPackage() {
262         Account[] accounts = mAccountManager.getAccountsByTypeForPackage(
263                 Fixtures.TYPE_STANDARD_UNAFFILIATED,
264                 getContext().getPackageName());
265         assertEquals(0, accounts.length);
266     }
267 
268     /**
269      * Tests startAddAccountSession when calling package doesn't have the same sig as the
270      * authenticator.
271      * An encrypted session bundle should always be returned without password.
272      */
273     // TODO: Either allow instant app to expose content provider, or move the content provider
274     // out of the test app.
275     @AppModeFull
testStartAddAccountSession()276     public void testStartAddAccountSession() throws
277             OperationCanceledException, AuthenticatorException, IOException, RemoteException {
278         setupAccounts();
279 
280         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
281         Bundle options = createOptionsWithAccountName(accountName);
282 
283         AccountManagerFuture<Bundle> future = mAccountManager.startAddAccountSession(
284                 Fixtures.TYPE_STANDARD_UNAFFILIATED,
285                 null /* authTokenType */,
286                 null /* requiredFeatures */,
287                 options,
288                 null /* activity */,
289                 null /* callback */,
290                 null /* handler */);
291 
292         Bundle result = future.getResult();
293         assertTrue(future.isDone());
294         assertNotNull(result);
295 
296         // Validate that auth token was stripped from result.
297         assertNull(result.get(AccountManager.KEY_AUTHTOKEN));
298 
299         // Validate returned data
300         validateSessionBundleAndPasswordAndStatusTokenResult(result);
301         resetAccounts();
302     }
303 
304     /**
305      * Tests startUpdateCredentialsSession when calling package doesn't have the same sig as
306      * the authenticator.
307      * An encrypted session bundle should always be returned without password.
308      */
309     // TODO: Either allow instant app to expose content provider, or move the content provider
310     // out of the test app.
311     @AppModeFull
testStartUpdateCredentialsSession()312     public void testStartUpdateCredentialsSession() throws
313             OperationCanceledException, AuthenticatorException, IOException, RemoteException {
314         setupAccounts();
315 
316         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
317         Bundle options = createOptionsWithAccountName(accountName);
318 
319         AccountManagerFuture<Bundle> future = mAccountManager.startUpdateCredentialsSession(
320                 Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
321                 null /* authTokenType */,
322                 options,
323                 null /* activity */,
324                 null /* callback */,
325                 null /* handler */);
326 
327         Bundle result = future.getResult();
328         assertTrue(future.isDone());
329         assertNotNull(result);
330         // Validate no auth token in result.
331         assertNull(result.get(AccountManager.KEY_AUTHTOKEN));
332 
333         // Validate returned data
334         validateSessionBundleAndPasswordAndStatusTokenResult(result);
335         resetAccounts();
336     }
337 
338     /**
339      * Tests finishSession default implementation with overridden startAddAccountSession
340      * implementation. AuthenticatorException is expected because default AbstractAuthenticator
341      * implementation cannot understand customized session bundle.
342      */
testDefaultFinishSessiontWithStartAddAccountSessionImpl()343     public void testDefaultFinishSessiontWithStartAddAccountSessionImpl()
344             throws OperationCanceledException, AuthenticatorException, IOException {
345         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
346         // Creates session bundle to be returned by custom implementation of
347         // startAddAccountSession of authenticator.
348         Bundle sessionBundle = new Bundle();
349         sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
350         sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE,
351                 Fixtures.TYPE_STANDARD_UNAFFILIATED);
352         Bundle options = new Bundle();
353         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
354         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
355 
356         // First get an encrypted session bundle from custom startAddAccountSession implementation.
357         AccountManagerFuture<Bundle> future = mAccountManager.startAddAccountSession(
358                 Fixtures.TYPE_STANDARD_UNAFFILIATED,
359                 null /* authTokenType */,
360                 null /* requiredFeatures */,
361                 options,
362                 null /* activity */,
363                 null /* callback */,
364                 null /* handler */);
365 
366         Bundle result = future.getResult();
367         assertTrue(future.isDone());
368         assertNotNull(result);
369 
370         Bundle decryptedBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
371         assertNotNull(decryptedBundle);
372 
373         try {
374             // Call default implementation of finishSession of authenticator
375             // with encrypted session bundle.
376             future = mAccountManager.finishSession(
377                     decryptedBundle,
378                     null /* activity */,
379                     null /* callback */,
380                     null /* handler */);
381             future.getResult();
382 
383             fail("Should have thrown AuthenticatorException if finishSession is not overridden.");
384         } catch (AuthenticatorException e) {
385         }
386     }
387 
388     /**
389      * Tests finishSession default implementation with overridden startUpdateCredentialsSession
390      * implementation. AuthenticatorException is expected because default implementation cannot
391      * understand custom session bundle.
392      */
testDefaultFinishSessionWithCustomStartUpdateCredentialsSessionImpl()393     public void testDefaultFinishSessionWithCustomStartUpdateCredentialsSessionImpl()
394             throws OperationCanceledException, AuthenticatorException, IOException {
395         String accountName = Fixtures.PREFIX_NAME_SUCCESS + "@" + Fixtures.SUFFIX_NAME_FIXTURE;
396         // Creates session bundle to be returned by custom implementation of
397         // startUpdateCredentialsSession of authenticator.
398         Bundle sessionBundle = new Bundle();
399         sessionBundle.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
400         sessionBundle.putString(AccountManager.KEY_ACCOUNT_TYPE,
401                 Fixtures.TYPE_STANDARD_UNAFFILIATED);
402         Bundle options = new Bundle();
403         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
404         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
405 
406         // First get an encrypted session bundle from custom
407         // startUpdateCredentialsSession implementation.
408         AccountManagerFuture<Bundle> future = mAccountManager.startUpdateCredentialsSession(
409                 Fixtures.ACCOUNT_UNAFFILIATED_FIXTURE_SUCCESS,
410                 null /* authTokenType */,
411                 options,
412                 null /* activity */,
413                 null /* callback */,
414                 null /* handler */);
415 
416         Bundle result = future.getResult();
417         assertTrue(future.isDone());
418         assertNotNull(result);
419 
420         Bundle decryptedBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
421         assertNotNull(decryptedBundle);
422 
423         try {
424             // Call default implementation of finishSession of authenticator
425             // with encrypted session bundle.
426             future = mAccountManager.finishSession(
427                     decryptedBundle,
428                     null /* activity */,
429                     null /* callback */,
430                     null /* handler */);
431             future.getResult();
432 
433             fail("Should have thrown AuthenticatorException if finishSession is not overridden.");
434         } catch (AuthenticatorException e) {
435         }
436     }
437 
setupAccounts()438     private void setupAccounts() throws RemoteException {
439         ContentResolver resolver = getContext().getContentResolver();
440         mProviderClient = resolver.acquireContentProviderClient(
441                 AuthenticatorContentProvider.AUTHORITY);
442         /*
443          * This will install a bunch of accounts on the device
444          * (see Fixtures.getFixtureAccountNames()).
445          */
446         mProviderClient.call(AuthenticatorContentProvider.METHOD_SETUP, null, null);
447     }
448 
resetAccounts()449     private void resetAccounts() throws RemoteException {
450         try {
451             mProviderClient.call(AuthenticatorContentProvider.METHOD_TEARDOWN, null, null);
452         } finally {
453             mProviderClient.release();
454         }
455     }
456 
createOptionsWithAccountName(final String accountName)457     private Bundle createOptionsWithAccountName(final String accountName) {
458         Bundle options = new Bundle();
459         options.putString(Fixtures.KEY_ACCOUNT_NAME, accountName);
460         options.putBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE, SESSION_BUNDLE);
461         return options;
462     }
463 
validateSessionBundleAndPasswordAndStatusTokenResult(Bundle result)464     private void validateSessionBundleAndPasswordAndStatusTokenResult(Bundle result)
465         throws RemoteException {
466         Bundle sessionBundle = result.getBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE);
467         assertNotNull(sessionBundle);
468         // Assert that session bundle is encrypted and hence data not visible.
469         assertNull(sessionBundle.getString(SESSION_DATA_NAME_1));
470         // Validate that no password is returned in the result for unaffiliated package.
471         assertNull(result.getString(AccountManager.KEY_PASSWORD));
472         assertEquals(Fixtures.ACCOUNT_STATUS_TOKEN_UNAFFILIATED,
473                 result.getString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN));
474     }
475 }
476