diff --git a/mobile/android/base/fxa/FxAccountConstants.java b/mobile/android/base/fxa/FxAccountConstants.java
index a13c18110883..e181cf3de37e 100644
--- a/mobile/android/base/fxa/FxAccountConstants.java
+++ b/mobile/android/base/fxa/FxAccountConstants.java
@@ -10,11 +10,16 @@ public class FxAccountConstants {
public static final String GLOBAL_LOG_TAG = "FxAccounts";
public static final String ACCOUNT_TYPE = AppConstants.MOZ_ANDROID_SHARED_FXACCOUNT_TYPE;
+ // Must be a client ID allocated with "canGrant" privileges!
+ public static final String OAUTH_CLIENT_ID_FENNEC = "3332a18d142636cb";
+
public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
+ public static final String DEFAULT_OAUTH_SERVER_ENDPOINT = "https://oauth.accounts.firefox.com/v1";
- public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://api-accounts.stage.mozaws.net/v1";
- public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
+ public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://stable.dev.lcip.org/auth/v1";
+ public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://stable.dev.lcip.org/syncserver/token/1.0/sync/1.5";
+ public static final String STAGE_OAUTH_SERVER_ENDPOINT = "https://oauth-stable.dev.lcip.org/v1";
// You must be at least 13 years old, on the day of creation, to create a Firefox Account.
public static final int MINIMUM_AGE_TO_CREATE_AN_ACCOUNT = 13;
diff --git a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
index 5d81822521ca..6f4d4bc71f41 100644
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -4,6 +4,13 @@
package org.mozilla.gecko.fxa.activities;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.R;
import org.mozilla.gecko.background.common.log.Logger;
@@ -17,12 +24,14 @@ import org.mozilla.gecko.fxa.login.Married;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
+import org.mozilla.gecko.reading.ReadingListConstants;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.util.HardwareUtils;
import android.accounts.Account;
+import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -38,13 +47,7 @@ import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen;
import android.text.TextUtils;
import android.text.format.DateUtils;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
+import android.widget.Toast;
/**
@@ -58,15 +61,16 @@ public class FxAccountStatusFragment
implements OnPreferenceClickListener, OnPreferenceChangeListener {
private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName();
- /**
- * If a device claims to have synced before this date, we will assume it has never synced.
- */
- private static final Date EARLIEST_VALID_SYNCED_DATE;
- static {
- final Calendar c = GregorianCalendar.getInstance();
- c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
- EARLIEST_VALID_SYNCED_DATE = c.getTime();
- }
+ /**
+ * If a device claims to have synced before this date, we will assume it has never synced.
+ */
+ private static final Date EARLIEST_VALID_SYNCED_DATE;
+ static {
+ final Calendar c = GregorianCalendar.getInstance();
+ c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
+ EARLIEST_VALID_SYNCED_DATE = c.getTime();
+ }
+
// When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
// before trying to sync. Should we kill off the fragment before the sync
// request happens, that's okay: the runnable will run if the UI thread is
@@ -84,6 +88,15 @@ public class FxAccountStatusFragment
// configured to use a custom Sync server. In debug mode, this is set.
private static boolean ALWAYS_SHOW_SYNC_SERVER = false;
+ // If the user clicks the email field this many times, the debug / personal
+ // information logging setting will toggle. The setting is not permanent: it
+ // lasts until this process is killed. We don't want to dump PII to the log
+ // for a long time!
+ private final int NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG =
+ // !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) || defined(MOZ_DEBUG)
+ (!AppConstants.MOZILLA_OFFICIAL || AppConstants.NIGHTLY_BUILD || AppConstants.DEBUG_BUILD) ? 5 : -1 /* infinite */;
+ private int debugClickCount = 0;
+
protected PreferenceCategory accountCategory;
protected Preference emailPreference;
protected Preference authServerPreference;
@@ -177,6 +190,8 @@ public class FxAccountStatusFragment
ALWAYS_SHOW_SYNC_SERVER = true;
}
+ emailPreference.setOnPreferenceClickListener(this);
+
needsPasswordPreference.setOnPreferenceClickListener(this);
needsVerificationPreference.setOnPreferenceClickListener(this);
needsFinishMigratingPreference.setOnPreferenceClickListener(this);
@@ -214,6 +229,17 @@ public class FxAccountStatusFragment
@Override
public boolean onPreferenceClick(Preference preference) {
+ if (preference == emailPreference) {
+ debugClickCount += 1;
+ if (NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG > 0 && debugClickCount >= NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG) {
+ debugClickCount = 0;
+ FxAccountUtils.LOG_PERSONAL_INFORMATION = !FxAccountUtils.LOG_PERSONAL_INFORMATION;
+ Toast.makeText(getActivity(), "Toggled logging Firefox Account personal information!", Toast.LENGTH_LONG).show();
+ hardRefresh(); // Display or hide debug options.
+ }
+ return true;
+ }
+
if (preference == needsPasswordPreference) {
Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
final Bundle extras = getExtrasForAccount();
@@ -764,6 +790,16 @@ public class FxAccountStatusFragment
Logger.info(LOG_TAG, "Force syncing.");
fxAccount.requestSync(FirefoxAccounts.FORCE);
// No sense refreshing, since the sync will complete in the future.
+ } else if ("debug_forget_reading_list_oauth_token".equals(key)) {
+ final Account account = fxAccount.getAndroidAccount();
+ final AccountManager accountManager = AccountManager.get(getActivity());
+ final String authToken = accountManager.peekAuthToken(account, ReadingListConstants.AUTH_TOKEN_TYPE);
+ if (authToken != null) {
+ Logger.info(LOG_TAG, "Forgetting reading list oauth token: " + authToken);
+ accountManager.invalidateAuthToken(account.type, authToken);
+ } else {
+ Logger.warn(LOG_TAG, "No reading list oauth token to forget!");
+ }
} else if ("debug_forget_certificate".equals(key)) {
State state = fxAccount.getState();
try {
@@ -775,6 +811,17 @@ public class FxAccountStatusFragment
Logger.info(LOG_TAG, "Not in Married state; can't forget certificate.");
// Ignore.
}
+ } else if ("debug_invalidate_certificate".equals(key)) {
+ State state = fxAccount.getState();
+ try {
+ Married married = (Married) state;
+ Logger.info(LOG_TAG, "Invalidating certificate.");
+ fxAccount.setState(married.makeCohabitingState().withCertificate("INVALID CERTIFICATE"));
+ refresh();
+ } catch (ClassCastException e) {
+ Logger.info(LOG_TAG, "Not in Married state; can't invalidate certificate.");
+ // Ignore.
+ }
} else if ("debug_require_password".equals(key)) {
Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password.");
State state = fxAccount.getState();
@@ -790,6 +837,14 @@ public class FxAccountStatusFragment
State state = fxAccount.getState();
fxAccount.setState(state.makeMigratedFromSync11State(null));
refresh();
+ } else if ("debug_make_account_stage".equals(key)) {
+ Logger.info(LOG_TAG, "Moving Account endpoints, in place, to stage. Deleting Sync and RL prefs and requiring password.");
+ fxAccount.unsafeTransitionToStageEndpoints();
+ refresh();
+ } else if ("debug_make_account_default".equals(key)) {
+ Logger.info(LOG_TAG, "Moving Account endpoints, in place, to default (production). Deleting Sync and RL prefs and requiring password.");
+ fxAccount.unsafeTransitionToDefaultEndpoints();
+ refresh();
} else {
return false;
}
@@ -807,20 +862,12 @@ public class FxAccountStatusFragment
// We don't want to use Android resource strings for debug UI, so we just
// use the keys throughout.
- final Preference debugCategory = ensureFindPreference("debug_category");
+ final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category");
debugCategory.setTitle(debugCategory.getKey());
- String[] debugKeys = new String[] {
- "debug_refresh",
- "debug_dump",
- "debug_force_sync",
- "debug_forget_certificate",
- "debug_require_password",
- "debug_require_upgrade",
- "debug_migrated_from_sync11" };
- for (String debugKey : debugKeys) {
- final Preference button = ensureFindPreference(debugKey);
- button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
+ for (int i = 0; i < debugCategory.getPreferenceCount(); i++) {
+ final Preference button = debugCategory.getPreference(i);
+ button.setTitle(button.getKey()); // Not very friendly, but this is for debugging only!
button.setOnPreferenceClickListener(listener);
}
}
diff --git a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
index af5c8bfb37bb..23ac389bf58a 100644
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
@@ -620,4 +620,35 @@ public class AndroidFxAccount {
return neverSynced;
}
}
+
+ // Debug only! This is dangerous!
+ public void unsafeTransitionToDefaultEndpoints() {
+ unsafeTransitionToStageEndpoints(
+ FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT,
+ FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
+ }
+
+ // Debug only! This is dangerous!
+ public void unsafeTransitionToStageEndpoints() {
+ unsafeTransitionToStageEndpoints(
+ FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT,
+ FxAccountConstants.STAGE_TOKEN_SERVER_ENDPOINT);
+ }
+
+ protected void unsafeTransitionToStageEndpoints(String authServerEndpoint, String tokenServerEndpoint) {
+ try {
+ getReadingListPrefs().edit().clear().commit();
+ } catch (UnsupportedEncodingException | GeneralSecurityException e) {
+ // Ignore.
+ }
+ try {
+ getSyncPrefs().edit().clear().commit();
+ } catch (UnsupportedEncodingException | GeneralSecurityException e) {
+ // Ignore.
+ }
+ State state = getState();
+ setState(state.makeSeparatedState());
+ accountManager.setUserData(account, ACCOUNT_KEY_IDP_SERVER, authServerEndpoint);
+ accountManager.setUserData(account, ACCOUNT_KEY_TOKEN_SERVER, tokenServerEndpoint);
+ }
}
diff --git a/mobile/android/base/fxa/login/Cohabiting.java b/mobile/android/base/fxa/login/Cohabiting.java
index 882af01b10da..dd3477a79e56 100644
--- a/mobile/android/base/fxa/login/Cohabiting.java
+++ b/mobile/android/base/fxa/login/Cohabiting.java
@@ -18,6 +18,10 @@ public class Cohabiting extends TokensAndKeysState {
super(StateLabel.Cohabiting, email, uid, sessionToken, kA, kB, keyPair);
}
+ public Married withCertificate(String certificate) {
+ return new Married(email, uid, sessionToken, kA, kB, keyPair, certificate);
+ }
+
@Override
public void execute(final ExecuteDelegate delegate) {
delegate.getClient().sign(sessionToken, keyPair.getPublic().toJSONObject(), delegate.getCertificateDurationInMilliseconds(),
@@ -39,7 +43,7 @@ public class Cohabiting extends TokensAndKeysState {
FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
}
}
- delegate.handleTransition(new LogMessage("sign succeeded"), new Married(email, uid, sessionToken, kA, kB, keyPair, certificate));
+ delegate.handleTransition(new LogMessage("sign succeeded"), withCertificate(certificate));
}
});
}
diff --git a/mobile/android/base/fxa/login/Married.java b/mobile/android/base/fxa/login/Married.java
index 02f8b7ad2dd3..7d882b5a0815 100644
--- a/mobile/android/base/fxa/login/Married.java
+++ b/mobile/android/base/fxa/login/Married.java
@@ -112,7 +112,7 @@ public class Married extends TokensAndKeysState {
return this.clientState;
}
- public State makeCohabitingState() {
+ public Cohabiting makeCohabitingState() {
return new Cohabiting(email, uid, sessionToken, kA, kB, keyPair);
}
}
diff --git a/mobile/android/base/reading/ReadingListConstants.java b/mobile/android/base/reading/ReadingListConstants.java
index bc52443a065b..b12dbaa97c45 100644
--- a/mobile/android/base/reading/ReadingListConstants.java
+++ b/mobile/android/base/reading/ReadingListConstants.java
@@ -12,7 +12,8 @@ public class ReadingListConstants {
public static final String DEFAULT_DEV_ENDPOINT = "https://readinglist.dev.mozaws.net/v1/";
public static final String DEFAULT_PROD_ENDPOINT = "https://readinglist.services.mozilla.com/v1/";
- public static final String OAUTH_ENDPOINT_PROD = "https://oauth.accounts.firefox.com/v1";
+ public static final String OAUTH_SCOPE_READINGLIST = "readinglist";
+ public static final String AUTH_TOKEN_TYPE = "oauth::" + OAUTH_SCOPE_READINGLIST;
public static boolean DEBUG = false;
}
diff --git a/mobile/android/base/reading/ReadingListSyncAdapter.java b/mobile/android/base/reading/ReadingListSyncAdapter.java
index d8bc001c4499..87be010335f5 100644
--- a/mobile/android/base/reading/ReadingListSyncAdapter.java
+++ b/mobile/android/base/reading/ReadingListSyncAdapter.java
@@ -25,6 +25,7 @@ import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10.Authorizati
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
+import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
@@ -48,8 +49,6 @@ import android.os.Bundle;
public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
public static final String PREF_LOCAL_NAME = "device.localname";
- public static final String OAUTH_CLIENT_ID_FENNEC = "3332a18d142636cb";
- public static final String OAUTH_SCOPE_READINGLIST = "readinglist";
private static final String LOG_TAG = ReadingListSyncAdapter.class.getSimpleName();
private static final long TIMEOUT_SECONDS = 60;
@@ -145,7 +144,7 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
return;
}
- final String oauthServerUri = ReadingListConstants.OAUTH_ENDPOINT_PROD;
+ final String oauthServerUri = FxAccountConstants.STAGE_OAUTH_SERVER_ENDPOINT;
final String authServerEndpoint = fxAccount.getAccountServerURI();
final String audience = FxAccountUtils.getAudienceForURL(oauthServerUri); // The assertion gets traded in for an oauth bearer token.
@@ -195,8 +194,8 @@ public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
JSONWebTokenUtils.dumpAssertion(assertion);
- final String clientID = OAUTH_CLIENT_ID_FENNEC;
- final String scope = OAUTH_SCOPE_READINGLIST;
+ final String clientID = FxAccountConstants.OAUTH_CLIENT_ID_FENNEC;
+ final String scope = ReadingListConstants.OAUTH_SCOPE_READINGLIST;
syncWithAssertion(clientID, scope, assertion, sharedPrefs, extras);
} catch (Exception e) {
syncDelegate.handleError(e);
diff --git a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
index 2d7c58a29fa2..7386d52f307f 100644
--- a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
@@ -126,10 +126,14 @@
+
+
-
+
+
+