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