summaryrefslogtreecommitdiff
path: root/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch
diff options
context:
space:
mode:
Diffstat (limited to 'data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch')
-rw-r--r--data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch551
1 files changed, 0 insertions, 551 deletions
diff --git a/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch b/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch
deleted file mode 100644
index bbd6f8d..0000000
--- a/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch
+++ /dev/null
@@ -1,551 +0,0 @@
-From 4a92f9ee1ba35989f82a24bba18806f8616a5be8 Mon Sep 17 00:00:00 2001
-From: Mark Hammond <mhammond@skippinet.com.au>
-Date: Sat, 14 Jun 2014 14:33:20 +1000
-Subject: Bug 1013064 (part 2) - Store sensitive FxA credentials in the login
- manager. r=ckarlof,dveditz
-
----
- services/fxaccounts/FxAccounts.jsm | 170 ++++++++++++++++++
- services/fxaccounts/FxAccountsCommon.js | 13 ++
- services/fxaccounts/moz.build | 5 +-
- .../tests/xpcshell/test_loginmgr_storage.js | 196 +++++++++++++++++++++
- services/fxaccounts/tests/xpcshell/xpcshell.ini | 2 +
- services/sync/modules/util.js | 12 +-
- 6 files changed, 396 insertions(+), 2 deletions(-)
- create mode 100644 services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
-
-diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm
-index cc521b8..e9328dd 100644
---- a/services/fxaccounts/FxAccounts.jsm
-+++ b/services/fxaccounts/FxAccounts.jsm
-@@ -298,17 +298,22 @@ function FxAccountsInternal() {
- // able to abort all work on the first sign-in process. The currentTimer and
- // currentAccountState are used for this purpose.
- // (XXX - should the timer be directly on the currentAccountState?)
- this.currentTimer = null;
- this.currentAccountState = new AccountState(this);
-
- // We don't reference |profileDir| in the top-level module scope
- // as we may be imported before we know where it is.
-+ // We only want the fancy new LoginManagerStorage on desktop.
-+#if defined(MOZ_B2G)
- this.signedInUserStorage = new JSONStorage({
-+#else
-+ this.signedInUserStorage = new LoginManagerStorage({
-+#endif
- filename: DEFAULT_STORAGE_FILENAME,
- baseDir: OS.Constants.Path.profileDir,
- });
- }
-
- /**
- * The internal API's prototype.
- */
-@@ -896,16 +901,181 @@ JSONStorage.prototype = {
- .then(CommonUtils.writeJSON.bind(null, contents, this.path));
- },
-
- get: function() {
- return CommonUtils.readJSON(this.path);
- }
- };
-
-+/**
-+ * LoginManagerStorage constructor that creates instances that may set/get
-+ * from a combination of a clear-text JSON file and stored securely in
-+ * the nsILoginManager.
-+ *
-+ * @param options {
-+ * filename: of the plain-text file to write to
-+ * baseDir: directory where the file resides
-+ * }
-+ * @return instance
-+ */
-+
-+function LoginManagerStorage(options) {
-+ // we reuse the JSONStorage for writing the plain-text stuff.
-+ this.jsonStorage = new JSONStorage(options);
-+}
-+
-+LoginManagerStorage.prototype = {
-+ // The fields in the credentials JSON object that are stored in plain-text
-+ // in the profile directory. All other fields are stored in the login manager,
-+ // and thus are only available when the master-password is unlocked.
-+
-+ // a hook point for testing.
-+ get _isLoggedIn() {
-+ return Services.logins.isLoggedIn;
-+ },
-+
-+ // Clear any data from the login manager. Returns true if the login manager
-+ // was unlocked (even if no existing logins existed) or false if it was
-+ // locked (meaning we don't even know if it existed or not.)
-+ _clearLoginMgrData: Task.async(function* () {
-+ try { // Services.logins might be third-party and broken...
-+ yield Services.logins.initializationPromise;
-+ if (!this._isLoggedIn) {
-+ return false;
-+ }
-+ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
-+ for (let login of logins) {
-+ Services.logins.removeLogin(login);
-+ }
-+ return true;
-+ } catch (ex) {
-+ log.error("Failed to clear login data: ${}", ex);
-+ return false;
-+ }
-+ }),
-+
-+ set: Task.async(function* (contents) {
-+ if (!contents) {
-+ // User is signing out - write the null to the json file.
-+ yield this.jsonStorage.set(contents);
-+
-+ // And nuke it from the login manager.
-+ let cleared = yield this._clearLoginMgrData();
-+ if (!cleared) {
-+ // just log a message - we verify that the email address matches when
-+ // we reload it, so having a stale entry doesn't really hurt.
-+ log.info("not removing credentials from login manager - not logged in");
-+ }
-+ return;
-+ }
-+
-+ // We are saving actual data.
-+ // Split the data into 2 chunks - one to go to the plain-text, and the
-+ // other to write to the login manager.
-+ let toWriteJSON = {version: contents.version};
-+ let accountDataJSON = toWriteJSON.accountData = {};
-+ let toWriteLoginMgr = {version: contents.version};
-+ let accountDataLoginMgr = toWriteLoginMgr.accountData = {};
-+ for (let [name, value] of Iterator(contents.accountData)) {
-+ if (FXA_PWDMGR_PLAINTEXT_FIELDS.indexOf(name) >= 0) {
-+ accountDataJSON[name] = value;
-+ } else {
-+ accountDataLoginMgr[name] = value;
-+ }
-+ }
-+ yield this.jsonStorage.set(toWriteJSON);
-+
-+ try { // Services.logins might be third-party and broken...
-+ // and the stuff into the login manager.
-+ yield Services.logins.initializationPromise;
-+ // If MP is locked we silently fail - the user may need to re-auth
-+ // next startup.
-+ if (!this._isLoggedIn) {
-+ log.info("not saving credentials to login manager - not logged in");
-+ return;
-+ }
-+ // write the rest of the data to the login manager.
-+ let loginInfo = new Components.Constructor(
-+ "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
-+ let login = new loginInfo(FXA_PWDMGR_HOST,
-+ null, // aFormSubmitURL,
-+ FXA_PWDMGR_REALM, // aHttpRealm,
-+ contents.accountData.email, // aUsername
-+ JSON.stringify(toWriteLoginMgr), // aPassword
-+ "", // aUsernameField
-+ "");// aPasswordField
-+
-+ let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null,
-+ FXA_PWDMGR_REALM);
-+ if (existingLogins.length) {
-+ Services.logins.modifyLogin(existingLogins[0], login);
-+ } else {
-+ Services.logins.addLogin(login);
-+ }
-+ } catch (ex) {
-+ log.error("Failed to save data to the login manager: ${}", ex);
-+ }
-+ }),
-+
-+ get: Task.async(function* () {
-+ // we need to suck some data from the .json file in the profile dir and
-+ // some other from the login manager.
-+ let data = yield this.jsonStorage.get();
-+ if (!data) {
-+ // no user logged in, nuke the storage data incase we couldn't remove
-+ // it previously and then we are done.
-+ yield this._clearLoginMgrData();
-+ return null;
-+ }
-+
-+ // if we have encryption keys it must have been saved before we
-+ // used the login manager, so re-save it.
-+ if (data.accountData.kA || data.accountData.kB || data.keyFetchToken) {
-+ log.info("account data needs migration to the login manager.");
-+ yield this.set(data);
-+ }
-+
-+ try { // Services.logins might be third-party and broken...
-+ // read the data from the login manager and merge it for return.
-+ yield Services.logins.initializationPromise;
-+
-+ if (!this._isLoggedIn) {
-+ log.info("returning partial account data as the login manager is locked.");
-+ return data;
-+ }
-+
-+ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
-+ if (logins.length == 0) {
-+ // This could happen if the MP was locked when we wrote the data.
-+ log.info("Can't find the rest of the credentials in the login manager");
-+ return data;
-+ }
-+ let login = logins[0];
-+ if (login.username == data.accountData.email) {
-+ let lmData = JSON.parse(login.password);
-+ if (lmData.version == data.version) {
-+ // Merge the login manager data
-+ copyObjectProperties(lmData.accountData, data.accountData);
-+ } else {
-+ log.info("version field in the login manager doesn't match - ignoring it");
-+ yield this._clearLoginMgrData();
-+ }
-+ } else {
-+ log.info("username in the login manager doesn't match - ignoring it");
-+ yield this._clearLoginMgrData();
-+ }
-+ } catch (ex) {
-+ log.error("Failed to get data from the login manager: ${}", ex);
-+ }
-+ return data;
-+ }),
-+
-+}
-+
- // A getter for the instance to export
- XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() {
- let a = new FxAccounts();
-
- // XXX Bug 947061 - We need a strategy for resuming email verification after
- // browser restart
- a.loadAndPoll();
-
-diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js
-index 2bef093..476e528 100644
---- a/services/fxaccounts/FxAccountsCommon.js
-+++ b/services/fxaccounts/FxAccountsCommon.js
-@@ -170,10 +170,23 @@ SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_NONCE] = ERROR_INVALID_AUTH
- SERVER_ERRNO_TO_ERROR[ERRNO_ENDPOINT_NO_LONGER_SUPPORTED] = ERROR_ENDPOINT_NO_LONGER_SUPPORTED;
- SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_LOGIN_METHOD] = ERROR_INCORRECT_LOGIN_METHOD;
- SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD] = ERROR_INCORRECT_KEY_RETRIEVAL_METHOD;
- SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_API_VERSION] = ERROR_INCORRECT_API_VERSION;
- SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_EMAIL_CASE] = ERROR_INCORRECT_EMAIL_CASE;
- SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMP_UNAVAILABLE] = ERROR_SERVICE_TEMP_UNAVAILABLE;
- SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR] = ERROR_UNKNOWN;
-
-+// FxAccounts has the ability to "split" the credentials between a plain-text
-+// JSON file in the profile dir and in the login manager.
-+// These constants relate to that.
-+
-+// The fields we save in the plaintext JSON.
-+// See bug 1013064 comments 23-25 for why the sessionToken is "safe"
-+this.FXA_PWDMGR_PLAINTEXT_FIELDS = ["email", "verified", "authAt",
-+ "sessionToken", "uid"];
-+// The pseudo-host we use in the login manager
-+this.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
-+// The realm we use in the login manager.
-+this.FXA_PWDMGR_REALM = "Firefox Accounts credentials";
-+
- // Allow this file to be imported via Components.utils.import().
- this.EXPORTED_SYMBOLS = Object.keys(this);
-diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build
-index f959714..1bb8c09 100644
---- a/services/fxaccounts/moz.build
-+++ b/services/fxaccounts/moz.build
-@@ -5,16 +5,19 @@
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
- PARALLEL_DIRS += ['interfaces']
-
- TEST_DIRS += ['tests']
-
- EXTRA_JS_MODULES += [
- 'Credentials.jsm',
-- 'FxAccounts.jsm',
- 'FxAccountsClient.jsm',
- 'FxAccountsCommon.js'
- ]
-
-+EXTRA_PP_JS_MODULES += [
-+ 'FxAccounts.jsm',
-+]
-+
- # For now, we will only be using the FxA manager in B2G.
- if CONFIG['MOZ_B2G']:
- EXTRA_JS_MODULES += ['FxAccountsManager.jsm']
-diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
-new file mode 100644
-index 0000000..297b256
---- /dev/null
-+++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
-@@ -0,0 +1,196 @@
-+/* Any copyright is dedicated to the Public Domain.
-+ * http://creativecommons.org/publicdomain/zero/1.0/ */
-+
-+"use strict";
-+
-+// Tests for FxAccounts, storage and the master password.
-+
-+// Stop us hitting the real auth server.
-+Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost");
-+
-+Cu.import("resource://gre/modules/Services.jsm");
-+Cu.import("resource://gre/modules/FxAccounts.jsm");
-+Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-+Cu.import("resource://gre/modules/FxAccountsCommon.js");
-+Cu.import("resource://gre/modules/osfile.jsm");
-+Cu.import("resource://services-common/utils.js");
-+Cu.import("resource://gre/modules/FxAccountsCommon.js");
-+
-+initTestLogging("Trace");
-+// See verbose logging from FxAccounts.jsm
-+Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG");
-+
-+function run_test() {
-+ run_next_test();
-+}
-+
-+function getLoginMgrData() {
-+ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
-+ if (logins.length == 0) {
-+ return null;
-+ }
-+ Assert.equal(logins.length, 1, "only 1 login available");
-+ return logins[0];
-+}
-+
-+add_task(function test_simple() {
-+ let fxa = new FxAccounts({});
-+
-+ let creds = {
-+ email: "test@example.com",
-+ sessionToken: "sessionToken",
-+ kA: "the kA value",
-+ kB: "the kB value",
-+ verified: true
-+ };
-+ yield fxa.setSignedInUser(creds);
-+
-+ // This should have stored stuff in both the .json file in the profile
-+ // dir, and the login dir.
-+ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
-+ let data = yield CommonUtils.readJSON(path);
-+
-+ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
-+ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
-+ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
-+
-+ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
-+ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
-+
-+ let login = getLoginMgrData();
-+ Assert.strictEqual(login.username, creds.email, "email matches");
-+ let loginData = JSON.parse(login.password);
-+ Assert.strictEqual(loginData.version, data.version, "same version flag in both places");
-+ Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr");
-+ Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr");
-+
-+ Assert.ok(!("email" in loginData), "email not stored in the login mgr json");
-+ Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json");
-+ Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json");
-+
-+ yield fxa.signOut(/* localOnly = */ true);
-+ Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
-+});
-+
-+add_task(function test_MPLocked() {
-+ let fxa = new FxAccounts({});
-+
-+ let creds = {
-+ email: "test@example.com",
-+ sessionToken: "sessionToken",
-+ kA: "the kA value",
-+ kB: "the kB value",
-+ verified: true
-+ };
-+
-+ // tell the storage that the MP is locked.
-+ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false);
-+ yield fxa.setSignedInUser(creds);
-+
-+ // This should have stored stuff in the .json, and the login manager stuff
-+ // will not exist.
-+ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
-+ let data = yield CommonUtils.readJSON(path);
-+
-+ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
-+ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
-+ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
-+
-+ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
-+ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
-+
-+ Assert.strictEqual(getLoginMgrData(), null, "login mgr data doesn't exist");
-+ yield fxa.signOut(/* localOnly = */ true)
-+});
-+
-+add_task(function test_consistentWithMPEdgeCases() {
-+ let fxa = new FxAccounts({});
-+
-+ let creds1 = {
-+ email: "test@example.com",
-+ sessionToken: "sessionToken",
-+ kA: "the kA value",
-+ kB: "the kB value",
-+ verified: true
-+ };
-+
-+ let creds2 = {
-+ email: "test2@example.com",
-+ sessionToken: "sessionToken2",
-+ kA: "the kA value2",
-+ kB: "the kB value2",
-+ verified: false,
-+ };
-+
-+ // Log a user in while MP is unlocked.
-+ yield fxa.setSignedInUser(creds1);
-+
-+ // tell the storage that the MP is locked - this will prevent logout from
-+ // being able to clear the data.
-+ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false);
-+
-+ // now set the second credentials.
-+ yield fxa.setSignedInUser(creds2);
-+
-+ // We should still have creds1 data in the login manager.
-+ let login = getLoginMgrData();
-+ Assert.strictEqual(login.username, creds1.email);
-+ // and that we do have the first kA in the login manager.
-+ Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds1.kA,
-+ "stale data still in login mgr");
-+
-+ // Make a new FxA instance (otherwise the values in memory will be used.)
-+ // Because we haven't overridden _isLoggedIn for this new instance it will
-+ // treat the MP as unlocked.
-+ let fxa = new FxAccounts({});
-+
-+ let accountData = yield fxa.getSignedInUser();
-+ Assert.strictEqual(accountData.email, creds2.email);
-+ // we should have no kA at all.
-+ Assert.strictEqual(accountData.kA, undefined, "stale kA wasn't used");
-+ yield fxa.signOut(/* localOnly = */ true)
-+});
-+
-+add_task(function test_migration() {
-+ // manually write out the full creds data to the JSON - this will look like
-+ // old data that needs migration.
-+ let creds = {
-+ email: "test@example.com",
-+ sessionToken: "sessionToken",
-+ kA: "the kA value",
-+ kB: "the kB value",
-+ verified: true
-+ };
-+ let toWrite = {
-+ version: 1,
-+ accountData: creds,
-+ };
-+
-+ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
-+ let data = yield CommonUtils.writeJSON(toWrite, path);
-+
-+ // Create an FxA object - and tell it to load the data.
-+ let fxa = new FxAccounts({});
-+ data = yield fxa.getSignedInUser();
-+
-+ Assert.deepEqual(data, creds, "we should have everything available");
-+
-+ // now sniff the data on disk - it should have been magically migrated.
-+ data = yield CommonUtils.readJSON(path);
-+
-+ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
-+ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
-+ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
-+
-+ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
-+ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
-+
-+ // and it should magically be in the login manager.
-+ let login = getLoginMgrData();
-+ Assert.strictEqual(login.username, creds.email);
-+ // and that we do have the first kA in the login manager.
-+ Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds.kA,
-+ "kA was migrated");
-+
-+ yield fxa.signOut(/* localOnly = */ true)
-+});
-diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.ini b/services/fxaccounts/tests/xpcshell/xpcshell.ini
-index 9d9bffe..4448d76 100644
---- a/services/fxaccounts/tests/xpcshell/xpcshell.ini
-+++ b/services/fxaccounts/tests/xpcshell/xpcshell.ini
-@@ -1,10 +1,12 @@
- [DEFAULT]
- head = head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests/unit/head_http.js
- tail =
-
- [test_accounts.js]
- [test_client.js]
- [test_credentials.js]
-+[test_loginmgr_storage.js]
-+skip-if = appname == 'b2g' # login manager storage only used on desktop.
- [test_manager.js]
- run-if = appname == 'b2g'
- reason = FxAccountsManager is only available for B2G for now
-diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js
-index 194e0b2..4691428 100644
---- a/services/sync/modules/util.js
-+++ b/services/sync/modules/util.js
-@@ -14,16 +14,23 @@ Cu.import("resource://services-common/async.js", this);
- Cu.import("resource://services-crypto/utils.js");
- Cu.import("resource://services-sync/constants.js");
- Cu.import("resource://gre/modules/Preferences.jsm");
- Cu.import("resource://gre/modules/Services.jsm", this);
- Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
- Cu.import("resource://gre/modules/osfile.jsm", this);
- Cu.import("resource://gre/modules/Task.jsm", this);
-
-+// FxAccountsCommon.js doesn't use a "namespace", so create one here.
-+XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
-+ let FxAccountsCommon = {};
-+ Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
-+ return FxAccountsCommon;
-+});
-+
- /*
- * Utility functions
- */
-
- this.Utils = {
- // Alias in functions from CommonUtils. These previously were defined here.
- // In the ideal world, references to these would be removed.
- nextTick: CommonUtils.nextTick,
-@@ -589,18 +596,21 @@ this.Utils = {
- * reset when we drop sync credentials, etc.
- */
- getSyncCredentialsHosts: function() {
- // This is somewhat expensive and the result static, so we cache the result.
- if (this._syncCredentialsHosts) {
- return this._syncCredentialsHosts;
- }
- let result = new Set();
-- // the legacy sync host.
-+ // the legacy sync host
- result.add(PWDMGR_HOST);
-+ // the FxA host
-+ result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
-+ //
- // The FxA hosts - these almost certainly all have the same hostname, but
- // better safe than sorry...
- for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
- "identity.fxaccounts.remote.signup.uri",
- "identity.fxaccounts.remote.signin.uri",
- "identity.fxaccounts.settings.uri"]) {
- let prefVal;
- try {
---
-1.8.3.msysgit.0
-