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 this.mResponse = response; 164 this.mAccountType = accountType; 165 this.mAuthTokenType = authTokenType; 166 this.mRequiredFeatures = requiredFeatures; 167 this.mOptionsAddAccount = options; 168 AccountManager am = AccountManager.get(mContext); 169 am.addAccountExplicitly(AccountManagerTest.ACCOUNT, "fakePassword", null); 170 return createResultBundle(); 171 } 172 173 /** 174 * Update the locally stored credentials for an account. 175 */ 176 @Override updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)177 public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, 178 String authTokenType, Bundle options) throws NetworkErrorException { 179 this.mResponse = response; 180 this.mAccount = account; 181 this.mAuthTokenType = authTokenType; 182 this.mOptionsUpdateCredentials = options; 183 return createResultBundle(); 184 } 185 186 /** 187 * Returns a Bundle that contains the Intent of the activity that can be used to edit the 188 * properties. In order to indicate success the activity should call response.setResult() 189 * with a non-null Bundle. 190 */ 191 @Override editProperties(AccountAuthenticatorResponse response, String accountType)192 public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) { 193 this.mResponse = response; 194 this.mAccountType = accountType; 195 return createResultBundle(); 196 } 197 198 /** 199 * Checks that the user knows the credentials of an account. 200 */ 201 @Override confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)202 public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, 203 Bundle options) throws NetworkErrorException { 204 this.mResponse = response; 205 this.mAccount = account; 206 this.mOptionsConfirmCredentials = options; 207 Bundle result = new Bundle(); 208 if (options.containsKey(KEY_RETURN_INTENT)) { 209 Intent intent = new Intent(); 210 intent.setClassName("android.accounts.cts", "android.accounts.cts.AccountDummyActivity"); 211 result.putParcelable(AccountManager.KEY_INTENT, intent); 212 } else { 213 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 214 } 215 216 return result; 217 } 218 219 /** 220 * Gets the authtoken for an account. 221 */ 222 @Override getAuthToken( AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)223 public Bundle getAuthToken( 224 AccountAuthenticatorResponse response, 225 Account account, 226 String authTokenType, 227 Bundle options) throws NetworkErrorException { 228 Log.w(TAG, "MockAuth - getAuthToken@" + System.currentTimeMillis()); 229 mIsRecentlyCalled.set(true); 230 this.mResponse = response; 231 this.mAccount = account; 232 this.mAuthTokenType = authTokenType; 233 this.mOptionsGetAuthToken = options; 234 Bundle result = new Bundle(); 235 236 result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); 237 result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); 238 String token; 239 if (AccountManagerTest.AUTH_EXPIRING_TOKEN_TYPE.equals(authTokenType)) { 240 /* 241 * The resultant token should simply be the expiration timestamp. E.g. the time after 242 * which getting a new AUTH_EXPIRING_TOKEN_TYPE typed token will return a different 243 * value. 244 */ 245 long expiry = System.currentTimeMillis() + mTokenDurationMillis; 246 result.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, expiry); 247 } 248 result.putString( 249 AccountManager.KEY_AUTHTOKEN, 250 Integer.toString(mTokenCounter.incrementAndGet())); 251 return result; 252 } 253 254 /** 255 * Ask the authenticator for a localized label for the given authTokenType. 256 */ 257 @Override getAuthTokenLabel(String authTokenType)258 public String getAuthTokenLabel(String authTokenType) { 259 this.mAuthTokenType = authTokenType; 260 return AccountManagerTest.AUTH_TOKEN_LABEL; 261 } 262 263 /** 264 * Checks if the account supports all the specified authenticator specific features. 265 */ 266 @Override hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)267 public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, 268 String[] features) throws NetworkErrorException { 269 270 this.mResponse = response; 271 this.mAccount = account; 272 this.mFeatures = features; 273 274 Bundle result = new Bundle(); 275 if (null == features) { 276 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 277 } 278 else { 279 boolean booleanResult = true; 280 for (String feature: features) { 281 if (!mockFeatureList.contains(feature)) { 282 booleanResult = false; 283 break; 284 } 285 } 286 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, booleanResult); 287 } 288 return result; 289 } 290 291 @Override getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account)292 public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, 293 Account account) throws NetworkErrorException { 294 final Bundle result = new Bundle(); 295 if (ACCOUNT_NAME_FOR_NEW_REMOVE_API.equals(account.name)) { 296 Intent intent = AccountRemovalDummyActivity.createIntent(mContext); 297 // Pass in the authenticator response, so that account removal can 298 // be 299 // completed 300 intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response); 301 intent.putExtra(KEY_ACCOUNT_INFO, account); 302 result.putParcelable(AccountManager.KEY_INTENT, intent); 303 // Adding this following line to reject account installation 304 // requests 305 // coming from old removeAccount API. 306 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 307 } else if (ACCOUNT_NAME_FOR_DEFAULT_IMPL.equals(account.name)) { 308 return super.getAccountRemovalAllowed(response, account); 309 } else { 310 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 311 } 312 return result; 313 } 314 315 @Override addAccountFromCredentials(final AccountAuthenticatorResponse response, Account account, Bundle accountCredentials)316 public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response, 317 Account account, 318 Bundle accountCredentials) throws NetworkErrorException { 319 return super.addAccountFromCredentials(response, account, accountCredentials); 320 } 321 322 @Override getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, final Account account)323 public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, 324 final Account account) throws NetworkErrorException { 325 return super.getAccountCredentialsForCloning(response, account); 326 } 327 328 329 /** 330 * Start add account flow of the specified accountType to authenticate user. 331 */ 332 @Override startAddAccountSession(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)333 public Bundle startAddAccountSession(AccountAuthenticatorResponse response, 334 String accountType, 335 String authTokenType, 336 String[] requiredFeatures, 337 Bundle options) throws NetworkErrorException { 338 this.mResponse = response; 339 this.mAccountType = accountType; 340 this.mAuthTokenType = authTokenType; 341 this.mRequiredFeatures = requiredFeatures; 342 this.mOptionsStartAddAccountSession = options; 343 344 String accountName = null; 345 Bundle sessionBundle = null; 346 if (options != null) { 347 accountName = options.getString(Fixtures.KEY_ACCOUNT_NAME); 348 sessionBundle = options.getBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE); 349 } 350 351 Bundle result = new Bundle(); 352 if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 353 // fill bundle with a success result. 354 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 355 result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 356 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 357 result.putString(AccountManager.KEY_PASSWORD, AccountManagerTest.ACCOUNT_PASSWORD); 358 result.putString(AccountManager.KEY_AUTHTOKEN, 359 Integer.toString(mTokenCounter.incrementAndGet())); 360 } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) { 361 // Specify data to be returned by the eventual activity. 362 Intent eventualActivityResultData = new Intent(); 363 eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN, 364 Integer.toString(mTokenCounter.incrementAndGet())); 365 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 366 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 367 eventualActivityResultData.putExtra(AccountManager.KEY_PASSWORD, 368 AccountManagerTest.ACCOUNT_PASSWORD); 369 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, 370 sessionBundle); 371 // Fill result with Intent. 372 Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); 373 intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData); 374 intent.putExtra(Fixtures.KEY_CALLBACK, response); 375 376 result.putParcelable(AccountManager.KEY_INTENT, intent); 377 } else { 378 // fill with error 379 fillResultWithError(result, options); 380 } 381 return result; 382 } 383 384 /** 385 * Start update credentials flow to re-auth user without updating locally stored credentials 386 * for an account. 387 */ 388 @Override startUpdateCredentialsSession(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)389 public Bundle startUpdateCredentialsSession(AccountAuthenticatorResponse response, 390 Account account, 391 String authTokenType, 392 Bundle options) throws NetworkErrorException { 393 mResponse = response; 394 mAccount = account; 395 mAuthTokenType = authTokenType; 396 mOptionsStartUpdateCredentialsSession = options; 397 398 String accountName = null; 399 Bundle sessionBundle = null; 400 if (options != null) { 401 accountName = options.getString(Fixtures.KEY_ACCOUNT_NAME); 402 sessionBundle = options.getBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE); 403 } 404 405 Bundle result = new Bundle(); 406 if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 407 // fill bundle with a success result. 408 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 409 result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 410 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 411 result.putString(AccountManager.KEY_PASSWORD, AccountManagerTest.ACCOUNT_PASSWORD); 412 result.putString(AccountManager.KEY_AUTHTOKEN, 413 Integer.toString(mTokenCounter.incrementAndGet())); 414 } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) { 415 // Specify data to be returned by the eventual activity. 416 Intent eventualActivityResultData = new Intent(); 417 eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN, 418 Integer.toString(mTokenCounter.incrementAndGet())); 419 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_STATUS_TOKEN, 420 AccountManagerTest.ACCOUNT_STATUS_TOKEN); 421 eventualActivityResultData.putExtra(AccountManager.KEY_PASSWORD, 422 AccountManagerTest.ACCOUNT_PASSWORD); 423 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, 424 sessionBundle); 425 // Fill result with Intent. 426 Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); 427 intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData); 428 intent.putExtra(Fixtures.KEY_CALLBACK, response); 429 430 result.putParcelable(AccountManager.KEY_INTENT, intent); 431 } else { 432 // fill with error 433 fillResultWithError(result, options); 434 } 435 return result; 436 } 437 438 /** 439 * Finishes account session started by adding the account to device or updating the local 440 * credentials. 441 */ 442 @Override finishSession(AccountAuthenticatorResponse response, String accountType, Bundle sessionBundle)443 public Bundle finishSession(AccountAuthenticatorResponse response, 444 String accountType, 445 Bundle sessionBundle) throws NetworkErrorException { 446 this.mResponse = response; 447 this.mAccountType = accountType; 448 this.mOptionsFinishSession = sessionBundle; 449 450 String accountName = null; 451 if (sessionBundle != null) { 452 accountName = sessionBundle.getString(Fixtures.KEY_ACCOUNT_NAME); 453 } 454 455 Bundle result = new Bundle(); 456 if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 457 // fill bundle with a success result. 458 result.putString(AccountManager.KEY_ACCOUNT_NAME, AccountManagerTest.ACCOUNT_NAME); 459 result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTest.ACCOUNT_TYPE); 460 result.putString(AccountManager.KEY_AUTHTOKEN, 461 Integer.toString(mTokenCounter.incrementAndGet())); 462 } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) { 463 // Specify data to be returned by the eventual activity. 464 Intent eventualActivityResultData = new Intent(); 465 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME, 466 AccountManagerTest.ACCOUNT_NAME); 467 eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE, 468 AccountManagerTest.ACCOUNT_TYPE); 469 eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN, 470 Integer.toString(mTokenCounter.incrementAndGet())); 471 472 // Fill result with Intent. 473 Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class); 474 intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData); 475 intent.putExtra(Fixtures.KEY_CALLBACK, response); 476 477 result.putParcelable(AccountManager.KEY_INTENT, intent); 478 } else { 479 // fill with error 480 fillResultWithError(result, sessionBundle); 481 } 482 return result; 483 } 484 fillResultWithError(Bundle result, Bundle options)485 private void fillResultWithError(Bundle result, Bundle options) { 486 int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE; 487 String errorMsg = "Default Error Message"; 488 if (options != null) { 489 errorCode = options.getInt(AccountManager.KEY_ERROR_CODE); 490 errorMsg = options.getString(AccountManager.KEY_ERROR_MESSAGE); 491 } 492 result.putInt(AccountManager.KEY_ERROR_CODE, errorCode); 493 result.putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); 494 } 495 496 /** 497 * Checks if the credentials of the account should be updated. 498 */ 499 @Override isCredentialsUpdateSuggested( final AccountAuthenticatorResponse response, Account account, String statusToken)500 public Bundle isCredentialsUpdateSuggested( 501 final AccountAuthenticatorResponse response, 502 Account account, 503 String statusToken) throws NetworkErrorException { 504 this.mResponse = response; 505 this.mAccount = account; 506 this.mStatusToken = statusToken; 507 508 Bundle result = new Bundle(); 509 if (account.name.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) { 510 // fill bundle with a success result. 511 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 512 } else { 513 // fill with error 514 int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE; 515 String errorMsg = "Default Error Message"; 516 result.putInt(AccountManager.KEY_ERROR_CODE, errorCode); 517 result.putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg); 518 } 519 520 response.onResult(result); 521 return null; 522 } 523 } 524