Bug 1346061 part 1 - Expose getSessionToken() on State. r?nalexander
We need to access sessionToken in the Engaged state in order to perform device
registration. We expose getSessionToken() on the base State class, to allow
customers to get the sessionToken easily instead of having to downcast the
TokensAndKeysState/Engaged states.
MozReview-Commit-ID: 8s2C350noUG
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
@@ -13,17 +13,16 @@ import android.util.Log;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountClient;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount.InvalidFxAState;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoBundle;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
@@ -118,45 +117,47 @@ public class FxAccountDeviceRegistrator
final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
intent.putExtra("org.mozilla.gecko.intent.PROFILE_NAME", fxAccount.getProfile());
return intent;
}
@Override
public void handleMessage(String event, GeckoBundle message, EventCallback callback) {
if ("FxAccountsPush:Subscribe:Response".equals(event)) {
- try {
- doFxaRegistration(message.getBundle("subscription"));
- } catch (InvalidFxAState e) {
- Log.d(LOG_TAG, "Invalid state when trying to register with FxA ", e);
- }
+ doFxaRegistration(message.getBundle("subscription"));
} else {
Log.e(LOG_TAG, "No action defined for " + event);
}
}
- private void doFxaRegistration(GeckoBundle subscription) throws InvalidFxAState {
+ private void doFxaRegistration(GeckoBundle subscription) {
final Context context = this.context.get();
if (this.context == null) {
throw new IllegalStateException("Application context has been gc'ed");
}
doFxaRegistration(context, subscription, true);
}
- private static void doFxaRegistration(final Context context, final GeckoBundle subscription, final boolean allowRecursion) throws InvalidFxAState {
+ private static void doFxaRegistration(final Context context, final GeckoBundle subscription, final boolean allowRecursion) {
String pushCallback = subscription.getString("pushCallback");
String pushPublicKey = subscription.getString("pushPublicKey");
String pushAuthKey = subscription.getString("pushAuthKey");
final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
if (fxAccount == null) {
Log.e(LOG_TAG, "AndroidFxAccount is null");
return;
}
- final byte[] sessionToken = fxAccount.getSessionToken();
+ final byte[] sessionToken;
+ try {
+ sessionToken = fxAccount.getState().getSessionToken();
+ } catch (State.NotASessionTokenState e) {
+ Log.e(LOG_TAG, "Could not get a session token", e);
+ return;
+ }
final FxAccountDevice device;
String deviceId = fxAccount.getDeviceId();
String clientName = getClientName(fxAccount, context);
if (TextUtils.isEmpty(deviceId)) {
Log.i(LOG_TAG, "Attempting registration for a new device");
device = FxAccountDevice.forRegister(clientName, "mobile", pushCallback, pushPublicKey, pushAuthKey);
} else {
Log.i(LOG_TAG, "Attempting registration for an existing device");
@@ -291,23 +292,18 @@ public class FxAccountDeviceRegistrator
public void handleSuccess(FxAccountDevice[] devices) {
for (FxAccountDevice device : devices) {
if (device.isCurrentDevice) {
fxAccount.setFxAUserData(device.id, 0, 0L); // Reset device registration version/timestamp
if (!allowRecursion) {
Log.d(LOG_TAG, "Failure to register a device on the second try");
break;
}
- try {
- doFxaRegistration(context, subscription, false);
- return;
- } catch (InvalidFxAState e) {
- Log.d(LOG_TAG, "Invalid state when trying to recover from a session conflict ", e);
- break;
- }
+ doFxaRegistration(context, subscription, false);
+ return;
}
}
onError();
}
});
}
private void setupListeners() throws ClassNotFoundException, NoSuchMethodException,
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -25,17 +25,16 @@ import org.mozilla.gecko.background.comm
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.fxa.FirefoxAccounts;
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.fxa.login.TokensAndKeysState;
import org.mozilla.gecko.fxa.sync.FxAccountProfileService;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
@@ -605,34 +604,16 @@ public class AndroidFxAccount {
StateLabel stateLabel = StateLabel.valueOf(stateLabelString);
Logger.debug(LOG_TAG, "Account is in state " + stateLabel);
return StateFactory.fromJSONObject(stateLabel, new ExtendedJSONObject(stateString));
} catch (Exception e) {
throw new IllegalStateException("could not get state", e);
}
}
- public byte[] getSessionToken() throws InvalidFxAState {
- State state = getState();
- StateLabel stateLabel = state.getStateLabel();
- if (stateLabel == StateLabel.Cohabiting || stateLabel == StateLabel.Married) {
- TokensAndKeysState tokensAndKeysState = (TokensAndKeysState) state;
- return tokensAndKeysState.getSessionToken();
- }
- throw new InvalidFxAState("Cannot get sessionToken: not in a TokensAndKeysState state");
- }
-
- public static class InvalidFxAState extends Exception {
- private static final long serialVersionUID = -8537626959811195978L;
-
- public InvalidFxAState(String message) {
- super(message);
- }
- }
-
/**
* <b>For debugging only!</b>
*/
public void dump() {
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
return;
}
ExtendedJSONObject o = toJSONObject();
@@ -690,19 +671,19 @@ public class AndroidFxAccount {
}
// Update intent with tokens and service URI.
intent.putExtra(FxAccountConstants.ACCOUNT_OAUTH_SERVICE_ENDPOINT_KEY, getOAuthServerURI());
// Deleted broadcasts are package-private, so there's no security risk include the tokens in the extras
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_AUTH_TOKENS, tokens.toArray(new String[tokens.size()]));
try {
- intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN, getSessionToken());
- } catch (InvalidFxAState e) {
- Logger.warn(LOG_TAG, "Could not get a session token, ignoring.", e);
+ intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN, getState().getSessionToken());
+ } catch (State.NotASessionTokenState e) {
+ // Ignore, if sessionToken is null we won't try to do anything anyway.
}
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SERVER_URI, getAccountServerURI());
intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_DEVICE_ID, getDeviceId());
return intent;
}
/**
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Engaged.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Engaged.java
@@ -1,28 +1,28 @@
/* 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.login;
-import java.security.NoSuchAlgorithmException;
-
import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.AccountVerified;
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LocalError;
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.RemoteError;
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
+import java.security.NoSuchAlgorithmException;
+
public class Engaged extends State {
private static final String LOG_TAG = Engaged.class.getSimpleName();
protected final byte[] sessionToken;
protected final byte[] keyFetchToken;
protected final byte[] unwrapkB;
public Engaged(String email, String uid, boolean verified, byte[] unwrapkB, byte[] sessionToken, byte[] keyFetchToken) {
@@ -80,12 +80,13 @@ public class Engaged extends State {
@Override
public Action getNeededAction() {
if (!verified) {
return Action.NeedsVerification;
}
return Action.None;
}
+ @Override
public byte[] getSessionToken() {
return sessionToken;
}
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/State.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/State.java
@@ -6,16 +6,25 @@ package org.mozilla.gecko.fxa.login;
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
public abstract class State {
public static final long CURRENT_VERSION = 3L;
+ public class NotASessionTokenState extends Exception {
+
+ private static final long serialVersionUID = 8628129091996684799L;
+
+ public NotASessionTokenState(String message) {
+ super(message);
+ }
+ }
+
public enum StateLabel {
Engaged,
Cohabiting,
Married,
Separated,
Doghouse,
MigratedFromSync11,
}
@@ -64,9 +73,14 @@ public abstract class State {
public State makeMigratedFromSync11State(String password) {
return new MigratedFromSync11(email, uid, verified, password);
}
public abstract void execute(ExecuteDelegate delegate);
public abstract Action getNeededAction();
+
+ // Should be overridden in states that have a sessionToken
+ public byte[] getSessionToken() throws NotASessionTokenState {
+ throw new NotASessionTokenState("Cannot get a session token in " + stateLabel);
+ }
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/TokensAndKeysState.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/TokensAndKeysState.java
@@ -29,16 +29,17 @@ public abstract class TokensAndKeysState
// Fields are non-null by constructor.
o.put("sessionToken", Utils.byte2Hex(sessionToken));
o.put("kA", Utils.byte2Hex(kA));
o.put("kB", Utils.byte2Hex(kB));
o.put("keyPair", keyPair.toJSONObject());
return o;
}
+ @Override
public byte[] getSessionToken() {
return sessionToken;
}
@Override
public Action getNeededAction() {
return Action.None;
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
@@ -24,16 +24,17 @@ import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountClient;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClientException;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.sync.CommandProcessor;
import org.mozilla.gecko.sync.CommandProcessor.Command;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.CryptoException;
@@ -189,19 +190,21 @@ public class SyncClientsEngineStage exte
Log.e(LOG_TAG, "Can't notify other clients: no account");
return;
}
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
final ExtendedJSONObject payload = createNotifyDevicesPayload();
final byte[] sessionToken;
try {
- sessionToken = fxAccount.getSessionToken();
- } catch (AndroidFxAccount.InvalidFxAState invalidFxAState) {
- Log.e(LOG_TAG, "Could not get session token", invalidFxAState);
+ sessionToken = fxAccount.getState().getSessionToken();
+ } catch (State.NotASessionTokenState e) {
+ // Most of the time we should never reach this, but there can be races with the account
+ // state, so better safe than sorry.
+ Log.e(LOG_TAG, "Could not get a session token during Sync (?)", e);
return;
}
// API doc : https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountdevicesnotify
final FxAccountClient fxAccountClient = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
fxAccountClient.notifyDevices(sessionToken, devicesToNotify, payload, NOTIFY_TAB_SENT_TTL_SECS, new FxAccountClient20.RequestDelegate<ExtendedJSONObject>() {
@Override
public void handleError(Exception e) {