Bug 1147473 - Expose Firefox Account debug information from Settings activity. r=rnewman

========

afcbbba32c
Author: Nick Alexander <nalexander@mozilla.com>
Date:   Tue Mar 24 23:24:27 2015 -0700

    Bug 1147473 - Part 2: Make it possible to toggle Firefox Accounts debug / PII logging at runtime.

    Tap the email address in the Firefox Account settings activity 5 times.
    Only available when:
    !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) || defined(MOZ_DEBUG)

========

261f912d12
Author: Nick Alexander <nalexander@mozilla.com>
Date:   Tue Mar 24 22:10:05 2015 -0700

    Bug 1147473 - Part 1: Add additional (dangerous!) debug commands.

    These make it easier to test oauth token authorization, especially using
    stage servers.

========

c1509baa77
Author: Nick Alexander <nalexander@mozilla.com>
Date:   Tue Mar 24 22:06:43 2015 -0700

    Bug 1142596 - Pre: Move constants around.

========

2c676500c3
Author: Nick Alexander <nalexander@mozilla.com>
Date:   Thu Mar 26 11:28:22 2015 -0700

    Bug 1142596 - Pre: Make it easier to construct intermediate states.
This commit is contained in:
Nick Alexander 2015-03-27 12:16:38 -07:00
parent 034aad3db7
commit 033e8220a8
8 changed files with 130 additions and 39 deletions

View File

@ -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;

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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));
}
});
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -126,10 +126,14 @@
<Preference android:key="debug_refresh" />
<Preference android:key="debug_dump" />
<Preference android:key="debug_force_sync" />
<Preference android:key="debug_invalidate_certificate" />
<Preference android:key="debug_forget_reading_list_oauth_token" />
<Preference android:key="debug_forget_certificate" />
<Preference android:key="debug_require_password" />
<Preference android:key="debug_require_upgrade" />
<Preference android:key="debug_migrated_from_sync11" />
</PreferenceCategory>
<Preference android:key="debug_make_account_stage" />
<Preference android:key="debug_make_account_default" />
</PreferenceCategory>
</PreferenceScreen>