--- 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
*/
@@ -499,17 +482,17 @@ this.FxAccountsWebChannelHelpers.prototy
* 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);
+ return prevAcctHash && prevAcctHash != CryptoUtils.sha256Base64(acctName);
},
/**
* Show the user a warning dialog that the data from the previous account
* and the new account will be merged.
*
* @private
*/
--- 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/services/fxaccounts/tests/xpcshell/test_web_channel.js
+++ b/services/fxaccounts/tests/xpcshell/test_web_channel.js
@@ -1,14 +1,15 @@
/* 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://services-crypto/utils.js");
const { FxAccountsWebChannel, FxAccountsWebChannelHelpers } =
Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm", {});
const URL_STRING = "https://example.com";
const mockSendingContext = {
browser: {},
principal: {},
@@ -333,17 +334,18 @@ add_task(async function test_helpers_log
return new Promise(resolve => {
// ensure fxAccounts is informed of the new user being signed in.
do_check_eq(accountData.email, "testuser@testuser.com");
// verifiedCanLinkAccount should be stripped in the data.
do_check_false("verifiedCanLinkAccount" in accountData);
// previously signed in user preference is updated.
- do_check_eq(helpers.getPreviousAccountNameHashPref(), helpers.sha256("testuser@testuser.com"));
+ do_check_eq(helpers.getPreviousAccountNameHashPref(),
+ CryptoUtils.sha256Base64("testuser@testuser.com"));
resolve();
});
}
}
});
// ensure the previous account pref is overwritten.
--- 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"],