summaryrefslogtreecommitdiffstats
path: root/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java')
-rw-r--r--mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java362
1 files changed, 0 insertions, 362 deletions
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java
deleted file mode 100644
index 3f2c5620d..000000000
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AccountPickler.java
+++ /dev/null
@@ -1,362 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.fxa.authenticator;
-
-import java.io.FileOutputStream;
-import java.io.PrintStream;
-import java.security.NoSuchAlgorithmException;
-import java.security.spec.InvalidKeySpecException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.StateFactory;
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-import org.mozilla.gecko.sync.NonObjectJSONException;
-import org.mozilla.gecko.sync.Utils;
-
-import android.content.Context;
-
-/**
- * Android deletes Account objects when the Authenticator that owns the Account
- * disappears. This happens when an App is installed to the SD card and the SD
- * card is un-mounted or the device is rebooted.
- * <p>
- * We work around this by pickling the current Firefox account data every sync
- * and unpickling when we check if Firefox accounts exist (called from Fennec).
- * <p>
- * Android just doesn't support installing Apps that define long-lived Services
- * and/or own Account types onto the SD card. The documentation says not to do
- * it. There are hordes of developers who want to do it, and have tried to
- * register for almost every "package installation changed" broadcast intent
- * that Android supports. They all explicitly state that the package that has
- * changed does *not* receive the broadcast intent, thereby preventing an App
- * from re-establishing its state.
- * <p>
- * <a href="http://developer.android.com/guide/topics/data/install-location.html">Reference.</a>
- * <p>
- * <b>Quote</b>: Your AbstractThreadedSyncAdapter and all its sync functionality
- * will not work until external storage is remounted.
- * <p>
- * <b>Quote</b>: Your running Service will be killed and will not be restarted
- * when external storage is remounted. You can, however, register for the
- * ACTION_EXTERNAL_APPLICATIONS_AVAILABLE broadcast Intent, which will notify
- * your application when applications installed on external storage have become
- * available to the system again. At which time, you can restart your Service.
- * <p>
- * Problem: <a href="http://code.google.com/p/android/issues/detail?id=8485">that intent doesn't work</a>!
- * <p>
- * See bug 768102 for more information in the context of Sync.
- */
-public class AccountPickler {
- public static final String LOG_TAG = AccountPickler.class.getSimpleName();
-
- public static final long PICKLE_VERSION = 3;
-
- public static final String KEY_PICKLE_VERSION = "pickle_version";
- public static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
-
- public static final String KEY_ACCOUNT_VERSION = "account_version";
- public static final String KEY_ACCOUNT_TYPE = "account_type";
- public static final String KEY_EMAIL = "email";
- public static final String KEY_PROFILE = "profile";
- public static final String KEY_IDP_SERVER_URI = "idpServerURI";
- public static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
- public static final String KEY_PROFILE_SERVER_URI = "profileServerURI";
-
- public static final String KEY_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP = "authoritiesToSyncAutomaticallyMap";
-
- // Deprecated, but maintained for migration purposes.
- public static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
-
- public static final String KEY_BUNDLE = "bundle";
-
- /**
- * Remove Firefox account persisted to disk.
- * This operation is synchronized to avoid race condition while deleting the account.
- *
- * @param context Android context.
- * @param filename name of persisted pickle file; must not contain path separators.
- * @return <code>true</code> if given pickle existed and was successfully deleted.
- */
- public synchronized static boolean deletePickle(final Context context, final String filename) {
- return context.deleteFile(filename);
- }
-
- public static ExtendedJSONObject toJSON(final AndroidFxAccount account, final long now) {
- final ExtendedJSONObject o = new ExtendedJSONObject();
- o.put(KEY_PICKLE_VERSION, PICKLE_VERSION);
- o.put(KEY_PICKLE_TIMESTAMP, now);
-
- o.put(KEY_ACCOUNT_VERSION, AndroidFxAccount.CURRENT_ACCOUNT_VERSION);
- o.put(KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
- o.put(KEY_EMAIL, account.getEmail());
- o.put(KEY_PROFILE, account.getProfile());
- o.put(KEY_IDP_SERVER_URI, account.getAccountServerURI());
- o.put(KEY_TOKEN_SERVER_URI, account.getTokenServerURI());
- o.put(KEY_PROFILE_SERVER_URI, account.getProfileServerURI());
-
- final ExtendedJSONObject p = new ExtendedJSONObject();
- for (Entry<String, Boolean> pair : account.getAuthoritiesToSyncAutomaticallyMap().entrySet()) {
- p.put(pair.getKey(), pair.getValue());
- }
- o.put(KEY_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP, p);
-
- // TODO: If prefs version changes under us, SyncPrefsPath will change, "clearing" prefs.
-
- final ExtendedJSONObject bundle = account.unbundle();
- if (bundle == null) {
- Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting.");
- return null;
- }
- o.put(KEY_BUNDLE, bundle);
-
- return o;
- }
-
- /**
- * Persist Firefox account to disk as a JSON object.
- * This operation is synchronized to avoid race condition while deleting the account.
- *
- * @param account the AndroidFxAccount to persist to disk
- * @param filename name of file to persist to; must not contain path separators.
- */
- public synchronized static void pickle(final AndroidFxAccount account, final String filename) {
- final ExtendedJSONObject o = toJSON(account, System.currentTimeMillis());
- writeToDisk(account.context, filename, o);
- }
-
- private static void writeToDisk(final Context context, final String filename,
- final ExtendedJSONObject pickle) {
- try {
- final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
- try {
- final PrintStream ps = new PrintStream(fos);
- try {
- ps.print(pickle.toJSONString());
- Logger.debug(LOG_TAG, "Persisted " + pickle.keySet().size() +
- " account settings to " + filename + ".");
- } finally {
- ps.close();
- }
- } finally {
- fos.close();
- }
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Caught exception persisting account settings to " + filename +
- "; ignoring.", e);
- }
- }
-
- /**
- * Create Android account from saved JSON object. Assumes that an account does not exist.
- * This operation is synchronized to avoid race condition while deleting the account.
- *
- * @param context
- * Android context.
- * @param filename
- * name of file to read from; must not contain path separators.
- * @return created Android account, or null on error.
- */
- public synchronized static AndroidFxAccount unpickle(final Context context, final String filename) {
- final String jsonString = Utils.readFile(context, filename);
- if (jsonString == null) {
- Logger.info(LOG_TAG, "Pickle file '" + filename + "' not found; aborting.");
- return null;
- }
-
- ExtendedJSONObject json = null;
- try {
- json = new ExtendedJSONObject(jsonString);
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception reading pickle file '" + filename + "'; aborting.", e);
- return null;
- }
-
- final UnpickleParams params;
- try {
- params = UnpickleParams.fromJSON(json);
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Got exception extracting unpickle json; aborting.", e);
- return null;
- }
-
- final AndroidFxAccount account;
- try {
- account = AndroidFxAccount.addAndroidAccount(context, params.email, params.profile,
- params.authServerURI, params.tokenServerURI, params.profileServerURI, params.state,
- params.authoritiesToSyncAutomaticallyMap,
- params.accountVersion,
- true, params.bundle);
- } catch (Exception e) {
- Logger.warn(LOG_TAG, "Exception when adding Android Account; aborting.", e);
- return null;
- }
-
- if (account == null) {
- Logger.warn(LOG_TAG, "Failed to add Android Account; aborting.");
- return null;
- }
-
- Long timestamp = json.getLong(KEY_PICKLE_TIMESTAMP);
- if (timestamp == null) {
- Logger.warn(LOG_TAG, "Did not find timestamp in pickle file; ignoring.");
- timestamp = -1L;
- }
-
- Logger.info(LOG_TAG, "Un-pickled Android account named " + params.email + " (version " +
- params.pickleVersion + ", pickled at " + timestamp + ").");
-
- return account;
- }
-
- private static class UnpickleParams {
- private Long pickleVersion;
-
- private int accountVersion;
- private String email;
- private String profile;
- private String authServerURI;
- private String tokenServerURI;
- private String profileServerURI;
- private final Map<String, Boolean> authoritiesToSyncAutomaticallyMap = new HashMap<>();
-
- private ExtendedJSONObject bundle;
- private State state;
-
- private UnpickleParams() {
- }
-
- private static UnpickleParams fromJSON(final ExtendedJSONObject json)
- throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
- final UnpickleParams params = new UnpickleParams();
- params.pickleVersion = json.getLong(KEY_PICKLE_VERSION);
- if (params.pickleVersion == null) {
- throw new IllegalStateException("Pickle version not found.");
- }
-
- /*
- * Version 1 and version 2 are identical, except version 2 throws if the
- * internal Android Account type has changed. Version 1 used to throw in
- * this case, but we intentionally used the pickle file to migrate across
- * Account types, bumping the version simultaneously.
- *
- * Version 3 replaces "isSyncEnabled" with a map (String -> Boolean)
- * associating Android authorities to whether or not they are configured
- * to sync automatically.
- */
- switch (params.pickleVersion.intValue()) {
- case 3: {
- // Sanity check.
- final String accountType = json.getString(KEY_ACCOUNT_TYPE);
- if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
- throw new IllegalStateException("Account type has changed from " + accountType + " to " + FxAccountConstants.ACCOUNT_TYPE + ".");
- }
-
- params.unpickleV3(json);
- }
- break;
-
- case 2: {
- // Sanity check.
- final String accountType = json.getString(KEY_ACCOUNT_TYPE);
- if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
- throw new IllegalStateException("Account type has changed from " + accountType + " to " + FxAccountConstants.ACCOUNT_TYPE + ".");
- }
-
- params.unpickleV1(json);
- }
- break;
-
- case 1: {
- // Warn about account type changing, but don't throw over it.
- final String accountType = json.getString(KEY_ACCOUNT_TYPE);
- if (!FxAccountConstants.ACCOUNT_TYPE.equals(accountType)) {
- Logger.warn(LOG_TAG, "Account type has changed from " + accountType + " to " + FxAccountConstants.ACCOUNT_TYPE + "; ignoring.");
- }
-
- params.unpickleV1(json);
- }
- break;
-
- default:
- throw new IllegalStateException("Unknown pickle version, " + params.pickleVersion + ".");
- }
-
- return params;
- }
-
- private void unpickleV1(final ExtendedJSONObject json)
- throws NonObjectJSONException, NoSuchAlgorithmException, InvalidKeySpecException {
-
- this.accountVersion = json.getIntegerSafely(KEY_ACCOUNT_VERSION);
- this.email = json.getString(KEY_EMAIL);
- this.profile = json.getString(KEY_PROFILE);
- this.authServerURI = json.getString(KEY_IDP_SERVER_URI);
- this.tokenServerURI = json.getString(KEY_TOKEN_SERVER_URI);
- this.profileServerURI = json.getString(KEY_PROFILE_SERVER_URI);
-
- // Fallback to default value when profile server URI was not pickled.
- if (this.profileServerURI == null) {
- this.profileServerURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(this.authServerURI)
- ? FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT
- : FxAccountConstants.STAGE_PROFILE_SERVER_ENDPOINT;
- }
-
- // We get the default value for everything except syncing browser data.
- this.authoritiesToSyncAutomaticallyMap.put(BrowserContract.AUTHORITY, json.getBoolean(KEY_IS_SYNCING_ENABLED));
-
- this.bundle = json.getObject(KEY_BUNDLE);
- if (bundle == null) {
- throw new IllegalStateException("Pickle bundle is null.");
- }
- this.state = getState(bundle);
- }
-
- private void unpickleV3(final ExtendedJSONObject json)
- throws NonObjectJSONException, NoSuchAlgorithmException, InvalidKeySpecException {
- // We'll overwrite the extracted sync automatically map.
- unpickleV1(json);
-
- // Extract the map of authorities to sync automatically.
- authoritiesToSyncAutomaticallyMap.clear();
- final ExtendedJSONObject o = json.getObject(KEY_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP);
- if (o == null) {
- return;
- }
- for (String key : o.keySet()) {
- final Boolean enabled = o.getBoolean(key);
- if (enabled != null) {
- authoritiesToSyncAutomaticallyMap.put(key, enabled);
- }
- }
- }
-
- private State getState(final ExtendedJSONObject bundle) throws InvalidKeySpecException,
- NonObjectJSONException, NoSuchAlgorithmException {
- // TODO: Should copy-pasta BUNDLE_KEY_STATE & LABEL to this file to ensure we maintain
- // old versions?
- final StateLabel stateLabelString = StateLabel.valueOf(
- bundle.getString(AndroidFxAccount.BUNDLE_KEY_STATE_LABEL));
- final String stateString = bundle.getString(AndroidFxAccount.BUNDLE_KEY_STATE);
- if (stateLabelString == null || stateString == null) {
- throw new IllegalStateException("stateLabel and stateString must not be null, but: " +
- "(stateLabel == null) = " + (stateLabelString == null) +
- " and (stateString == null) = " + (stateString == null));
- }
-
- try {
- return StateFactory.fromJSONObject(stateLabelString, new ExtendedJSONObject(stateString));
- } catch (Exception e) {
- throw new IllegalStateException("could not get state", e);
- }
- }
- }
-}