summaryrefslogtreecommitdiff
path: root/services/fxaccounts/FxAccountsWebChannel.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'services/fxaccounts/FxAccountsWebChannel.jsm')
-rw-r--r--services/fxaccounts/FxAccountsWebChannel.jsm474
1 files changed, 0 insertions, 474 deletions
diff --git a/services/fxaccounts/FxAccountsWebChannel.jsm b/services/fxaccounts/FxAccountsWebChannel.jsm
deleted file mode 100644
index 810d93c651..0000000000
--- a/services/fxaccounts/FxAccountsWebChannel.jsm
+++ /dev/null
@@ -1,474 +0,0 @@
-/* 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/. */
-
-/**
- * Firefox Accounts Web Channel.
- *
- * Uses the WebChannel component to receive messages
- * about account state changes.
- */
-
-this.EXPORTED_SYMBOLS = ["EnsureFxAccountsWebChannel"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
- "resource://gre/modules/WebChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
- "resource://gre/modules/FxAccounts.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsStorageManagerCanStoreField",
- "resource://gre/modules/FxAccountsStorage.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_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 PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
-const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
-
-/**
- * A helper function that extracts the message and stack from an error object.
- * Returns a `{ message, stack }` tuple. `stack` will be null if the error
- * doesn't have a stack trace.
- */
-function getErrorDetails(error) {
- let details = { message: String(error), stack: null };
-
- // Adapted from Console.jsm.
- if (error.stack) {
- let frames = [];
- for (let frame = error.stack; frame; frame = frame.caller) {
- frames.push(String(frame).padStart(4));
- }
- details.stack = frames.join("\n");
- }
-
- return details;
-}
-
-/**
- * Create a new FxAccountsWebChannel to listen for account updates
- *
- * @param {Object} options Options
- * @param {Object} options
- * @param {String} options.content_uri
- * The FxA Content server uri
- * @param {String} options.channel_id
- * The ID of the WebChannel
- * @param {String} options.helpers
- * Helpers functions. Should only be passed in for testing.
- * @constructor
- */
-this.FxAccountsWebChannel = function(options) {
- if (!options) {
- throw new Error("Missing configuration options");
- }
- if (!options["content_uri"]) {
- throw new Error("Missing 'content_uri' option");
- }
- this._contentUri = options.content_uri;
-
- if (!options["channel_id"]) {
- throw new Error("Missing 'channel_id' option");
- }
- this._webChannelId = options.channel_id;
-
- // options.helpers is only specified by tests.
- this._helpers = options.helpers || new FxAccountsWebChannelHelpers(options);
-
- this._setupChannel();
-};
-
-this.FxAccountsWebChannel.prototype = {
- /**
- * WebChannel that is used to communicate with content page
- */
- _channel: null,
-
- /**
- * Helpers interface that does the heavy lifting.
- */
- _helpers: null,
-
- /**
- * WebChannel ID.
- */
- _webChannelId: null,
- /**
- * WebChannel origin, used to validate origin of messages
- */
- _webChannelOrigin: null,
-
- /**
- * Release all resources that are in use.
- */
- tearDown() {
- this._channel.stopListening();
- this._channel = null;
- this._channelCallback = null;
- },
-
- /**
- * Configures and registers a new WebChannel
- *
- * @private
- */
- _setupChannel() {
- // if this.contentUri is present but not a valid URI, then this will throw an error.
- try {
- this._webChannelOrigin = Services.io.newURI(this._contentUri, null, null);
- this._registerChannel();
- } catch (e) {
- log.error(e);
- throw e;
- }
- },
-
- _receiveMessage(message, sendingContext) {
- let command = message.command;
- let data = message.data;
-
- switch (command) {
- case COMMAND_PROFILE_CHANGE:
- Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, data.uid);
- break;
- case COMMAND_LOGIN:
- this._helpers.login(data).catch(error =>
- this._sendError(error, message, sendingContext));
- break;
- case COMMAND_LOGOUT:
- case COMMAND_DELETE:
- this._helpers.logout(data.uid).catch(error =>
- this._sendError(error, message, sendingContext));
- break;
- case COMMAND_CAN_LINK_ACCOUNT:
- let canLinkAccount = this._helpers.shouldAllowRelink(data.email);
-
- let response = {
- command: command,
- messageId: message.messageId,
- data: { ok: canLinkAccount }
- };
-
- log.debug("FxAccountsWebChannel response", response);
- this._channel.send(response, sendingContext);
- break;
- case COMMAND_SYNC_PREFERENCES:
- this._helpers.openSyncPreferences(sendingContext.browser, data.entryPoint);
- break;
- case COMMAND_CHANGE_PASSWORD:
- this._helpers.changePassword(data).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);
- this._channel.send({
- command: incomingMessage.command,
- messageId: incomingMessage.messageId,
- data: {
- error: getErrorDetails(error),
- },
- }, sendingContext);
- },
-
- /**
- * Create a new channel with the WebChannelBroker, setup a callback listener
- * @private
- */
- _registerChannel() {
- /**
- * Processes messages that are called back from the FxAccountsChannel
- *
- * @param webChannelId {String}
- * Command webChannelId
- * @param message {Object}
- * Command message
- * @param sendingContext {Object}
- * Message sending context.
- * @param sendingContext.browser {browser}
- * The <browser> object that captured the
- * WebChannelMessageToChrome.
- * @param sendingContext.eventTarget {EventTarget}
- * The <EventTarget> where the message was sent.
- * @param sendingContext.principal {Principal}
- * The <Principal> of the EventTarget where the message was sent.
- * @private
- *
- */
- let listener = (webChannelId, message, sendingContext) => {
- if (message) {
- log.debug("FxAccountsWebChannel message received", message.command);
- if (logPII) {
- log.debug("FxAccountsWebChannel message details", message);
- }
- try {
- this._receiveMessage(message, sendingContext);
- } catch (error) {
- this._sendError(error, message, sendingContext);
- }
- }
- };
-
- this._channelCallback = listener;
- this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
- this._channel.listen(listener);
- log.debug("FxAccountsWebChannel registered: " + this._webChannelId + " with origin " + this._webChannelOrigin.prePath);
- }
-};
-
-this.FxAccountsWebChannelHelpers = function(options) {
- options = options || {};
-
- this._fxAccounts = options.fxAccounts || fxAccounts;
-};
-
-this.FxAccountsWebChannelHelpers.prototype = {
- // If the last fxa account used for sync isn't this account, we display
- // a modal dialog checking they really really want to do this...
- // (This is sync-specific, so ideally would be in sync's identity module,
- // but it's a little more seamless to do here, and sync is currently the
- // only fxa consumer, so...
- shouldAllowRelink(acctName) {
- return !this._needRelinkWarning(acctName) ||
- this._promptForRelink(acctName);
- },
-
- /**
- * New users are asked in the content server whether they want to
- * customize which data should be synced. The user is only shown
- * the dialog listing the possible data types upon verification.
- *
- * Save a bit into prefs that is read on verification to see whether
- * to show the list of data types that can be saved.
- */
- setShowCustomizeSyncPref(showCustomizeSyncPref) {
- Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, showCustomizeSyncPref);
- },
-
- getShowCustomizeSyncPref() {
- return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION);
- },
-
- /**
- * stores sync login info it in the fxaccounts service
- *
- * @param accountData the user's account data and credentials
- */
- login(accountData) {
- if (accountData.customizeSync) {
- this.setShowCustomizeSyncPref(true);
- delete accountData.customizeSync;
- }
-
- if (accountData.declinedSyncEngines) {
- let declinedSyncEngines = accountData.declinedSyncEngines;
- log.debug("Received declined engines", declinedSyncEngines);
- Weave.Service.engineManager.setDeclined(declinedSyncEngines);
- declinedSyncEngines.forEach(engine => {
- Services.prefs.setBoolPref("services.sync.engine." + engine, false);
- });
-
- // if we got declinedSyncEngines that means we do not need to show the customize screen.
- this.setShowCustomizeSyncPref(false);
- delete accountData.declinedSyncEngines;
- }
-
- // the user has already been shown the "can link account"
- // screen. No need to keep this data around.
- delete accountData.verifiedCanLinkAccount;
-
- // Remember who it was so we can log out next time.
- this.setPreviousAccountNameHashPref(accountData.email);
-
- // A sync-specific hack - we want to ensure sync has been initialized
- // before we set the signed-in user.
- let xps = Cc["@mozilla.org/weave/service;1"]
- .getService(Ci.nsISupports)
- .wrappedJSObject;
- return xps.whenLoaded().then(() => {
- return this._fxAccounts.setSignedInUser(accountData);
- });
- },
-
- /**
- * logout the fxaccounts service
- *
- * @param the uid of the account which have been logged out
- */
- logout(uid) {
- return fxAccounts.getSignedInUser().then(userData => {
- if (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);
- }
- });
- },
-
- changePassword(credentials) {
- // If |credentials| has fields that aren't handled by accounts storage,
- // updateUserAccountData will throw - mainly to prevent errors in code
- // that hard-codes field names.
- // However, in this case the field names aren't really in our control.
- // We *could* still insist the server know what fields names are valid,
- // but that makes life difficult for the server when Firefox adds new
- // features (ie, new fields) - forcing the server to track a map of
- // versions to supported field names doesn't buy us much.
- // So we just remove field names we know aren't handled.
- let newCredentials = {
- deviceId: null
- };
- for (let name of Object.keys(credentials)) {
- if (name == "email" || name == "uid" || FxAccountsStorageManagerCanStoreField(name)) {
- newCredentials[name] = credentials[name];
- } else {
- log.info("changePassword ignoring unsupported field", name);
- }
- }
- return this._fxAccounts.updateUserAccountData(newCredentials)
- .then(() => this._fxAccounts.updateDeviceRegistration());
- },
-
- /**
- * Get the hash of account name of the previously signed in account
- */
- getPreviousAccountNameHashPref() {
- try {
- return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
- } catch (_) {
- return "";
- }
- },
-
- /**
- * Given an account name, set the hash of the previously signed in account
- *
- * @param acctName the account name of the user's account.
- */
- setPreviousAccountNameHashPref(acctName) {
- let string = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- string.data = this.sha256(acctName);
- Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
- },
-
- /**
- * Given a string, returns the SHA265 hash in base64
- */
- sha256(str) {
- let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = "UTF-8";
- // Data is an array of bytes.
- let data = converter.convertToByteArray(str, {});
- let hasher = Cc["@mozilla.org/security/hash;1"]
- .createInstance(Ci.nsICryptoHash);
- hasher.init(hasher.SHA256);
- hasher.update(data, data.length);
-
- return hasher.finish(true);
- },
-
- /**
- * Open Sync Preferences in the current tab of the browser
- *
- * @param {Object} browser the browser in which to open preferences
- * @param {String} [entryPoint] entryPoint to use for logging
- */
- openSyncPreferences(browser, entryPoint) {
- let uri = "about:preferences";
- if (entryPoint) {
- uri += "?entrypoint=" + encodeURIComponent(entryPoint);
- }
- uri += "#sync";
-
- browser.loadURI(uri);
- },
-
- /**
- * If a user signs in using a different account, the data from the
- * previous account and the new account will be merged. Ask the user
- * if they want to continue.
- *
- * @private
- */
- _needRelinkWarning(acctName) {
- let prevAcctHash = this.getPreviousAccountNameHashPref();
- return prevAcctHash && prevAcctHash != this.sha256(acctName);
- },
-
- /**
- * Show the user a warning dialog that the data from the previous account
- * and the new account will be merged.
- *
- * @private
- */
- _promptForRelink(acctName) {
- let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
- let continueLabel = sb.GetStringFromName("continue.label");
- let title = sb.GetStringFromName("relinkVerify.title");
- let description = sb.formatStringFromName("relinkVerify.description",
- [acctName], 1);
- let body = sb.GetStringFromName("relinkVerify.heading") +
- "\n\n" + description;
- let ps = Services.prompt;
- let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
- (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
- ps.BUTTON_POS_1_DEFAULT;
-
- // If running in context of the browser chrome, window does not exist.
- var targetWindow = typeof window === 'undefined' ? null : window;
- let pressed = Services.prompt.confirmEx(targetWindow, title, body, buttonFlags,
- continueLabel, null, null, null,
- {});
- return pressed === 0; // 0 is the "continue" button
- }
-};
-
-var singleton;
-// The entry-point for this module, which ensures only one of our channels is
-// ever created - we require this because the WebChannel is global in scope
-// (eg, it uses the observer service to tell interested parties of interesting
-// things) and allowing multiple channels would cause such notifications to be
-// sent multiple times.
-this.EnsureFxAccountsWebChannel = function() {
- let contentUri = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
- if (singleton && singleton._contentUri !== contentUri) {
- singleton.tearDown();
- singleton = null;
- }
- if (!singleton) {
- try {
- if (contentUri) {
- // The FxAccountsWebChannel listens for events and updates
- // the state machine accordingly.
- singleton = new this.FxAccountsWebChannel({
- content_uri: contentUri,
- channel_id: WEBCHANNEL_ID,
- });
- } else {
- log.warn("FxA WebChannel functionaly is disabled due to no URI pref.");
- }
- } catch (ex) {
- log.error("Failed to create FxA WebChannel", ex);
- }
- }
-}