From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../fxaccounts/tests/xpcshell/test_web_channel.js | 1358 ++++++++++++++++++++ 1 file changed, 1358 insertions(+) create mode 100644 services/fxaccounts/tests/xpcshell/test_web_channel.js (limited to 'services/fxaccounts/tests/xpcshell/test_web_channel.js') diff --git a/services/fxaccounts/tests/xpcshell/test_web_channel.js b/services/fxaccounts/tests/xpcshell/test_web_channel.js new file mode 100644 index 0000000000..63742439ea --- /dev/null +++ b/services/fxaccounts/tests/xpcshell/test_web_channel.js @@ -0,0 +1,1358 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { ON_PROFILE_CHANGE_NOTIFICATION, WEBCHANNEL_ID, log } = + ChromeUtils.import("resource://gre/modules/FxAccountsCommon.js"); +const { CryptoUtils } = ChromeUtils.importESModule( + "resource://services-crypto/utils.sys.mjs" +); +const { FxAccountsWebChannel, FxAccountsWebChannelHelpers } = + ChromeUtils.importESModule( + "resource://gre/modules/FxAccountsWebChannel.sys.mjs" + ); + +const URL_STRING = "https://example.com"; + +const mockSendingContext = { + browsingContext: { top: { embedderElement: {} } }, + principal: {}, + eventTarget: {}, +}; + +add_test(function () { + validationHelper(undefined, "Error: Missing configuration options"); + + validationHelper( + { + channel_id: WEBCHANNEL_ID, + }, + "Error: Missing 'content_uri' option" + ); + + validationHelper( + { + content_uri: "bad uri", + channel_id: WEBCHANNEL_ID, + }, + /NS_ERROR_MALFORMED_URI/ + ); + + validationHelper( + { + content_uri: URL_STRING, + }, + "Error: Missing 'channel_id' option" + ); + + run_next_test(); +}); + +add_task(async function test_rejection_reporting() { + Services.prefs.setBoolPref( + "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess", + false + ); + + let mockMessage = { + command: "fxaccounts:login", + messageId: "1234", + data: { email: "testuser@testuser.com" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + login(accountData) { + equal( + accountData.email, + "testuser@testuser.com", + "Should forward incoming message data to the helper" + ); + return Promise.reject(new Error("oops")); + }, + }, + }); + + let promiseSend = new Promise(resolve => { + channel._channel.send = (message, context) => { + resolve({ message, context }); + }; + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); + + let { message, context } = await promiseSend; + + equal(context, mockSendingContext, "Should forward the original context"); + equal( + message.command, + "fxaccounts:login", + "Should include the incoming command" + ); + equal(message.messageId, "1234", "Should include the message ID"); + equal( + message.data.error.message, + "Error: oops", + "Should convert the error message to a string" + ); + notStrictEqual( + message.data.error.stack, + null, + "Should include the stack for JS error rejections" + ); +}); + +add_test(function test_exception_reporting() { + let mockMessage = { + command: "fxaccounts:sync_preferences", + messageId: "5678", + data: { entryPoint: "fxa:verification_complete" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + openSyncPreferences(browser, entryPoint) { + equal( + entryPoint, + "fxa:verification_complete", + "Should forward incoming message data to the helper" + ); + throw new TypeError("splines not reticulated"); + }, + }, + }); + + channel._channel.send = (message, context) => { + equal(context, mockSendingContext, "Should forward the original context"); + equal( + message.command, + "fxaccounts:sync_preferences", + "Should include the incoming command" + ); + equal(message.messageId, "5678", "Should include the message ID"); + equal( + message.data.error.message, + "TypeError: splines not reticulated", + "Should convert the exception to a string" + ); + notStrictEqual( + message.data.error.stack, + null, + "Should include the stack for JS exceptions" + ); + + run_next_test(); + }; + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_error_message_remove_profile_path() { + const errors = { + windows: { + err: new Error( + "Win error 183 during operation rename on file C:\\Users\\Some Computer\\AppData\\Roaming\\" + + "Mozilla\\Firefox\\Profiles\\dbzjmzxa.default\\signedInUser.json (Cannot create a file)" + ), + expected: + "Error: Win error 183 during operation rename on file C:[REDACTED]signedInUser.json (Cannot create a file)", + }, + unix: { + err: new Error( + "Unix error 28 during operation write on file /Users/someuser/Library/Application Support/" + + "Firefox/Profiles/dbzjmzxa.default-release-7/signedInUser.json (No space left on device)" + ), + expected: + "Error: Unix error 28 during operation write on file [REDACTED]signedInUser.json (No space left on device)", + }, + netpath: { + err: new Error( + "Win error 32 during operation rename on file \\\\SVC.LOC\\HOMEDIRS$\\USERNAME\\Mozilla\\" + + "Firefox\\Profiles\\dbzjmzxa.default-release-7\\signedInUser.json (No space left on device)" + ), + expected: + "Error: Win error 32 during operation rename on file [REDACTED]signedInUser.json (No space left on device)", + }, + mount: { + err: new Error( + "Win error 649 during operation rename on file C:\\SnapVolumes\\MountPoints\\" + + "{9e399ec5-0000-0000-0000-100000000000}\\SVROOT\\Users\\username\\AppData\\Roaming\\Mozilla\\Firefox\\" + + "Profiles\\dbzjmzxa.default-release\\signedInUser.json (The create operation failed)" + ), + expected: + "Error: Win error 649 during operation rename on file C:[REDACTED]signedInUser.json " + + "(The create operation failed)", + }, + }; + const mockMessage = { + command: "fxaccounts:sync_preferences", + messageId: "1234", + }; + const channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + }); + + let testNum = 0; + const toTest = Object.keys(errors).length; + for (const key in errors) { + let error = errors[key]; + channel._channel.send = (message, context) => { + equal( + message.data.error.message, + error.expected, + "Should remove the profile path from the error message" + ); + testNum++; + if (testNum === toTest) { + run_next_test(); + } + }; + channel._sendError(error.err, mockMessage, mockSendingContext); + } +}); + +add_test(function test_profile_image_change_message() { + var mockMessage = { + command: "profile:change", + data: { uid: "foo" }, + }; + + makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) { + Assert.equal(data, "foo"); + run_next_test(); + }); + + var channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_login_message() { + let mockMessage = { + command: "fxaccounts:login", + data: { email: "testuser@testuser.com" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + login(accountData) { + Assert.equal(accountData.email, "testuser@testuser.com"); + run_next_test(); + return Promise.resolve(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_logout_message() { + let mockMessage = { + command: "fxaccounts:logout", + data: { uid: "foo" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + logout(uid) { + Assert.equal(uid, "foo"); + run_next_test(); + return Promise.resolve(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_delete_message() { + let mockMessage = { + command: "fxaccounts:delete", + data: { uid: "foo" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + logout(uid) { + Assert.equal(uid, "foo"); + run_next_test(); + return Promise.resolve(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_can_link_account_message() { + let mockMessage = { + command: "fxaccounts:can_link_account", + data: { email: "testuser@testuser.com" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + shouldAllowRelink(email) { + Assert.equal(email, "testuser@testuser.com"); + run_next_test(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_sync_preferences_message() { + let mockMessage = { + command: "fxaccounts:sync_preferences", + data: { entryPoint: "fxa:verification_complete" }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + openSyncPreferences(browser, entryPoint) { + Assert.equal(entryPoint, "fxa:verification_complete"); + Assert.equal( + browser, + mockSendingContext.browsingContext.top.embedderElement + ); + run_next_test(); + }, + }, + }); + + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); +}); + +add_test(function test_fxa_status_message() { + let mockMessage = { + command: "fxaccounts:fxa_status", + messageId: 123, + data: { + service: "sync", + context: "fx_desktop_v3", + }, + }; + + let channel = new FxAccountsWebChannel({ + channel_id: WEBCHANNEL_ID, + content_uri: URL_STRING, + helpers: { + async getFxaStatus(service, sendingContext, isPairing, context) { + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + Assert.ok(!isPairing); + Assert.equal(context, "fx_desktop_v3"); + return { + signedInUser: { + email: "testuser@testuser.com", + sessionToken: "session-token", + uid: "uid", + verified: true, + }, + capabilities: { + engines: ["creditcards", "addresses"], + }, + }; + }, + }, + }); + + channel._channel = { + send(response, sendingContext) { + Assert.equal(response.command, "fxaccounts:fxa_status"); + Assert.equal(response.messageId, 123); + + let signedInUser = response.data.signedInUser; + Assert.ok(!!signedInUser); + Assert.equal(signedInUser.email, "testuser@testuser.com"); + Assert.equal(signedInUser.sessionToken, "session-token"); + Assert.equal(signedInUser.uid, "uid"); + Assert.equal(signedInUser.verified, true); + + deepEqual(response.data.capabilities.engines, [ + "creditcards", + "addresses", + ]); + + 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, + content_uri: URL_STRING, + }); + + // no error is expected. + channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext); + run_next_test(); +}); + +add_test(function test_helpers_should_allow_relink_same_email() { + let helpers = new FxAccountsWebChannelHelpers(); + + helpers.setPreviousAccountNameHashPref("testuser@testuser.com"); + Assert.ok(helpers.shouldAllowRelink("testuser@testuser.com")); + + run_next_test(); +}); + +add_test(function test_helpers_should_allow_relink_different_email() { + let helpers = new FxAccountsWebChannelHelpers(); + + helpers.setPreviousAccountNameHashPref("testuser@testuser.com"); + + helpers._promptForRelink = acctName => { + return acctName === "allowed_to_relink@testuser.com"; + }; + + Assert.ok(helpers.shouldAllowRelink("allowed_to_relink@testuser.com")); + Assert.ok(!helpers.shouldAllowRelink("not_allowed_to_relink@testuser.com")); + + run_next_test(); +}); + +add_task(async function test_helpers_login_without_customize_sync() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // ensure fxAccounts is informed of the new user being signed in. + Assert.equal(accountData.email, "testuser@testuser.com"); + + // verifiedCanLinkAccount should be stripped in the data. + Assert.equal(false, "verifiedCanLinkAccount" in accountData); + + resolve(); + }); + }, + }, + telemetry: { + recordConnection: sinon.spy(), + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + // ensure the previous account pref is overwritten. + helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: false, + }); + Assert.ok( + helpers._fxAccounts.telemetry.recordConnection.calledWith([], "webchannel") + ); +}); + +add_task(async function test_helpers_login_set_previous_account_name_hash() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // previously signed in user preference is updated. + Assert.equal( + helpers.getPreviousAccountNameHashPref(), + CryptoUtils.sha256Base64("newuser@testuser.com") + ); + resolve(); + }); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + // ensure the previous account pref is overwritten. + helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); + + await helpers.login({ + email: "newuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: false, + verified: true, + }); +}); + +add_task( + async function test_helpers_login_dont_set_previous_account_name_hash_for_unverified_emails() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // previously signed in user preference should not be updated. + Assert.equal( + helpers.getPreviousAccountNameHashPref(), + CryptoUtils.sha256Base64("lastuser@testuser.com") + ); + resolve(); + }); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + // ensure the previous account pref is overwritten. + helpers.setPreviousAccountNameHashPref("lastuser@testuser.com"); + + await helpers.login({ + email: "newuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: false, + }); + } +); + +add_task(async function test_helpers_login_with_customize_sync() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // ensure fxAccounts is informed of the new user being signed in. + Assert.equal(accountData.email, "testuser@testuser.com"); + + // customizeSync should be stripped in the data. + Assert.equal(false, "customizeSync" in accountData); + + resolve(); + }); + }, + }, + telemetry: { + recordConnection: sinon.spy(), + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() {}, + }, + }, + }, + }); + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: true, + }); + Assert.ok( + helpers._fxAccounts.telemetry.recordConnection.calledWith([], "webchannel") + ); +}); + +add_task( + async function test_helpers_login_with_customize_sync_and_declined_engines() { + let configured = false; + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + setSignedInUser(accountData) { + return new Promise(resolve => { + // ensure fxAccounts is informed of the new user being signed in. + Assert.equal(accountData.email, "testuser@testuser.com"); + + // customizeSync should be stripped in the data. + Assert.equal(false, "customizeSync" in accountData); + Assert.equal(false, "services" in accountData); + resolve(); + }); + }, + }, + telemetry: { + recordConnection: sinon.spy(), + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() { + configured = true; + }, + }, + }, + }, + }); + + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.addons"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.bookmarks"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.history"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.passwords"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.prefs"), + true + ); + Assert.equal(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: true, + services: { + sync: { + offeredEngines: [ + "addons", + "bookmarks", + "history", + "passwords", + "prefs", + ], + declinedEngines: ["addons", "prefs"], + }, + }, + }); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.addons"), + false + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.bookmarks"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.history"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.passwords"), + true + ); + Assert.equal( + Services.prefs.getBoolPref("services.sync.engine.prefs"), + false + ); + Assert.equal(Services.prefs.getBoolPref("services.sync.engine.tabs"), true); + Assert.ok(configured, "sync was configured"); + Assert.ok( + helpers._fxAccounts.telemetry.recordConnection.calledWith( + ["sync"], + "webchannel" + ) + ); + } +); + +add_task(async function test_helpers_login_with_offered_sync_engines() { + let helpers; + let configured = false; + const setSignedInUserCalled = new Promise(resolve => { + helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + async setSignedInUser(accountData) { + resolve(accountData); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() { + configured = true; + }, + }, + }, + }, + }); + }); + + Services.prefs.setBoolPref("services.sync.engine.creditcards", false); + Services.prefs.setBoolPref("services.sync.engine.addresses", false); + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + customizeSync: true, + services: { + sync: { + declinedEngines: ["addresses"], + offeredEngines: ["creditcards", "addresses"], + }, + }, + }); + + const accountData = await setSignedInUserCalled; + + // ensure fxAccounts is informed of the new user being signed in. + equal(accountData.email, "testuser@testuser.com"); + + // services should be stripped in the data. + ok(!("services" in accountData)); + // credit cards was offered but not declined. + equal(Services.prefs.getBoolPref("services.sync.engine.creditcards"), true); + // addresses was offered and explicitely declined. + equal(Services.prefs.getBoolPref("services.sync.engine.addresses"), false); + ok(configured); +}); + +add_task(async function test_helpers_login_nothing_offered() { + let helpers; + let configured = false; + const setSignedInUserCalled = new Promise(resolve => { + helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + async setSignedInUser(accountData) { + resolve(accountData); + }, + }, + telemetry: { + recordConnection() {}, + }, + }, + weaveXPCOM: { + whenLoaded() {}, + Weave: { + Service: { + configure() { + configured = true; + }, + }, + }, + }, + }); + }); + + // doesn't really matter if it's *all* engines... + const allEngines = [ + "addons", + "addresses", + "bookmarks", + "creditcards", + "history", + "passwords", + "prefs", + ]; + for (let name of allEngines) { + Services.prefs.clearUserPref("services.sync.engine." + name); + } + + await helpers.login({ + email: "testuser@testuser.com", + verifiedCanLinkAccount: true, + services: { + sync: {}, + }, + }); + + const accountData = await setSignedInUserCalled; + // ensure fxAccounts is informed of the new user being signed in. + equal(accountData.email, "testuser@testuser.com"); + + for (let name of allEngines) { + Assert.ok(!Services.prefs.prefHasUserValue("services.sync.engine." + name)); + } + Assert.ok(configured); +}); + +add_test(function test_helpers_open_sync_preferences() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: {}, + }); + + let mockBrowser = { + loadURI(uri) { + Assert.equal( + uri.spec, + "about:preferences?entrypoint=fxa%3Averification_complete#sync" + ); + run_next_test(); + }, + }; + + helpers.openSyncPreferences(mockBrowser, "fxa:verification_complete"); +}); + +add_task(async function test_helpers_getFxAStatus_extra_engines() { + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + return Promise.resolve({ + email: "testuser@testuser.com", + sessionToken: "sessionToken", + uid: "uid", + verified: true, + }); + }, + }, + }, + privateBrowsingUtils: { + isBrowserPrivate: () => true, + }, + }); + + Services.prefs.setBoolPref( + "services.sync.engine.creditcards.available", + true + ); + // Not defining "services.sync.engine.addresses.available" on purpose. + + let fxaStatus = await helpers.getFxaStatus("sync", mockSendingContext); + ok(!!fxaStatus); + ok(!!fxaStatus.signedInUser); + deepEqual(fxaStatus.capabilities.engines, ["creditcards"]); +}); + +add_task(async function test_helpers_getFxaStatus_allowed_signedInUser() { + let wasCalled = { + getUserAccountData: false, + shouldAllowFxaStatus: false, + }; + + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + wasCalled.getUserAccountData = true; + return Promise.resolve({ + email: "testuser@testuser.com", + sessionToken: "sessionToken", + uid: "uid", + verified: true, + }); + }, + }, + }, + }); + + helpers.shouldAllowFxaStatus = (service, sendingContext) => { + wasCalled.shouldAllowFxaStatus = true; + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + + return true; + }; + + return helpers.getFxaStatus("sync", mockSendingContext).then(fxaStatus => { + Assert.ok(!!fxaStatus); + Assert.ok(wasCalled.getUserAccountData); + Assert.ok(wasCalled.shouldAllowFxaStatus); + + Assert.ok(!!fxaStatus.signedInUser); + let { signedInUser } = fxaStatus; + + Assert.equal(signedInUser.email, "testuser@testuser.com"); + Assert.equal(signedInUser.sessionToken, "sessionToken"); + Assert.equal(signedInUser.uid, "uid"); + Assert.ok(signedInUser.verified); + + // These properties are filtered and should not + // be returned to the requester. + Assert.equal(false, "kSync" in signedInUser); + Assert.equal(false, "kXCS" in signedInUser); + Assert.equal(false, "kExtSync" in signedInUser); + Assert.equal(false, "kExtKbHash" in signedInUser); + }); +}); + +add_task(async function test_helpers_getFxaStatus_allowed_no_signedInUser() { + let wasCalled = { + getUserAccountData: false, + shouldAllowFxaStatus: false, + }; + + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + wasCalled.getUserAccountData = true; + return Promise.resolve(null); + }, + }, + }, + }); + + helpers.shouldAllowFxaStatus = (service, sendingContext) => { + wasCalled.shouldAllowFxaStatus = true; + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + + return true; + }; + + return helpers.getFxaStatus("sync", mockSendingContext).then(fxaStatus => { + Assert.ok(!!fxaStatus); + Assert.ok(wasCalled.getUserAccountData); + Assert.ok(wasCalled.shouldAllowFxaStatus); + + Assert.equal(null, fxaStatus.signedInUser); + }); +}); + +add_task(async function test_helpers_getFxaStatus_not_allowed() { + let wasCalled = { + getUserAccountData: false, + shouldAllowFxaStatus: false, + }; + + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + getUserAccountData() { + wasCalled.getUserAccountData = true; + return Promise.resolve(null); + }, + }, + }, + }); + + helpers.shouldAllowFxaStatus = ( + service, + sendingContext, + isPairing, + context + ) => { + wasCalled.shouldAllowFxaStatus = true; + Assert.equal(service, "sync"); + Assert.equal(sendingContext, mockSendingContext); + Assert.ok(!isPairing); + Assert.equal(context, "fx_desktop_v3"); + + return false; + }; + + return helpers + .getFxaStatus("sync", mockSendingContext, false, "fx_desktop_v3") + .then(fxaStatus => { + Assert.ok(!!fxaStatus); + Assert.ok(!wasCalled.getUserAccountData); + Assert.ok(wasCalled.shouldAllowFxaStatus); + + Assert.equal(null, fxaStatus.signedInUser); + }); +}); + +add_task( + async function test_helpers_shouldAllowFxaStatus_sync_service_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "sync", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_desktop_context_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false, + "fx_desktop_v3" + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_oauth_service_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "dcdb5ae7add825d2", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_no_service_not_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return false; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_sync_service_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "sync", + mockSendingContext, + false + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_desktop_context_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false, + "fx_desktop_v3" + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_oauth_service_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "dcdb5ae7add825d2", + mockSendingContext, + false + ); + Assert.ok(!shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_oauth_service_pairing_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "dcdb5ae7add825d2", + mockSendingContext, + true + ); + Assert.ok(shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task( + async function test_helpers_shouldAllowFxaStatus_no_service_private_browsing() { + let wasCalled = { + isPrivateBrowsingMode: false, + }; + let helpers = new FxAccountsWebChannelHelpers({}); + + helpers.isPrivateBrowsingMode = sendingContext => { + wasCalled.isPrivateBrowsingMode = true; + Assert.equal(sendingContext, mockSendingContext); + return true; + }; + + let shouldAllowFxaStatus = helpers.shouldAllowFxaStatus( + "", + mockSendingContext, + false + ); + Assert.ok(!shouldAllowFxaStatus); + Assert.ok(wasCalled.isPrivateBrowsingMode); + } +); + +add_task(async function test_helpers_isPrivateBrowsingMode_private_browsing() { + let wasCalled = { + isBrowserPrivate: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + privateBrowsingUtils: { + isBrowserPrivate(browser) { + wasCalled.isBrowserPrivate = true; + Assert.equal( + browser, + mockSendingContext.browsingContext.top.embedderElement + ); + return true; + }, + }, + }); + + let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext); + Assert.ok(isPrivateBrowsingMode); + Assert.ok(wasCalled.isBrowserPrivate); +}); + +add_task(async function test_helpers_isPrivateBrowsingMode_private_browsing() { + let wasCalled = { + isBrowserPrivate: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + privateBrowsingUtils: { + isBrowserPrivate(browser) { + wasCalled.isBrowserPrivate = true; + Assert.equal( + browser, + mockSendingContext.browsingContext.top.embedderElement + ); + return false; + }, + }, + }); + + let isPrivateBrowsingMode = helpers.isPrivateBrowsingMode(mockSendingContext); + Assert.ok(!isPrivateBrowsingMode); + Assert.ok(wasCalled.isBrowserPrivate); +}); + +add_task(async function test_helpers_change_password() { + let wasCalled = { + updateUserAccountData: false, + updateDeviceRegistration: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + updateUserAccountData(credentials) { + return new Promise(resolve => { + Assert.ok(credentials.hasOwnProperty("email")); + Assert.ok(credentials.hasOwnProperty("uid")); + Assert.ok(credentials.hasOwnProperty("unwrapBKey")); + Assert.ok(credentials.hasOwnProperty("device")); + Assert.equal(null, credentials.device); + Assert.equal(null, credentials.encryptedSendTabKeys); + // "foo" isn't a field known by storage, so should be dropped. + Assert.ok(!credentials.hasOwnProperty("foo")); + wasCalled.updateUserAccountData = true; + + resolve(); + }); + }, + + updateDeviceRegistration() { + Assert.equal(arguments.length, 0); + wasCalled.updateDeviceRegistration = true; + return Promise.resolve(); + }, + }, + }, + }); + await helpers.changePassword({ + email: "email", + uid: "uid", + unwrapBKey: "unwrapBKey", + foo: "foo", + }); + Assert.ok(wasCalled.updateUserAccountData); + Assert.ok(wasCalled.updateDeviceRegistration); +}); + +add_task(async function test_helpers_change_password_with_error() { + let wasCalled = { + updateUserAccountData: false, + updateDeviceRegistration: false, + }; + let helpers = new FxAccountsWebChannelHelpers({ + fxAccounts: { + _internal: { + updateUserAccountData() { + wasCalled.updateUserAccountData = true; + return Promise.reject(); + }, + + updateDeviceRegistration() { + wasCalled.updateDeviceRegistration = true; + return Promise.resolve(); + }, + }, + }, + }); + try { + await helpers.changePassword({}); + Assert.equal(false, "changePassword should have rejected"); + } catch (_) { + Assert.ok(wasCalled.updateUserAccountData); + Assert.ok(!wasCalled.updateDeviceRegistration); + } +}); + +function makeObserver(aObserveTopic, aObserveFunc) { + let callback = function (aSubject, aTopic, aData) { + log.debug("observed " + aTopic + " " + aData); + if (aTopic == aObserveTopic) { + removeMe(); + aObserveFunc(aSubject, aTopic, aData); + } + }; + + function removeMe() { + log.debug("removing observer for " + aObserveTopic); + Services.obs.removeObserver(callback, aObserveTopic); + } + + Services.obs.addObserver(callback, aObserveTopic); + return removeMe; +} + +function validationHelper(params, expected) { + try { + new FxAccountsWebChannel(params); + } catch (e) { + if (typeof expected === "string") { + return Assert.equal(e.toString(), expected); + } + return Assert.ok(e.toString().match(expected)); + } + throw new Error("Validation helper error"); +} -- cgit v1.2.3