Bug 1308038 - Add handshake capability to share state with FxA. r=markh, r=rkelly

This commit is contained in:
Shane Tomlinson 2017-02-02 09:16:54 +00:00
parent ede9469c8a
commit 7a827756f9
2 changed files with 369 additions and 2 deletions

View File

@ -34,6 +34,7 @@ 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";
@ -173,6 +174,22 @@ this.FxAccountsWebChannel.prototype = {
this._helpers.changePassword(data).catch(error =>
this._sendError(error, message, sendingContext));
break;
case COMMAND_FXA_STATUS:
log.debug("fxa_status received");
const service = data && data.service;
this._helpers.getFxaStatus(service, sendingContext)
.then(fxaStatus => {
let response = {
command,
messageId: message.messageId,
data: fxaStatus
};
this._channel.send(response, sendingContext);
}).catch(error =>
this._sendError(error, message, sendingContext)
);
break;
default:
log.warn("Unrecognized FxAccountsWebChannel command", command);
break;
@ -297,7 +314,7 @@ this.FxAccountsWebChannelHelpers.prototype = {
*/
logout(uid) {
return fxAccounts.getSignedInUser().then(userData => {
if (userData.uid === uid) {
if (userData && userData.uid === uid) {
// true argument is `localOnly`, because server-side stuff
// has already been taken care of by the content server
return fxAccounts.signOut(true);
@ -306,6 +323,71 @@ this.FxAccountsWebChannelHelpers.prototype = {
});
},
/**
* Check if `sendingContext` is in private browsing mode.
*/
isPrivateBrowsingMode(sendingContext) {
if (!sendingContext ||
!sendingContext.browser ||
!sendingContext.browser.docShell ||
sendingContext.browser.docShell.usePrivateBrowsing === undefined) {
log.error("Unable to check for private browsing mode, assuming true");
return true;
}
const isPrivateBrowsing = sendingContext.browser.docShell.usePrivateBrowsing;
log.debug("is private browsing", isPrivateBrowsing);
return isPrivateBrowsing;
},
/**
* Check whether sending fxa_status data should be allowed.
*/
shouldAllowFxaStatus(service, sendingContext) {
// Return user data for any service in non-PB mode. In PB mode,
// only return user data if service==="sync".
//
// This behaviour allows users to click the "Manage Account"
// link from about:preferences#sync while in PB mode and things
// "just work". While in non-PB mode, users can sign into
// Pocket w/o entering their password a 2nd time, while in PB
// mode they *will* have to enter their email/password again.
//
// The difference in behaviour is to try to match user
// expectations as to what is and what isn't part of the browser.
// Sync is viewed as an integral part of the browser, interacting
// with FxA as part of a Sync flow should work all the time. If
// Sync is broken in PB mode, users will think Firefox is broken.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1323853
log.debug("service", service);
return !this.isPrivateBrowsingMode(sendingContext) || service === "sync";
},
/**
* Get fxa_status 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 getFxaStatus(service, sendingContext) {
let signedInUser = null;
if (this.shouldAllowFxaStatus(service, sendingContext)) {
const userData = await this._fxAccounts.getSignedInUser();
if (userData) {
signedInUser = {
email: userData.email,
sessionToken: userData.sessionToken,
uid: userData.uid,
verified: userData.verified
};
}
}
return {
signedInUser
};
},
changePassword(credentials) {
// If |credentials| has fields that aren't handled by accounts storage,
// updateUserAccountData will throw - mainly to prevent errors in code

View File

@ -10,7 +10,11 @@ const { FxAccountsWebChannel, FxAccountsWebChannelHelpers } =
const URL_STRING = "https://example.com";
const mockSendingContext = {
browser: {},
browser: {
docShell: {
usePrivateBrowsing: false
}
},
principal: {},
eventTarget: {}
};
@ -235,6 +239,51 @@ add_test(function test_sync_preferences_message() {
channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
});
add_test(function test_fxa_status_message() {
let mockMessage = {
command: "fxaccounts:fxa_status",
messageId: 123,
data: {
service: "sync"
}
};
let channel = new FxAccountsWebChannel({
channel_id: WEBCHANNEL_ID,
content_uri: URL_STRING,
helpers: {
async getFxaStatus() {
return {
signedInUser: {
email: "testuser@testuser.com",
sessionToken: "session-token",
uid: "uid",
verified: true
}
};
}
}
});
channel._channel = {
send(response, sendingContext) {
do_check_eq(response.command, "fxaccounts:fxa_status");
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.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",
@ -384,6 +433,242 @@ add_test(function test_helpers_open_sync_preferences() {
helpers.openSyncPreferences(mockBrowser, "fxa:verification_complete");
});
add_task(function* test_helpers_getFxaStatus_allowed_signedInUser() {
let wasCalled = {
getSignedInUser: false,
shouldAllowFxaStatus: false
};
let helpers = new FxAccountsWebChannelHelpers({
fxAccounts: {
getSignedInUser() {
wasCalled.getSignedInUser = true;
return Promise.resolve({
email: "testuser@testuser.com",
kA: "kA",
kb: "kB",
sessionToken: "sessionToken",
uid: "uid",
verified: true
});
}
}
});
helpers.shouldAllowFxaStatus = (service, sendingContext) => {
wasCalled.shouldAllowFxaStatus = true;
do_check_eq(service, "sync");
do_check_eq(sendingContext, mockSendingContext);
return true;
};
return helpers.getFxaStatus("sync", mockSendingContext)
.then(fxaStatus => {
do_check_true(!!fxaStatus);
do_check_true(wasCalled.getSignedInUser);
do_check_true(wasCalled.shouldAllowFxaStatus);
do_check_true(!!fxaStatus.signedInUser);
let {signedInUser} = fxaStatus;
do_check_eq(signedInUser.email, "testuser@testuser.com");
do_check_eq(signedInUser.sessionToken, "sessionToken");
do_check_eq(signedInUser.uid, "uid");
do_check_true(signedInUser.verified);
// These properties are filtered and should not
// be returned to the requester.
do_check_false("kA" in signedInUser);
do_check_false("kB" in signedInUser);
});
});
add_task(function* test_helpers_getFxaStatus_allowed_no_signedInUser() {
let wasCalled = {
getSignedInUser: false,
shouldAllowFxaStatus: false
};
let helpers = new FxAccountsWebChannelHelpers({
fxAccounts: {
getSignedInUser() {
wasCalled.getSignedInUser = true;
return Promise.resolve(null);
}
}
});
helpers.shouldAllowFxaStatus = (service, sendingContext) => {
wasCalled.shouldAllowFxaStatus = true;
do_check_eq(service, "sync");
do_check_eq(sendingContext, mockSendingContext);
return true;
};
return helpers.getFxaStatus("sync", mockSendingContext)
.then(fxaStatus => {
do_check_true(!!fxaStatus);
do_check_true(wasCalled.getSignedInUser);
do_check_true(wasCalled.shouldAllowFxaStatus);
do_check_null(fxaStatus.signedInUser);
});
});
add_task(function* test_helpers_getFxaStatus_not_allowed() {
let wasCalled = {
getSignedInUser: false,
shouldAllowFxaStatus: false
};
let helpers = new FxAccountsWebChannelHelpers({
fxAccounts: {
getSignedInUser() {
wasCalled.getSignedInUser = true;
return Promise.resolve(null);
}
}
});
helpers.shouldAllowFxaStatus = (service, sendingContext) => {
wasCalled.shouldAllowFxaStatus = true;
do_check_eq(service, "sync");
do_check_eq(sendingContext, mockSendingContext);
return false;
};
return helpers.getFxaStatus("sync", mockSendingContext)
.then(fxaStatus => {
do_check_true(!!fxaStatus);
do_check_false(wasCalled.getSignedInUser);
do_check_true(wasCalled.shouldAllowFxaStatus);
do_check_null(fxaStatus.signedInUser);
});
});
add_task(function* test_helpers_shouldAllowFxaStatus_sync_service_not_private_browsing() {
let wasCalled = {
isPrivateBrowsingMode: false
};
let helpers = new FxAccountsWebChannelHelpers({});
helpers.isPrivateBrowsingMode = (sendingContext) => {
wasCalled.isPrivateBrowsingMode = true;
do_check_eq(sendingContext, mockSendingContext);
return false;
}
let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus("sync", mockSendingContext);
do_check_true(shouldAllowFxaStatus);
do_check_true(wasCalled.isPrivateBrowsingMode);
});
add_task(function* test_helpers_shouldAllowFxaStatus_oauth_service_not_private_browsing() {
let wasCalled = {
isPrivateBrowsingMode: false
};
let helpers = new FxAccountsWebChannelHelpers({});
helpers.isPrivateBrowsingMode = (sendingContext) => {
wasCalled.isPrivateBrowsingMode = true;
do_check_eq(sendingContext, mockSendingContext);
return false;
}
let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus("dcdb5ae7add825d2", mockSendingContext);
do_check_true(shouldAllowFxaStatus);
do_check_true(wasCalled.isPrivateBrowsingMode);
});
add_task(function* test_helpers_shouldAllowFxaStatus_no_service_not_private_browsing() {
let wasCalled = {
isPrivateBrowsingMode: false
};
let helpers = new FxAccountsWebChannelHelpers({});
helpers.isPrivateBrowsingMode = (sendingContext) => {
wasCalled.isPrivateBrowsingMode = true;
do_check_eq(sendingContext, mockSendingContext);
return false;
}
let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus("", mockSendingContext);
do_check_true(shouldAllowFxaStatus);
do_check_true(wasCalled.isPrivateBrowsingMode);
});
add_task(function* test_helpers_shouldAllowFxaStatus_sync_service_private_browsing() {
let wasCalled = {
isPrivateBrowsingMode: false
};
let helpers = new FxAccountsWebChannelHelpers({});
helpers.isPrivateBrowsingMode = (sendingContext) => {
wasCalled.isPrivateBrowsingMode = true;
do_check_eq(sendingContext, mockSendingContext);
return true;
}
let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus("sync", mockSendingContext);
do_check_true(shouldAllowFxaStatus);
do_check_true(wasCalled.isPrivateBrowsingMode);
});
add_task(function* test_helpers_shouldAllowFxaStatus_oauth_service_private_browsing() {
let wasCalled = {
isPrivateBrowsingMode: false
};
let helpers = new FxAccountsWebChannelHelpers({});
helpers.isPrivateBrowsingMode = (sendingContext) => {
wasCalled.isPrivateBrowsingMode = true;
do_check_eq(sendingContext, mockSendingContext);
return true;
}
let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus("dcdb5ae7add825d2", mockSendingContext);
do_check_false(shouldAllowFxaStatus);
do_check_true(wasCalled.isPrivateBrowsingMode);
});
add_task(function* test_helpers_shouldAllowFxaStatus_no_service_private_browsing() {
let wasCalled = {
isPrivateBrowsingMode: false
};
let helpers = new FxAccountsWebChannelHelpers({});
helpers.isPrivateBrowsingMode = (sendingContext) => {
wasCalled.isPrivateBrowsingMode = true;
do_check_eq(sendingContext, mockSendingContext);
return true;
}
let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus("", mockSendingContext);
do_check_false(shouldAllowFxaStatus);
do_check_true(wasCalled.isPrivateBrowsingMode);
});
add_task(function* test_helpers_isPrivateBrowsingMode_private_browsing() {
let helpers = new FxAccountsWebChannelHelpers({});
mockSendingContext.browser.docShell.usePrivateBrowsing = true;
let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext);
do_check_true(isPrivateBrowsingMode);
});
add_task(function* test_helpers_isPrivateBrowsingMode_private_browsing() {
let helpers = new FxAccountsWebChannelHelpers({});
mockSendingContext.browser.docShell.usePrivateBrowsing = false;
let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext);
do_check_false(isPrivateBrowsingMode);
});
add_task(function* test_helpers_change_password() {
let wasCalled = {
updateUserAccountData: false,