зеркало из https://github.com/mozilla/fxa-archive.git
Proof of concept artifacts for CAD phase 4, QR code pairing flow. (#277)
This commit is contained in:
Родитель
8ebe90676e
Коммит
3860d2acf1
|
@ -0,0 +1,524 @@
|
|||
diff --git a/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java b/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
|
||||
--- a/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
|
||||
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
|
||||
@@ -185,16 +185,23 @@ public class VideoCaptureAndroid impleme
|
||||
Log.d(TAG, "Camera orientation: " + info.orientation +
|
||||
" .Device orientation: " + getDeviceOrientation());
|
||||
Camera.Parameters parameters = camera.getParameters();
|
||||
Log.d(TAG, "isVideoStabilizationSupported: " +
|
||||
parameters.isVideoStabilizationSupported());
|
||||
if (parameters.isVideoStabilizationSupported()) {
|
||||
parameters.setVideoStabilization(true);
|
||||
}
|
||||
+
|
||||
+List<String> focusModes = parameters.getSupportedFocusModes();
|
||||
+ if (focusModes.contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) {
|
||||
+ Log.d(TAG, "Enable continuous auto focus mode.");
|
||||
+ parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
|
||||
+ }
|
||||
+
|
||||
parameters.setPictureSize(width, height);
|
||||
parameters.setPreviewSize(width, height);
|
||||
|
||||
// Check if requested fps range is supported by camera,
|
||||
// otherwise calculate frame drop ratio.
|
||||
List<int[]> supportedFpsRanges = parameters.getSupportedPreviewFpsRange();
|
||||
frameDropRatio = Integer.MAX_VALUE;
|
||||
for (int i = 0; i < supportedFpsRanges.size(); i++) {
|
||||
diff --git a/mobile/android/base/android-services.mozbuild b/mobile/android/base/android-services.mozbuild
|
||||
--- a/mobile/android/base/android-services.mozbuild
|
||||
+++ b/mobile/android/base/android-services.mozbuild
|
||||
@@ -837,16 +837,17 @@ sync_java_files = [TOPSRCDIR + '/mobile/
|
||||
'fxa/FxAccountConstants.java',
|
||||
'fxa/FxAccountPushHandler.java',
|
||||
'fxa/login/BaseRequestDelegate.java',
|
||||
'fxa/login/Cohabiting.java',
|
||||
'fxa/login/Doghouse.java',
|
||||
'fxa/login/Engaged.java',
|
||||
'fxa/login/FxAccountLoginStateMachine.java',
|
||||
'fxa/login/FxAccountLoginTransition.java',
|
||||
+ 'fxa/login/HouseHunting.java',
|
||||
'fxa/login/Married.java',
|
||||
'fxa/login/MigratedFromSync11.java',
|
||||
'fxa/login/Separated.java',
|
||||
'fxa/login/State.java',
|
||||
'fxa/login/StateFactory.java',
|
||||
'fxa/login/TokensAndKeysState.java',
|
||||
'fxa/receivers/FxAccountDeletedService.java',
|
||||
'fxa/receivers/FxAccountUpgradeReceiver.java',
|
||||
diff --git a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java b/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
|
||||
--- a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
|
||||
+++ b/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
|
||||
@@ -16,16 +16,17 @@ import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.mozilla.gecko.background.fxa.FxAccountUtils;
|
||||
import org.mozilla.gecko.fxa.FirefoxAccounts;
|
||||
import org.mozilla.gecko.fxa.FxAccountConstants;
|
||||
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
|
||||
import org.mozilla.gecko.fxa.login.Engaged;
|
||||
+import org.mozilla.gecko.fxa.login.HouseHunting;
|
||||
import org.mozilla.gecko.fxa.login.State;
|
||||
import org.mozilla.gecko.restrictions.Restrictable;
|
||||
import org.mozilla.gecko.restrictions.Restrictions;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.util.BundleEventListener;
|
||||
import org.mozilla.gecko.util.EventCallback;
|
||||
import org.mozilla.gecko.util.GeckoBundle;
|
||||
@@ -93,27 +94,40 @@ public class AccountsHelper implements B
|
||||
AndroidFxAccount.invalidateCaches();
|
||||
|
||||
AndroidFxAccount fxAccount = null;
|
||||
try {
|
||||
final GeckoBundle json = message.getBundle("json");
|
||||
final String email = json.getString("email");
|
||||
final String uid = json.getString("uid");
|
||||
final boolean verified = json.getBoolean("verified", false);
|
||||
- final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey"));
|
||||
final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken"));
|
||||
- final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken"));
|
||||
+
|
||||
+ final byte[] kA = Utils.hex2Byte(json.getString("kA", ""));
|
||||
+ final byte[] kB = Utils.hex2Byte(json.getString("kB", ""));
|
||||
+ final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey", ""));
|
||||
+ final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken", ""));
|
||||
+
|
||||
final String authServerEndpoint = json.getString("authServerEndpoint",
|
||||
FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT);
|
||||
final String tokenServerEndpoint = json.getString("tokenServerEndpoint",
|
||||
FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
|
||||
final String profileServerEndpoint = json.getString("profileServerEndpoint",
|
||||
FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT);
|
||||
// TODO: handle choose what to Sync.
|
||||
- State state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
|
||||
+
|
||||
+ State state;
|
||||
+ Log.w(LOGTAG, "Creating fxaccount from JSON");
|
||||
+
|
||||
+ if (kA.length != 0 && kB.length != 0) {
|
||||
+ state = new HouseHunting(email, uid, verified, sessionToken, kA, kB);
|
||||
+ } else {
|
||||
+ state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
|
||||
+ }
|
||||
+
|
||||
fxAccount = AndroidFxAccount.addAndroidAccount(mContext,
|
||||
email,
|
||||
mProfile.getName(),
|
||||
authServerEndpoint,
|
||||
tokenServerEndpoint,
|
||||
profileServerEndpoint,
|
||||
state,
|
||||
AndroidFxAccount.DEFAULT_AUTHORITIES_TO_SYNC_AUTOMATICALLY_MAP);
|
||||
@@ -179,28 +193,38 @@ public class AccountsHelper implements B
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final boolean verified = json.getBoolean("verified", false);
|
||||
final byte[] unwrapkB = Utils.hex2Byte(json.getString("unwrapBKey", ""));
|
||||
final byte[] sessionToken = Utils.hex2Byte(json.getString("sessionToken", ""));
|
||||
final byte[] keyFetchToken = Utils.hex2Byte(json.getString("keyFetchToken", ""));
|
||||
+ final byte[] kA = Utils.hex2Byte(json.getString("kA"));
|
||||
+ final byte[] kB = Utils.hex2Byte(json.getString("kB"));
|
||||
|
||||
- if (unwrapkB.length == 0 || sessionToken.length == 0 || keyFetchToken.length == 0) {
|
||||
+ if (sessionToken.length == 0 || (
|
||||
+ ! (unwrapkB.length > 0 && keyFetchToken.length > 0) &&
|
||||
+ ! (kA.length > 0 && kB.length > 0))) {
|
||||
final String error = "Cannot update Firefox Account from JSON: invalid key/tokens";
|
||||
Log.e(LOGTAG, error);
|
||||
if (callback != null) {
|
||||
callback.sendError(error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
- final State state = new Engaged(email, uid, verified, unwrapkB,
|
||||
- sessionToken, keyFetchToken);
|
||||
+ State _state;
|
||||
+
|
||||
+ if (kA.length != 0 && kB.length != 0) {
|
||||
+ _state = new HouseHunting(email, uid, verified, sessionToken, kA, kB);
|
||||
+ } else {
|
||||
+ _state = new Engaged(email, uid, verified, unwrapkB, sessionToken, keyFetchToken);
|
||||
+ }
|
||||
+ final State state = _state;
|
||||
|
||||
final AndroidFxAccount fxAccount = new AndroidFxAccount(mContext, account);
|
||||
fxAccount.setState(state);
|
||||
|
||||
if (callback != null) {
|
||||
callback.sendSuccess(true);
|
||||
}
|
||||
|
||||
diff --git 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
|
||||
--- 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,17 +1,16 @@
|
||||
/* 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 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;
|
||||
@@ -40,25 +39,16 @@ public class Engaged extends State {
|
||||
o.put("unwrapkB", Utils.byte2Hex(unwrapkB));
|
||||
o.put("sessionToken", Utils.byte2Hex(sessionToken));
|
||||
o.put("keyFetchToken", Utils.byte2Hex(keyFetchToken));
|
||||
return o;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final ExecuteDelegate delegate) {
|
||||
- BrowserIDKeyPair theKeyPair;
|
||||
- try {
|
||||
- theKeyPair = delegate.generateKeyPair();
|
||||
- } catch (NoSuchAlgorithmException e) {
|
||||
- delegate.handleTransition(new LocalError(e), new Doghouse(email, uid, verified));
|
||||
- return;
|
||||
- }
|
||||
- final BrowserIDKeyPair keyPair = theKeyPair;
|
||||
-
|
||||
delegate.getClient().keys(keyFetchToken, new BaseRequestDelegate<TwoKeys>(this, delegate) {
|
||||
@Override
|
||||
public void handleSuccess(TwoKeys result) {
|
||||
byte[] kB;
|
||||
try {
|
||||
kB = FxAccountUtils.unwrapkB(unwrapkB, result.wrapkB);
|
||||
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
|
||||
FxAccountUtils.pii(LOG_TAG, "Fetched kA: " + Utils.byte2Hex(result.kA));
|
||||
@@ -67,17 +57,17 @@ public class Engaged extends State {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
delegate.handleTransition(new RemoteError(e), new Separated(email, uid, verified));
|
||||
return;
|
||||
}
|
||||
Transition transition = verified
|
||||
? new LogMessage("keys succeeded")
|
||||
: new AccountVerified();
|
||||
- delegate.handleTransition(transition, new Cohabiting(email, uid, sessionToken, result.kA, kB, keyPair));
|
||||
+ delegate.handleTransition(transition, new HouseHunting(email, uid, verified, sessionToken, result.kA, kB));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Action getNeededAction() {
|
||||
if (!verified) {
|
||||
return Action.NeedsVerification;
|
||||
diff --git 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
|
||||
--- 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
|
||||
@@ -17,16 +17,17 @@ public abstract class State {
|
||||
|
||||
public NotASessionTokenState(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public enum StateLabel {
|
||||
Engaged,
|
||||
+ HouseHunting,
|
||||
Cohabiting,
|
||||
Married,
|
||||
Separated,
|
||||
Doghouse,
|
||||
MigratedFromSync11,
|
||||
}
|
||||
|
||||
public enum Action {
|
||||
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java
|
||||
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java
|
||||
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java
|
||||
@@ -138,16 +138,24 @@ public class StateFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exactly the same as {@link fromJSONObjectV2}, except that there's a new
|
||||
* MigratedFromSyncV11 state.
|
||||
*/
|
||||
protected static State fromJSONObjectV3(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
|
||||
switch (stateLabel) {
|
||||
+ case HouseHunting:
|
||||
+ return new HouseHunting(
|
||||
+ o.getString("email"),
|
||||
+ o.getString("uid"),
|
||||
+ o.getBoolean("verified"),
|
||||
+ Utils.hex2Byte(o.getString("sessionToken")),
|
||||
+ Utils.hex2Byte(o.getString("kA")),
|
||||
+ Utils.hex2Byte(o.getString("kB")));
|
||||
case MigratedFromSync11:
|
||||
return new MigratedFromSync11(
|
||||
o.getString("email"),
|
||||
o.getString("uid"),
|
||||
o.getBoolean("verified"),
|
||||
o.getString("password"));
|
||||
default:
|
||||
return fromJSONObjectV2(stateLabel, o);
|
||||
diff --git a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
|
||||
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
|
||||
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
|
||||
@@ -593,41 +593,43 @@ public class FxAccountSyncAdapter extend
|
||||
syncDelegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
final State state;
|
||||
try {
|
||||
state = fxAccount.getState();
|
||||
} catch (Exception e) {
|
||||
+ Logger.error(LOG_TAG, "Could not getState", e);
|
||||
fxAccount.releaseSharedAccountStateLock();
|
||||
syncDelegate.handleError(e);
|
||||
return;
|
||||
}
|
||||
|
||||
final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
|
||||
stateMachine.advance(state, StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
|
||||
@Override
|
||||
public void handleNotMarried(State notMarried) {
|
||||
- Logger.info(LOG_TAG, "handleNotMarried: in " + notMarried.getStateLabel());
|
||||
+ Logger.warn(LOG_TAG, "handleNotMarried: in " + notMarried.getStateLabel());
|
||||
schedulePolicy.onHandleFinal(notMarried.getNeededAction());
|
||||
syncDelegate.handleCannotSync(notMarried);
|
||||
- if (notMarried.getStateLabel() == StateLabel.Engaged) {
|
||||
+ final StateLabel stateLabel = notMarried.getStateLabel();
|
||||
+ if (stateLabel == StateLabel.Engaged || stateLabel == StateLabel.HouseHunting) {
|
||||
onSessionTokenStateReached(context, fxAccount);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
|
||||
return shouldPerformSync(tokenBackoffHandler, "token", extras);
|
||||
}
|
||||
diff --git a/services/fxaccounts/FxAccountsWebChannel.jsm b/services/fxaccounts/FxAccountsWebChannel.jsm
|
||||
--- a/services/fxaccounts/FxAccountsWebChannel.jsm
|
||||
+++ b/services/fxaccounts/FxAccountsWebChannel.jsm
|
||||
@@ -26,16 +26,17 @@ XPCOMUtils.defineLazyModuleGetter(this,
|
||||
"resource://gre/modules/FxAccountsStorage.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Weave",
|
||||
"resource://services-sync/main.js");
|
||||
|
||||
const COMMAND_PROFILE_CHANGE = "profile:change";
|
||||
const COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account";
|
||||
+const COMMAND_KEYS = "fxaccounts:keys";
|
||||
const COMMAND_LOGIN = "fxaccounts:login";
|
||||
const COMMAND_LOGOUT = "fxaccounts:logout";
|
||||
const COMMAND_DELETE = "fxaccounts:delete";
|
||||
const COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences";
|
||||
const COMMAND_CHANGE_PASSWORD = "fxaccounts:change_password";
|
||||
const COMMAND_FXA_STATUS = "fxaccounts:fxa_status";
|
||||
|
||||
const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
|
||||
@@ -192,16 +193,31 @@ this.FxAccountsWebChannel.prototype = {
|
||||
messageId: message.messageId,
|
||||
data: fxaStatus
|
||||
};
|
||||
this._channel.send(response, sendingContext);
|
||||
}).catch(error =>
|
||||
this._sendError(error, message, sendingContext)
|
||||
);
|
||||
break;
|
||||
+ case COMMAND_KEYS:
|
||||
+ log.debug("keys request received");
|
||||
+
|
||||
+ this._helpers.getUserData(sendingContext)
|
||||
+ .then(userData => {
|
||||
+ let response = {
|
||||
+ command,
|
||||
+ messageId: message.messageId,
|
||||
+ data: userData
|
||||
+ }
|
||||
+ this._channel.send(response, sendingContext);
|
||||
+ }).catch(error =>
|
||||
+ this._sendError(error, message, sendingContext)
|
||||
+ );
|
||||
+ break;
|
||||
default:
|
||||
log.warn("Unrecognized FxAccountsWebChannel command", command);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_sendError(error, incomingMessage, sendingContext) {
|
||||
log.error("Failed to handle FxAccountsWebChannel message", error);
|
||||
@@ -402,16 +418,41 @@ this.FxAccountsWebChannelHelpers.prototy
|
||||
return {
|
||||
signedInUser,
|
||||
capabilities: {
|
||||
engines: this._getAvailableExtraEngines()
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
+ /**
|
||||
+ * Get keys information. Resolves to { signedInUser: <user_data> }.
|
||||
+ * If returning status information is not allowed or no user is signed into
|
||||
+ * Sync, `user_data` will be null.
|
||||
+ */
|
||||
+ async getUserData() {
|
||||
+ let keys = null;
|
||||
+
|
||||
+ const userData = await this._fxAccounts.getKeys()
|
||||
+ if (userData) {
|
||||
+ keys = {
|
||||
+ email: userData.email,
|
||||
+ kA: userData.kA,
|
||||
+ kB: userData.kB,
|
||||
+ sessionToken: userData.sessionToken,
|
||||
+ uid: userData.uid,
|
||||
+ verified: userData.verified
|
||||
+ };
|
||||
+ }
|
||||
+
|
||||
+ return {
|
||||
+ signedInUser: keys
|
||||
+ };
|
||||
+ },
|
||||
+
|
||||
_getAvailableExtraEngines() {
|
||||
return EXTRA_ENGINES.filter(engineName => {
|
||||
try {
|
||||
return Services.prefs.getBoolPref(`services.sync.engine.${engineName}.available`);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
diff --git a/services/fxaccounts/tests/xpcshell/test_web_channel.js b/services/fxaccounts/tests/xpcshell/test_web_channel.js
|
||||
--- a/services/fxaccounts/tests/xpcshell/test_web_channel.js
|
||||
+++ b/services/fxaccounts/tests/xpcshell/test_web_channel.js
|
||||
@@ -280,16 +280,65 @@ add_test(function test_fxa_status_messag
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
};
|
||||
|
||||
channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
|
||||
});
|
||||
|
||||
+add_test(function test_keys_message() {
|
||||
+ let mockMessage = {
|
||||
+ command: "fxaccounts:keys",
|
||||
+ messageId: 123,
|
||||
+ data: {
|
||||
+ service: "sync"
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ let channel = new FxAccountsWebChannel({
|
||||
+ channel_id: WEBCHANNEL_ID,
|
||||
+ content_uri: URL_STRING,
|
||||
+ helpers: {
|
||||
+ async getUserData() {
|
||||
+ return {
|
||||
+ signedInUser: {
|
||||
+ email: "testuser@testuser.com",
|
||||
+ kA: "kA",
|
||||
+ kB: "kB",
|
||||
+ sessionToken: "session-token",
|
||||
+ uid: "uid",
|
||||
+ verified: true
|
||||
+ }
|
||||
+ };
|
||||
+ }
|
||||
+ }
|
||||
+ });
|
||||
+
|
||||
+ channel._channel = {
|
||||
+ send(response, sendingContext) {
|
||||
+ do_check_eq(response.command, "fxaccounts:keys");
|
||||
+ do_check_eq(response.messageId, 123);
|
||||
+
|
||||
+ let signedInUser = response.data.signedInUser;
|
||||
+ do_check_true(!!signedInUser);
|
||||
+ do_check_eq(signedInUser.email, "testuser@testuser.com");
|
||||
+ do_check_eq(signedInUser.kA, "kA");
|
||||
+ do_check_eq(signedInUser.kB, "kB");
|
||||
+ do_check_eq(signedInUser.sessionToken, "session-token");
|
||||
+ do_check_eq(signedInUser.uid, "uid");
|
||||
+ do_check_eq(signedInUser.verified, true);
|
||||
+
|
||||
+ run_next_test();
|
||||
+ }
|
||||
+ };
|
||||
+
|
||||
+ channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
|
||||
+});
|
||||
+
|
||||
add_test(function test_unrecognized_message() {
|
||||
let mockMessage = {
|
||||
command: "fxaccounts:unrecognized",
|
||||
data: {}
|
||||
};
|
||||
|
||||
let channel = new FxAccountsWebChannel({
|
||||
channel_id: WEBCHANNEL_ID,
|
||||
@@ -877,16 +926,53 @@ add_task(async function test_helpers_cha
|
||||
await helpers.changePassword({});
|
||||
do_check_false("changePassword should have rejected");
|
||||
} catch (_) {
|
||||
do_check_true(wasCalled.updateUserAccountData);
|
||||
do_check_false(wasCalled.updateDeviceRegistration);
|
||||
}
|
||||
});
|
||||
|
||||
+add_task(async function test_helpers_getUserData() {
|
||||
+ let wasCalled = {
|
||||
+ getKeys: false
|
||||
+ };
|
||||
+
|
||||
+ let helpers = new FxAccountsWebChannelHelpers({
|
||||
+ fxAccounts: {
|
||||
+ getKeys() {
|
||||
+ wasCalled.getKeys = true;
|
||||
+ return Promise.resolve({
|
||||
+ email: "testuser@testuser.com",
|
||||
+ kA: "kA",
|
||||
+ kB: "kB",
|
||||
+ sessionToken: "session-token",
|
||||
+ uid: "uid",
|
||||
+ verified: true
|
||||
+ });
|
||||
+ }
|
||||
+ }
|
||||
+ });
|
||||
+
|
||||
+ return helpers.getUserData()
|
||||
+ .then(userData => {
|
||||
+ do_check_true(!!userData);
|
||||
+ do_check_true(wasCalled.getKeys);
|
||||
+
|
||||
+ let signedInUser = userData.signedInUser;
|
||||
+ do_check_true(!!signedInUser);
|
||||
+ do_check_eq(signedInUser.email, "testuser@testuser.com");
|
||||
+ do_check_eq(signedInUser.kA, "kA");
|
||||
+ do_check_eq(signedInUser.kB, "kB");
|
||||
+ do_check_eq(signedInUser.sessionToken, "session-token");
|
||||
+ do_check_eq(signedInUser.uid, "uid");
|
||||
+ do_check_eq(signedInUser.verified, true);
|
||||
+ });
|
||||
+});
|
||||
+
|
||||
function makeObserver(aObserveTopic, aObserveFunc) {
|
||||
let callback = function(aSubject, aTopic, aData) {
|
||||
log.debug("observed " + aTopic + " " + aData);
|
||||
if (aTopic == aObserveTopic) {
|
||||
removeMe();
|
||||
aObserveFunc(aSubject, aTopic, aData);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,147 @@
|
|||
# QR code base pairing flow P.O.C.
|
||||
|
||||
The idea is to pair a mobile device to a desktop device by scanning
|
||||
a QR code, no typing needed.
|
||||
|
||||
## How it works
|
||||
|
||||
At the highest level, the basic idea is for a mobile device to
|
||||
start syncing by populating all necessary info based on info
|
||||
already held by a desktop device.
|
||||
|
||||
At a more detailed level, the minimum info necessary for a device
|
||||
to start syncing without typing a password is a uid, sessionToken,
|
||||
kA, and kB.
|
||||
|
||||
If one device already has these items, a 2nd device can use them
|
||||
to start syncing itself.
|
||||
|
||||
What is needed:
|
||||
|
||||
1. A way to get a uid, sessionToken, kA, and kB out of device A.
|
||||
2. A secure way of transmitting this info from device A to device B.
|
||||
3. A way for device B to use this info to start syncing.
|
||||
|
||||
## Getting a uid, sessionToken, kA, and kB out of device A
|
||||
|
||||
On Firefox Desktop, a web channel message (fxaccounts:keys) is used
|
||||
to request the required info from the browser. A new command is
|
||||
used instead of overridding an existing command so that the browser
|
||||
could choose to implement some UI asking the user for permission
|
||||
to extract the keys. The browser responds to the request
|
||||
with a JSON response that contains the uid, sessionToken, kA, and kB.
|
||||
|
||||
## Securely transmitting info from device A to device B.
|
||||
|
||||
Once the browser responds to the fxaccounts:keys request,
|
||||
the bundle is encrypted using a symmetric cipher using a locally
|
||||
generated random keypair. A request is made to the auth-server
|
||||
to send an SMS and store the encrypted bundle with the SMS's signinCode.
|
||||
The signinCode is returned to the content-server.
|
||||
|
||||
A URL is generated to the content-server's pairing page. The URL's hash
|
||||
portion contains both the signinCode and the key used to encrypt
|
||||
the bundle. Because the key is stored in a hash variable, neither Mozilla
|
||||
nor any other server learn the key needed to decrypt the bundle.
|
||||
A QR code is generated from the URL and displayed to the user.
|
||||
|
||||
The user then opens the QR code scanner from the URL bar in Fennec
|
||||
and scans the generated QR code.
|
||||
|
||||
The content server looks at the hash parameters to learn the
|
||||
signinCode and encrytion key. It consumes the signinCode from
|
||||
the auth-server and uses the encryption key to decrypt the bundle.
|
||||
Again, the bundle contains a uid, sessionToken, kA, and kB.
|
||||
|
||||
## Device B uses this info to start syncing.
|
||||
|
||||
Device B then requests a new sessionToken using the sessionToken
|
||||
from the bundle so that each device has its own sessionToken and a
|
||||
new device record is created for device B.
|
||||
|
||||
The content-server sends an `fxaccounts:login` message with
|
||||
the newly generated sessionToken, uid, kA, and kB to the browser.
|
||||
|
||||
The browser accepts this info, and instead of doing the existing flow
|
||||
where it uses unwrapBKey and a keyFetchToken to fetch kA and kB,
|
||||
it uses the passed in kA and kB directly. The browser now has everything
|
||||
it needs to start syncing.
|
||||
|
||||
## P.O.C. Code
|
||||
|
||||
Code for Firefox desktop and Android is at:
|
||||
|
||||
https://gist.github.com/shane-tomlinson/4cd89cdecde9a12499e45f24f59bd004
|
||||
|
||||
Code for fxa-content-server is at:
|
||||
|
||||
https://github.com/shane-tomlinson/fxa-content-server/tree/qr-code-pair-flow
|
||||
|
||||
Code for fxa-auth-server is at:
|
||||
|
||||
https://github.com/shane-tomlinson/fxa-auth-server/tree/signin-codes
|
||||
|
||||
Code for fxa-auth-db-mysql is at:
|
||||
|
||||
https://github.com/shane-tomlinson/fxa-auth-db-mysql/tree/signin-codes
|
||||
|
||||
## Running the P.O.C. in Firefox Desktop and Android
|
||||
|
||||
1. Update fxa-local-dev to use the QR code branches:
|
||||
1. Check out the content-server, auth-server, and auth-db-mysql branches referred to above. After checking out the branches, run `npm install` in each server subdirectory.
|
||||
2. In fxa-local-dev, update servers.json to point at the branches in step 1.1.
|
||||
3. In fxa-local-dev, restart the servers using `pm2 kill && npm run start-android`. Note the server URLs.
|
||||
2. Update mozilla-central with the patch from https://gist.github.com/shane-tomlinson/4cd89cdecde9a12499e45f24f59bd004
|
||||
3. Set up and build Firefox Desktop
|
||||
1. Run ./mach bootstrap, set up a "Desktop Artifact build"
|
||||
2. Run ./mach build
|
||||
3. Run ./mach run
|
||||
4. Open about:config, update the appropriate fxaccounts config values to point to the servers from steps 1.3.
|
||||
4. Set up and build Firefox for Android
|
||||
1. See https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/Build_Instructions/Simple_Firefox_for_Android_build as a reference.
|
||||
2. Run ./mach boostrap, set up an "Android Artifact build"
|
||||
3. Run ./mach build
|
||||
4. Connect an Android device with USB debugging enabled to the desktop machine.
|
||||
5. Run ./mach package && ./mach install && ./mach run
|
||||
6. Open about:config, update the appropriate fxaccounts config values to point to the servers from steps 1.3.
|
||||
5. Play with the UI!
|
||||
1. In Firefox Desktop, sign up for a new Firefox Account via about:preferences#sync.
|
||||
2. Open the verification link in the same browser as 5.1. Fill out a valid phone number, click submit.
|
||||
3. A QR code should appear. On your Android device, open the Fennec process started by step 4.5.
|
||||
4. Open the QR code scanner from the URL bar.
|
||||
5. Scan the QR code - FxA should open and you should be signed in to Sync.
|
||||
|
||||
## Fx for iOS
|
||||
|
||||
A P.O.C. has not yet been written for Firefox for iOS. Firefox for iOS
|
||||
uses a different communication mechanism than Firefox for Android.
|
||||
In Fx for iOS, the browser only listens for messages from FxA from within
|
||||
the browser's "settings" panel. This has the effect that login messages sent
|
||||
from FxA when opened from a QR code are ignored.
|
||||
|
||||
To get a P.O.C. running on Fx for iOS, one of two things is needed:
|
||||
|
||||
1. Fx for iOS must listen to messages from arbitrary tabs.
|
||||
2. Fx for iOS must open the link from the QR code within the FxA panel in settings.
|
||||
|
||||
The first intuitively feels like more work but may serve us better in
|
||||
the long run since similar functionality is needed
|
||||
[for other features](https://github.com/mozilla/fxa-content-server/issues/5434).
|
||||
|
||||
## Limitations/deficiencies of the P.O.C.
|
||||
|
||||
1. No encryption occurs when storing the bundle on the auth-server. All the
|
||||
hooks are in place to encrypt/decrypt the bundle, but time limitations
|
||||
made me defer this.
|
||||
2. The sessionToken passed to device B is not cloned, instead it is used
|
||||
directly, causing device A to eventually log out. The auth-server does
|
||||
not yet [have an endpoint to clone a session](https://github.com/mozilla/fxa-auth-server/issues/2236).
|
||||
3. Fx for iOS does not work (see above). Once the messaging has been
|
||||
figured out, it should be straight forward to update the Fx for iOS state
|
||||
machine in a similar manner to the Fx for Android state machine, and may
|
||||
be easier because the new state needed in Fx for Android already exists.
|
||||
4. It is not possible to pair a desktop device with another desktop device.
|
||||
|
||||
|
||||
|
||||
|
Загрузка…
Ссылка в новой задаче