☠☠ backed out by 753df1b7c5ed ☠ ☠ | |
author | Edouard Oger <eoger@fastmail.com> |
Mon, 21 Aug 2017 17:01:57 -0400 | |
changeset 430840 | f384a524cac6e769af1498d1b67ec33834a551d5 |
parent 430839 | 9d26a627e2f825eee2276d7644ea3c1455b628b4 |
child 430841 | 753df1b7c5edb84daf585e0fb434f67755c60be2 |
push id | 7771 |
push user | ryanvm@gmail.com |
push date | Sun, 17 Sep 2017 03:17:38 +0000 |
treeherder | mozilla-beta@3d2edf73fb90 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | markh |
bugs | 1383663 |
milestone | 57.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/services/crypto/modules/utils.js +++ b/services/crypto/modules/utils.js @@ -108,16 +108,25 @@ this.CryptoUtils = { sha256(message) { let hasher = Cc["@mozilla.org/security/hash;1"] .createInstance(Ci.nsICryptoHash); hasher.init(hasher.SHA256); return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher)); }, + sha256Base64(message) { + let data = this._utf8Converter.convertToByteArray(message, {}); + let hasher = Cc["@mozilla.org/security/hash;1"] + .createInstance(Ci.nsICryptoHash); + hasher.init(hasher.SHA256); + hasher.update(data, data.length); + return hasher.finish(true); + }, + /** * Produce an HMAC key object from a key string. */ makeHMACKey: function makeHMACKey(str) { return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str); }, /**
--- a/services/fxaccounts/FxAccounts.jsm +++ b/services/fxaccounts/FxAccounts.jsm @@ -46,18 +46,19 @@ var publicProperties = [ "getAssertion", "getDeviceId", "getDeviceList", "getKeys", "getOAuthToken", "getProfileCache", "getSignedInUser", "getSignedInUserProfile", + "handleAccountDestroyed", "handleDeviceDisconnection", - "handleAccountDestroyed", + "handleEmailUpdated", "hasLocalSession", "invalidateCertificate", "loadAndPoll", "localtimeOffsetMsec", "notifyDevices", "now", "promiseAccountsChangeProfileURI", "promiseAccountsForceSigninURI", @@ -588,35 +589,34 @@ FxAccountsInternal.prototype = { }); }, /** * Update account data for the currently signed in user. * * @param credentials * The credentials object containing the fields to be updated. - * This object must contain |email| and |uid| fields and they must + * This object must contain the |uid| field and it must * match the currently signed in user. */ updateUserAccountData(credentials) { log.debug("updateUserAccountData called with fields", Object.keys(credentials)); if (logPII) { log.debug("updateUserAccountData called with data", credentials); } let currentAccountState = this.currentAccountState; return currentAccountState.promiseInitialized.then(() => { - return currentAccountState.getUserAccountData(["email", "uid"]); + return currentAccountState.getUserAccountData(["uid"]); }).then(existing => { - if (existing.email != credentials.email || existing.uid != credentials.uid) { + if (existing.uid != credentials.uid) { throw new Error("The specified credentials aren't for the current user"); } - // We need to nuke email and uid as storage will complain if we try and - // update them (even when the value is the same) + // We need to nuke uid as storage will complain if we try and + // update it (even when the value is the same) credentials = Cu.cloneInto(credentials, {}); // clone it first - delete credentials.email; delete credentials.uid; return currentAccountState.updateUserAccountData(credentials); }); }, /** * returns a promise that fires with the assertion. If there is no verified * signed-in user, fires with null. @@ -1602,16 +1602,21 @@ FxAccountsInternal.prototype = { if (isLocalDevice) { this.signOut(true); } const data = JSON.stringify({ isLocalDevice }); Services.obs.notifyObservers(null, ON_DEVICE_DISCONNECTED_NOTIFICATION, data); return null; }, + handleEmailUpdated(newEmail) { + Services.prefs.setStringPref(PREF_LAST_FXA_USER, CryptoUtils.sha256Base64(newEmail)); + return this.currentAccountState.updateUserAccountData({ email: newEmail }); + }, + async handleAccountDestroyed(uid) { const accountData = await this.currentAccountState.getUserAccountData(); const localUid = accountData ? accountData.uid : null; if (!localUid) { log.info(`Account destroyed push notification received, but we're already logged-out`); return null; } if (uid == localUid) {
--- a/services/fxaccounts/FxAccountsCommon.js +++ b/services/fxaccounts/FxAccountsCommon.js @@ -108,16 +108,18 @@ exports.UI_REQUEST_SIGN_IN_FLOW = "signI exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication"; // The OAuth client ID for Firefox Desktop exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776"; // Firefox Accounts WebChannel ID exports.WEBCHANNEL_ID = "account_updates"; +exports.PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash"; + // Server errno. // From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format exports.ERRNO_ACCOUNT_ALREADY_EXISTS = 101; exports.ERRNO_ACCOUNT_DOES_NOT_EXIST = 102; exports.ERRNO_INCORRECT_PASSWORD = 103; exports.ERRNO_UNVERIFIED_ACCOUNT = 104; exports.ERRNO_INVALID_VERIFICATION_CODE = 105; exports.ERRNO_NOT_VALID_JSON_BODY = 106;
--- a/services/fxaccounts/FxAccountsProfile.jsm +++ b/services/fxaccounts/FxAccountsProfile.jsm @@ -66,83 +66,76 @@ this.FxAccountsProfile.prototype = { _notifyProfileChange(uid) { this._isNotifying = true; Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid); this._isNotifying = false; }, // Cache fetched data and send out a notification so that UI can update. - _cacheProfile(response) { + async _cacheProfile(response) { + const profile = response.body; + const userData = await this.fxa.getSignedInUser(); + if (profile.uid != userData.uid) { + throw new Error("The fetched profile does not correspond with the current account.") + } let profileCache = { - profile: response.body, + profile, etag: response.etag }; - - return this.fxa.setProfileCache(profileCache) - .then(() => { - return this.fxa.getSignedInUser(); - }) - .then(userData => { - log.debug("notifying profile changed for user ${uid}", userData); - this._notifyProfileChange(userData.uid); - return response.body; - }); + await this.fxa.setProfileCache(profileCache); + if (profile.email != userData.email) { + await this.fxa.handleEmailUpdated(profile.email); + } + log.debug("notifying profile changed for user ${uid}", userData); + this._notifyProfileChange(userData.uid); + return profile; }, - _fetchAndCacheProfileInternal() { - let onFinally = () => { + async _fetchAndCacheProfileInternal() { + try { + const profileCache = await this.fxa.getProfileCache(); + const etag = profileCache ? profileCache.etag : null; + const response = await this.client.fetchProfile(etag); + + // response may be null if the profile was not modified (same ETag). + if (!response) { + return null; + } + return await this._cacheProfile(response); + } finally { this._cachedAt = Date.now(); this._currentFetchPromise = null; } - return this.fxa.getProfileCache() - .then(profileCache => { - const etag = profileCache ? profileCache.etag : null; - return this.client.fetchProfile(etag); - }) - .then(response => { - // response may be null if the profile was not modified (same ETag). - return response ? this._cacheProfile(response) : null; - }) - .then(body => { // finally block - onFinally(); - // body may be null if the profile was not modified - return body; - }, err => { - onFinally(); - throw err; - }); }, _fetchAndCacheProfile() { if (!this._currentFetchPromise) { this._currentFetchPromise = this._fetchAndCacheProfileInternal(); } return this._currentFetchPromise; }, // Returns cached data right away if available, then fetches the latest profile // data in the background. After data is fetched a notification will be sent // out if the profile has changed. - getProfile() { - return this.fxa.getProfileCache() - .then(profileCache => { - if (profileCache) { - if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) { - // Note that _fetchAndCacheProfile isn't returned, so continues - // in the background. - this._fetchAndCacheProfile().catch(err => { - log.error("Background refresh of profile failed", err); - }); - } else { - log.trace("not checking freshness of profile as it remains recent"); - } - return profileCache.profile; - } - return this._fetchAndCacheProfile(); + async getProfile() { + const profileCache = await this.fxa.getProfileCache(); + if (!profileCache) { + return this._fetchAndCacheProfile(); + } + if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) { + // Note that _fetchAndCacheProfile isn't returned, so continues + // in the background. + this._fetchAndCacheProfile().catch(err => { + log.error("Background refresh of profile failed", err); }); + } else { + log.trace("not checking freshness of profile as it remains recent"); + } + return profileCache.profile; }, QueryInterface: XPCOMUtils.generateQI([ Ci.nsIObserver, Ci.nsISupportsWeakReference, ]), };
--- a/services/fxaccounts/FxAccountsStorage.jsm +++ b/services/fxaccounts/FxAccountsStorage.jsm @@ -203,21 +203,18 @@ this.FxAccountsStorageManager.prototype // a different user, nor to set the user as signed-out. async updateAccountData(newFields) { await this._promiseInitialized; if (!("uid" in this.cachedPlain)) { // If this storage instance shows no logged in user, then you can't // update fields. throw new Error("No user is logged in"); } - if (!newFields || "uid" in newFields || "email" in newFields) { - // Once we support - // user changing email address this may need to change, but it's not - // clear how we would be told of such a change anyway... - throw new Error("Can't change uid or email address"); + if (!newFields || "uid" in newFields) { + throw new Error("Can't change uid"); } log.debug("_updateAccountData with items", Object.keys(newFields)); // work out what bucket. for (let [name, value] of Object.entries(newFields)) { if (FXA_PWDMGR_MEMORY_FIELDS.has(name)) { if (value == null) { delete this.cachedMemory[name]; } else {
--- a/services/fxaccounts/FxAccountsWebChannel.jsm +++ b/services/fxaccounts/FxAccountsWebChannel.jsm @@ -24,28 +24,28 @@ XPCOMUtils.defineLazyModuleGetter(this, XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts", "resource://gre/modules/FxAccounts.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsStorageManagerCanStoreField", "resource://gre/modules/FxAccountsStorage.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Weave", "resource://services-sync/main.js"); +XPCOMUtils.defineLazyModuleGetter(this, "CryptoUtils", + "resource://services-crypto/utils.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 COMMAND_FXA_STATUS = "fxaccounts:fxa_status"; -const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash"; - // These engines were added years after Sync had been introduced, they need // special handling since they are system add-ons and are un-available on // older versions of Firefox. const EXTRA_ENGINES = ["addresses", "creditcards"]; /** * 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 @@ -454,34 +454,17 @@ this.FxAccountsWebChannelHelpers.prototy }, /** * 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) { - Services.prefs.setStringPref(PREF_LAST_FXA_USER, this.sha256(acctName)); - }, - - /** - * 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); + Services.prefs.setStringPref(PREF_LAST_FXA_USER, CryptoUtils.sha256Base64(acctName)); }, /** * 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 */
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -294,31 +294,25 @@ add_task(async function test_update_acco email: credentials.email, uid: credentials.uid, assertion: "new_assertion", } await account.updateUserAccountData(newCreds); do_check_eq((await account.getSignedInUser()).assertion, "new_assertion", "new field value was saved"); - // but we should fail attempting to change email or uid. - newCreds = { - email: "someoneelse@example.com", - uid: credentials.uid, - assertion: "new_assertion", - } - await Assert.rejects(account.updateUserAccountData(newCreds)); + // but we should fail attempting to change the uid. newCreds = { email: credentials.email, uid: "another_uid", assertion: "new_assertion", } await Assert.rejects(account.updateUserAccountData(newCreds)); - // should fail without email or uid. + // should fail without the uid. newCreds = { assertion: "new_assertion", } await Assert.rejects(account.updateUserAccountData(newCreds)); // and should fail with a field name that's not known by storage. newCreds = { email: credentials.email,
--- a/services/fxaccounts/tests/xpcshell/test_profile.js +++ b/services/fxaccounts/tests/xpcshell/test_profile.js @@ -1,16 +1,17 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; Cu.import("resource://gre/modules/FxAccountsCommon.js"); Cu.import("resource://gre/modules/FxAccountsProfileClient.jsm"); Cu.import("resource://gre/modules/FxAccountsProfile.jsm"); +Cu.import("resource://gre/modules/PromiseUtils.jsm"); const URL_STRING = "https://example.com"; Services.prefs.setCharPref("identity.fxaccounts.settings.uri", "https://example.com/settings"); const STATUS_SUCCESS = 200; /** * Mock request responder @@ -54,18 +55,21 @@ let mockResponseError = function(error) let mockClient = function(fxa) { let options = { serverURL: "http://127.0.0.1:1111/v1", fxa, } return new FxAccountsProfileClient(options); }; +const ACCOUNT_UID = "abc123"; +const ACCOUNT_EMAIL = "foo@bar.com"; const ACCOUNT_DATA = { - uid: "abc123" + uid: ACCOUNT_UID, + email: ACCOUNT_EMAIL }; function FxaMock() { } FxaMock.prototype = { currentAccountState: { profile: null, get isCurrent() { @@ -117,23 +121,23 @@ add_test(function cacheProfile_change() let profile = CreateFxAccountsProfile(fxa); makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function(subject, topic, data) { do_check_eq(data, ACCOUNT_DATA.uid); do_check_true(setProfileCacheCalled); run_next_test(); }); - return profile._cacheProfile({ body: { avatar: "myurl" }, etag: "bogusetag" }); + return profile._cacheProfile({ body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myurl" }, etag: "bogusetag" }); }); add_test(function fetchAndCacheProfile_ok() { let client = mockClient(mockFxa()); client.fetchProfile = function() { - return Promise.resolve({ body: { avatar: "myimg"} }); + return Promise.resolve({ body: { uid: ACCOUNT_UID, avatar: "myimg"} }); }; let profile = CreateFxAccountsProfile(null, client); profile._cachedAt = 12345; profile._cacheProfile = function(toCache) { do_check_eq(toCache.body.avatar, "myimg"); return Promise.resolve(toCache.body); }; @@ -164,17 +168,17 @@ add_test(function fetchAndCacheProfile_a }); add_test(function fetchAndCacheProfile_sendsETag() { let fxa = mockFxa(); fxa.profileCache = { profile: {}, etag: "bogusETag" }; let client = mockClient(fxa); client.fetchProfile = function(etag) { do_check_eq(etag, "bogusETag"); - return Promise.resolve({ body: { avatar: "myimg"} }); + return Promise.resolve({ body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg"} }); }; let profile = CreateFxAccountsProfile(fxa, client); return profile._fetchAndCacheProfile() .then(result => { run_next_test(); }); }); @@ -190,36 +194,28 @@ add_task(async function fetchAndCachePro }); let numFetches = 0; let client = mockClient(mockFxa()); client.fetchProfile = function() { numFetches += 1; return promiseProfile; }; let fxa = mockFxa(); - fxa.getProfileCache = () => { - // We do this because we are gonna have a race condition and fetchProfile will - // not be called before we check numFetches. - return { - then(thenFunc) { - return thenFunc(); - } - } - }; let profile = CreateFxAccountsProfile(fxa, client); let request1 = profile._fetchAndCacheProfile(); profile._fetchAndCacheProfile(); + await new Promise(res => setTimeout(res, 0)); // Yield so fetchProfile() is called (promise) // should be one request made to fetch the profile (but the promise returned // by it remains unresolved) do_check_eq(numFetches, 1); // resolve the promise. - resolveProfile({ body: { avatar: "myimg"} }); + resolveProfile({ body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg"} }); // both requests should complete with the same data. let got1 = await request1; do_check_eq(got1.avatar, "myimg"); let got2 = await request1; do_check_eq(got2.avatar, "myimg"); // and still only 1 request was made. @@ -237,29 +233,21 @@ add_task(async function fetchAndCachePro }); let numFetches = 0; let client = mockClient(mockFxa()); client.fetchProfile = function() { numFetches += 1; return promiseProfile; }; let fxa = mockFxa(); - fxa.getProfileCache = () => { - // We do this because we are gonna have a race condition and fetchProfile will - // not be called before we check numFetches. - return { - then(thenFunc) { - return thenFunc(); - } - } - }; let profile = CreateFxAccountsProfile(fxa, client); let request1 = profile._fetchAndCacheProfile(); let request2 = profile._fetchAndCacheProfile(); + await new Promise(res => setTimeout(res, 0)); // Yield so fetchProfile() is called (promise) // should be one request made to fetch the profile (but the promise returned // by it remains unresolved) do_check_eq(numFetches, 1); // reject the promise. rejectProfile("oh noes"); @@ -278,27 +266,27 @@ add_task(async function fetchAndCachePro } catch (ex) { if (ex != "oh noes") { throw ex; } } // but a new request should works. client.fetchProfile = function() { - return Promise.resolve({body: { avatar: "myimg"}}); + return Promise.resolve({body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg"}}); }; let got = await profile._fetchAndCacheProfile(); do_check_eq(got.avatar, "myimg"); }); add_test(function fetchAndCacheProfile_alreadyCached() { let cachedUrl = "cachedurl"; let fxa = mockFxa(); - fxa.profileCache = { profile: { avatar: cachedUrl }, etag: "bogusETag" }; + fxa.profileCache = { profile: { uid: ACCOUNT_UID, avatar: cachedUrl }, etag: "bogusETag" }; let client = mockClient(fxa); client.fetchProfile = function(etag) { do_check_eq(etag, "bogusETag"); return Promise.resolve(null); }; let profile = CreateFxAccountsProfile(fxa, client); profile._cacheProfile = function(toCache) { @@ -313,56 +301,70 @@ add_test(function fetchAndCacheProfile_a }); }); // Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the // last one doesn't kick off a new request to check the cached copy is fresh. add_task(async function fetchAndCacheProfileAfterThreshold() { let numFetches = 0; let client = mockClient(mockFxa()); - client.fetchProfile = function() { + client.fetchProfile = async function() { numFetches += 1; - return Promise.resolve({ avatar: "myimg"}); + return {body: {uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg"}}; }; let profile = CreateFxAccountsProfile(null, client); profile.PROFILE_FRESHNESS_THRESHOLD = 1000; await profile.getProfile(); do_check_eq(numFetches, 1); await profile.getProfile(); do_check_eq(numFetches, 1); await new Promise(resolve => { do_timeout(1000, resolve); }); + let origFetchAndCatch = profile._fetchAndCacheProfile; + let backgroundFetchDone = PromiseUtils.defer(); + profile._fetchAndCacheProfile = async () => { + await origFetchAndCatch.call(profile); + backgroundFetchDone.resolve(); + } await profile.getProfile(); + await backgroundFetchDone.promise; do_check_eq(numFetches, 2); }); // Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the // last one *does* kick off a new request if ON_PROFILE_CHANGE_NOTIFICATION // is sent. add_task(async function fetchAndCacheProfileBeforeThresholdOnNotification() { let numFetches = 0; let client = mockClient(mockFxa()); - client.fetchProfile = function() { + client.fetchProfile = async function() { numFetches += 1; - return Promise.resolve({ avatar: "myimg"}); + return {body: {uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg"}}; }; let profile = CreateFxAccountsProfile(null, client); profile.PROFILE_FRESHNESS_THRESHOLD = 1000; await profile.getProfile(); do_check_eq(numFetches, 1); Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION); + let origFetchAndCatch = profile._fetchAndCacheProfile; + let backgroundFetchDone = PromiseUtils.defer(); + profile._fetchAndCacheProfile = async () => { + await origFetchAndCatch.call(profile); + backgroundFetchDone.resolve(); + } await profile.getProfile(); + await backgroundFetchDone.promise; do_check_eq(numFetches, 2); }); add_test(function tearDown_ok() { let profile = CreateFxAccountsProfile(); do_check_true(!!profile.client); do_check_true(!!profile.fxa); @@ -374,17 +376,17 @@ add_test(function tearDown_ok() { run_next_test(); }); add_test(function getProfile_ok() { let cachedUrl = "myurl"; let didFetch = false; let fxa = mockFxa(); - fxa.profileCache = { profile: { avatar: cachedUrl } }; + fxa.profileCache = { profile: { uid: ACCOUNT_UID, avatar: cachedUrl } }; let profile = CreateFxAccountsProfile(fxa); profile._fetchAndCacheProfile = function() { didFetch = true; return Promise.resolve(); }; return profile.getProfile() @@ -397,37 +399,37 @@ add_test(function getProfile_ok() { add_test(function getProfile_no_cache() { let fetchedUrl = "newUrl"; let fxa = mockFxa(); fxa.profileCache = null; let profile = CreateFxAccountsProfile(fxa); profile._fetchAndCacheProfile = function() { - return Promise.resolve({ avatar: fetchedUrl }); + return Promise.resolve({ uid: ACCOUNT_UID, avatar: fetchedUrl }); }; return profile.getProfile() .then(result => { do_check_eq(result.avatar, fetchedUrl); run_next_test(); }); }); add_test(function getProfile_has_cached_fetch_deleted() { let cachedUrl = "myurl"; let fxa = mockFxa(); let client = mockClient(fxa); client.fetchProfile = function() { - return Promise.resolve({ body: { avatar: null } }); + return Promise.resolve({ body: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: null } }); }; let profile = CreateFxAccountsProfile(fxa, client); - fxa.profileCache = { profile: { avatar: cachedUrl } }; + fxa.profileCache = { profile: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: cachedUrl } }; // instead of checking this in a mocked "save" function, just check after the // observer makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function(subject, topic, data) { profile.getProfile() .then(profileData => { do_check_null(profileData.avatar); run_next_test(); @@ -437,28 +439,43 @@ add_test(function getProfile_has_cached_ return profile.getProfile() .then(result => { do_check_eq(result.avatar, "myurl"); }); }); add_test(function getProfile_fetchAndCacheProfile_throws() { let fxa = mockFxa(); - fxa.profileCache = { profile: { avatar: "myimg" } }; + fxa.profileCache = { profile: { uid: ACCOUNT_UID, email: ACCOUNT_EMAIL, avatar: "myimg" } }; let profile = CreateFxAccountsProfile(fxa); profile._fetchAndCacheProfile = () => Promise.reject(new Error()); return profile.getProfile() .then(result => { do_check_eq(result.avatar, "myimg"); run_next_test(); }); }); +add_test(function getProfile_email_changed() { + let fxa = mockFxa(); + let client = mockClient(fxa); + client.fetchProfile = function() { + return Promise.resolve({ body: { uid: ACCOUNT_UID, email: "newemail@bar.com" } }); + }; + fxa.handleEmailUpdated = email => { + do_check_eq(email, "newemail@bar.com"); + run_next_test(); + }; + + let profile = CreateFxAccountsProfile(fxa, client); + return profile._fetchAndCacheProfile(); +}); + function makeObserver(aObserveTopic, aObserveFunc) { let callback = function(aSubject, aTopic, aData) { log.debug("observed " + aTopic + " " + aData); if (aTopic == aObserveTopic) { removeMe(); aObserveFunc(aSubject, aTopic, aData); } };
--- a/tools/lint/eslint/modules.json +++ b/tools/lint/eslint/modules.json @@ -79,17 +79,17 @@ "forms.jsm": ["FormData"], "FormAutofillHeuristics.jsm": ["FormAutofillHeuristics", "LabelUtils"], "FormAutofillSync.jsm": ["AddressesEngine", "CreditCardsEngine"], "frame.js": ["Collector", "Runner", "events", "runTestFile", "log", "timers", "persisted", "shutdownApplication"], "FrameScriptManager.jsm": ["getNewLoaderID"], "fxa_utils.js": ["initializeIdentityWithTokenServerResponse"], "fxaccounts.jsm": ["Authentication"], "FxAccounts.jsm": ["fxAccounts", "FxAccounts"], - "FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_FXA_UPDATE_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"], + "FxAccountsCommon.js": ["log", "logPII", "FXACCOUNTS_PERMISSION", "DATA_FORMAT_VERSION", "DEFAULT_STORAGE_FILENAME", "ASSERTION_LIFETIME", "ASSERTION_USE_PERIOD", "CERT_LIFETIME", "KEY_LIFETIME", "POLL_SESSION", "ONLOGIN_NOTIFICATION", "ONVERIFIED_NOTIFICATION", "ONLOGOUT_NOTIFICATION", "ON_FXA_UPDATE_NOTIFICATION", "ON_DEVICE_CONNECTED_NOTIFICATION", "ON_DEVICE_DISCONNECTED_NOTIFICATION", "ON_PROFILE_UPDATED_NOTIFICATION", "ON_PASSWORD_CHANGED_NOTIFICATION", "ON_PASSWORD_RESET_NOTIFICATION", "ON_VERIFY_LOGIN_NOTIFICATION", "ON_ACCOUNT_DESTROYED_NOTIFICATION", "ON_COLLECTION_CHANGED_NOTIFICATION", "FXA_PUSH_SCOPE_ACCOUNT_UPDATE", "ON_PROFILE_CHANGE_NOTIFICATION", "ON_ACCOUNT_STATE_CHANGE_NOTIFICATION", "UI_REQUEST_SIGN_IN_FLOW", "UI_REQUEST_REFRESH_AUTH", "FX_OAUTH_CLIENT_ID", "WEBCHANNEL_ID", "PREF_LAST_FXA_USER", "ERRNO_ACCOUNT_ALREADY_EXISTS", "ERRNO_ACCOUNT_DOES_NOT_EXIST", "ERRNO_INCORRECT_PASSWORD", "ERRNO_UNVERIFIED_ACCOUNT", "ERRNO_INVALID_VERIFICATION_CODE", "ERRNO_NOT_VALID_JSON_BODY", "ERRNO_INVALID_BODY_PARAMETERS", "ERRNO_MISSING_BODY_PARAMETERS", "ERRNO_INVALID_REQUEST_SIGNATURE", "ERRNO_INVALID_AUTH_TOKEN", "ERRNO_INVALID_AUTH_TIMESTAMP", "ERRNO_MISSING_CONTENT_LENGTH", "ERRNO_REQUEST_BODY_TOO_LARGE", "ERRNO_TOO_MANY_CLIENT_REQUESTS", "ERRNO_INVALID_AUTH_NONCE", "ERRNO_ENDPOINT_NO_LONGER_SUPPORTED", "ERRNO_INCORRECT_LOGIN_METHOD", "ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD", "ERRNO_INCORRECT_API_VERSION", "ERRNO_INCORRECT_EMAIL_CASE", "ERRNO_ACCOUNT_LOCKED", "ERRNO_ACCOUNT_UNLOCKED", "ERRNO_UNKNOWN_DEVICE", "ERRNO_DEVICE_SESSION_CONFLICT", "ERRNO_SERVICE_TEMP_UNAVAILABLE", "ERRNO_PARSE", "ERRNO_NETWORK", "ERRNO_UNKNOWN_ERROR", "OAUTH_SERVER_ERRNO_OFFSET", "ERRNO_UNKNOWN_CLIENT_ID", "ERRNO_INCORRECT_CLIENT_SECRET", "ERRNO_INCORRECT_REDIRECT_URI", "ERRNO_INVALID_FXA_ASSERTION", "ERRNO_UNKNOWN_CODE", "ERRNO_INCORRECT_CODE", "ERRNO_EXPIRED_CODE", "ERRNO_OAUTH_INVALID_TOKEN", "ERRNO_INVALID_REQUEST_PARAM", "ERRNO_INVALID_RESPONSE_TYPE", "ERRNO_UNAUTHORIZED", "ERRNO_FORBIDDEN", "ERRNO_INVALID_CONTENT_TYPE", "ERROR_ACCOUNT_ALREADY_EXISTS", "ERROR_ACCOUNT_DOES_NOT_EXIST", "ERROR_ACCOUNT_LOCKED", "ERROR_ACCOUNT_UNLOCKED", "ERROR_ALREADY_SIGNED_IN_USER", "ERROR_DEVICE_SESSION_CONFLICT", "ERROR_ENDPOINT_NO_LONGER_SUPPORTED", "ERROR_INCORRECT_API_VERSION", "ERROR_INCORRECT_EMAIL_CASE", "ERROR_INCORRECT_KEY_RETRIEVAL_METHOD", "ERROR_INCORRECT_LOGIN_METHOD", "ERROR_INVALID_EMAIL", "ERROR_INVALID_AUDIENCE", "ERROR_INVALID_AUTH_TOKEN", "ERROR_INVALID_AUTH_TIMESTAMP", "ERROR_INVALID_AUTH_NONCE", "ERROR_INVALID_BODY_PARAMETERS", "ERROR_INVALID_PASSWORD", "ERROR_INVALID_VERIFICATION_CODE", "ERROR_INVALID_REFRESH_AUTH_VALUE", "ERROR_INVALID_REQUEST_SIGNATURE", "ERROR_INTERNAL_INVALID_USER", "ERROR_MISSING_BODY_PARAMETERS", "ERROR_MISSING_CONTENT_LENGTH", "ERROR_NO_TOKEN_SESSION", "ERROR_NO_SILENT_REFRESH_AUTH", "ERROR_NOT_VALID_JSON_BODY", "ERROR_OFFLINE", "ERROR_PERMISSION_DENIED", "ERROR_REQUEST_BODY_TOO_LARGE", "ERROR_SERVER_ERROR", "ERROR_SYNC_DISABLED", "ERROR_TOO_MANY_CLIENT_REQUESTS", "ERROR_SERVICE_TEMP_UNAVAILABLE", "ERROR_UI_ERROR", "ERROR_UI_REQUEST", "ERROR_PARSE", "ERROR_NETWORK", "ERROR_UNKNOWN", "ERROR_UNKNOWN_DEVICE", "ERROR_UNVERIFIED_ACCOUNT", "ERROR_UNKNOWN_CLIENT_ID", "ERROR_INCORRECT_CLIENT_SECRET", "ERROR_INCORRECT_REDIRECT_URI", "ERROR_INVALID_FXA_ASSERTION", "ERROR_UNKNOWN_CODE", "ERROR_INCORRECT_CODE", "ERROR_EXPIRED_CODE", "ERROR_OAUTH_INVALID_TOKEN", "ERROR_INVALID_REQUEST_PARAM", "ERROR_INVALID_RESPONSE_TYPE", "ERROR_UNAUTHORIZED", "ERROR_FORBIDDEN", "ERROR_INVALID_CONTENT_TYPE", "ERROR_NO_ACCOUNT", "ERROR_AUTH_ERROR", "ERROR_INVALID_PARAMETER", "ERROR_CODE_METHOD_NOT_ALLOWED", "ERROR_MSG_METHOD_NOT_ALLOWED", "FXA_PWDMGR_PLAINTEXT_FIELDS", "FXA_PWDMGR_SECURE_FIELDS", "FXA_PWDMGR_MEMORY_FIELDS", "FXA_PWDMGR_REAUTH_WHITELIST", "FXA_PWDMGR_HOST", "FXA_PWDMGR_REALM", "SERVER_ERRNO_TO_ERROR", "ERROR_TO_GENERAL_ERROR_CLASS"], "FxAccountsOAuthGrantClient.jsm": ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"], "FxAccountsProfileClient.jsm": ["FxAccountsProfileClient", "FxAccountsProfileClientError"], "FxAccountsPush.js": ["FxAccountsPushService"], "FxAccountsStorage.jsm": ["FxAccountsStorageManagerCanStoreField", "FxAccountsStorageManager"], "FxAccountsWebChannel.jsm": ["EnsureFxAccountsWebChannel"], "gDevTools.jsm": ["gDevTools", "gDevToolsBrowser"], "gDevTools.jsm": ["gDevTools", "gDevToolsBrowser"], "Geometry.jsm": ["Point", "Rect"],