mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1227527 - Implement basic FxA device registration. r=markh
This commit is contained in:
parent
4a650c46d6
commit
35173f76e6
@ -1064,6 +1064,9 @@ pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
|
||||
pref("identity.fxaccounts.remote.oauth.uri", "https://oauth.accounts.firefox.com/v1");
|
||||
pref("identity.fxaccounts.remote.profile.uri", "https://profile.accounts.firefox.com/v1");
|
||||
|
||||
// Disable Firefox Accounts device registration until bug 1238895 is fixed.
|
||||
pref("identity.fxaccounts.skipDeviceRegistration", true);
|
||||
|
||||
// Enable mapped array buffer.
|
||||
#ifndef XP_WIN
|
||||
pref("dom.mapped_arraybuffer.enabled", true);
|
||||
|
@ -24,6 +24,7 @@ const ORIGINAL_SENDCUSTOM = SystemAppProxy._sendCustomEvent;
|
||||
do_register_cleanup(function() {
|
||||
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", ORIGINAL_AUTH_URI);
|
||||
SystemAppProxy._sendCustomEvent = ORIGINAL_SENDCUSTOM;
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration");
|
||||
});
|
||||
|
||||
// Make profile available so that fxaccounts can store user data
|
||||
@ -39,6 +40,9 @@ function run_test() {
|
||||
}
|
||||
|
||||
add_task(function test_overall() {
|
||||
// FxA device registration throws from this context
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
|
||||
|
||||
do_check_neq(FxAccountsMgmtService, null);
|
||||
});
|
||||
|
||||
@ -105,6 +109,9 @@ add_test(function test_invalidEmailCase_signIn() {
|
||||
// Point the FxAccountsClient's hawk rest request client to the mock server
|
||||
Services.prefs.setCharPref("identity.fxaccounts.auth.uri", server.baseURI);
|
||||
|
||||
// FxA device registration throws from this context
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
|
||||
|
||||
// Receive a mozFxAccountsChromeEvent message
|
||||
function onMessage(subject, topic, data) {
|
||||
let message = subject.wrappedJSObject;
|
||||
@ -164,6 +171,9 @@ add_test(function test_invalidEmailCase_signIn() {
|
||||
add_test(function testHandleGetAssertionError_defaultCase() {
|
||||
do_test_pending();
|
||||
|
||||
// FxA device registration throws from this context
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
|
||||
|
||||
FxAccountsManager.getAssertion(null).then(
|
||||
success => {
|
||||
// getAssertion should throw with invalid audience
|
||||
|
@ -30,6 +30,9 @@ XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthGrantClient",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfile",
|
||||
"resource://gre/modules/FxAccountsProfile.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
|
||||
"resource://services-sync/util.js");
|
||||
|
||||
// All properties exposed by the public FxAccounts API.
|
||||
var publicProperties = [
|
||||
"accountStatus",
|
||||
@ -37,6 +40,7 @@ var publicProperties = [
|
||||
"getAccountsSignInURI",
|
||||
"getAccountsSignUpURI",
|
||||
"getAssertion",
|
||||
"getDeviceId",
|
||||
"getKeys",
|
||||
"getSignedInUser",
|
||||
"getOAuthToken",
|
||||
@ -51,6 +55,7 @@ var publicProperties = [
|
||||
"resendVerificationEmail",
|
||||
"setSignedInUser",
|
||||
"signOut",
|
||||
"updateDeviceRegistration",
|
||||
"whenVerified"
|
||||
];
|
||||
|
||||
@ -490,7 +495,9 @@ FxAccountsInternal.prototype = {
|
||||
// We're telling the caller that this is durable now (although is that
|
||||
// really something we should commit to? Why not let the write happen in
|
||||
// the background? Already does for updateAccountData ;)
|
||||
return currentAccountState.promiseInitialized.then(() => {
|
||||
return currentAccountState.promiseInitialized.then(() =>
|
||||
this.updateDeviceRegistration()
|
||||
).then(() => {
|
||||
Services.telemetry.getHistogramById("FXA_CONFIGURED").add(1);
|
||||
this.notifyObservers(ONLOGIN_NOTIFICATION);
|
||||
if (!this.isUserEmailVerified(credentials)) {
|
||||
@ -539,6 +546,26 @@ FxAccountsInternal.prototype = {
|
||||
}).then(result => currentState.resolve(result));
|
||||
},
|
||||
|
||||
getDeviceId() {
|
||||
return this.currentAccountState.getUserAccountData()
|
||||
.then(data => {
|
||||
if (data) {
|
||||
if (data.isDeviceStale || !data.deviceId) {
|
||||
// A previous device registration attempt failed or there is no
|
||||
// device id. Either way, we should register the device with FxA
|
||||
// before returning the id to the caller.
|
||||
return this._registerOrUpdateDevice(data);
|
||||
}
|
||||
|
||||
// Return the device id that we already registered with the server.
|
||||
return data.deviceId;
|
||||
}
|
||||
|
||||
// Without a signed-in user, there can be no device id.
|
||||
return null;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Resend the verification email fot the currently signed-in user.
|
||||
*
|
||||
@ -605,10 +632,15 @@ FxAccountsInternal.prototype = {
|
||||
let currentState = this.currentAccountState;
|
||||
let sessionToken;
|
||||
let tokensToRevoke;
|
||||
let deviceId;
|
||||
return currentState.getUserAccountData().then(data => {
|
||||
// Save the session token for use in the call to signOut below.
|
||||
sessionToken = data && data.sessionToken;
|
||||
tokensToRevoke = data && data.oauthTokens;
|
||||
// Save the session token, tokens to revoke and the
|
||||
// device id for use in the call to signOut below.
|
||||
if (data) {
|
||||
sessionToken = data.sessionToken;
|
||||
tokensToRevoke = data.oauthTokens;
|
||||
deviceId = data.deviceId;
|
||||
}
|
||||
return this._signOutLocal();
|
||||
}).then(() => {
|
||||
// FxAccountsManager calls here, then does its own call
|
||||
@ -620,7 +652,7 @@ FxAccountsInternal.prototype = {
|
||||
// This can happen in the background and shouldn't block
|
||||
// the user from signing out. The server must tolerate
|
||||
// clients just disappearing, so this call should be best effort.
|
||||
return this._signOutServer(sessionToken);
|
||||
return this._signOutServer(sessionToken, deviceId);
|
||||
}).catch(err => {
|
||||
log.error("Error during remote sign out of Firefox Accounts", err);
|
||||
}).then(() => {
|
||||
@ -652,11 +684,22 @@ FxAccountsInternal.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
_signOutServer: function signOutServer(sessionToken) {
|
||||
// For now we assume the service being logged out from is Sync - we might
|
||||
// need to revisit this when this FxA code is used in a context that
|
||||
// isn't Sync.
|
||||
return this.fxAccountsClient.signOut(sessionToken, {service: "sync"});
|
||||
_signOutServer(sessionToken, deviceId) {
|
||||
// For now we assume the service being logged out from is Sync, so
|
||||
// we must tell the server to either destroy the device or sign out
|
||||
// (if no device exists). We might need to revisit this when this
|
||||
// FxA code is used in a context that isn't Sync.
|
||||
|
||||
const options = { service: "sync" };
|
||||
|
||||
if (deviceId) {
|
||||
log.debug("destroying device and session");
|
||||
return this.fxAccountsClient.signOutAndDestroyDevice(sessionToken, deviceId, options)
|
||||
.then(() => this.currentAccountState.updateUserAccountData({ deviceId: null }));
|
||||
}
|
||||
|
||||
log.debug("destroying session");
|
||||
return this.fxAccountsClient.signOut(sessionToken, options);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1334,6 +1377,127 @@ FxAccountsInternal.prototype = {
|
||||
}
|
||||
).catch(err => Promise.reject(this._errorToErrorClass(err)));
|
||||
},
|
||||
|
||||
// Attempt to update the auth server with whatever device details are stored
|
||||
// in the account data. Returns a promise that always resolves, never rejects.
|
||||
// If the promise resolves to a value, that value is the device id.
|
||||
updateDeviceRegistration() {
|
||||
return this.getSignedInUser().then(signedInUser => {
|
||||
if (signedInUser) {
|
||||
return this._registerOrUpdateDevice(signedInUser);
|
||||
}
|
||||
}).catch(error => this._logErrorAndSetStaleDeviceFlag(error));
|
||||
},
|
||||
|
||||
_registerOrUpdateDevice(signedInUser) {
|
||||
try {
|
||||
// Allow tests to skip device registration because:
|
||||
// 1. It makes remote requests to the auth server.
|
||||
// 2. _getDeviceName does not work from xpcshell.
|
||||
// 3. The B2G tests fail when attempting to import services-sync/util.js.
|
||||
if (Services.prefs.getBoolPref("identity.fxaccounts.skipDeviceRegistration")) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
} catch(ignore) {}
|
||||
|
||||
return Promise.resolve().then(() => {
|
||||
const deviceName = this._getDeviceName();
|
||||
|
||||
if (signedInUser.deviceId) {
|
||||
log.debug("updating existing device details");
|
||||
return this.fxAccountsClient.updateDevice(
|
||||
signedInUser.sessionToken, signedInUser.deviceId, deviceName);
|
||||
}
|
||||
|
||||
log.debug("registering new device details");
|
||||
return this.fxAccountsClient.registerDevice(
|
||||
signedInUser.sessionToken, deviceName, this._getDeviceType());
|
||||
}).then(device =>
|
||||
this.currentAccountState.updateUserAccountData({
|
||||
deviceId: device.id,
|
||||
isDeviceStale: null
|
||||
}).then(() => device.id)
|
||||
).catch(error => this._handleDeviceError(error, signedInUser.sessionToken));
|
||||
},
|
||||
|
||||
_getDeviceName() {
|
||||
return Utils.getDeviceName();
|
||||
},
|
||||
|
||||
_getDeviceType() {
|
||||
return Utils.getDeviceType();
|
||||
},
|
||||
|
||||
_handleDeviceError(error, sessionToken) {
|
||||
return Promise.resolve().then(() => {
|
||||
if (error.code === 400) {
|
||||
if (error.errno === ERRNO_UNKNOWN_DEVICE) {
|
||||
return this._recoverFromUnknownDevice();
|
||||
}
|
||||
|
||||
if (error.errno === ERRNO_DEVICE_SESSION_CONFLICT) {
|
||||
return this._recoverFromDeviceSessionConflict(error, sessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
return this._logErrorAndSetStaleDeviceFlag(error);
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
||||
_recoverFromUnknownDevice() {
|
||||
// FxA did not recognise the device id. Handle it by clearing the device
|
||||
// id on the account data. At next sync or next sign-in, registration is
|
||||
// retried and should succeed.
|
||||
log.warn("unknown device id, clearing the local device data");
|
||||
return this.currentAccountState.updateUserAccountData({ deviceId: null })
|
||||
.catch(error => this._logErrorAndSetStaleDeviceFlag(error));
|
||||
},
|
||||
|
||||
_recoverFromDeviceSessionConflict(error, sessionToken) {
|
||||
// FxA has already associated this session with a different device id.
|
||||
// Perhaps we were beaten in a race to register. Handle the conflict:
|
||||
// 1. Fetch the list of devices for the current user from FxA.
|
||||
// 2. Look for ourselves in the list.
|
||||
// 3. If we find a match, set the correct device id and the stale device
|
||||
// flag on the account data and return the correct device id. At next
|
||||
// sync or next sign-in, registration is retried and should succeed.
|
||||
// 4. If we don't find a match, log the original error.
|
||||
log.warn("device session conflict, attempting to ascertain the correct device id");
|
||||
return this.fxAccountsClient.getDeviceList(sessionToken)
|
||||
.then(devices => {
|
||||
const matchingDevices = devices.filter(device => device.isCurrentDevice);
|
||||
const length = matchingDevices.length;
|
||||
if (length === 1) {
|
||||
const deviceId = matchingDevices[0].id
|
||||
return this.currentAccountState.updateUserAccountData({
|
||||
deviceId,
|
||||
isDeviceStale: true
|
||||
}).then(() => deviceId);
|
||||
}
|
||||
if (length > 1) {
|
||||
log.error("insane server state, " + length + " devices for this session");
|
||||
}
|
||||
return this._logErrorAndSetStaleDeviceFlag(error);
|
||||
}).catch(secondError => {
|
||||
log.error("failed to recover from device-session conflict", secondError);
|
||||
this._logErrorAndSetStaleDeviceFlag(error)
|
||||
});
|
||||
},
|
||||
|
||||
_logErrorAndSetStaleDeviceFlag(error) {
|
||||
// Device registration should never cause other operations to fail.
|
||||
// If we've reached this point, just log the error and set the stale
|
||||
// device flag on the account data. At next sync or next sign-in,
|
||||
// registration will be retried.
|
||||
log.error("device registration failed", error);
|
||||
return this.currentAccountState.updateUserAccountData({
|
||||
isDeviceStale: true
|
||||
}).catch(secondError => {
|
||||
log.error(
|
||||
"failed to set stale device flag, device registration won't be retried",
|
||||
secondError);
|
||||
}).then(() => {});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
@ -193,7 +193,7 @@ this.FxAccountsClient.prototype = {
|
||||
signOut: function (sessionTokenHex, options = {}) {
|
||||
let path = "/session/destroy";
|
||||
if (options.service) {
|
||||
path += "?service=" + options.service;
|
||||
path += "?service=" + encodeURIComponent(options.service);
|
||||
}
|
||||
return this._request(path, "POST",
|
||||
deriveHawkCredentials(sessionTokenHex, "sessionToken"));
|
||||
@ -348,6 +348,116 @@ this.FxAccountsClient.prototype = {
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a new device
|
||||
*
|
||||
* @method registerDevice
|
||||
* @param sessionTokenHex
|
||||
* Session token obtained from signIn
|
||||
* @param name
|
||||
* Device name
|
||||
* @param type
|
||||
* Device type (mobile|desktop)
|
||||
* @return Promise
|
||||
* Resolves to an object:
|
||||
* {
|
||||
* id: Device identifier
|
||||
* createdAt: Creation time (milliseconds since epoch)
|
||||
* name: Name of device
|
||||
* type: Type of device (mobile|desktop)
|
||||
* }
|
||||
*/
|
||||
registerDevice(sessionTokenHex, name, type) {
|
||||
let path = "/account/device";
|
||||
|
||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||
let body = { name, type };
|
||||
|
||||
return this._request(path, "POST", creds, body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update the session or name for an existing device
|
||||
*
|
||||
* @method updateDevice
|
||||
* @param sessionTokenHex
|
||||
* Session token obtained from signIn
|
||||
* @param id
|
||||
* Device identifier
|
||||
* @param name
|
||||
* Device name
|
||||
* @return Promise
|
||||
* Resolves to an object:
|
||||
* {
|
||||
* id: Device identifier
|
||||
* name: Device name
|
||||
* }
|
||||
*/
|
||||
updateDevice(sessionTokenHex, id, name) {
|
||||
let path = "/account/device";
|
||||
|
||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||
let body = { id, name };
|
||||
|
||||
return this._request(path, "POST", creds, body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a device and its associated session token, signing the user
|
||||
* out of the server.
|
||||
*
|
||||
* @method signOutAndDestroyDevice
|
||||
* @param sessionTokenHex
|
||||
* Session token obtained from signIn
|
||||
* @param id
|
||||
* Device identifier
|
||||
* @param [options]
|
||||
* Options object
|
||||
* @param [options.service]
|
||||
* `service` query parameter
|
||||
* @return Promise
|
||||
* Resolves to an empty object:
|
||||
* {}
|
||||
*/
|
||||
signOutAndDestroyDevice(sessionTokenHex, id, options={}) {
|
||||
let path = "/account/device/destroy";
|
||||
|
||||
if (options.service) {
|
||||
path += "?service=" + encodeURIComponent(options.service);
|
||||
}
|
||||
|
||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||
let body = { id };
|
||||
|
||||
return this._request(path, "POST", creds, body);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a list of currently registered devices
|
||||
*
|
||||
* @method getDeviceList
|
||||
* @param sessionTokenHex
|
||||
* Session token obtained from signIn
|
||||
* @return Promise
|
||||
* Resolves to an array of objects:
|
||||
* [
|
||||
* {
|
||||
* id: Device id
|
||||
* isCurrentDevice: Boolean indicating whether the item
|
||||
* represents the current device
|
||||
* name: Device name
|
||||
* type: Device type (mobile|desktop)
|
||||
* },
|
||||
* ...
|
||||
* ]
|
||||
*/
|
||||
getDeviceList(sessionTokenHex) {
|
||||
let path = "/account/devices";
|
||||
let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
|
||||
|
||||
return this._request(path, "GET", creds, {});
|
||||
},
|
||||
|
||||
_clearBackoff: function() {
|
||||
this.backoffError = null;
|
||||
},
|
||||
|
@ -124,6 +124,10 @@ exports.ERRNO_INCORRECT_LOGIN_METHOD = 117;
|
||||
exports.ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD = 118;
|
||||
exports.ERRNO_INCORRECT_API_VERSION = 119;
|
||||
exports.ERRNO_INCORRECT_EMAIL_CASE = 120;
|
||||
exports.ERRNO_ACCOUNT_LOCKED = 121;
|
||||
exports.ERRNO_ACCOUNT_UNLOCKED = 122;
|
||||
exports.ERRNO_UNKNOWN_DEVICE = 123;
|
||||
exports.ERRNO_DEVICE_SESSION_CONFLICT = 124;
|
||||
exports.ERRNO_SERVICE_TEMP_UNAVAILABLE = 201;
|
||||
exports.ERRNO_PARSE = 997;
|
||||
exports.ERRNO_NETWORK = 998;
|
||||
@ -150,7 +154,10 @@ exports.ERRNO_INVALID_CONTENT_TYPE = 113 + exports.OAUTH_SERVER_ERRNO_
|
||||
// Errors.
|
||||
exports.ERROR_ACCOUNT_ALREADY_EXISTS = "ACCOUNT_ALREADY_EXISTS";
|
||||
exports.ERROR_ACCOUNT_DOES_NOT_EXIST = "ACCOUNT_DOES_NOT_EXIST ";
|
||||
exports.ERROR_ACCOUNT_LOCKED = "ACCOUNT_LOCKED";
|
||||
exports.ERROR_ACCOUNT_UNLOCKED = "ACCOUNT_UNLOCKED";
|
||||
exports.ERROR_ALREADY_SIGNED_IN_USER = "ALREADY_SIGNED_IN_USER";
|
||||
exports.ERROR_DEVICE_SESSION_CONFLICT = "DEVICE_SESSION_CONFLICT";
|
||||
exports.ERROR_ENDPOINT_NO_LONGER_SUPPORTED = "ENDPOINT_NO_LONGER_SUPPORTED";
|
||||
exports.ERROR_INCORRECT_API_VERSION = "INCORRECT_API_VERSION";
|
||||
exports.ERROR_INCORRECT_EMAIL_CASE = "INCORRECT_EMAIL_CASE";
|
||||
@ -184,6 +191,7 @@ exports.ERROR_UI_REQUEST = "UI_REQUEST";
|
||||
exports.ERROR_PARSE = "PARSE_ERROR";
|
||||
exports.ERROR_NETWORK = "NETWORK_ERROR";
|
||||
exports.ERROR_UNKNOWN = "UNKNOWN_ERROR";
|
||||
exports.ERROR_UNKNOWN_DEVICE = "UNKNOWN_DEVICE";
|
||||
exports.ERROR_UNVERIFIED_ACCOUNT = "UNVERIFIED_ACCOUNT";
|
||||
|
||||
// OAuth errors.
|
||||
@ -218,7 +226,8 @@ exports.ERROR_MSG_METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED";
|
||||
// The fields we save in the plaintext JSON.
|
||||
// See bug 1013064 comments 23-25 for why the sessionToken is "safe"
|
||||
exports.FXA_PWDMGR_PLAINTEXT_FIELDS = new Set(
|
||||
["email", "verified", "authAt", "sessionToken", "uid", "oauthTokens", "profile"]);
|
||||
["email", "verified", "authAt", "sessionToken", "uid", "oauthTokens", "profile",
|
||||
"deviceId", "isDeviceStale"]);
|
||||
|
||||
// Fields we store in secure storage if it exists.
|
||||
exports.FXA_PWDMGR_SECURE_FIELDS = new Set(
|
||||
@ -267,6 +276,10 @@ SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_LOGIN_METHOD] = ERROR_INCORRECT_LO
|
||||
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_ACCOUNT_LOCKED] = ERROR_ACCOUNT_LOCKED;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_UNLOCKED] = ERROR_ACCOUNT_UNLOCKED;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_DEVICE] = ERROR_UNKNOWN_DEVICE;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_DEVICE_SESSION_CONFLICT] = ERROR_DEVICE_SESSION_CONFLICT;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMP_UNAVAILABLE] = ERROR_SERVICE_TEMP_UNAVAILABLE;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR] = ERROR_UNKNOWN;
|
||||
SERVER_ERRNO_TO_ERROR[ERRNO_NETWORK] = ERROR_NETWORK;
|
||||
@ -290,7 +303,10 @@ SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_CONTENT_TYPE] = ERROR_INVALID_CONT
|
||||
// Map internal errors to more generic error classes for consumers
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_ALREADY_EXISTS] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_DOES_NOT_EXIST] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_LOCKED] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ACCOUNT_UNLOCKED] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ALREADY_SIGNED_IN_USER] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_DEVICE_SESSION_CONFLICT] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_ENDPOINT_NO_LONGER_SUPPORTED] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_API_VERSION] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_EMAIL_CASE] = ERROR_AUTH_ERROR;
|
||||
@ -314,6 +330,7 @@ ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NO_SILENT_REFRESH_AUTH] = ERROR_AUTH_
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NOT_VALID_JSON_BODY] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_PERMISSION_DENIED] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_REQUEST_BODY_TOO_LARGE] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNKNOWN_DEVICE] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNVERIFIED_ACCOUNT] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UI_ERROR] = ERROR_AUTH_ERROR;
|
||||
ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UI_REQUEST] = ERROR_AUTH_ERROR;
|
||||
|
@ -264,7 +264,9 @@ this.FxAccountsWebChannelHelpers.prototype = {
|
||||
logout(uid) {
|
||||
return fxAccounts.getSignedInUser().then(userData => {
|
||||
if (userData.uid === uid) {
|
||||
return fxAccounts.signOut();
|
||||
// true argument is `localOnly`, because server-side stuff
|
||||
// has already been taken care of by the content server
|
||||
return fxAccounts.signOut(true);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -127,7 +127,8 @@ function MockFxAccountsClient() {
|
||||
|
||||
this.signCertificate = function() { throw "no" };
|
||||
|
||||
this.signOut = function() { return Promise.resolve(); };
|
||||
this.signOut = () => Promise.resolve();
|
||||
this.signOutAndDestroyDevice = () => Promise.resolve({});
|
||||
|
||||
FxAccountsClient.apply(this);
|
||||
}
|
||||
@ -162,6 +163,9 @@ function MockFxAccounts() {
|
||||
this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]);
|
||||
return this._d_signCertificate.promise;
|
||||
},
|
||||
_registerOrUpdateDevice() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
fxAccountsClient: new MockFxAccountsClient()
|
||||
});
|
||||
}
|
||||
@ -179,6 +183,12 @@ function MakeFxAccounts(internal = {}) {
|
||||
return new AccountState(storage);
|
||||
};
|
||||
}
|
||||
if (!internal._signOutServer) {
|
||||
internal._signOutServer = () => Promise.resolve();
|
||||
}
|
||||
if (!internal._registerOrUpdateDevice) {
|
||||
internal._registerOrUpdateDevice = () => Promise.resolve();
|
||||
}
|
||||
return new FxAccounts(internal);
|
||||
}
|
||||
|
||||
@ -210,7 +220,7 @@ add_test(function test_non_https_remote_server_uri() {
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function test_get_signed_in_user_initially_unset() {
|
||||
add_task(function* test_get_signed_in_user_initially_unset() {
|
||||
_("Check getSignedInUser initially and after signout reports no user");
|
||||
let account = MakeFxAccounts();
|
||||
let credentials = {
|
||||
@ -555,7 +565,7 @@ add_test(function test_overlapping_signins() {
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function test_getAssertion() {
|
||||
add_task(function* test_getAssertion() {
|
||||
let fxa = new MockFxAccounts();
|
||||
|
||||
do_check_throws(function() {
|
||||
@ -675,7 +685,7 @@ add_task(function test_getAssertion() {
|
||||
_("----- DONE ----\n");
|
||||
});
|
||||
|
||||
add_task(function test_resend_email_not_signed_in() {
|
||||
add_task(function* test_resend_email_not_signed_in() {
|
||||
let fxa = new MockFxAccounts();
|
||||
|
||||
try {
|
||||
@ -762,21 +772,98 @@ add_test(function test_resend_email() {
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_sign_out() {
|
||||
let fxa = new MockFxAccounts();
|
||||
let remoteSignOutCalled = false;
|
||||
let client = fxa.internal.fxAccountsClient;
|
||||
client.signOut = function() { remoteSignOutCalled = true; return Promise.resolve(); };
|
||||
makeObserver(ONLOGOUT_NOTIFICATION, function() {
|
||||
log.debug("test_sign_out_with_remote_error observed onlogout");
|
||||
// user should be undefined after sign out
|
||||
fxa.internal.getUserAccountData().then(user => {
|
||||
do_check_eq(user, null);
|
||||
do_check_true(remoteSignOutCalled);
|
||||
run_next_test();
|
||||
add_task(function* test_sign_out_with_device() {
|
||||
const fxa = new MockFxAccounts();
|
||||
|
||||
const credentials = getTestUser("alice");
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const user = yield fxa.internal.getUserAccountData();
|
||||
do_check_true(user);
|
||||
Object.keys(credentials).forEach(key => do_check_eq(credentials[key], user[key]));
|
||||
|
||||
const spy = {
|
||||
signOut: { count: 0 },
|
||||
signOutAndDeviceDestroy: { count: 0, args: [] }
|
||||
};
|
||||
const client = fxa.internal.fxAccountsClient;
|
||||
client.signOut = function () {
|
||||
spy.signOut.count += 1;
|
||||
return Promise.resolve();
|
||||
};
|
||||
client.signOutAndDestroyDevice = function () {
|
||||
spy.signOutAndDeviceDestroy.count += 1;
|
||||
spy.signOutAndDeviceDestroy.args.push(arguments);
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const promise = new Promise(resolve => {
|
||||
makeObserver(ONLOGOUT_NOTIFICATION, () => {
|
||||
log.debug("test_sign_out_with_device observed onlogout");
|
||||
// user should be undefined after sign out
|
||||
fxa.internal.getUserAccountData().then(user2 => {
|
||||
do_check_eq(user2, null);
|
||||
do_check_eq(spy.signOut.count, 0);
|
||||
do_check_eq(spy.signOutAndDeviceDestroy.count, 1);
|
||||
do_check_eq(spy.signOutAndDeviceDestroy.args[0].length, 3);
|
||||
do_check_eq(spy.signOutAndDeviceDestroy.args[0][0], credentials.sessionToken);
|
||||
do_check_eq(spy.signOutAndDeviceDestroy.args[0][1], credentials.deviceId);
|
||||
do_check_true(spy.signOutAndDeviceDestroy.args[0][2]);
|
||||
do_check_eq(spy.signOutAndDeviceDestroy.args[0][2].service, "sync");
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
fxa.signOut();
|
||||
|
||||
yield fxa.signOut();
|
||||
|
||||
yield promise;
|
||||
});
|
||||
|
||||
add_task(function* test_sign_out_without_device() {
|
||||
const fxa = new MockFxAccounts();
|
||||
|
||||
const credentials = getTestUser("alice");
|
||||
delete credentials.deviceId;
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const user = yield fxa.internal.getUserAccountData();
|
||||
|
||||
const spy = {
|
||||
signOut: { count: 0, args: [] },
|
||||
signOutAndDeviceDestroy: { count: 0 }
|
||||
};
|
||||
const client = fxa.internal.fxAccountsClient;
|
||||
client.signOut = function () {
|
||||
spy.signOut.count += 1;
|
||||
spy.signOut.args.push(arguments);
|
||||
return Promise.resolve();
|
||||
};
|
||||
client.signOutAndDestroyDevice = function () {
|
||||
spy.signOutAndDeviceDestroy.count += 1;
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
const promise = new Promise(resolve => {
|
||||
makeObserver(ONLOGOUT_NOTIFICATION, () => {
|
||||
log.debug("test_sign_out_without_device observed onlogout");
|
||||
// user should be undefined after sign out
|
||||
fxa.internal.getUserAccountData().then(user2 => {
|
||||
do_check_eq(user2, null);
|
||||
do_check_eq(spy.signOut.count, 1);
|
||||
do_check_eq(spy.signOut.args[0].length, 2);
|
||||
do_check_eq(spy.signOut.args[0][0], credentials.sessionToken);
|
||||
do_check_true(spy.signOut.args[0][1]);
|
||||
do_check_eq(spy.signOut.args[0][1].service, "sync");
|
||||
do_check_eq(spy.signOutAndDeviceDestroy.count, 0);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
yield fxa.signOut();
|
||||
|
||||
yield promise;
|
||||
});
|
||||
|
||||
add_test(function test_sign_out_with_remote_error() {
|
||||
@ -1087,7 +1174,10 @@ add_test(function test_getSignedInUserProfile() {
|
||||
},
|
||||
tearDown: function() {},
|
||||
};
|
||||
let fxa = new FxAccounts({});
|
||||
let fxa = new FxAccounts({
|
||||
_signOutServer() { return Promise.resolve(); },
|
||||
_registerOrUpdateDevice() { return Promise.resolve(); }
|
||||
});
|
||||
|
||||
fxa.setSignedInUser(alice).then(() => {
|
||||
fxa.internal._profile = mockProfile;
|
||||
@ -1180,6 +1270,7 @@ function getTestUser(name) {
|
||||
return {
|
||||
email: name + "@example.com",
|
||||
uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348",
|
||||
deviceId: name + "'s device id",
|
||||
sessionToken: name + "'s session token",
|
||||
keyFetchToken: name + "'s keyfetch token",
|
||||
unwrapBKey: expandHex("44"),
|
||||
|
@ -0,0 +1,465 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
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/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
initTestLogging("Trace");
|
||||
|
||||
var log = Log.repository.getLogger("Services.FxAccounts.test");
|
||||
log.level = Log.Level.Debug;
|
||||
|
||||
Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
|
||||
Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace;
|
||||
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
|
||||
Services.prefs.setCharPref("identity.fxaccounts.oauth.client_id", "abc123");
|
||||
Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", "http://example.com/v1");
|
||||
Services.prefs.setCharPref("identity.fxaccounts.settings.uri", "http://accounts.example.com/");
|
||||
|
||||
function MockStorageManager() {
|
||||
}
|
||||
|
||||
MockStorageManager.prototype = {
|
||||
initialize(accountData) {
|
||||
this.accountData = accountData;
|
||||
},
|
||||
|
||||
finalize() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
getAccountData() {
|
||||
return Promise.resolve(this.accountData);
|
||||
},
|
||||
|
||||
updateAccountData(updatedFields) {
|
||||
for (let [name, value] of Iterator(updatedFields)) {
|
||||
if (value == null) {
|
||||
delete this.accountData[name];
|
||||
} else {
|
||||
this.accountData[name] = value;
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
||||
deleteAccountData() {
|
||||
this.accountData = null;
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
function MockFxAccountsClient(device) {
|
||||
this._email = "nobody@example.com";
|
||||
this._verified = false;
|
||||
this._deletedOnServer = false; // for testing accountStatus
|
||||
|
||||
// mock calls up to the auth server to determine whether the
|
||||
// user account has been verified
|
||||
this.recoveryEmailStatus = function (sessionToken) {
|
||||
// simulate a call to /recovery_email/status
|
||||
return Promise.resolve({
|
||||
email: this._email,
|
||||
verified: this._verified
|
||||
});
|
||||
};
|
||||
|
||||
this.accountStatus = function(uid) {
|
||||
let deferred = Promise.defer();
|
||||
deferred.resolve(!!uid && (!this._deletedOnServer));
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
const { id: deviceId, name: deviceName, type: deviceType, sessionToken } = device;
|
||||
|
||||
this.registerDevice = (st, name, type) => Promise.resolve({ id: deviceId, name });
|
||||
this.updateDevice = (st, id, name) => Promise.resolve({ id, name });
|
||||
this.signOutAndDestroyDevice = () => Promise.resolve({});
|
||||
this.getDeviceList = (st) =>
|
||||
Promise.resolve([
|
||||
{ id: deviceId, name: deviceName, type: deviceType, isCurrentDevice: st === sessionToken }
|
||||
]);
|
||||
|
||||
FxAccountsClient.apply(this);
|
||||
}
|
||||
MockFxAccountsClient.prototype = {
|
||||
__proto__: FxAccountsClient.prototype
|
||||
}
|
||||
|
||||
function MockFxAccounts(device = {}) {
|
||||
return new FxAccounts({
|
||||
_getDeviceName() {
|
||||
return device.name || "mock device name";
|
||||
},
|
||||
fxAccountsClient: new MockFxAccountsClient(device)
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_updateDeviceRegistration_with_new_device() {
|
||||
const deviceName = "foo";
|
||||
const deviceType = "bar";
|
||||
|
||||
const credentials = getTestUser("baz");
|
||||
delete credentials.deviceId;
|
||||
const fxa = new MockFxAccounts({ name: deviceName });
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = {
|
||||
registerDevice: { count: 0, args: [] },
|
||||
updateDevice: { count: 0, args: [] },
|
||||
getDeviceList: { count: 0, args: [] }
|
||||
};
|
||||
const client = fxa.internal.fxAccountsClient;
|
||||
client.registerDevice = function () {
|
||||
spy.registerDevice.count += 1;
|
||||
spy.registerDevice.args.push(arguments);
|
||||
return Promise.resolve({
|
||||
id: "newly-generated device id",
|
||||
createdAt: Date.now(),
|
||||
name: deviceName,
|
||||
type: deviceType
|
||||
});
|
||||
};
|
||||
client.updateDevice = function () {
|
||||
spy.updateDevice.count += 1;
|
||||
spy.updateDevice.args.push(arguments);
|
||||
return Promise.resolve({});
|
||||
};
|
||||
client.getDeviceList = function () {
|
||||
spy.getDeviceList.count += 1;
|
||||
spy.getDeviceList.args.push(arguments);
|
||||
return Promise.resolve([]);
|
||||
};
|
||||
|
||||
const result = yield fxa.updateDeviceRegistration();
|
||||
|
||||
do_check_eq(result, "newly-generated device id");
|
||||
do_check_eq(spy.updateDevice.count, 0);
|
||||
do_check_eq(spy.getDeviceList.count, 0);
|
||||
do_check_eq(spy.registerDevice.count, 1);
|
||||
do_check_eq(spy.registerDevice.args[0].length, 3);
|
||||
do_check_eq(spy.registerDevice.args[0][0], credentials.sessionToken);
|
||||
do_check_eq(spy.registerDevice.args[0][1], deviceName);
|
||||
do_check_eq(spy.registerDevice.args[0][2], "desktop");
|
||||
|
||||
const state = fxa.internal.currentAccountState;
|
||||
const data = yield state.getUserAccountData();
|
||||
|
||||
do_check_eq(data.deviceId, "newly-generated device id");
|
||||
do_check_false(data.isDeviceStale);
|
||||
});
|
||||
|
||||
add_task(function* test_updateDeviceRegistration_with_existing_device() {
|
||||
const deviceName = "phil's device";
|
||||
const deviceType = "desktop";
|
||||
|
||||
const credentials = getTestUser("pb");
|
||||
const fxa = new MockFxAccounts({ name: deviceName });
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = {
|
||||
registerDevice: { count: 0, args: [] },
|
||||
updateDevice: { count: 0, args: [] },
|
||||
getDeviceList: { count: 0, args: [] }
|
||||
};
|
||||
const client = fxa.internal.fxAccountsClient;
|
||||
client.registerDevice = function () {
|
||||
spy.registerDevice.count += 1;
|
||||
spy.registerDevice.args.push(arguments);
|
||||
return Promise.resolve({});
|
||||
};
|
||||
client.updateDevice = function () {
|
||||
spy.updateDevice.count += 1;
|
||||
spy.updateDevice.args.push(arguments);
|
||||
return Promise.resolve({
|
||||
id: credentials.deviceId,
|
||||
name: deviceName
|
||||
});
|
||||
};
|
||||
client.getDeviceList = function () {
|
||||
spy.getDeviceList.count += 1;
|
||||
spy.getDeviceList.args.push(arguments);
|
||||
return Promise.resolve([]);
|
||||
};
|
||||
const result = yield fxa.updateDeviceRegistration();
|
||||
|
||||
do_check_eq(result, credentials.deviceId);
|
||||
do_check_eq(spy.registerDevice.count, 0);
|
||||
do_check_eq(spy.getDeviceList.count, 0);
|
||||
do_check_eq(spy.updateDevice.count, 1);
|
||||
do_check_eq(spy.updateDevice.args[0].length, 3);
|
||||
do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
|
||||
do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
|
||||
do_check_eq(spy.updateDevice.args[0][2], deviceName);
|
||||
|
||||
const state = fxa.internal.currentAccountState;
|
||||
const data = yield state.getUserAccountData();
|
||||
|
||||
do_check_eq(data.deviceId, credentials.deviceId);
|
||||
do_check_false(data.isDeviceStale);
|
||||
});
|
||||
|
||||
add_task(function* test_updateDeviceRegistration_with_unknown_device_error() {
|
||||
const deviceName = "foo";
|
||||
const deviceType = "bar";
|
||||
|
||||
const credentials = getTestUser("baz");
|
||||
const fxa = new MockFxAccounts({ name: deviceName });
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = {
|
||||
registerDevice: { count: 0, args: [] },
|
||||
updateDevice: { count: 0, args: [] },
|
||||
getDeviceList: { count: 0, args: [] }
|
||||
};
|
||||
const client = fxa.internal.fxAccountsClient;
|
||||
client.registerDevice = function () {
|
||||
spy.registerDevice.count += 1;
|
||||
spy.registerDevice.args.push(arguments);
|
||||
return Promise.resolve({
|
||||
id: "a different newly-generated device id",
|
||||
createdAt: Date.now(),
|
||||
name: deviceName,
|
||||
type: deviceType
|
||||
});
|
||||
};
|
||||
client.updateDevice = function () {
|
||||
spy.updateDevice.count += 1;
|
||||
spy.updateDevice.args.push(arguments);
|
||||
return Promise.reject({
|
||||
code: 400,
|
||||
errno: ERRNO_UNKNOWN_DEVICE
|
||||
});
|
||||
};
|
||||
client.getDeviceList = function () {
|
||||
spy.getDeviceList.count += 1;
|
||||
spy.getDeviceList.args.push(arguments);
|
||||
return Promise.resolve([]);
|
||||
};
|
||||
|
||||
const result = yield fxa.updateDeviceRegistration();
|
||||
|
||||
do_check_null(result);
|
||||
do_check_eq(spy.getDeviceList.count, 0);
|
||||
do_check_eq(spy.registerDevice.count, 0);
|
||||
do_check_eq(spy.updateDevice.count, 1);
|
||||
do_check_eq(spy.updateDevice.args[0].length, 3);
|
||||
do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
|
||||
do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
|
||||
do_check_eq(spy.updateDevice.args[0][2], deviceName);
|
||||
|
||||
const state = fxa.internal.currentAccountState;
|
||||
const data = yield state.getUserAccountData();
|
||||
|
||||
do_check_null(data.deviceId);
|
||||
do_check_false(data.isDeviceStale);
|
||||
});
|
||||
|
||||
add_task(function* test_updateDeviceRegistration_with_device_session_conflict_error() {
|
||||
const deviceName = "foo";
|
||||
const deviceType = "bar";
|
||||
|
||||
const credentials = getTestUser("baz");
|
||||
const fxa = new MockFxAccounts({ name: deviceName });
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = {
|
||||
registerDevice: { count: 0, args: [] },
|
||||
updateDevice: { count: 0, args: [], times: [] },
|
||||
getDeviceList: { count: 0, args: [] }
|
||||
};
|
||||
const client = fxa.internal.fxAccountsClient;
|
||||
client.registerDevice = function () {
|
||||
spy.registerDevice.count += 1;
|
||||
spy.registerDevice.args.push(arguments);
|
||||
return Promise.resolve({});
|
||||
};
|
||||
client.updateDevice = function () {
|
||||
spy.updateDevice.count += 1;
|
||||
spy.updateDevice.args.push(arguments);
|
||||
spy.updateDevice.time = Date.now();
|
||||
if (spy.updateDevice.count === 1) {
|
||||
return Promise.reject({
|
||||
code: 400,
|
||||
errno: ERRNO_DEVICE_SESSION_CONFLICT
|
||||
});
|
||||
}
|
||||
return Promise.resolve({
|
||||
id: credentials.deviceId,
|
||||
name: deviceName
|
||||
});
|
||||
};
|
||||
client.getDeviceList = function () {
|
||||
spy.getDeviceList.count += 1;
|
||||
spy.getDeviceList.args.push(arguments);
|
||||
spy.getDeviceList.time = Date.now();
|
||||
return Promise.resolve([
|
||||
{ id: "ignore", name: "ignore", type: "ignore", isCurrentDevice: false },
|
||||
{ id: credentials.deviceId, name: deviceName, type: deviceType, isCurrentDevice: true }
|
||||
]);
|
||||
};
|
||||
|
||||
const result = yield fxa.updateDeviceRegistration();
|
||||
|
||||
do_check_eq(result, credentials.deviceId);
|
||||
do_check_eq(spy.registerDevice.count, 0);
|
||||
do_check_eq(spy.updateDevice.count, 1);
|
||||
do_check_eq(spy.updateDevice.args[0].length, 3);
|
||||
do_check_eq(spy.updateDevice.args[0][0], credentials.sessionToken);
|
||||
do_check_eq(spy.updateDevice.args[0][1], credentials.deviceId);
|
||||
do_check_eq(spy.updateDevice.args[0][2], deviceName);
|
||||
do_check_eq(spy.getDeviceList.count, 1);
|
||||
do_check_eq(spy.getDeviceList.args[0].length, 1);
|
||||
do_check_eq(spy.getDeviceList.args[0][0], credentials.sessionToken);
|
||||
do_check_true(spy.getDeviceList.time >= spy.updateDevice.time);
|
||||
|
||||
const state = fxa.internal.currentAccountState;
|
||||
const data = yield state.getUserAccountData();
|
||||
|
||||
do_check_eq(data.deviceId, credentials.deviceId);
|
||||
do_check_true(data.isDeviceStale);
|
||||
});
|
||||
|
||||
add_task(function* test_updateDeviceRegistration_with_unrecoverable_error() {
|
||||
const deviceName = "foo";
|
||||
const deviceType = "bar";
|
||||
|
||||
const credentials = getTestUser("baz");
|
||||
delete credentials.deviceId;
|
||||
const fxa = new MockFxAccounts({ name: deviceName });
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = {
|
||||
registerDevice: { count: 0, args: [] },
|
||||
updateDevice: { count: 0, args: [] },
|
||||
getDeviceList: { count: 0, args: [] }
|
||||
};
|
||||
const client = fxa.internal.fxAccountsClient;
|
||||
client.registerDevice = function () {
|
||||
spy.registerDevice.count += 1;
|
||||
spy.registerDevice.args.push(arguments);
|
||||
return Promise.reject({
|
||||
code: 400,
|
||||
errno: ERRNO_TOO_MANY_CLIENT_REQUESTS
|
||||
});
|
||||
};
|
||||
client.updateDevice = function () {
|
||||
spy.updateDevice.count += 1;
|
||||
spy.updateDevice.args.push(arguments);
|
||||
return Promise.resolve({});
|
||||
};
|
||||
client.getDeviceList = function () {
|
||||
spy.getDeviceList.count += 1;
|
||||
spy.getDeviceList.args.push(arguments);
|
||||
return Promise.resolve([]);
|
||||
};
|
||||
|
||||
const result = yield fxa.updateDeviceRegistration();
|
||||
|
||||
do_check_null(result);
|
||||
do_check_eq(spy.getDeviceList.count, 0);
|
||||
do_check_eq(spy.updateDevice.count, 0);
|
||||
do_check_eq(spy.registerDevice.count, 1);
|
||||
do_check_eq(spy.registerDevice.args[0].length, 3);
|
||||
|
||||
const state = fxa.internal.currentAccountState;
|
||||
const data = yield state.getUserAccountData();
|
||||
|
||||
do_check_null(data.deviceId);
|
||||
});
|
||||
|
||||
add_task(function* test_getDeviceId_with_no_device_id_invokes_device_registration() {
|
||||
const credentials = getTestUser("foo");
|
||||
credentials.verified = true;
|
||||
delete credentials.deviceId;
|
||||
const fxa = new MockFxAccounts();
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = { count: 0, args: [] };
|
||||
fxa.internal._registerOrUpdateDevice = function () {
|
||||
spy.count += 1;
|
||||
spy.args.push(arguments);
|
||||
return Promise.resolve("bar");
|
||||
};
|
||||
|
||||
const result = yield fxa.internal.getDeviceId();
|
||||
|
||||
do_check_eq(spy.count, 1);
|
||||
do_check_eq(spy.args[0].length, 1);
|
||||
do_check_eq(spy.args[0][0].email, credentials.email);
|
||||
do_check_null(spy.args[0][0].deviceId);
|
||||
do_check_eq(result, "bar");
|
||||
});
|
||||
|
||||
add_task(function* test_getDeviceId_with_device_id_and_stale_flag_invokes_device_registration() {
|
||||
const credentials = getTestUser("foo");
|
||||
credentials.verified = true;
|
||||
const fxa = new MockFxAccounts();
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = { count: 0, args: [] };
|
||||
fxa.internal.currentAccountState.getUserAccountData =
|
||||
() => Promise.resolve({ deviceId: credentials.deviceId, isDeviceStale: true });
|
||||
fxa.internal._registerOrUpdateDevice = function () {
|
||||
spy.count += 1;
|
||||
spy.args.push(arguments);
|
||||
return Promise.resolve("wibble");
|
||||
};
|
||||
|
||||
const result = yield fxa.internal.getDeviceId();
|
||||
|
||||
do_check_eq(spy.count, 1);
|
||||
do_check_eq(spy.args[0].length, 1);
|
||||
do_check_eq(spy.args[0][0].deviceId, credentials.deviceId);
|
||||
do_check_eq(result, "wibble");
|
||||
});
|
||||
|
||||
add_task(function* test_getDeviceId_with_device_id_and_no_stale_flag_doesnt_invoke_device_registration() {
|
||||
const credentials = getTestUser("foo");
|
||||
credentials.verified = true;
|
||||
const fxa = new MockFxAccounts();
|
||||
yield fxa.internal.setSignedInUser(credentials);
|
||||
|
||||
const spy = { count: 0 };
|
||||
fxa.internal._registerOrUpdateDevice = function () {
|
||||
spy.count += 1;
|
||||
return Promise.resolve("bar");
|
||||
};
|
||||
|
||||
const result = yield fxa.internal.getDeviceId();
|
||||
|
||||
do_check_eq(spy.count, 0);
|
||||
do_check_eq(result, "foo's device id");
|
||||
});
|
||||
|
||||
function expandHex(two_hex) {
|
||||
// Return a 64-character hex string, encoding 32 identical bytes.
|
||||
let eight_hex = two_hex + two_hex + two_hex + two_hex;
|
||||
let thirtytwo_hex = eight_hex + eight_hex + eight_hex + eight_hex;
|
||||
return thirtytwo_hex + thirtytwo_hex;
|
||||
};
|
||||
|
||||
function expandBytes(two_hex) {
|
||||
return CommonUtils.hexToBytes(expandHex(two_hex));
|
||||
};
|
||||
|
||||
function getTestUser(name) {
|
||||
return {
|
||||
email: name + "@example.com",
|
||||
uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348",
|
||||
deviceId: name + "'s device id",
|
||||
sessionToken: name + "'s session token",
|
||||
keyFetchToken: name + "'s keyfetch token",
|
||||
unwrapBKey: expandHex("44"),
|
||||
verified: false
|
||||
};
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ function deferredStop(server) {
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
add_task(function test_authenticated_get_request() {
|
||||
add_task(function* test_authenticated_get_request() {
|
||||
let message = "{\"msg\": \"Great Success!\"}";
|
||||
let credentials = {
|
||||
id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
|
||||
@ -65,7 +65,7 @@ add_task(function test_authenticated_get_request() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_authenticated_post_request() {
|
||||
add_task(function* test_authenticated_post_request() {
|
||||
let credentials = {
|
||||
id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
|
||||
key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
|
||||
@ -90,7 +90,7 @@ add_task(function test_authenticated_post_request() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_500_error() {
|
||||
add_task(function* test_500_error() {
|
||||
let message = "<h1>Ooops!</h1>";
|
||||
let method = "GET";
|
||||
|
||||
@ -113,7 +113,7 @@ add_task(function test_500_error() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_backoffError() {
|
||||
add_task(function* test_backoffError() {
|
||||
let method = "GET";
|
||||
let server = httpd_setup({
|
||||
"/retryDelay": function(request, response) {
|
||||
@ -160,7 +160,7 @@ add_task(function test_backoffError() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_signUp() {
|
||||
add_task(function* test_signUp() {
|
||||
let creationMessage_noKey = JSON.stringify({
|
||||
uid: "uid",
|
||||
sessionToken: "sessionToken"
|
||||
@ -238,7 +238,7 @@ add_task(function test_signUp() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_signIn() {
|
||||
add_task(function* test_signIn() {
|
||||
let sessionMessage_noKey = JSON.stringify({
|
||||
sessionToken: FAKE_SESSION_TOKEN
|
||||
});
|
||||
@ -329,7 +329,7 @@ add_task(function test_signIn() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_signOut() {
|
||||
add_task(function* test_signOut() {
|
||||
let signoutMessage = JSON.stringify({});
|
||||
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
|
||||
let signedOut = false;
|
||||
@ -366,7 +366,7 @@ add_task(function test_signOut() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_recoveryEmailStatus() {
|
||||
add_task(function* test_recoveryEmailStatus() {
|
||||
let emailStatus = JSON.stringify({verified: true});
|
||||
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
|
||||
let tries = 0;
|
||||
@ -404,7 +404,7 @@ add_task(function test_recoveryEmailStatus() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_resendVerificationEmail() {
|
||||
add_task(function* test_resendVerificationEmail() {
|
||||
let emptyMessage = "{}";
|
||||
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
|
||||
let tries = 0;
|
||||
@ -441,7 +441,7 @@ add_task(function test_resendVerificationEmail() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_accountKeys() {
|
||||
add_task(function* test_accountKeys() {
|
||||
// Four calls to accountKeys(). The first one should work correctly, and we
|
||||
// should get a valid bundle back, in exchange for our keyFetch token, from
|
||||
// which we correctly derive kA and wrapKB. The subsequent three calls
|
||||
@ -522,7 +522,7 @@ add_task(function test_accountKeys() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_signCertificate() {
|
||||
add_task(function* test_signCertificate() {
|
||||
let certSignMessage = JSON.stringify({cert: {bar: "baz"}});
|
||||
let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
|
||||
let tries = 0;
|
||||
@ -564,7 +564,7 @@ add_task(function test_signCertificate() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_accountExists() {
|
||||
add_task(function* test_accountExists() {
|
||||
let sessionMessage = JSON.stringify({sessionToken: FAKE_SESSION_TOKEN});
|
||||
let existsMessage = JSON.stringify({error: "wrong password", code: 400, errno: 103});
|
||||
let doesntExistMessage = JSON.stringify({error: "no such account", code: 400, errno: 102});
|
||||
@ -625,6 +625,173 @@ add_task(function test_accountExists() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function* test_registerDevice() {
|
||||
const DEVICE_ID = "device id";
|
||||
const DEVICE_NAME = "device name";
|
||||
const DEVICE_TYPE = "device type";
|
||||
const ERROR_NAME = "test that the client promise rejects";
|
||||
|
||||
const server = httpd_setup({
|
||||
"/account/device": function(request, response) {
|
||||
const body = JSON.parse(CommonUtils.readBytesFromInputStream(request.bodyInputStream));
|
||||
|
||||
if (body.id || !body.name || !body.type || Object.keys(body).length !== 2) {
|
||||
response.setStatusLine(request.httpVersion, 400, "Invalid request");
|
||||
return response.bodyOutputStream.write("{}", 2);
|
||||
}
|
||||
|
||||
if (body.name === ERROR_NAME) {
|
||||
response.setStatusLine(request.httpVersion, 500, "Alas");
|
||||
return response.bodyOutputStream.write("{}", 2);
|
||||
}
|
||||
|
||||
body.id = DEVICE_ID;
|
||||
body.createdAt = Date.now();
|
||||
|
||||
const responseMessage = JSON.stringify(body);
|
||||
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(responseMessage, responseMessage.length);
|
||||
},
|
||||
});
|
||||
|
||||
const client = new FxAccountsClient(server.baseURI);
|
||||
const result = yield client.registerDevice(FAKE_SESSION_TOKEN, DEVICE_NAME, DEVICE_TYPE);
|
||||
|
||||
do_check_true(result);
|
||||
do_check_eq(Object.keys(result).length, 4);
|
||||
do_check_eq(result.id, DEVICE_ID);
|
||||
do_check_eq(typeof result.createdAt, 'number');
|
||||
do_check_true(result.createdAt > 0);
|
||||
do_check_eq(result.name, DEVICE_NAME);
|
||||
do_check_eq(result.type, DEVICE_TYPE);
|
||||
|
||||
try {
|
||||
yield client.registerDevice(FAKE_SESSION_TOKEN, ERROR_NAME, DEVICE_TYPE);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(unexpectedError) {
|
||||
do_check_eq(unexpectedError.code, 500);
|
||||
}
|
||||
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function* test_updateDevice() {
|
||||
const DEVICE_ID = "some other id";
|
||||
const DEVICE_NAME = "some other name";
|
||||
const ERROR_ID = "test that the client promise rejects";
|
||||
|
||||
const server = httpd_setup({
|
||||
"/account/device": function(request, response) {
|
||||
const body = JSON.parse(CommonUtils.readBytesFromInputStream(request.bodyInputStream));
|
||||
|
||||
if (!body.id || !body.name || body.type || Object.keys(body).length !== 2) {
|
||||
response.setStatusLine(request.httpVersion, 400, "Invalid request");
|
||||
return response.bodyOutputStream.write("{}", 2);
|
||||
}
|
||||
|
||||
if (body.id === ERROR_ID) {
|
||||
response.setStatusLine(request.httpVersion, 500, "Alas");
|
||||
return response.bodyOutputStream.write("{}", 2);
|
||||
}
|
||||
|
||||
const responseMessage = JSON.stringify(body);
|
||||
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write(responseMessage, responseMessage.length);
|
||||
},
|
||||
});
|
||||
|
||||
const client = new FxAccountsClient(server.baseURI);
|
||||
const result = yield client.updateDevice(FAKE_SESSION_TOKEN, DEVICE_ID, DEVICE_NAME);
|
||||
|
||||
do_check_true(result);
|
||||
do_check_eq(Object.keys(result).length, 2);
|
||||
do_check_eq(result.id, DEVICE_ID);
|
||||
do_check_eq(result.name, DEVICE_NAME);
|
||||
|
||||
try {
|
||||
yield client.updateDevice(FAKE_SESSION_TOKEN, ERROR_ID, DEVICE_NAME);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(unexpectedError) {
|
||||
do_check_eq(unexpectedError.code, 500);
|
||||
}
|
||||
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function* test_signOutAndDestroyDevice() {
|
||||
const DEVICE_ID = "device id";
|
||||
const ERROR_ID = "test that the client promise rejects";
|
||||
|
||||
const server = httpd_setup({
|
||||
"/account/device/destroy": function(request, response) {
|
||||
const body = JSON.parse(CommonUtils.readBytesFromInputStream(request.bodyInputStream));
|
||||
|
||||
if (!body.id) {
|
||||
response.setStatusLine(request.httpVersion, 400, "Invalid request");
|
||||
return response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
|
||||
}
|
||||
|
||||
if (body.id === ERROR_ID) {
|
||||
response.setStatusLine(request.httpVersion, 500, "Alas");
|
||||
return response.bodyOutputStream.write("{}", 2);
|
||||
}
|
||||
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write("{}", 2);
|
||||
},
|
||||
});
|
||||
|
||||
const client = new FxAccountsClient(server.baseURI);
|
||||
const result = yield client.signOutAndDestroyDevice(FAKE_SESSION_TOKEN, DEVICE_ID);
|
||||
|
||||
do_check_true(result);
|
||||
do_check_eq(Object.keys(result).length, 0);
|
||||
|
||||
try {
|
||||
yield client.signOutAndDestroyDevice(FAKE_SESSION_TOKEN, ERROR_ID);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(unexpectedError) {
|
||||
do_check_eq(unexpectedError.code, 500);
|
||||
}
|
||||
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function* test_getDeviceList() {
|
||||
let canReturnDevices;
|
||||
|
||||
const server = httpd_setup({
|
||||
"/account/devices": function(request, response) {
|
||||
if (canReturnDevices) {
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
response.bodyOutputStream.write("[]", 2);
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 500, "Alas");
|
||||
response.bodyOutputStream.write("{}", 2);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const client = new FxAccountsClient(server.baseURI);
|
||||
|
||||
canReturnDevices = true;
|
||||
const result = yield client.getDeviceList(FAKE_SESSION_TOKEN);
|
||||
do_check_true(Array.isArray(result));
|
||||
do_check_eq(result.length, 0);
|
||||
|
||||
try {
|
||||
canReturnDevices = false;
|
||||
yield client.getDeviceList(FAKE_SESSION_TOKEN);
|
||||
do_throw("Expected to catch an exception");
|
||||
} catch(unexpectedError) {
|
||||
do_check_eq(unexpectedError.code, 500);
|
||||
}
|
||||
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function* test_client_metrics() {
|
||||
Services.telemetry.getKeyedHistogramById("FXA_HAWK_ERRORS").clear();
|
||||
|
||||
@ -663,7 +830,7 @@ add_task(function* test_client_metrics() {
|
||||
yield deferredStop(server);
|
||||
});
|
||||
|
||||
add_task(function test_email_case() {
|
||||
add_task(function* test_email_case() {
|
||||
let canonicalEmail = "greta.garbo@gmail.com";
|
||||
let clientEmail = "Greta.Garbo@gmail.COM";
|
||||
let attempts = 0;
|
||||
|
@ -29,7 +29,7 @@ var vectors = {
|
||||
};
|
||||
|
||||
// A simple test suite with no utf8 encoding madness.
|
||||
add_task(function test_onepw_setup_credentials() {
|
||||
add_task(function* test_onepw_setup_credentials() {
|
||||
let email = "francine@example.org";
|
||||
let password = CommonUtils.encodeUTF8("i like pie");
|
||||
|
||||
@ -66,7 +66,7 @@ add_task(function test_onepw_setup_credentials() {
|
||||
do_check_eq(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9");
|
||||
});
|
||||
|
||||
add_task(function test_client_stretch_kdf() {
|
||||
add_task(function* test_client_stretch_kdf() {
|
||||
let pbkdf2 = CryptoUtils.pbkdf2Generate;
|
||||
let hkdf = CryptoUtils.hkdf;
|
||||
let expected = vectors["client stretch-KDF"];
|
||||
|
@ -44,8 +44,16 @@ function getLoginMgrData() {
|
||||
return logins[0];
|
||||
}
|
||||
|
||||
add_task(function test_simple() {
|
||||
let fxa = new FxAccounts({});
|
||||
function createFxAccounts() {
|
||||
return new FxAccounts({
|
||||
_getDeviceName() {
|
||||
return "mock device name";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* test_simple() {
|
||||
let fxa = createFxAccounts();
|
||||
|
||||
let creds = {
|
||||
uid: "abcd",
|
||||
@ -84,8 +92,8 @@ add_task(function test_simple() {
|
||||
Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
|
||||
});
|
||||
|
||||
add_task(function test_MPLocked() {
|
||||
let fxa = new FxAccounts({});
|
||||
add_task(function* test_MPLocked() {
|
||||
let fxa = createFxAccounts();
|
||||
|
||||
let creds = {
|
||||
uid: "abcd",
|
||||
@ -118,10 +126,10 @@ add_task(function test_MPLocked() {
|
||||
});
|
||||
|
||||
|
||||
add_task(function test_consistentWithMPEdgeCases() {
|
||||
add_task(function* test_consistentWithMPEdgeCases() {
|
||||
setLoginMgrLoggedInState(true);
|
||||
|
||||
let fxa = new FxAccounts({});
|
||||
let fxa = createFxAccounts();
|
||||
|
||||
let creds1 = {
|
||||
uid: "uid1",
|
||||
@ -161,7 +169,7 @@ add_task(function test_consistentWithMPEdgeCases() {
|
||||
// Make a new FxA instance (otherwise the values in memory will be used)
|
||||
// and we want the login manager to be unlocked.
|
||||
setLoginMgrLoggedInState(true);
|
||||
fxa = new FxAccounts({});
|
||||
fxa = createFxAccounts();
|
||||
|
||||
let accountData = yield fxa.getSignedInUser();
|
||||
Assert.strictEqual(accountData.email, creds2.email);
|
||||
@ -172,7 +180,7 @@ add_task(function test_consistentWithMPEdgeCases() {
|
||||
|
||||
// A test for the fact we will accept either a UID or email when looking in
|
||||
// the login manager.
|
||||
add_task(function test_uidMigration() {
|
||||
add_task(function* test_uidMigration() {
|
||||
setLoginMgrLoggedInState(true);
|
||||
Assert.strictEqual(getLoginMgrData(), null, "expect no logins at the start");
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
Cu.import("resource://gre/modules/FxAccountsCommon.js");
|
||||
Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
const CLIENT_OPTIONS = {
|
||||
serverURL: "http://127.0.0.1:9010/v1",
|
||||
@ -145,6 +146,7 @@ add_test(function networkErrorResponse () {
|
||||
serverURL: "http://",
|
||||
client_id: "abc123"
|
||||
});
|
||||
Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
|
||||
client.getTokenFromAssertion("assertion", "scope")
|
||||
.then(
|
||||
null,
|
||||
@ -155,7 +157,8 @@ add_test(function networkErrorResponse () {
|
||||
do_check_eq(e.error, ERROR_NETWORK);
|
||||
run_next_test();
|
||||
}
|
||||
);
|
||||
).catch(() => {}).then(() =>
|
||||
Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration"));
|
||||
});
|
||||
|
||||
add_test(function unsupportedMethod () {
|
||||
|
@ -50,7 +50,7 @@ function promiseStopServer(server) {
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function getAndRevokeToken () {
|
||||
add_task(function* getAndRevokeToken () {
|
||||
let server = startServer();
|
||||
let clientOptions = {
|
||||
serverURL: "http://localhost:" + server.identity.primaryPort + "/v1",
|
||||
|
@ -70,6 +70,10 @@ function MockFxAccountsClient() {
|
||||
};
|
||||
|
||||
this.signOut = function() { return Promise.resolve(); };
|
||||
this.registerDevice = function() { return Promise.resolve(); };
|
||||
this.updateDevice = function() { return Promise.resolve(); };
|
||||
this.signOutAndDestroyDevice = function() { return Promise.resolve(); };
|
||||
this.getDeviceList = function() { return Promise.resolve(); };
|
||||
|
||||
FxAccountsClient.apply(this);
|
||||
}
|
||||
@ -78,7 +82,7 @@ MockFxAccountsClient.prototype = {
|
||||
__proto__: FxAccountsClient.prototype
|
||||
}
|
||||
|
||||
function MockFxAccounts() {
|
||||
function MockFxAccounts(device={}) {
|
||||
return new FxAccounts({
|
||||
fxAccountsClient: new MockFxAccountsClient(),
|
||||
newAccountState(credentials) {
|
||||
@ -87,6 +91,9 @@ function MockFxAccounts() {
|
||||
storage.initialize(credentials);
|
||||
return new AccountState(storage);
|
||||
},
|
||||
_getDeviceName() {
|
||||
return "mock device name";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -110,7 +117,7 @@ function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_task(function testCacheStorage() {
|
||||
add_task(function* testCacheStorage() {
|
||||
let fxa = yield createMockFxA();
|
||||
|
||||
// Hook what the impl calls to save to disk.
|
||||
|
@ -67,6 +67,10 @@ function MockFxAccountsClient() {
|
||||
};
|
||||
|
||||
this.signOut = function() { return Promise.resolve(); };
|
||||
this.registerDevice = function() { return Promise.resolve(); };
|
||||
this.updateDevice = function() { return Promise.resolve(); };
|
||||
this.signOutAndDestroyDevice = function() { return Promise.resolve(); };
|
||||
this.getDeviceList = function() { return Promise.resolve(); };
|
||||
|
||||
FxAccountsClient.apply(this);
|
||||
}
|
||||
@ -91,6 +95,9 @@ function MockFxAccounts(mockGrantClient) {
|
||||
Services.obs.notifyObservers(null, "testhelper-fxa-revoke-complete", null);
|
||||
});
|
||||
},
|
||||
_getDeviceName() {
|
||||
return "mock device name";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -138,7 +145,7 @@ MockFxAccountsOAuthGrantClient.prototype = {
|
||||
activeTokens: null,
|
||||
}
|
||||
|
||||
add_task(function testRevoke() {
|
||||
add_task(function* testRevoke() {
|
||||
let client = new MockFxAccountsOAuthGrantClient();
|
||||
let tokenOptions = { scope: "test-scope", client: client };
|
||||
let fxa = yield createMockFxA(client);
|
||||
@ -165,7 +172,7 @@ add_task(function testRevoke() {
|
||||
notEqual(token1, token2, "got a different token");
|
||||
});
|
||||
|
||||
add_task(function testSignOutDestroysTokens() {
|
||||
add_task(function* testSignOutDestroysTokens() {
|
||||
let client = new MockFxAccountsOAuthGrantClient();
|
||||
let fxa = yield createMockFxA(client);
|
||||
|
||||
@ -190,7 +197,7 @@ add_task(function testSignOutDestroysTokens() {
|
||||
equal(client.activeTokens.size, 0);
|
||||
});
|
||||
|
||||
add_task(function testTokenRaces() {
|
||||
add_task(function* testTokenRaces() {
|
||||
// Here we do 2 concurrent fetches each for 2 different token scopes (ie,
|
||||
// 4 token fetches in total).
|
||||
// This should provoke a potential race in the token fetching but we should
|
||||
|
@ -168,7 +168,7 @@ add_test(function fetchAndCacheProfile_ok() {
|
||||
|
||||
// Check that a second profile request when one is already in-flight reuses
|
||||
// the in-flight one.
|
||||
add_task(function fetchAndCacheProfileOnce() {
|
||||
add_task(function* fetchAndCacheProfileOnce() {
|
||||
// A promise that remains unresolved while we fire off 2 requests for
|
||||
// a profile.
|
||||
let resolveProfile;
|
||||
@ -205,7 +205,7 @@ add_task(function fetchAndCacheProfileOnce() {
|
||||
|
||||
// Check that sharing a single fetch promise works correctly when the promise
|
||||
// is rejected.
|
||||
add_task(function fetchAndCacheProfileOnce() {
|
||||
add_task(function* fetchAndCacheProfileOnce() {
|
||||
// A promise that remains unresolved while we fire off 2 requests for
|
||||
// a profile.
|
||||
let rejectProfile;
|
||||
@ -251,7 +251,7 @@ add_task(function fetchAndCacheProfileOnce() {
|
||||
|
||||
// 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(function fetchAndCacheProfileAfterThreshold() {
|
||||
add_task(function* fetchAndCacheProfileAfterThreshold() {
|
||||
let numFetches = 0;
|
||||
let client = mockClient(mockFxa());
|
||||
client.fetchProfile = function () {
|
||||
@ -278,7 +278,7 @@ add_task(function fetchAndCacheProfileAfterThreshold() {
|
||||
// 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(function fetchAndCacheProfileBeforeThresholdOnNotification() {
|
||||
add_task(function* fetchAndCacheProfileBeforeThresholdOnNotification() {
|
||||
let numFetches = 0;
|
||||
let client = mockClient(mockFxa());
|
||||
client.fetchProfile = function () {
|
||||
|
@ -97,6 +97,7 @@ add_storage_task(function* checkNewUser(sm) {
|
||||
uid: "uid",
|
||||
email: "someone@somewhere.com",
|
||||
kA: "kA",
|
||||
deviceId: "device id"
|
||||
};
|
||||
sm.plainStorage = new MockedPlainStorage()
|
||||
if (sm.secureStorage) {
|
||||
@ -107,10 +108,12 @@ add_storage_task(function* checkNewUser(sm) {
|
||||
Assert.equal(accountData.uid, initialAccountData.uid);
|
||||
Assert.equal(accountData.email, initialAccountData.email);
|
||||
Assert.equal(accountData.kA, initialAccountData.kA);
|
||||
Assert.equal(accountData.deviceId, initialAccountData.deviceId);
|
||||
|
||||
// and it should have been written to storage.
|
||||
Assert.equal(sm.plainStorage.data.accountData.uid, initialAccountData.uid);
|
||||
Assert.equal(sm.plainStorage.data.accountData.email, initialAccountData.email);
|
||||
Assert.equal(sm.plainStorage.data.accountData.deviceId, initialAccountData.deviceId);
|
||||
// check secure
|
||||
if (sm.secureStorage) {
|
||||
Assert.equal(sm.secureStorage.data.accountData.kA, initialAccountData.kA);
|
||||
@ -121,7 +124,12 @@ add_storage_task(function* checkNewUser(sm) {
|
||||
|
||||
// Initialized without account data but storage has it available.
|
||||
add_storage_task(function* checkEverythingRead(sm) {
|
||||
sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
|
||||
sm.plainStorage = new MockedPlainStorage({
|
||||
uid: "uid",
|
||||
email: "someone@somewhere.com",
|
||||
deviceId: "wibble",
|
||||
isDeviceStale: true
|
||||
});
|
||||
if (sm.secureStorage) {
|
||||
sm.secureStorage = new MockedSecureStorage(null);
|
||||
}
|
||||
@ -130,16 +138,27 @@ add_storage_task(function* checkEverythingRead(sm) {
|
||||
Assert.ok(accountData, "read account data");
|
||||
Assert.equal(accountData.uid, "uid");
|
||||
Assert.equal(accountData.email, "someone@somewhere.com");
|
||||
Assert.equal(accountData.deviceId, "wibble");
|
||||
Assert.equal(accountData.isDeviceStale, true);
|
||||
// Update the data - we should be able to fetch it back and it should appear
|
||||
// in our storage.
|
||||
yield sm.updateAccountData({verified: true, kA: "kA", kB: "kB"});
|
||||
yield sm.updateAccountData({
|
||||
verified: true,
|
||||
kA: "kA",
|
||||
kB: "kB",
|
||||
isDeviceStale: false
|
||||
});
|
||||
accountData = yield sm.getAccountData();
|
||||
Assert.equal(accountData.kB, "kB");
|
||||
Assert.equal(accountData.kA, "kA");
|
||||
Assert.equal(accountData.deviceId, "wibble");
|
||||
Assert.equal(accountData.isDeviceStale, false);
|
||||
// Check the new value was written to storage.
|
||||
yield sm._promiseStorageComplete; // storage is written in the background.
|
||||
// "verified" is a plain-text field.
|
||||
// "verified", "deviceId" and "isDeviceStale" are plain-text fields.
|
||||
Assert.equal(sm.plainStorage.data.accountData.verified, true);
|
||||
Assert.equal(sm.plainStorage.data.accountData.deviceId, "wibble");
|
||||
Assert.equal(sm.plainStorage.data.accountData.isDeviceStale, false);
|
||||
// "kA" and "foo" are secure
|
||||
if (sm.secureStorage) {
|
||||
Assert.equal(sm.secureStorage.data.accountData.kA, "kA");
|
||||
|
@ -4,6 +4,8 @@ tail =
|
||||
skip-if = toolkit == 'android'
|
||||
|
||||
[test_accounts.js]
|
||||
[test_accounts_device_registration.js]
|
||||
skip-if = appname == 'b2g'
|
||||
[test_client.js]
|
||||
skip-if = toolkit == 'gonk' # times out, bug 1073639
|
||||
[test_credentials.js]
|
||||
|
@ -182,6 +182,9 @@ MIN_PASS_LENGTH: 8,
|
||||
|
||||
LOG_DATE_FORMAT: "%Y-%m-%d %H:%M:%S",
|
||||
|
||||
DEVICE_TYPE_DESKTOP: "desktop",
|
||||
DEVICE_TYPE_MOBILE: "mobile",
|
||||
|
||||
})) {
|
||||
this[key] = val;
|
||||
this.EXPORTED_SYMBOLS.push(key);
|
||||
|
@ -9,6 +9,7 @@ this.EXPORTED_SYMBOLS = [
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://services-common/async.js");
|
||||
Cu.import("resource://services-common/stringbundle.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/engines.js");
|
||||
@ -16,6 +17,9 @@ Cu.import("resource://services-sync/record.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
|
||||
"resource://gre/modules/FxAccounts.jsm");
|
||||
|
||||
const CLIENTS_TTL = 1814400; // 21 days
|
||||
const CLIENTS_TTL_REFRESH = 604800; // 7 days
|
||||
|
||||
@ -64,14 +68,14 @@ ClientEngine.prototype = {
|
||||
// Aggregate some stats on the composition of clients on this account
|
||||
get stats() {
|
||||
let stats = {
|
||||
hasMobile: this.localType == "mobile",
|
||||
hasMobile: this.localType == DEVICE_TYPE_MOBILE,
|
||||
names: [this.localName],
|
||||
numClients: 1,
|
||||
};
|
||||
|
||||
for (let id in this._store._remoteClients) {
|
||||
let {name, type} = this._store._remoteClients[id];
|
||||
stats.hasMobile = stats.hasMobile || type == "mobile";
|
||||
stats.hasMobile = stats.hasMobile || type == DEVICE_TYPE_MOBILE;
|
||||
stats.names.push(name);
|
||||
stats.numClients++;
|
||||
}
|
||||
@ -117,18 +121,15 @@ ClientEngine.prototype = {
|
||||
},
|
||||
|
||||
get localName() {
|
||||
let localName = Svc.Prefs.get("client.name", "");
|
||||
if (localName != "")
|
||||
return localName;
|
||||
|
||||
return this.localName = Utils.getDefaultDeviceName();
|
||||
return this.localName = Utils.getDeviceName();
|
||||
},
|
||||
set localName(value) {
|
||||
Svc.Prefs.set("client.name", value);
|
||||
fxAccounts.updateDeviceRegistration();
|
||||
},
|
||||
|
||||
get localType() {
|
||||
return Svc.Prefs.get("client.type", "desktop");
|
||||
return Utils.getDeviceType();
|
||||
},
|
||||
set localType(value) {
|
||||
Svc.Prefs.set("client.type", value);
|
||||
@ -136,7 +137,7 @@ ClientEngine.prototype = {
|
||||
|
||||
isMobile: function isMobile(id) {
|
||||
if (this._store._remoteClients[id])
|
||||
return this._store._remoteClients[id].type == "mobile";
|
||||
return this._store._remoteClients[id].type == DEVICE_TYPE_MOBILE;
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -434,6 +435,13 @@ ClientStore.prototype = {
|
||||
|
||||
// Package the individual components into a record for the local client
|
||||
if (id == this.engine.localID) {
|
||||
let cb = Async.makeSpinningCallback();
|
||||
fxAccounts.getDeviceId().then(id => cb(null, id), cb);
|
||||
try {
|
||||
record.fxaDeviceId = cb.wait();
|
||||
} catch(error) {
|
||||
this._log.warn("failed to get fxa device id", error);
|
||||
}
|
||||
record.name = this.engine.localName;
|
||||
record.type = this.engine.localType;
|
||||
record.commands = this.engine.localCommands;
|
||||
|
@ -669,6 +669,20 @@ this.Utils = {
|
||||
Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
|
||||
|
||||
return Str.sync.get("client.name2", [user, appName, system]);
|
||||
},
|
||||
|
||||
getDeviceName() {
|
||||
const deviceName = Svc.Prefs.get("client.name", "");
|
||||
|
||||
if (deviceName === "") {
|
||||
return this.getDefaultDeviceName();
|
||||
}
|
||||
|
||||
return deviceName;
|
||||
},
|
||||
|
||||
getDeviceType() {
|
||||
return Svc.Prefs.get("client.type", DEVICE_TYPE_DESKTOP);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -8,4 +8,5 @@ user_pref("dom.ipc.tabs.shutdownTimeoutSecs", 0);
|
||||
user_pref("dom.mozBrowserFramesEnabled", "%(OOP)s");
|
||||
user_pref("dom.mozBrowserFramesWhitelist","app://test-container.gaiamobile.org,http://mochi.test:8888");
|
||||
user_pref("dom.testing.datastore_enabled_for_hosted_apps", true);
|
||||
user_pref('identity.fxaccounts.skipDeviceRegistration', true);
|
||||
user_pref("marionette.force-local", true);
|
||||
|
@ -253,6 +253,9 @@ user_pref("identity.fxaccounts.remote.signin.uri", "https://%(server)s/fxa-signi
|
||||
user_pref("identity.fxaccounts.settings.uri", "https://%(server)s/fxa-settings");
|
||||
user_pref('identity.fxaccounts.remote.webchannel.uri', 'https://%(server)s/');
|
||||
|
||||
// We don't want browser tests to perform FxA device registration.
|
||||
user_pref('identity.fxaccounts.skipDeviceRegistration', true);
|
||||
|
||||
// Increase the APZ content response timeout in tests to 1 minute.
|
||||
// This is to accommodate the fact that test environments tends to be slower
|
||||
// than production environments (with the b2g emulator being the slowest of them
|
||||
|
Loading…
Reference in New Issue
Block a user