1 /* 2 * Copyright (C) 2009 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.AbstractAccountAuthenticator; 20 import android.accounts.Account; 21 import android.accounts.AccountAuthenticatorResponse; 22 import android.accounts.AccountManager; 23 import android.accounts.NetworkErrorException; 24 import android.accounts.cts.common.Fixtures; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.os.Bundle; 28 import android.util.Log; 29 30 import java.util.ArrayList; 31 import java.util.concurrent.atomic.AtomicBoolean; 32 import java.util.concurrent.atomic.AtomicInteger; 33 34 /** 35 * A simple Mock Account Authenticator 36 */ 37 public class MockAccountAuthenticator extends AbstractAccountAuthenticator { 38 private static String TAG = "AccountManagerTest"; 39 40 public static String KEY_ACCOUNT_INFO = "key_account_info"; 41 public static String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "key_account_authenticator_response"; 42 public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API = "call new removeAccount api"; 43 public static String ACCOUNT_NAME_FOR_DEFAULT_IMPL = "call super api"; 44 // Key for triggering return intent flow 45 public static String KEY_RETURN_INTENT = "return an intent"; 46 public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API1 = "call new removeAccount api"; 47 48 private final Context mContext; 49 private final AtomicInteger mTokenCounter = new AtomicInteger(0); 50 private final AtomicBoolean mIsRecentlyCalled = new AtomicBoolean(false); 51 52 AccountAuthenticatorResponse mResponse; 53 String mAccountType; 54 String mAuthTokenType; 55 String[] mRequiredFeatures; 56 public Bundle mOptionsUpdateCredentials; 57 public Bundle mOptionsConfirmCredentials; 58 public Bundle mOptionsAddAccount; 59 public Bundle mOptionsGetAuthToken; 60 public Bundle mOptionsStartAddAccountSession; 61 public Bundle mOptionsStartUpdateCredentialsSession; 62 public Bundle mOptionsFinishSession; 63 Account mAccount; 64 String[] mFeatures; 65 String mStatusToken; 66 67 final ArrayList<String> mockFeatureList = new ArrayList<String>(); 68 private final long mTokenDurationMillis = 1000; // 1 second 69 MockAccountAuthenticator(Context context)70 public MockAccountAuthenticator(Context context) { 71 super(context); 72 mContext = context; 73 74 // Create some mock features 75 mockFeatureList.add(AccountManagerTest.FEATURE_1); 76 mockFeatureList.add(AccountManagerTest.FEATURE_2); 77 } 78 getTokenDurationMillis()79 public long getTokenDurationMillis() { 80 return mTokenDurationMillis; 81 } 82 isRecentlyCalled()83 public boolean isRecentlyCalled() { 84 return mIsRecentlyCalled.getAndSet(false); 85 } 86 getLastTokenServed()87 public String getLastTokenServed() { 88 return Integer.toString(mTokenCounter.get()); 89 } 90 getResponse()91 public AccountAuthenticatorResponse getResponse() { 92 return mResponse; 93 } 94 getAccountType()95 public String getAccountType() { 96 return mAccountType; 97 } 98 getAuthTokenType()99 public String getAuthTokenType() { 100 return mAuthTokenType; 101 } 102 getRequiredFeatures()103 public String[] getRequiredFeatures() { 104 return mRequiredFeatures; 105 } 106 getAccount()107 public Account getAccount() { 108 return mAccount; 109 } 110 getFeatures()111 public String[] getFeatures() { 112 return mFeatures; 113 } 114 getStatusToken()115 public String getStatusToken() { 116 return mStatusToken; 117 } 118 clearData()119 public void clearData() { 120 mResponse = null; 121 mAccountType = null; 122 mAuthTokenType = null; 123 mRequiredFeatures = null; 124 mOptionsUpdateCredentials = null; 125 mOptionsAddAccount = null; 126 mOptionsGetAuthToken = null; 127 mOptionsConfirmCredentials = null; 128 mOptionsStartAddAccountSession = null; 129 mOptionsStartUpdateCredentialsSession = null; 130 mOptionsFinishSession = null; 131 mAccount = null; 132 mFeatures = null; 133 mStatusToken = null; 134 } 135 callAccountAuthenticated()136 public void callAccountAuthenticated() { 137 AccountManager am = AccountManager.get(mContext); 138 am.notifyAccountAuthenticated(mAccount); 139 } 140 callSetPassword()141 public void callSetPassword() { 142 AccountManager am = AccountManager.get(mContext); 143 am.setPassword(mAccount, "password"); 144 } 145 createResultBundle()146 private Bundle createResultBundle() { 147 Bundle result = new Bundle(); 148 result.putString(AccountManager.KEY_ACCOUNT_NAME, AccountManagerTest.ACCOUNT_NAME); 149 result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTest.ACCOUNT_TYPE); 150 result.putString( 151 AccountManager.KEY_AUTHTOKEN, 152 Integer.toString(mTokenCounter.incrementAndGet())); 153 return result; 154 } 155 156 /** 157 * Adds an account of the specified accountType. 158 */ 159 @Override addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)160 public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 161 String authTokenType, String[] requiredFeatures, Bundle options) 162 throws NetworkErrorException { 163 Log.i(TAG, "MockAuth - addAccount"); 164 this.mResponse = response; 165 this.mAccountType = accountType; 166 this.mAuthTokenType = authTokenType; 167 this.mRequiredFeatures = requiredFeatures; 168 this.mOptionsAddAccount = options; 169 AccountManager am = AccountManager.get(mContext); 170 am.addAccountExplicitly(AccountManagerTest.ACCOUNT, "fakePassword", null); 171 return createResultBundle(); 172 } 173 174 /** 175 * Update the locally stored credentials for an account. 176 */ 177 @Override updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)178 public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, 179 String authTokenType, Bundle options) throws NetworkErrorException { 180 this.mResponse = response; 181 this.mAccount = account; 182 this.mAuthTokenType = authTokenType; 183 this.mOptionsUpdateCredentials = options; 184 return createResultBundle(); 185 } 186 187 /** 188 * Returns a Bundle that contains the Intent of the activity that can be used to edit the 189 * properties. In order to indicate success the activity should call response.setResult() 190 * with a non-null Bundle. 191 */ 192 @Override editProperties(AccountAuthenticatorResponse response, String accountType)193 public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { 194 this.mResponse = response; 195 this.mAccountType = accountType; 196 return createResultBundle(); 197 } 198 199 /** 200 * Checks that the user knows the credentials of an account. 201 */ 202 @Override confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)203 public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, 204 Bundle options) throws NetworkErrorException { 205 this.mResponse = response; 206 this.mAccount = account; 207 this.mOptionsConfirmCredentials = options; 208 Bundle result = new Bundle(); 209 if (options.containsKey(KEY_RETURN_INTENT)) { 210 Intent intent = new Intent(); 211 intent.setClassName("android.accounts.cts", "android.accounts.cts.AccountDummyActivity"); 212 result.putParcelable(AccountManager.KEY_INTENT, intent); 213 } else { 214 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 215 } 216 217 return result; 218 } 219 220 /** 221 * Gets the authtoken for an account. 222 */ 223 @Override getAuthToken( AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)224 public Bundle getAuthToken( 225 AccountAuthenticatorResponse response, 226 Account account, 227 String authTokenType, 228 Bundle options) throws NetworkErrorException { 229 Log.i(TAG, "MockAuth - getAuthToken@" + System.currentTimeMillis()); 230 mIsRecentlyCalled.set(true); 231 this.mResponse = response; 232 this.mAccount = account; 233 this.mAuthTokenType = authTokenType; 234 this.mOptionsGetAuthToken = options; 235 Bundle result = new Bundle(); 236 237 result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); 238 result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); 239 String token; 240 if (AccountManagerTest.AUTH_EXPIRING_TOKEN_TYPE.equals(authTokenType)) { 241 /* 242 * The resultant token should simply be the expiration timestamp. E.g. the time after 243 * which getting a new AUTH_EXPIRING_TOKEN_TYPE typed token will return a different 244 * value. 245 */ 246 long expiry = System.currentTimeMillis() + mTokenDurationMillis; 247 result.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, expiry); 248 } 249 result.putString( 250 AccountManager.KEY_AUTHTOKEN, 251 Integer.toString(mTokenCounter.incrementAndGet())); 252 return result; 253 } 254 255 /** 256 * Ask the authenticator for a localized label for the given authTokenType. 257 */ 258 @Override getAuthTokenLabel(String authTokenType)259 public String getAuthTokenLabel(String authTokenType) { 260 this.mAuthTokenType = authTokenType; 261 return AccountManagerTest.AUTH_TOKEN_LABEL; 262 } 263 264 /** 265 * Checks if the account supports all the specified authenticator specific features. 266 */ 267 @Override hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)268 public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, 269 String[] features) throws NetworkErrorException { 270 Log.i(TAG, "MockAuth - hasFeatures"); 271 this.mResponse = response; 272 this.mAccount = account; 273 this.mFeatures = features; 274 275 Bundle result = new Bundle(); 276 if (null == features) { 277 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 278 } 279 else { 280 boolean booleanResult = true; 281 for (String feature: features) { 282 if (!mockFeatureList.contains(feature)) { 283 booleanResult = false; 284 break; 285 } 286 } 287 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, booleanResult); 288 } 289 return result; 290 } 291 292 @Override getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account)293 public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, 294 Account account) throws NetworkErrorException { 295 Log.i(TAG, "MockAuth - getAccountRemovalAllowed"); 296 final Bundle result = new Bundle(); 297 if (ACCOUNT_NAME_FOR_NEW_REMOVE_API.equals(account.name)) { 298 Intent intent = AccountRemovalDummyActivity.createIntent(mContext); 299 // Pass in the authenticator response, so that account removal can 300 // be 301 // completed 302 intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); 303 intent.putExtra(KEY_ACCOUNT_INFO, account); 304 result.putParcelable(AccountManager.KEY_INTENT, intent); 305 // Adding this following line to reject account installation 306 // requests 307 // coming from old removeAccount API. 308 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 309 } else if (ACCOUNT_NAME_FOR_DEFAULT_IMPL.equals(account.name)) { 310 return super.getAccountRemovalAllowed(response, account); 311 } else { 312 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 313 } 314 return result; 315 } 316 317 @Override addAccountFromCredentials(final AccountAuthenticatorResponse response, Account account, Bundle accountCredentials)318 public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response, 319 Account account, 320 Bundle accountCredentials) throws NetworkErrorException { 321 return super.addAccountFromCredentials(response, account, accountCredentials); 322 } 323 324 @Override getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, final Account account)325 public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, 326 final Account account) throws NetworkErrorException { 327 return super.getAccountCredentialsForCloning(response, account); 328 } 329 330 331 /** 332 * Start add account flow of the specified accountType to authenticate user. 333 */ 334 @Override startAddAccountSession(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)335 public Bundle startAddAccountSession(AccountAuthenticatorResponse response, 336 String accountType, 337 String authTokenType, 338 String[] requiredFeatures, 339 Bundle options) throws NetworkErrorException { 340 this.mResponse = response; 341 this.mAccountType = accountType; 342 this.mAuthTokenType = authTokenType; 343 this.mRequiredFeatures = requiredFeatures; 344 this.mOptionsStartAddAccountSession = options; 345 346 String accountName = null; 347 Bundle sessionBundle = null; 348 if (options != null) { 349 accountName = options.getString(Fixtures.KEY_ACCOUNT_NAME); 350 sessionBundle = options.getBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE); 351 } 352 353 Bundle result = new Bundle(); 354 if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 355 // fill bundle with a success result. 356 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 357 result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 358 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 359 result.putString(AccountManager.KEY_PASSWORD, AccountManagerTest.ACCOUNT_PASSWORD); 360 result.putString(AccountManager.KEY_AUTHTOKEN, 361 Integer.toString(mTokenCounter.incrementAndGet())); 362 } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) { 363 // Specify data to be returned by the eventual activity. 364 Intent eventualActivityResultData = new Intent(); 365 eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN, 366 Integer.toString(mTokenCounter.incrementAndGet())); 367 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 368 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 369 eventualActivityResultData.putExtra(AccountManager.KEY_PASSWORD, 370 AccountManagerTest.ACCOUNT_PASSWORD); 371 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, 372 sessionBundle); 373 // Fill result with Intent. 374 Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); 375 intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData); 376 intent.putExtra(Fixtures.KEY_CALLBACK, response); 377 378 result.putParcelable(AccountManager.KEY_INTENT, intent); 379 } else { 380 // fill with error 381 fillResultWithError(result, options); 382 } 383 return result; 384 } 385 386 /** 387 * Start update credentials flow to re-auth user without updating locally stored credentials 388 * for an account. 389 */ 390 @Override startUpdateCredentialsSession(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)391 public Bundle startUpdateCredentialsSession(AccountAuthenticatorResponse response, 392 Account account, 393 String authTokenType, 394 Bundle options) throws NetworkErrorException { 395 Log.i(TAG, "MockAuth - startUpdateCredentialsSession"); 396 mResponse = response; 397 mAccount = account; 398 mAuthTokenType = authTokenType; 399 mOptionsStartUpdateCredentialsSession = options; 400 401 String accountName = null; 402 Bundle sessionBundle = null; 403 if (options != null) { 404 accountName = options.getString(Fixtures.KEY_ACCOUNT_NAME); 405 sessionBundle = options.getBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE); 406 } 407 408 Bundle result = new Bundle(); 409 if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 410 // fill bundle with a success result. 411 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 412 result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 413 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 414 result.putString(AccountManager.KEY_PASSWORD, AccountManagerTest.ACCOUNT_PASSWORD); 415 result.putString(AccountManager.KEY_AUTHTOKEN, 416 Integer.toString(mTokenCounter.incrementAndGet())); 417 } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) { 418 // Specify data to be returned by the eventual activity. 419 Intent eventualActivityResultData = new Intent(); 420 eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN, 421 Integer.toString(mTokenCounter.incrementAndGet())); 422 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 423 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 424 eventualActivityResultData.putExtra(AccountManager.KEY_PASSWORD, 425 AccountManagerTest.ACCOUNT_PASSWORD); 426 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, 427 sessionBundle); 428 // Fill result with Intent. 429 Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); 430 intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData); 431 intent.putExtra(Fixtures.KEY_CALLBACK, response); 432 433 result.putParcelable(AccountManager.KEY_INTENT, intent); 434 } else { 435 // fill with error 436 fillResultWithError(result, options); 437 } 438 return result; 439 } 440 441 /** 442 * Finishes account session started by adding the account to device or updating the local 443 * credentials. 444 */ 445 @Override finishSession(AccountAuthenticatorResponse response, String accountType, Bundle sessionBundle)446 public Bundle finishSession(AccountAuthenticatorResponse response, 447 String accountType, 448 Bundle sessionBundle) throws NetworkErrorException { 449 this.mResponse = response; 450 this.mAccountType = accountType; 451 this.mOptionsFinishSession = sessionBundle; 452 453 String accountName = null; 454 if (sessionBundle != null) { 455 accountName = sessionBundle.getString(Fixtures.KEY_ACCOUNT_NAME); 456 } 457 458 Bundle result = new Bundle(); 459 if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 460 // fill bundle with a success result. 461 result.putString(AccountManager.KEY_ACCOUNT_NAME, AccountManagerTest.ACCOUNT_NAME); 462 result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTest.ACCOUNT_TYPE); 463 result.putString(AccountManager.KEY_AUTHTOKEN, 464 Integer.toString(mTokenCounter.incrementAndGet())); 465 } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) { 466 // Specify data to be returned by the eventual activity. 467 Intent eventualActivityResultData = new Intent(); 468 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, 469 AccountManagerTest.ACCOUNT_NAME); 470 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, 471 AccountManagerTest.ACCOUNT_TYPE); 472 eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN, 473 Integer.toString(mTokenCounter.incrementAndGet())); 474 475 // Fill result with Intent. 476 Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); 477 intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData); 478 intent.putExtra(Fixtures.KEY_CALLBACK, response); 479 480 result.putParcelable(AccountManager.KEY_INTENT, intent); 481 } else { 482 // fill with error 483 fillResultWithError(result, sessionBundle); 484 } 485 return result; 486 } 487 fillResultWithError(Bundle result, Bundle options)488 private void fillResultWithError(Bundle result, Bundle options) { 489 int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE; 490 String errorMsg = "Default Error Message"; 491 if (options != null) { 492 errorCode = options.getInt(AccountManager.KEY_ERROR_CODE); 493 errorMsg = options.getString(AccountManager.KEY_ERROR_MESSAGE); 494 } 495 result.putInt(AccountManager.KEY_ERROR_CODE, errorCode); 496 result.putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); 497 } 498 499 /** 500 * Checks if the credentials of the account should be updated. 501 */ 502 @Override isCredentialsUpdateSuggested( final AccountAuthenticatorResponse response, Account account, String statusToken)503 public Bundle isCredentialsUpdateSuggested( 504 final AccountAuthenticatorResponse response, 505 Account account, 506 String statusToken) throws NetworkErrorException { 507 this.mResponse = response; 508 this.mAccount = account; 509 this.mStatusToken = statusToken; 510 511 Bundle result = new Bundle(); 512 if (account.name.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 513 // fill bundle with a success result. 514 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 515 } else { 516 // fill with error 517 int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE; 518 String errorMsg = "Default Error Message"; 519 result.putInt(AccountManager.KEY_ERROR_CODE, errorCode); 520 result.putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); 521 } 522 523 response.onResult(result); 524 return null; 525 } 526 } 527