1211 lines
35 KiB
JavaScript
1211 lines
35 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
const { FxAccounts } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/FxAccounts.sys.mjs"
|
|
);
|
|
const { FxAccountsClient } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/FxAccountsClient.sys.mjs"
|
|
);
|
|
const { FxAccountsDevice } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/FxAccountsDevice.sys.mjs"
|
|
);
|
|
const {
|
|
CLIENT_IS_THUNDERBIRD,
|
|
ERRNO_DEVICE_SESSION_CONFLICT,
|
|
ERRNO_TOO_MANY_CLIENT_REQUESTS,
|
|
ERRNO_UNKNOWN_DEVICE,
|
|
ON_DEVICE_CONNECTED_NOTIFICATION,
|
|
ON_DEVICE_DISCONNECTED_NOTIFICATION,
|
|
ON_DEVICELIST_UPDATED,
|
|
} = ChromeUtils.importESModule(
|
|
"resource://gre/modules/FxAccountsCommon.sys.mjs"
|
|
);
|
|
var { AccountState } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/FxAccounts.sys.mjs"
|
|
);
|
|
|
|
initTestLogging("Trace");
|
|
|
|
var log = Log.repository.getLogger("Services.FxAccounts.test");
|
|
log.level = Log.Level.Debug;
|
|
|
|
const BOGUS_PUBLICKEY =
|
|
"BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc";
|
|
const BOGUS_AUTHKEY = "GSsIiaD2Mr83iPqwFNK4rw";
|
|
|
|
Services.prefs.setStringPref("identity.fxaccounts.loglevel", "Trace");
|
|
|
|
const DEVICE_REGISTRATION_VERSION = 42;
|
|
|
|
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 Object.entries(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";
|
|
// Be careful relying on `this._verified` as it doesn't change if the user's
|
|
// state does via setting the `verified` flag in the user data.
|
|
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 () {
|
|
// simulate a call to /recovery_email/status
|
|
return Promise.resolve({
|
|
email: this._email,
|
|
verified: this._verified,
|
|
});
|
|
};
|
|
|
|
this.accountKeys = function (keyFetchToken) {
|
|
Assert.ok(keyFetchToken, "must be called with a key-fetch-token");
|
|
// ideally we'd check the verification status here to more closely simulate
|
|
// the server, but `this._verified` is a test-only construct and doesn't
|
|
// update when the user changes verification status.
|
|
Assert.ok(!this._deletedOnServer, "this test thinks the acct is deleted!");
|
|
return {
|
|
kA: "test-ka",
|
|
wrapKB: "X".repeat(32),
|
|
};
|
|
};
|
|
|
|
this.accountStatus = function (uid) {
|
|
return Promise.resolve(!!uid && !this._deletedOnServer);
|
|
};
|
|
|
|
this.registerDevice = (st, name) => Promise.resolve({ id: device.id, name });
|
|
this.updateDevice = (st, id, name) => Promise.resolve({ id, name });
|
|
this.signOut = () => Promise.resolve({});
|
|
this.getDeviceList = st =>
|
|
Promise.resolve([
|
|
{
|
|
id: device.id,
|
|
name: device.name,
|
|
type: device.type,
|
|
pushCallback: device.pushCallback,
|
|
pushEndpointExpired: device.pushEndpointExpired,
|
|
isCurrentDevice: st === device.sessionToken,
|
|
},
|
|
]);
|
|
|
|
FxAccountsClient.apply(this);
|
|
}
|
|
MockFxAccountsClient.prototype = {};
|
|
Object.setPrototypeOf(
|
|
MockFxAccountsClient.prototype,
|
|
FxAccountsClient.prototype
|
|
);
|
|
|
|
async function MockFxAccounts(credentials, device = {}) {
|
|
let fxa = new FxAccounts({
|
|
newAccountState(creds) {
|
|
// we use a real accountState but mocked storage.
|
|
let storage = new MockStorageManager();
|
|
storage.initialize(creds);
|
|
return new AccountState(storage);
|
|
},
|
|
fxAccountsClient: new MockFxAccountsClient(device, credentials),
|
|
fxaPushService: {
|
|
registerPushEndpoint() {
|
|
return new Promise(resolve => {
|
|
resolve({
|
|
endpoint: "http://mochi.test:8888",
|
|
getKey(type) {
|
|
return ChromeUtils.base64URLDecode(
|
|
type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY,
|
|
{ padding: "ignore" }
|
|
);
|
|
},
|
|
});
|
|
});
|
|
},
|
|
unsubscribe() {
|
|
return Promise.resolve();
|
|
},
|
|
},
|
|
commands: {
|
|
async availableCommands() {
|
|
return {};
|
|
},
|
|
},
|
|
device: {
|
|
DEVICE_REGISTRATION_VERSION,
|
|
_checkRemoteCommandsUpdateNeeded: async () => false,
|
|
},
|
|
VERIFICATION_POLL_TIMEOUT_INITIAL: 1,
|
|
});
|
|
fxa._internal.device._fxai = fxa._internal;
|
|
await fxa._internal.setSignedInUser(credentials);
|
|
Services.prefs.setStringPref(
|
|
"identity.fxaccounts.account.device.name",
|
|
device.name || "mock device name"
|
|
);
|
|
return fxa;
|
|
}
|
|
|
|
function updateUserAccountData(fxa, data) {
|
|
return fxa._internal.updateUserAccountData(data);
|
|
}
|
|
|
|
add_task(async function test_updateDeviceRegistration_with_new_device() {
|
|
const deviceName = "foo";
|
|
const deviceType = "bar";
|
|
|
|
const credentials = getTestUser("baz");
|
|
const fxa = await MockFxAccounts(credentials, { name: deviceName });
|
|
// Remove the current device registration (setSignedInUser does one!).
|
|
await updateUserAccountData(fxa, { uid: credentials.uid, device: null });
|
|
|
|
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([]);
|
|
};
|
|
|
|
await fxa.updateDeviceRegistration();
|
|
|
|
Assert.equal(spy.updateDevice.count, 0);
|
|
Assert.equal(spy.getDeviceList.count, 0);
|
|
Assert.equal(spy.registerDevice.count, 1);
|
|
Assert.equal(spy.registerDevice.args[0].length, 4);
|
|
Assert.equal(spy.registerDevice.args[0][0], credentials.sessionToken);
|
|
Assert.equal(spy.registerDevice.args[0][1], deviceName);
|
|
Assert.equal(spy.registerDevice.args[0][2], "desktop");
|
|
Assert.equal(
|
|
spy.registerDevice.args[0][3].pushCallback,
|
|
"http://mochi.test:8888"
|
|
);
|
|
Assert.equal(spy.registerDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
|
|
Assert.equal(spy.registerDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
|
|
|
|
const state = fxa._internal.currentAccountState;
|
|
const data = await state.getUserAccountData();
|
|
|
|
Assert.equal(data.device.id, "newly-generated device id");
|
|
Assert.equal(data.device.registrationVersion, DEVICE_REGISTRATION_VERSION);
|
|
await fxa.signOut(true);
|
|
});
|
|
|
|
add_task(async function test_updateDeviceRegistration_with_existing_device() {
|
|
const deviceId = "my device id";
|
|
const deviceName = "phil's device";
|
|
|
|
const credentials = getTestUser("pb");
|
|
const fxa = await MockFxAccounts(credentials, { name: deviceName });
|
|
await updateUserAccountData(fxa, {
|
|
uid: credentials.uid,
|
|
device: {
|
|
id: deviceId,
|
|
registeredCommandsKeys: [],
|
|
registrationVersion: 1, // < 42
|
|
},
|
|
});
|
|
|
|
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: deviceId,
|
|
name: deviceName,
|
|
});
|
|
};
|
|
client.getDeviceList = function () {
|
|
spy.getDeviceList.count += 1;
|
|
spy.getDeviceList.args.push(arguments);
|
|
return Promise.resolve([]);
|
|
};
|
|
await fxa.updateDeviceRegistration();
|
|
|
|
Assert.equal(spy.registerDevice.count, 0);
|
|
Assert.equal(spy.getDeviceList.count, 0);
|
|
Assert.equal(spy.updateDevice.count, 1);
|
|
Assert.equal(spy.updateDevice.args[0].length, 4);
|
|
Assert.equal(spy.updateDevice.args[0][0], credentials.sessionToken);
|
|
Assert.equal(spy.updateDevice.args[0][1], deviceId);
|
|
Assert.equal(spy.updateDevice.args[0][2], deviceName);
|
|
Assert.equal(
|
|
spy.updateDevice.args[0][3].pushCallback,
|
|
"http://mochi.test:8888"
|
|
);
|
|
Assert.equal(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
|
|
Assert.equal(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
|
|
|
|
const state = fxa._internal.currentAccountState;
|
|
const data = await state.getUserAccountData();
|
|
|
|
Assert.equal(data.device.id, deviceId);
|
|
Assert.equal(data.device.registrationVersion, DEVICE_REGISTRATION_VERSION);
|
|
await fxa.signOut(true);
|
|
});
|
|
|
|
add_task(
|
|
async function test_updateDeviceRegistration_with_unknown_device_error() {
|
|
const deviceName = "foo";
|
|
const deviceType = "bar";
|
|
const currentDeviceId = "my device id";
|
|
|
|
const credentials = getTestUser("baz");
|
|
const fxa = await MockFxAccounts(credentials, { name: deviceName });
|
|
await updateUserAccountData(fxa, {
|
|
uid: credentials.uid,
|
|
device: {
|
|
id: currentDeviceId,
|
|
registeredCommandsKeys: [],
|
|
registrationVersion: 1, // < 42
|
|
},
|
|
});
|
|
|
|
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([]);
|
|
};
|
|
|
|
await fxa.updateDeviceRegistration();
|
|
|
|
Assert.equal(spy.getDeviceList.count, 0);
|
|
Assert.equal(spy.registerDevice.count, 0);
|
|
Assert.equal(spy.updateDevice.count, 1);
|
|
Assert.equal(spy.updateDevice.args[0].length, 4);
|
|
Assert.equal(spy.updateDevice.args[0][0], credentials.sessionToken);
|
|
Assert.equal(spy.updateDevice.args[0][1], currentDeviceId);
|
|
Assert.equal(spy.updateDevice.args[0][2], deviceName);
|
|
Assert.equal(
|
|
spy.updateDevice.args[0][3].pushCallback,
|
|
"http://mochi.test:8888"
|
|
);
|
|
Assert.equal(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
|
|
Assert.equal(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
|
|
|
|
const state = fxa._internal.currentAccountState;
|
|
const data = await state.getUserAccountData();
|
|
|
|
Assert.equal(null, data.device);
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
async function test_updateDeviceRegistration_with_device_session_conflict_error() {
|
|
const deviceName = "foo";
|
|
const deviceType = "bar";
|
|
const currentDeviceId = "my device id";
|
|
const conflictingDeviceId = "conflicting device id";
|
|
|
|
const credentials = getTestUser("baz");
|
|
const fxa = await MockFxAccounts(credentials, { name: deviceName });
|
|
await updateUserAccountData(fxa, {
|
|
uid: credentials.uid,
|
|
device: {
|
|
id: currentDeviceId,
|
|
registeredCommandsKeys: [],
|
|
registrationVersion: 1, // < 42
|
|
},
|
|
});
|
|
|
|
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: conflictingDeviceId,
|
|
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: conflictingDeviceId,
|
|
name: deviceName,
|
|
type: deviceType,
|
|
isCurrentDevice: true,
|
|
},
|
|
]);
|
|
};
|
|
|
|
await fxa.updateDeviceRegistration();
|
|
|
|
Assert.equal(spy.registerDevice.count, 0);
|
|
Assert.equal(spy.updateDevice.count, 1);
|
|
Assert.equal(spy.updateDevice.args[0].length, 4);
|
|
Assert.equal(spy.updateDevice.args[0][0], credentials.sessionToken);
|
|
Assert.equal(spy.updateDevice.args[0][1], currentDeviceId);
|
|
Assert.equal(spy.updateDevice.args[0][2], deviceName);
|
|
Assert.equal(
|
|
spy.updateDevice.args[0][3].pushCallback,
|
|
"http://mochi.test:8888"
|
|
);
|
|
Assert.equal(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
|
|
Assert.equal(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
|
|
Assert.equal(spy.getDeviceList.count, 1);
|
|
Assert.equal(spy.getDeviceList.args[0].length, 1);
|
|
Assert.equal(spy.getDeviceList.args[0][0], credentials.sessionToken);
|
|
Assert.ok(spy.getDeviceList.time >= spy.updateDevice.time);
|
|
|
|
const state = fxa._internal.currentAccountState;
|
|
const data = await state.getUserAccountData();
|
|
|
|
Assert.equal(data.device.id, conflictingDeviceId);
|
|
Assert.equal(data.device.registrationVersion, null);
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
async function test_updateDeviceRegistration_with_unrecoverable_error() {
|
|
const deviceName = "foo";
|
|
|
|
const credentials = getTestUser("baz");
|
|
const fxa = await MockFxAccounts(credentials, { name: deviceName });
|
|
await updateUserAccountData(fxa, { uid: credentials.uid, device: null });
|
|
|
|
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([]);
|
|
};
|
|
|
|
await fxa.updateDeviceRegistration();
|
|
|
|
Assert.equal(spy.getDeviceList.count, 0);
|
|
Assert.equal(spy.updateDevice.count, 0);
|
|
Assert.equal(spy.registerDevice.count, 1);
|
|
Assert.equal(spy.registerDevice.args[0].length, 4);
|
|
|
|
const state = fxa._internal.currentAccountState;
|
|
const data = await state.getUserAccountData();
|
|
|
|
Assert.equal(null, data.device);
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
async function test_getDeviceId_with_no_device_id_invokes_device_registration() {
|
|
const credentials = getTestUser("foo");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
await updateUserAccountData(fxa, { uid: credentials.uid, device: null });
|
|
|
|
const spy = { count: 0, args: [] };
|
|
fxa._internal.currentAccountState.getUserAccountData = () =>
|
|
Promise.resolve({
|
|
email: credentials.email,
|
|
registrationVersion: DEVICE_REGISTRATION_VERSION,
|
|
});
|
|
fxa._internal.device._registerOrUpdateDevice = function () {
|
|
spy.count += 1;
|
|
spy.args.push(arguments);
|
|
return Promise.resolve("bar");
|
|
};
|
|
|
|
const result = await fxa.device.getLocalId();
|
|
|
|
Assert.equal(spy.count, 1);
|
|
Assert.equal(spy.args[0].length, 2);
|
|
Assert.equal(spy.args[0][1].email, credentials.email);
|
|
Assert.equal(null, spy.args[0][1].device);
|
|
Assert.equal(result, "bar");
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
async function test_getDeviceId_with_registration_version_outdated_invokes_device_registration() {
|
|
const credentials = getTestUser("foo");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
|
|
const spy = { count: 0, args: [] };
|
|
fxa._internal.currentAccountState.getUserAccountData = () =>
|
|
Promise.resolve({
|
|
device: {
|
|
id: "my id",
|
|
registrationVersion: 0,
|
|
registeredCommandsKeys: [],
|
|
},
|
|
});
|
|
fxa._internal.device._registerOrUpdateDevice = function () {
|
|
spy.count += 1;
|
|
spy.args.push(arguments);
|
|
return Promise.resolve("wibble");
|
|
};
|
|
|
|
const result = await fxa.device.getLocalId();
|
|
|
|
Assert.equal(spy.count, 1);
|
|
Assert.equal(spy.args[0].length, 2);
|
|
Assert.equal(spy.args[0][1].device.id, "my id");
|
|
Assert.equal(result, "wibble");
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
async function test_getDeviceId_with_device_id_and_uptodate_registration_version_doesnt_invoke_device_registration() {
|
|
const credentials = getTestUser("foo");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
|
|
const spy = { count: 0 };
|
|
fxa._internal.currentAccountState.getUserAccountData = async () => ({
|
|
device: {
|
|
id: "foo's device id",
|
|
registrationVersion: DEVICE_REGISTRATION_VERSION,
|
|
registeredCommandsKeys: [],
|
|
},
|
|
});
|
|
fxa._internal.device._registerOrUpdateDevice = function () {
|
|
spy.count += 1;
|
|
return Promise.resolve("bar");
|
|
};
|
|
|
|
const result = await fxa.device.getLocalId();
|
|
|
|
Assert.equal(spy.count, 0);
|
|
Assert.equal(result, "foo's device id");
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
async function test_getDeviceId_with_device_id_and_with_no_registration_version_invokes_device_registration() {
|
|
const credentials = getTestUser("foo");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
|
|
const spy = { count: 0, args: [] };
|
|
fxa._internal.currentAccountState.getUserAccountData = () =>
|
|
Promise.resolve({ device: { id: "wibble" } });
|
|
fxa._internal.device._registerOrUpdateDevice = function () {
|
|
spy.count += 1;
|
|
spy.args.push(arguments);
|
|
return Promise.resolve("wibble");
|
|
};
|
|
|
|
const result = await fxa.device.getLocalId();
|
|
|
|
Assert.equal(spy.count, 1);
|
|
Assert.equal(spy.args[0].length, 2);
|
|
Assert.equal(spy.args[0][1].device.id, "wibble");
|
|
Assert.equal(result, "wibble");
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(
|
|
{ skip_if: () => CLIENT_IS_THUNDERBIRD },
|
|
async function test_verification_updates_registration() {
|
|
const deviceName = "foo";
|
|
ensureOauthNotConfigured();
|
|
|
|
const credentials = getTestUser("baz");
|
|
const fxa = await MockFxAccounts(credentials, {
|
|
id: "device-id",
|
|
name: deviceName,
|
|
});
|
|
|
|
// We should already have a device registration, but without send-tab due to
|
|
// our inability to fetch keys for an unverified users.
|
|
const state = fxa._internal.currentAccountState;
|
|
const { device } = await state.getUserAccountData();
|
|
Assert.equal(device.registeredCommandsKeys.length, 0);
|
|
|
|
let updatePromise = new Promise(resolve => {
|
|
const old_registerOrUpdateDevice =
|
|
fxa.device._registerOrUpdateDevice.bind(fxa.device);
|
|
fxa.device._registerOrUpdateDevice = async function (
|
|
currentState,
|
|
signedInUser
|
|
) {
|
|
await old_registerOrUpdateDevice(currentState, signedInUser);
|
|
fxa.device._registerOrUpdateDevice = old_registerOrUpdateDevice;
|
|
resolve();
|
|
};
|
|
});
|
|
|
|
fxa._internal.checkEmailStatus = async function () {
|
|
credentials.verified = true;
|
|
return credentials;
|
|
};
|
|
|
|
await updatePromise;
|
|
|
|
const {
|
|
device: newDevice,
|
|
encryptedSendTabKeys,
|
|
encryptedCloseTabKeys,
|
|
} = await state.getUserAccountData();
|
|
Assert.equal(newDevice.registeredCommandsKeys.length, 2);
|
|
Assert.notEqual(encryptedSendTabKeys, null);
|
|
Assert.notEqual(encryptedCloseTabKeys, null);
|
|
await fxa.signOut(true);
|
|
}
|
|
);
|
|
|
|
add_task(async function test_devicelist_pushendpointexpired() {
|
|
const deviceId = "mydeviceid";
|
|
const credentials = getTestUser("baz");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
await updateUserAccountData(fxa, {
|
|
uid: credentials.uid,
|
|
device: {
|
|
id: deviceId,
|
|
registeredCommandsKeys: [],
|
|
registrationVersion: 1, // < 42
|
|
},
|
|
});
|
|
|
|
const spy = {
|
|
updateDevice: { count: 0, args: [] },
|
|
getDeviceList: { count: 0, args: [] },
|
|
};
|
|
const client = fxa._internal.fxAccountsClient;
|
|
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([
|
|
{
|
|
id: "mydeviceid",
|
|
name: "foo",
|
|
type: "desktop",
|
|
isCurrentDevice: true,
|
|
pushEndpointExpired: true,
|
|
pushCallback: "https://example.com",
|
|
},
|
|
]);
|
|
};
|
|
let polledForMissedCommands = false;
|
|
fxa._internal.commands.pollDeviceCommands = () => {
|
|
polledForMissedCommands = true;
|
|
};
|
|
|
|
await fxa.device.refreshDeviceList();
|
|
|
|
Assert.equal(spy.getDeviceList.count, 1);
|
|
Assert.equal(spy.updateDevice.count, 1);
|
|
Assert.ok(polledForMissedCommands);
|
|
await fxa.signOut(true);
|
|
});
|
|
|
|
add_task(async function test_devicelist_nopushcallback() {
|
|
const deviceId = "mydeviceid";
|
|
const credentials = getTestUser("baz");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
await updateUserAccountData(fxa, {
|
|
uid: credentials.uid,
|
|
device: {
|
|
id: deviceId,
|
|
registeredCommandsKeys: [],
|
|
registrationVersion: 1,
|
|
},
|
|
});
|
|
|
|
const spy = {
|
|
updateDevice: { count: 0, args: [] },
|
|
getDeviceList: { count: 0, args: [] },
|
|
};
|
|
const client = fxa._internal.fxAccountsClient;
|
|
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([
|
|
{
|
|
id: "mydeviceid",
|
|
name: "foo",
|
|
type: "desktop",
|
|
isCurrentDevice: true,
|
|
pushEndpointExpired: false,
|
|
pushCallback: null,
|
|
},
|
|
]);
|
|
};
|
|
|
|
let polledForMissedCommands = false;
|
|
fxa._internal.commands.pollDeviceCommands = () => {
|
|
polledForMissedCommands = true;
|
|
};
|
|
|
|
await fxa.device.refreshDeviceList();
|
|
|
|
Assert.equal(spy.getDeviceList.count, 1);
|
|
Assert.equal(spy.updateDevice.count, 1);
|
|
Assert.ok(polledForMissedCommands);
|
|
await fxa.signOut(true);
|
|
});
|
|
|
|
add_task(async function test_refreshDeviceList() {
|
|
let credentials = getTestUser("baz");
|
|
|
|
let storage = new MockStorageManager();
|
|
storage.initialize(credentials);
|
|
let state = new AccountState(storage);
|
|
|
|
let fxAccountsClient = new MockFxAccountsClient({
|
|
id: "deviceAAAAAA",
|
|
name: "iPhone",
|
|
type: "phone",
|
|
pushCallback: "http://mochi.test:8888",
|
|
pushEndpointExpired: false,
|
|
sessionToken: credentials.sessionToken,
|
|
});
|
|
let spy = {
|
|
getDeviceList: { count: 0 },
|
|
};
|
|
const deviceListUpdateObserver = {
|
|
count: 0,
|
|
observe() {
|
|
this.count++;
|
|
},
|
|
};
|
|
Services.obs.addObserver(deviceListUpdateObserver, ON_DEVICELIST_UPDATED);
|
|
|
|
fxAccountsClient.getDeviceList = (function (old) {
|
|
return function getDeviceList() {
|
|
spy.getDeviceList.count += 1;
|
|
return old.apply(this, arguments);
|
|
};
|
|
})(fxAccountsClient.getDeviceList);
|
|
let fxai = {
|
|
_now: Date.now(),
|
|
_generation: 0,
|
|
fxAccountsClient,
|
|
now() {
|
|
return this._now;
|
|
},
|
|
withVerifiedAccountState(func) {
|
|
// Ensure `func` is called asynchronously, and simulate the possibility
|
|
// of a different user signng in while the promise is in-flight.
|
|
const currentGeneration = this._generation;
|
|
return Promise.resolve()
|
|
.then(_ => func(state))
|
|
.then(result => {
|
|
if (currentGeneration < this._generation) {
|
|
throw new Error("Another user has signed in");
|
|
}
|
|
return result;
|
|
});
|
|
},
|
|
fxaPushService: {
|
|
registerPushEndpoint() {
|
|
return new Promise(resolve => {
|
|
resolve({
|
|
endpoint: "http://mochi.test:8888",
|
|
getKey(type) {
|
|
return ChromeUtils.base64URLDecode(
|
|
type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY,
|
|
{ padding: "ignore" }
|
|
);
|
|
},
|
|
});
|
|
});
|
|
},
|
|
unsubscribe() {
|
|
return Promise.resolve();
|
|
},
|
|
getSubscription() {
|
|
return Promise.resolve({
|
|
isExpired: () => {
|
|
return false;
|
|
},
|
|
endpoint: "http://mochi.test:8888",
|
|
});
|
|
},
|
|
},
|
|
async _handleTokenError(e) {
|
|
_(`Test failure: ${e} - ${e.stack}`);
|
|
throw e;
|
|
},
|
|
};
|
|
let device = new FxAccountsDevice(fxai);
|
|
device._checkRemoteCommandsUpdateNeeded = async () => false;
|
|
|
|
Assert.equal(
|
|
device.recentDeviceList,
|
|
null,
|
|
"Should not have device list initially"
|
|
);
|
|
Assert.ok(await device.refreshDeviceList(), "Should refresh list");
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
1,
|
|
`${ON_DEVICELIST_UPDATED} was notified`
|
|
);
|
|
Assert.deepEqual(
|
|
device.recentDeviceList,
|
|
[
|
|
{
|
|
id: "deviceAAAAAA",
|
|
name: "iPhone",
|
|
type: "phone",
|
|
pushCallback: "http://mochi.test:8888",
|
|
pushEndpointExpired: false,
|
|
isCurrentDevice: true,
|
|
},
|
|
],
|
|
"Should fetch device list"
|
|
);
|
|
Assert.equal(
|
|
spy.getDeviceList.count,
|
|
1,
|
|
"Should make request to refresh list"
|
|
);
|
|
Assert.ok(
|
|
!(await device.refreshDeviceList()),
|
|
"Should not refresh device list if fresh"
|
|
);
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
1,
|
|
`${ON_DEVICELIST_UPDATED} was not notified`
|
|
);
|
|
|
|
fxai._now += device.TIME_BETWEEN_FXA_DEVICES_FETCH_MS;
|
|
|
|
let refreshPromise = device.refreshDeviceList();
|
|
let secondRefreshPromise = device.refreshDeviceList();
|
|
Assert.ok(
|
|
await Promise.all([refreshPromise, secondRefreshPromise]),
|
|
"Should refresh list if stale"
|
|
);
|
|
Assert.equal(
|
|
spy.getDeviceList.count,
|
|
2,
|
|
"Should only make one request if called with pending request"
|
|
);
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
2,
|
|
`${ON_DEVICELIST_UPDATED} only notified once`
|
|
);
|
|
|
|
device.observe(null, ON_DEVICE_CONNECTED_NOTIFICATION);
|
|
await device.refreshDeviceList();
|
|
Assert.equal(
|
|
spy.getDeviceList.count,
|
|
3,
|
|
"Should refresh device list after connecting new device"
|
|
);
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
3,
|
|
`${ON_DEVICELIST_UPDATED} notified when new device connects`
|
|
);
|
|
device.observe(
|
|
null,
|
|
ON_DEVICE_DISCONNECTED_NOTIFICATION,
|
|
JSON.stringify({ isLocalDevice: false })
|
|
);
|
|
await device.refreshDeviceList();
|
|
Assert.equal(
|
|
spy.getDeviceList.count,
|
|
4,
|
|
"Should refresh device list after disconnecting device"
|
|
);
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
4,
|
|
`${ON_DEVICELIST_UPDATED} notified when device disconnects`
|
|
);
|
|
device.observe(
|
|
null,
|
|
ON_DEVICE_DISCONNECTED_NOTIFICATION,
|
|
JSON.stringify({ isLocalDevice: true })
|
|
);
|
|
await device.refreshDeviceList();
|
|
Assert.equal(
|
|
spy.getDeviceList.count,
|
|
4,
|
|
"Should not refresh device list after disconnecting this device"
|
|
);
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
4,
|
|
`${ON_DEVICELIST_UPDATED} not notified again`
|
|
);
|
|
|
|
let refreshBeforeResetPromise = device.refreshDeviceList({
|
|
ignoreCached: true,
|
|
});
|
|
fxai._generation++;
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
4,
|
|
`${ON_DEVICELIST_UPDATED} not notified`
|
|
);
|
|
await Assert.rejects(refreshBeforeResetPromise, /Another user has signed in/);
|
|
|
|
device.reset();
|
|
Assert.equal(
|
|
device.recentDeviceList,
|
|
null,
|
|
"Should clear device list after resetting"
|
|
);
|
|
Assert.ok(
|
|
await device.refreshDeviceList(),
|
|
"Should fetch new list after resetting"
|
|
);
|
|
Assert.equal(
|
|
deviceListUpdateObserver.count,
|
|
5,
|
|
`${ON_DEVICELIST_UPDATED} notified after reset`
|
|
);
|
|
Services.obs.removeObserver(deviceListUpdateObserver, ON_DEVICELIST_UPDATED);
|
|
});
|
|
|
|
add_task(async function test_push_resubscribe() {
|
|
let credentials = getTestUser("baz");
|
|
|
|
let storage = new MockStorageManager();
|
|
storage.initialize(credentials);
|
|
let state = new AccountState(storage);
|
|
|
|
let mockDevice = {
|
|
id: "deviceAAAAAA",
|
|
name: "iPhone",
|
|
type: "phone",
|
|
pushCallback: "http://mochi.test:8888",
|
|
pushEndpointExpired: false,
|
|
sessionToken: credentials.sessionToken,
|
|
};
|
|
|
|
var mockSubscription = {
|
|
isExpired: () => {
|
|
return false;
|
|
},
|
|
endpoint: "http://mochi.test:8888",
|
|
};
|
|
|
|
let fxAccountsClient = new MockFxAccountsClient(mockDevice);
|
|
|
|
const spy = {
|
|
_registerOrUpdateDevice: { count: 0 },
|
|
};
|
|
|
|
let fxai = {
|
|
_now: Date.now(),
|
|
_generation: 0,
|
|
fxAccountsClient,
|
|
now() {
|
|
return this._now;
|
|
},
|
|
withVerifiedAccountState(func) {
|
|
// Ensure `func` is called asynchronously, and simulate the possibility
|
|
// of a different user signng in while the promise is in-flight.
|
|
const currentGeneration = this._generation;
|
|
return Promise.resolve()
|
|
.then(_ => func(state))
|
|
.then(result => {
|
|
if (currentGeneration < this._generation) {
|
|
throw new Error("Another user has signed in");
|
|
}
|
|
return result;
|
|
});
|
|
},
|
|
fxaPushService: {
|
|
registerPushEndpoint() {
|
|
return new Promise(resolve => {
|
|
resolve({
|
|
endpoint: "http://mochi.test:8888",
|
|
getKey(type) {
|
|
return ChromeUtils.base64URLDecode(
|
|
type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY,
|
|
{ padding: "ignore" }
|
|
);
|
|
},
|
|
});
|
|
});
|
|
},
|
|
unsubscribe() {
|
|
return Promise.resolve();
|
|
},
|
|
getSubscription() {
|
|
return Promise.resolve(mockSubscription);
|
|
},
|
|
},
|
|
commands: {
|
|
async pollDeviceCommands() {},
|
|
},
|
|
async _handleTokenError(e) {
|
|
_(`Test failure: ${e} - ${e.stack}`);
|
|
throw e;
|
|
},
|
|
};
|
|
let device = new FxAccountsDevice(fxai);
|
|
device._checkRemoteCommandsUpdateNeeded = async () => false;
|
|
device._registerOrUpdateDevice = async () => {
|
|
spy._registerOrUpdateDevice.count += 1;
|
|
};
|
|
|
|
Assert.ok(await device.refreshDeviceList(), "Should refresh list");
|
|
Assert.equal(spy._registerOrUpdateDevice.count, 0, "not expecting a refresh");
|
|
|
|
mockDevice.pushEndpointExpired = true;
|
|
Assert.ok(
|
|
await device.refreshDeviceList({ ignoreCached: true }),
|
|
"Should refresh list"
|
|
);
|
|
Assert.equal(
|
|
spy._registerOrUpdateDevice.count,
|
|
1,
|
|
"end-point expired means should resubscribe"
|
|
);
|
|
|
|
mockDevice.pushEndpointExpired = false;
|
|
mockSubscription.isExpired = () => true;
|
|
Assert.ok(
|
|
await device.refreshDeviceList({ ignoreCached: true }),
|
|
"Should refresh list"
|
|
);
|
|
Assert.equal(
|
|
spy._registerOrUpdateDevice.count,
|
|
2,
|
|
"push service saying expired should resubscribe"
|
|
);
|
|
|
|
mockSubscription.isExpired = () => false;
|
|
mockSubscription.endpoint = "something-else";
|
|
Assert.ok(
|
|
await device.refreshDeviceList({ ignoreCached: true }),
|
|
"Should refresh list"
|
|
);
|
|
Assert.equal(
|
|
spy._registerOrUpdateDevice.count,
|
|
3,
|
|
"push service endpoint diff should resubscribe"
|
|
);
|
|
|
|
mockSubscription = null;
|
|
Assert.ok(
|
|
await device.refreshDeviceList({ ignoreCached: true }),
|
|
"Should refresh list"
|
|
);
|
|
Assert.equal(
|
|
spy._registerOrUpdateDevice.count,
|
|
4,
|
|
"push service saying no sub should resubscribe"
|
|
);
|
|
|
|
// reset everything to make sure we didn't leave something behind causing the above to
|
|
// not check what we thought it was.
|
|
mockSubscription = {
|
|
isExpired: () => {
|
|
return false;
|
|
},
|
|
endpoint: "http://mochi.test:8888",
|
|
};
|
|
Assert.ok(
|
|
await device.refreshDeviceList({ ignoreCached: true }),
|
|
"Should refresh list"
|
|
);
|
|
Assert.equal(
|
|
spy._registerOrUpdateDevice.count,
|
|
4,
|
|
"resetting to good data should not resubscribe"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_checking_remote_availableCommands_mismatch() {
|
|
const credentials = getTestUser("baz");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
fxa.device._checkRemoteCommandsUpdateNeeded =
|
|
FxAccountsDevice.prototype._checkRemoteCommandsUpdateNeeded;
|
|
fxa.commands.availableCommands = async () => {
|
|
return {
|
|
"https://identity.mozilla.com/cmd/open-uri": "local-keys",
|
|
};
|
|
};
|
|
|
|
const ourDevice = {
|
|
isCurrentDevice: true,
|
|
availableCommands: {
|
|
"https://identity.mozilla.com/cmd/open-uri": "remote-keys",
|
|
},
|
|
};
|
|
Assert.ok(
|
|
await fxa.device._checkRemoteCommandsUpdateNeeded(
|
|
ourDevice.availableCommands
|
|
)
|
|
);
|
|
});
|
|
|
|
add_task(async function test_checking_remote_availableCommands_match() {
|
|
const credentials = getTestUser("baz");
|
|
credentials.verified = true;
|
|
const fxa = await MockFxAccounts(credentials);
|
|
fxa.device._checkRemoteCommandsUpdateNeeded =
|
|
FxAccountsDevice.prototype._checkRemoteCommandsUpdateNeeded;
|
|
fxa.commands.availableCommands = async () => {
|
|
return {
|
|
"https://identity.mozilla.com/cmd/open-uri": "local-keys",
|
|
};
|
|
};
|
|
|
|
const ourDevice = {
|
|
isCurrentDevice: true,
|
|
availableCommands: {
|
|
"https://identity.mozilla.com/cmd/open-uri": "local-keys",
|
|
},
|
|
};
|
|
Assert.ok(
|
|
!(await fxa.device._checkRemoteCommandsUpdateNeeded(
|
|
ourDevice.availableCommands
|
|
))
|
|
);
|
|
});
|
|
|
|
function getTestUser(name) {
|
|
return {
|
|
email: name + "@example.com",
|
|
uid: "1ad7f502-4cc7-4ec1-a209-071fd2fae348",
|
|
sessionToken: name + "'s session token",
|
|
verified: false,
|
|
...MOCK_ACCOUNT_KEYS,
|
|
};
|
|
}
|