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