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