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