Bug 1888019 - Use a different client ID, scope and keys for Sync in Thunderbird. r=teshaq,sync-reviewers a=RyanVM

Differential Revision: https://phabricator.services.mozilla.com/D205783
This commit is contained in:
Geoff Lankow 2024-06-19 02:39:37 +00:00
parent d26cf26a79
commit a6ee611db1
21 changed files with 215 additions and 184 deletions

View file

@ -19,7 +19,7 @@ import {
FXA_PWDMGR_PLAINTEXT_FIELDS, FXA_PWDMGR_PLAINTEXT_FIELDS,
FXA_PWDMGR_REAUTH_ALLOWLIST, FXA_PWDMGR_REAUTH_ALLOWLIST,
FXA_PWDMGR_SECURE_FIELDS, FXA_PWDMGR_SECURE_FIELDS,
FX_OAUTH_CLIENT_ID, OAUTH_CLIENT_ID,
ON_ACCOUNT_STATE_CHANGE_NOTIFICATION, ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
ONLOGIN_NOTIFICATION, ONLOGIN_NOTIFICATION,
ONLOGOUT_NOTIFICATION, ONLOGOUT_NOTIFICATION,
@ -1089,10 +1089,7 @@ FxAccountsInternal.prototype = {
* @param { Object } tokenData: The token's data, with `tokenData.token` being the token itself * @param { Object } tokenData: The token's data, with `tokenData.token` being the token itself
**/ **/
destroyOAuthToken(tokenData) { destroyOAuthToken(tokenData) {
return this.fxAccountsClient.oauthDestroy( return this.fxAccountsClient.oauthDestroy(OAUTH_CLIENT_ID, tokenData.token);
FX_OAUTH_CLIENT_ID,
tokenData.token
);
}, },
_destroyAllOAuthTokens(tokenInfos) { _destroyAllOAuthTokens(tokenInfos) {
@ -1378,7 +1375,7 @@ FxAccountsInternal.prototype = {
async _doTokenFetchWithSessionToken(sessionToken, scopeString, ttl) { async _doTokenFetchWithSessionToken(sessionToken, scopeString, ttl) {
const result = await this.fxAccountsClient.accessTokenWithSessionToken( const result = await this.fxAccountsClient.accessTokenWithSessionToken(
sessionToken, sessionToken,
FX_OAUTH_CLIENT_ID, OAUTH_CLIENT_ID,
scopeString, scopeString,
ttl ttl
); );

View file

@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { import {
CLIENT_IS_THUNDERBIRD,
COMMAND_SENDTAB, COMMAND_SENDTAB,
COMMAND_SENDTAB_TAIL, COMMAND_SENDTAB_TAIL,
COMMAND_CLOSETAB, COMMAND_CLOSETAB,
@ -45,26 +46,30 @@ export class FxAccountsCommands {
} }
async availableCommands() { async availableCommands() {
// Invalid keys usually means the account is not verified yet.
const encryptedSendTabKeys = await this.sendTab.getEncryptedSendTabKeys();
let commands = {}; let commands = {};
if (encryptedSendTabKeys) { if (!CLIENT_IS_THUNDERBIRD) {
commands[COMMAND_SENDTAB] = encryptedSendTabKeys; // Invalid keys usually means the account is not verified yet.
} const encryptedSendTabKeys = await this.sendTab.getEncryptedSendTabKeys();
// Close Tab is still a worked-on feature, so we should not broadcast it widely yet if (encryptedSendTabKeys) {
let closeTabEnabled = Services.prefs.getBoolPref( commands[COMMAND_SENDTAB] = encryptedSendTabKeys;
"identity.fxaccounts.commands.remoteTabManagement.enabled", }
false
); // Close Tab is still a worked-on feature, so we should not broadcast it widely yet
if (closeTabEnabled) { let closeTabEnabled = Services.prefs.getBoolPref(
const encryptedCloseTabKeys = "identity.fxaccounts.commands.remoteTabManagement.enabled",
await this.closeTab.getEncryptedCloseTabKeys(); false
if (encryptedCloseTabKeys) { );
commands[COMMAND_CLOSETAB] = encryptedCloseTabKeys; if (closeTabEnabled) {
const encryptedCloseTabKeys =
await this.closeTab.getEncryptedCloseTabKeys();
if (encryptedCloseTabKeys) {
commands[COMMAND_CLOSETAB] = encryptedCloseTabKeys;
}
} }
} }
return commands; return commands;
} }

View file

@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs"; import { Log } from "resource://gre/modules/Log.sys.mjs";
import { LogManager } from "resource://gre/modules/LogManager.sys.mjs"; import { LogManager } from "resource://gre/modules/LogManager.sys.mjs";
@ -88,10 +89,22 @@ export let COMMAND_CLOSETAB_TAIL = "close-uri/v1";
export let COMMAND_CLOSETAB = COMMAND_PREFIX + COMMAND_CLOSETAB_TAIL; export let COMMAND_CLOSETAB = COMMAND_PREFIX + COMMAND_CLOSETAB_TAIL;
// OAuth // OAuth
export let FX_OAUTH_CLIENT_ID = "5882386c6d801776"; export let CLIENT_IS_THUNDERBIRD = AppConstants.MOZ_APP_NAME == "thunderbird";
let FX_OAUTH_CLIENT_ID = "5882386c6d801776";
let TB_OAUTH_CLIENT_ID = "8269bacd7bbc7f80";
export let OAUTH_CLIENT_ID = CLIENT_IS_THUNDERBIRD
? TB_OAUTH_CLIENT_ID
: FX_OAUTH_CLIENT_ID;
export let SCOPE_PROFILE = "profile"; export let SCOPE_PROFILE = "profile";
export let SCOPE_PROFILE_WRITE = "profile:write"; export let SCOPE_PROFILE_WRITE = "profile:write";
// Sync scope in Firefox.
export let SCOPE_OLD_SYNC = "https://identity.mozilla.com/apps/oldsync"; export let SCOPE_OLD_SYNC = "https://identity.mozilla.com/apps/oldsync";
// Sync scope in Thunderbird.
let SCOPE_THUNDERBIRD_SYNC = "https://identity.thunderbird.net/apps/sync";
// The scope to use for sync, depending on the current application.
export let SCOPE_APP_SYNC = CLIENT_IS_THUNDERBIRD
? SCOPE_THUNDERBIRD_SYNC
: SCOPE_OLD_SYNC;
// This scope was previously used to calculate a telemetry tracking identifier for // This scope was previously used to calculate a telemetry tracking identifier for
// the account, but that system has since been decommissioned. It's here entirely // the account, but that system has since been decommissioned. It's here entirely

View file

@ -6,7 +6,7 @@ import { RESTRequest } from "resource://services-common/rest.sys.mjs";
import { import {
log, log,
SCOPE_OLD_SYNC, SCOPE_APP_SYNC,
SCOPE_PROFILE, SCOPE_PROFILE,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; } from "resource://gre/modules/FxAccountsCommon.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
@ -344,7 +344,7 @@ export var FxAccountsConfig = {
async _getAuthParams() { async _getAuthParams() {
if (this._isOAuthFlow()) { if (this._isOAuthFlow()) {
const scopes = [SCOPE_OLD_SYNC, SCOPE_PROFILE]; const scopes = [SCOPE_APP_SYNC, SCOPE_PROFILE];
return lazy.fxAccounts._internal.beginOAuthFlow(scopes); return lazy.fxAccounts._internal.beginOAuthFlow(scopes);
} }
return { service: SYNC_PARAM }; return { service: SYNC_PARAM };

View file

@ -7,9 +7,9 @@ import { CommonUtils } from "resource://services-common/utils.sys.mjs";
import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs"; import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";
import { import {
SCOPE_OLD_SYNC, SCOPE_APP_SYNC,
DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY, DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY,
FX_OAUTH_CLIENT_ID, OAUTH_CLIENT_ID,
log, log,
logPII, logPII,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; } from "resource://gre/modules/FxAccountsCommon.sys.mjs";
@ -35,7 +35,7 @@ const DEPRECATED_SCOPE_WEBEXT_SYNC = "sync:addon_storage";
// These are the scopes that correspond to new storage for the `LEGACY_DERIVED_KEYS_NAMES`. // These are the scopes that correspond to new storage for the `LEGACY_DERIVED_KEYS_NAMES`.
// We will, if necessary, migrate storage for those keys so that it's associated with // We will, if necessary, migrate storage for those keys so that it's associated with
// these scopes. // these scopes.
const LEGACY_DERIVED_KEY_SCOPES = [SCOPE_OLD_SYNC]; const LEGACY_DERIVED_KEY_SCOPES = [SCOPE_APP_SYNC];
// These are scopes that we used to store, but are no longer using, // These are scopes that we used to store, but are no longer using,
// and hence should be deleted from storage if present. // and hence should be deleted from storage if present.
@ -474,23 +474,21 @@ export class FxAccountsKeys {
async _fetchScopedKeysMetadata(sessionToken) { async _fetchScopedKeysMetadata(sessionToken) {
// Hard-coded list of scopes that we know about. // Hard-coded list of scopes that we know about.
// This list will probably grow in future. // This list will probably grow in future.
const scopes = [SCOPE_OLD_SYNC].join(" "); const scopes = [SCOPE_APP_SYNC].join(" ");
const scopedKeysMetadata = const scopedKeysMetadata =
await this._fxai.fxAccountsClient.getScopedKeyData( await this._fxai.fxAccountsClient.getScopedKeyData(
sessionToken, sessionToken,
FX_OAUTH_CLIENT_ID, OAUTH_CLIENT_ID,
scopes scopes
); );
// The server may decline us permission for some of those scopes, although it really shouldn't. // The server may decline us permission for some of those scopes, although it really shouldn't.
// We can live without them...except for the OLDSYNC scope, whose absence would be catastrophic. // We can live without them...except for the sync scope, whose absence would be catastrophic.
if (!scopedKeysMetadata.hasOwnProperty(SCOPE_OLD_SYNC)) { if (!scopedKeysMetadata.hasOwnProperty(SCOPE_APP_SYNC)) {
log.warn( log.warn(
"The FxA server did not grant Firefox the `oldsync` scope; this is most unexpected!" + "The FxA server did not grant Firefox the sync scope; this is most unexpected!" +
` scopes were: ${Object.keys(scopedKeysMetadata)}` ` scopes were: ${Object.keys(scopedKeysMetadata)}`
); );
throw new Error( throw new Error("The FxA server did not grant Firefox the sync scope");
"The FxA server did not grant Firefox the `oldsync` scope"
);
} }
return scopedKeysMetadata; return scopedKeysMetadata;
} }
@ -645,7 +643,7 @@ export class FxAccountsKeys {
*/ */
async _deriveLegacyScopedKey(uid, kBbytes, scope, scopedKeyMetadata) { async _deriveLegacyScopedKey(uid, kBbytes, scope, scopedKeyMetadata) {
let kid, key; let kid, key;
if (scope == SCOPE_OLD_SYNC) { if (scope == SCOPE_APP_SYNC) {
kid = await this._deriveXClientState(kBbytes); kid = await this._deriveXClientState(kBbytes);
key = await this._deriveSyncKey(kBbytes); key = await this._deriveSyncKey(kBbytes);
} else { } else {

View file

@ -9,13 +9,13 @@ ChromeUtils.defineESModuleGetters(lazy, {
}); });
import { import {
FX_OAUTH_CLIENT_ID, OAUTH_CLIENT_ID,
SCOPE_PROFILE, SCOPE_PROFILE,
SCOPE_PROFILE_WRITE, SCOPE_PROFILE_WRITE,
SCOPE_OLD_SYNC, SCOPE_APP_SYNC,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs"; } from "resource://gre/modules/FxAccountsCommon.sys.mjs";
const VALID_SCOPES = [SCOPE_PROFILE, SCOPE_PROFILE_WRITE, SCOPE_OLD_SYNC]; const VALID_SCOPES = [SCOPE_PROFILE, SCOPE_PROFILE_WRITE, SCOPE_APP_SYNC];
export const ERROR_INVALID_SCOPES = "INVALID_SCOPES"; export const ERROR_INVALID_SCOPES = "INVALID_SCOPES";
export const ERROR_INVALID_STATE = "INVALID_STATE"; export const ERROR_INVALID_STATE = "INVALID_STATE";
@ -116,7 +116,7 @@ export class FxAccountsOAuth {
throw new Error(ERROR_INVALID_SCOPES); throw new Error(ERROR_INVALID_SCOPES);
} }
const queryParams = { const queryParams = {
client_id: FX_OAUTH_CLIENT_ID, client_id: OAUTH_CLIENT_ID,
action: "email", action: "email",
response_type: "code", response_type: "code",
access_type: "offline", access_type: "offline",
@ -194,15 +194,15 @@ export class FxAccountsOAuth {
sessionTokenHex, sessionTokenHex,
code, code,
verifier, verifier,
FX_OAUTH_CLIENT_ID OAUTH_CLIENT_ID
); );
if ( if (
requestedScopes.includes(SCOPE_OLD_SYNC) && requestedScopes.includes(SCOPE_APP_SYNC) &&
!scope.includes(SCOPE_OLD_SYNC) !scope.includes(SCOPE_APP_SYNC)
) { ) {
throw new Error(ERROR_SYNC_SCOPE_NOT_GRANTED); throw new Error(ERROR_SYNC_SCOPE_NOT_GRANTED);
} }
if (scope.includes(SCOPE_OLD_SYNC) && !keys_jwe) { if (scope.includes(SCOPE_APP_SYNC) && !keys_jwe) {
throw new Error(ERROR_NO_KEYS_JWE); throw new Error(ERROR_NO_KEYS_JWE);
} }
let scopedKeys; let scopedKeys;

View file

@ -28,7 +28,7 @@ import {
COMMAND_PAIR_COMPLETE, COMMAND_PAIR_COMPLETE,
COMMAND_PAIR_PREFERENCES, COMMAND_PAIR_PREFERENCES,
COMMAND_FIREFOX_VIEW, COMMAND_FIREFOX_VIEW,
FX_OAUTH_CLIENT_ID, OAUTH_CLIENT_ID,
ON_PROFILE_CHANGE_NOTIFICATION, ON_PROFILE_CHANGE_NOTIFICATION,
PREF_LAST_FXA_USER, PREF_LAST_FXA_USER,
WEBCHANNEL_ID, WEBCHANNEL_ID,
@ -647,7 +647,7 @@ FxAccountsWebChannelHelpers.prototype = {
return { return {
signedInUser, signedInUser,
clientId: FX_OAUTH_CLIENT_ID, clientId: OAUTH_CLIENT_ID,
capabilities, capabilities,
}; };
}, },

View file

@ -12,14 +12,14 @@ const { XPCOMUtils } = ChromeUtils.importESModule(
const { sinon } = ChromeUtils.importESModule( const { sinon } = ChromeUtils.importESModule(
"resource://testing-common/Sinon.sys.mjs" "resource://testing-common/Sinon.sys.mjs"
); );
const { SCOPE_OLD_SYNC } = ChromeUtils.importESModule( const { SCOPE_APP_SYNC, SCOPE_OLD_SYNC } = ChromeUtils.importESModule(
"resource://gre/modules/FxAccountsCommon.sys.mjs" "resource://gre/modules/FxAccountsCommon.sys.mjs"
); );
// Some mock key data, in both scoped-key and legacy field formats. // Some mock key data, in both scoped-key and legacy field formats.
const MOCK_ACCOUNT_KEYS = { const MOCK_ACCOUNT_KEYS = {
scopedKeys: { scopedKeys: {
[SCOPE_OLD_SYNC]: { [SCOPE_APP_SYNC]: {
kid: "1234567890123-u7u7u7u7u7u7u7u7u7u7uw", kid: "1234567890123-u7u7u7u7u7u7u7u7u7u7uw",
k: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg", k: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg",
kty: "oct", kty: "oct",

View file

@ -13,9 +13,10 @@ const { FxAccountsClient } = ChromeUtils.importESModule(
"resource://gre/modules/FxAccountsClient.sys.mjs" "resource://gre/modules/FxAccountsClient.sys.mjs"
); );
const { const {
CLIENT_IS_THUNDERBIRD,
ERRNO_INVALID_AUTH_TOKEN, ERRNO_INVALID_AUTH_TOKEN,
ERROR_NO_ACCOUNT, ERROR_NO_ACCOUNT,
FX_OAUTH_CLIENT_ID, OAUTH_CLIENT_ID,
ONLOGIN_NOTIFICATION, ONLOGIN_NOTIFICATION,
ONLOGOUT_NOTIFICATION, ONLOGOUT_NOTIFICATION,
ONVERIFIED_NOTIFICATION, ONVERIFIED_NOTIFICATION,
@ -34,7 +35,7 @@ const MOCK_TOKEN_RESPONSE = {
access_token: access_token:
"43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69", "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69",
token_type: "bearer", token_type: "bearer",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
expires_in: 21600, expires_in: 21600,
auth_at: 1589579900, auth_at: 1589579900,
}; };
@ -152,13 +153,13 @@ function MockFxAccountsClient() {
this.getScopedKeyData = function (sessionToken, client_id, scopes) { this.getScopedKeyData = function (sessionToken, client_id, scopes) {
Assert.ok(sessionToken); Assert.ok(sessionToken);
Assert.equal(client_id, FX_OAUTH_CLIENT_ID); Assert.equal(client_id, OAUTH_CLIENT_ID);
Assert.equal(scopes, SCOPE_OLD_SYNC); Assert.equal(scopes, SCOPE_APP_SYNC);
return new Promise(resolve => { return new Promise(resolve => {
do_timeout(50, () => { do_timeout(50, () => {
resolve({ resolve({
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
identifier: "https://identity.mozilla.com/apps/oldsync", identifier: SCOPE_APP_SYNC,
keyRotationSecret: keyRotationSecret:
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
keyRotationTimestamp: 1234567890123, keyRotationTimestamp: 1234567890123,
@ -678,7 +679,7 @@ add_test(function test_getKeyForScope() {
Assert.equal(!!user2.keyFetchToken, true); Assert.equal(!!user2.keyFetchToken, true);
Assert.equal(!!user2.unwrapBKey, true); Assert.equal(!!user2.unwrapBKey, true);
fxa.keys.getKeyForScope(SCOPE_OLD_SYNC).then(() => { fxa.keys.getKeyForScope(SCOPE_APP_SYNC).then(() => {
fxa._internal.getUserAccountData().then(user3 => { fxa._internal.getUserAccountData().then(user3 => {
// Now we should have keys // Now we should have keys
Assert.equal(fxa._internal.isUserEmailVerified(user3), true); Assert.equal(fxa._internal.isUserEmailVerified(user3), true);
@ -712,7 +713,7 @@ add_task(
await fxa.setSignedInUser(user); await fxa.setSignedInUser(user);
// getKeyForScope will run the migration // getKeyForScope will run the migration
await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); await fxa.keys.getKeyForScope(SCOPE_APP_SYNC);
let newUser = await fxa._internal.getUserAccountData(); let newUser = await fxa._internal.getUserAccountData();
// Then, the deprecated keys will be removed // Then, the deprecated keys will be removed
Assert.strictEqual(newUser.kExtSync, undefined); Assert.strictEqual(newUser.kExtSync, undefined);
@ -732,21 +733,21 @@ add_task(
user.scopedKeys = { user.scopedKeys = {
...MOCK_ACCOUNT_KEYS.scopedKeys, ...MOCK_ACCOUNT_KEYS.scopedKeys,
[DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY]: [DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY]:
MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_APP_SYNC],
[DEPRECATED_SCOPE_WEBEXT_SYNC]: [DEPRECATED_SCOPE_WEBEXT_SYNC]:
MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_APP_SYNC],
[EXTRA_SCOPE]: MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], [EXTRA_SCOPE]: MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_APP_SYNC],
}; };
await fxa.setSignedInUser(user); await fxa.setSignedInUser(user);
await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); await fxa.keys.getKeyForScope(SCOPE_APP_SYNC);
let newUser = await fxa._internal.getUserAccountData(); let newUser = await fxa._internal.getUserAccountData();
// It should have removed the deprecated ecosystem_telemetry key, // It should have removed the deprecated ecosystem_telemetry key,
// and the old kinto extension sync key // and the old kinto extension sync key
// but left the other keys intact. // but left the other keys intact.
const expectedScopedKeys = { const expectedScopedKeys = {
...MOCK_ACCOUNT_KEYS.scopedKeys, ...MOCK_ACCOUNT_KEYS.scopedKeys,
[EXTRA_SCOPE]: MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], [EXTRA_SCOPE]: MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_APP_SYNC],
}; };
Assert.deepEqual(newUser.scopedKeys, expectedScopedKeys); Assert.deepEqual(newUser.scopedKeys, expectedScopedKeys);
Assert.equal(newUser.ecosystemUserId, null); Assert.equal(newUser.ecosystemUserId, null);
@ -777,7 +778,7 @@ add_task(async function test_getKeyForScope_nonexistent_account() {
}); });
}); });
await Assert.rejects(fxa.keys.getKeyForScope(SCOPE_OLD_SYNC), err => { await Assert.rejects(fxa.keys.getKeyForScope(SCOPE_APP_SYNC), err => {
Assert.equal(err.message, ERROR_INVALID_ACCOUNT_STATE); Assert.equal(err.message, ERROR_INVALID_ACCOUNT_STATE);
return true; // expected error return true; // expected error
}); });
@ -808,7 +809,7 @@ add_task(async function test_getKeyForScope_invalid_token() {
Assert.notEqual(user.encryptedSendTabKeys, null); Assert.notEqual(user.encryptedSendTabKeys, null);
try { try {
await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); await fxa.keys.getKeyForScope(SCOPE_APP_SYNC);
Assert.ok(false); Assert.ok(false);
} catch (err) { } catch (err) {
Assert.equal(err.code, 401); Assert.equal(err.code, 401);
@ -831,8 +832,8 @@ add_task(async function test_getKeyForScope_oldsync() {
let client = fxa._internal.fxAccountsClient; let client = fxa._internal.fxAccountsClient;
client.getScopedKeyData = () => client.getScopedKeyData = () =>
Promise.resolve({ Promise.resolve({
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
identifier: "https://identity.mozilla.com/apps/oldsync", identifier: SCOPE_APP_SYNC,
keyRotationSecret: keyRotationSecret:
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
keyRotationTimestamp: 1510726317123, keyRotationTimestamp: 1510726317123,
@ -862,7 +863,7 @@ add_task(async function test_getKeyForScope_oldsync() {
await fxa.setSignedInUser(user); await fxa.setSignedInUser(user);
// We derive, persist and return the sync key // We derive, persist and return the sync key
const key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); const key = await fxa.keys.getKeyForScope(SCOPE_APP_SYNC);
// We verify the key returned matches what we would expect from the test vectors // We verify the key returned matches what we would expect from the test vectors
// kb = 2ee722fdd8ccaa721bdeb2d1b76560efef705b04349d9357c3e592cf4906e075 (from test vectors) // kb = 2ee722fdd8ccaa721bdeb2d1b76560efef705b04349d9357c3e592cf4906e075 (from test vectors)
@ -871,7 +872,7 @@ add_task(async function test_getKeyForScope_oldsync() {
// //
// k can be verified by HKDF(kb, undefined, "identity.mozilla.com/picl/v1/oldsync", 64) // k can be verified by HKDF(kb, undefined, "identity.mozilla.com/picl/v1/oldsync", 64)
Assert.deepEqual(key, { Assert.deepEqual(key, {
scope: SCOPE_OLD_SYNC, scope: SCOPE_APP_SYNC,
kid: "1510726317123-BAik7hEOdpGnPZnPBSdaTg", kid: "1510726317123-BAik7hEOdpGnPZnPBSdaTg",
k: "fwM5VZu0Spf5XcFRZYX2zk6MrqZP7zvovCBcvuKwgYMif3hz98FHmIVa3qjKjrW0J244Zj-P5oWaOcQbvypmpw", k: "fwM5VZu0Spf5XcFRZYX2zk6MrqZP7zvovCBcvuKwgYMif3hz98FHmIVa3qjKjrW0J244Zj-P5oWaOcQbvypmpw",
kty: "oct", kty: "oct",
@ -888,10 +889,10 @@ add_task(async function test_getScopedKeys_cached_key() {
}; };
await fxa.setSignedInUser(user); await fxa.setSignedInUser(user);
let key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); let key = await fxa.keys.getKeyForScope(SCOPE_APP_SYNC);
Assert.deepEqual(key, { Assert.deepEqual(key, {
scope: SCOPE_OLD_SYNC, scope: SCOPE_APP_SYNC,
...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], ...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_APP_SYNC],
}); });
}); });
@ -934,8 +935,8 @@ add_task(async function test_getScopedKeys_misconfigured_fxa_server() {
}; };
await fxa.setSignedInUser(user); await fxa.setSignedInUser(user);
await Assert.rejects( await Assert.rejects(
fxa.keys.getKeyForScope(SCOPE_OLD_SYNC), fxa.keys.getKeyForScope(SCOPE_APP_SYNC),
/The FxA server did not grant Firefox the `oldsync` scope/ /The FxA server did not grant Firefox the sync scope/
); );
}); });
@ -947,10 +948,10 @@ add_task(async function test_setScopedKeys() {
}; };
await fxa.setSignedInUser(user); await fxa.setSignedInUser(user);
await fxa.keys.setScopedKeys(MOCK_ACCOUNT_KEYS.scopedKeys); await fxa.keys.setScopedKeys(MOCK_ACCOUNT_KEYS.scopedKeys);
const key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC); const key = await fxa.keys.getKeyForScope(SCOPE_APP_SYNC);
Assert.deepEqual(key, { Assert.deepEqual(key, {
scope: SCOPE_OLD_SYNC, scope: SCOPE_APP_SYNC,
...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC], ...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_APP_SYNC],
}); });
}); });
@ -1166,7 +1167,10 @@ add_test(async function test_getOAuthTokenWithSessionToken() {
) => { ) => {
oauthTokenCalled = true; oauthTokenCalled = true;
Assert.equal(sessionTokenHex, "alice's session token"); Assert.equal(sessionTokenHex, "alice's session token");
Assert.equal(clientId, "5882386c6d801776"); Assert.equal(
clientId,
CLIENT_IS_THUNDERBIRD ? "a958794159236f76" : "5882386c6d801776"
);
Assert.equal(scope, "profile"); Assert.equal(scope, "profile");
Assert.equal(ttl, undefined); Assert.equal(ttl, undefined);
return MOCK_TOKEN_RESPONSE; return MOCK_TOKEN_RESPONSE;
@ -1445,7 +1449,7 @@ add_task(async function test_listAttachedOAuthClients() {
deviceId: "deadbeef", deviceId: "deadbeef",
sessionTokenId: null, sessionTokenId: null,
name: "Firefox Preview (no session token)", name: "Firefox Preview (no session token)",
scope: ["profile", "https://identity.mozilla.com/apps/oldsync"], scope: ["profile", SCOPE_APP_SYNC],
lastAccessTime: Date.now(), lastAccessTime: Date.now(),
}, },
{ {

View file

@ -9,6 +9,7 @@ const { FxAccounts } = ChromeUtils.importESModule(
add_task( add_task(
async function test_non_https_remote_server_uri_with_requireHttps_false() { async function test_non_https_remote_server_uri_with_requireHttps_false() {
Services.prefs.setStringPref("identity.fxaccounts.autoconfig.uri", "");
Services.prefs.setBoolPref("identity.fxaccounts.allowHttp", true); Services.prefs.setBoolPref("identity.fxaccounts.allowHttp", true);
Services.prefs.setStringPref( Services.prefs.setStringPref(
"identity.fxaccounts.remote.root", "identity.fxaccounts.remote.root",

View file

@ -13,6 +13,7 @@ const { FxAccountsDevice } = ChromeUtils.importESModule(
"resource://gre/modules/FxAccountsDevice.sys.mjs" "resource://gre/modules/FxAccountsDevice.sys.mjs"
); );
const { const {
CLIENT_IS_THUNDERBIRD,
ERRNO_DEVICE_SESSION_CONFLICT, ERRNO_DEVICE_SESSION_CONFLICT,
ERRNO_TOO_MANY_CLIENT_REQUESTS, ERRNO_TOO_MANY_CLIENT_REQUESTS,
ERRNO_UNKNOWN_DEVICE, ERRNO_UNKNOWN_DEVICE,
@ -625,48 +626,50 @@ add_task(
} }
); );
add_task(async function test_verification_updates_registration() { add_task(
const deviceName = "foo"; { skip_if: () => CLIENT_IS_THUNDERBIRD },
async function test_verification_updates_registration() {
const deviceName = "foo";
const credentials = getTestUser("baz"); const credentials = getTestUser("baz");
const fxa = await MockFxAccounts(credentials, { const fxa = await MockFxAccounts(credentials, {
id: "device-id", id: "device-id",
name: deviceName, name: deviceName,
}); });
// We should already have a device registration, but without send-tab due to // We should already have a device registration, but without send-tab due to
// our inability to fetch keys for an unverified users. // our inability to fetch keys for an unverified users.
const state = fxa._internal.currentAccountState; const state = fxa._internal.currentAccountState;
const { device } = await state.getUserAccountData(); const { device } = await state.getUserAccountData();
Assert.equal(device.registeredCommandsKeys.length, 0); Assert.equal(device.registeredCommandsKeys.length, 0);
let updatePromise = new Promise(resolve => { let updatePromise = new Promise(resolve => {
const old_registerOrUpdateDevice = fxa.device._registerOrUpdateDevice.bind( const old_registerOrUpdateDevice =
fxa.device fxa.device._registerOrUpdateDevice.bind(fxa.device);
); fxa.device._registerOrUpdateDevice = async function (
fxa.device._registerOrUpdateDevice = async function ( currentState,
currentState, signedInUser
signedInUser ) {
) { await old_registerOrUpdateDevice(currentState, signedInUser);
await old_registerOrUpdateDevice(currentState, signedInUser); fxa.device._registerOrUpdateDevice = old_registerOrUpdateDevice;
fxa.device._registerOrUpdateDevice = old_registerOrUpdateDevice; resolve();
resolve(); };
});
fxa._internal.checkEmailStatus = async function () {
credentials.verified = true;
return credentials;
}; };
});
fxa._internal.checkEmailStatus = async function () { await updatePromise;
credentials.verified = true;
return credentials;
};
await updatePromise; const { device: newDevice, encryptedSendTabKeys } =
await state.getUserAccountData();
const { device: newDevice, encryptedSendTabKeys } = Assert.equal(newDevice.registeredCommandsKeys.length, 1);
await state.getUserAccountData(); Assert.notEqual(encryptedSendTabKeys, null);
Assert.equal(newDevice.registeredCommandsKeys.length, 1); await fxa.signOut(true);
Assert.notEqual(encryptedSendTabKeys, null); }
await fxa.signOut(true); );
});
add_task(async function test_devicelist_pushendpointexpired() { add_task(async function test_devicelist_pushendpointexpired() {
const deviceId = "mydeviceid"; const deviceId = "mydeviceid";

View file

@ -620,7 +620,7 @@ add_task(async function test_accessTokenWithSessionToken() {
access_token: access_token:
"43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69", "43793fdfffec22eb39fc3c44ed09193a6fde4c24e5d6a73f73178597b268af69",
token_type: "bearer", token_type: "bearer",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
expires_in: 21600, expires_in: 21600,
auth_at: 1589579900, auth_at: 1589579900,
}); });
@ -634,7 +634,7 @@ add_task(async function test_accessTokenWithSessionToken() {
let sessionTokenHex = let sessionTokenHex =
"0599c36ebb5cad6feb9285b9547b65342b5434d55c07b33bffd4307ab8f82dc4"; "0599c36ebb5cad6feb9285b9547b65342b5434d55c07b33bffd4307ab8f82dc4";
let clientId = "5882386c6d801776"; let clientId = "5882386c6d801776";
let scope = "https://identity.mozilla.com/apps/oldsync"; let scope = SCOPE_APP_SYNC;
let ttl = 100; let ttl = 100;
let result = await client.accessTokenWithSessionToken( let result = await client.accessTokenWithSessionToken(
sessionTokenHex, sessionTokenHex,

View file

@ -8,16 +8,12 @@ const { getFxAccountsSingleton } = ChromeUtils.importESModule(
); );
const fxAccounts = getFxAccountsSingleton(); const fxAccounts = getFxAccountsSingleton();
const { ON_NEW_DEVICE_ID, PREF_ACCOUNT_ROOT } = ChromeUtils.importESModule( const { CLIENT_IS_THUNDERBIRD, ON_NEW_DEVICE_ID, PREF_ACCOUNT_ROOT } =
"resource://gre/modules/FxAccountsCommon.sys.mjs" ChromeUtils.importESModule("resource://gre/modules/FxAccountsCommon.sys.mjs");
const { TestUtils } = ChromeUtils.importESModule(
"resource://testing-common/TestUtils.sys.mjs"
); );
function promiseObserved(topic) {
return new Promise(res => {
Services.obs.addObserver(res, topic);
});
}
_("Misc tests for FxAccounts.device"); _("Misc tests for FxAccounts.device");
fxAccounts.device._checkRemoteCommandsUpdateNeeded = async () => false; fxAccounts.device._checkRemoteCommandsUpdateNeeded = async () => false;
@ -90,9 +86,16 @@ add_task(async function test_reset() {
.callsFake(async () => { .callsFake(async () => {
return { id: "foo" }; return { id: "foo" };
}); });
const notifyPromise = TestUtils.topicObserved(ON_NEW_DEVICE_ID);
await fxAccounts._internal.setSignedInUser(credentials); await fxAccounts._internal.setSignedInUser(credentials);
// wait for device registration to complete. await notifyPromise;
await promiseObserved(ON_NEW_DEVICE_ID); if (!CLIENT_IS_THUNDERBIRD) {
// Firefox fires the notification twice - once during `setSignedInUser` and
// once after it returns. It's the second notification we need to wait for.
// Thunderbird, OTOH, fires only once during `setSignedInUser` and that's
// what we need to wait for.
await TestUtils.topicObserved(ON_NEW_DEVICE_ID);
}
ok(!Services.prefs.prefHasUserValue(testPref)); ok(!Services.prefs.prefHasUserValue(testPref));
// signing the user out should reset the name pref. // signing the user out should reset the name pref.
const namePref = PREF_ACCOUNT_ROOT + "device.name"; const namePref = PREF_ACCOUNT_ROOT + "device.name";

View file

@ -40,7 +40,7 @@ add_task(async function test_derive_legacy_sync_key_test_vector() {
const uid = "aeaa1725c7a24ff983c6295725d5fc9b"; const uid = "aeaa1725c7a24ff983c6295725d5fc9b";
const kB = "eaf9570b7219a4187d3d6bf3cec2770c2e0719b7cc0dfbb38243d6f1881675e9"; const kB = "eaf9570b7219a4187d3d6bf3cec2770c2e0719b7cc0dfbb38243d6f1881675e9";
const scopedKeyMetadata = { const scopedKeyMetadata = {
identifier: "https://identity.mozilla.com/apps/oldsync", identifier: SCOPE_APP_SYNC,
keyRotationTimestamp: 1510726317123, keyRotationTimestamp: 1510726317123,
keyRotationSecret: keyRotationSecret:
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
@ -49,7 +49,7 @@ add_task(async function test_derive_legacy_sync_key_test_vector() {
const scopedKey = await keys._deriveLegacyScopedKey( const scopedKey = await keys._deriveLegacyScopedKey(
uid, uid,
CommonUtils.hexToBytes(kB), CommonUtils.hexToBytes(kB),
"https://identity.mozilla.com/apps/oldsync", SCOPE_APP_SYNC,
scopedKeyMetadata scopedKeyMetadata
); );
@ -71,8 +71,8 @@ add_task(async function test_derive_multiple_keys_at_once() {
keyRotationSecret: keyRotationSecret:
"517d478cb4f994aa69930416648a416fdaa1762c5abf401a2acf11a0f185e98d", "517d478cb4f994aa69930416648a416fdaa1762c5abf401a2acf11a0f185e98d",
}, },
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
identifier: "https://identity.mozilla.com/apps/oldsync", identifier: SCOPE_APP_SYNC,
keyRotationTimestamp: 1510726318123, keyRotationTimestamp: 1510726318123,
keyRotationSecret: keyRotationSecret:
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
@ -91,7 +91,7 @@ add_task(async function test_derive_multiple_keys_at_once() {
kid: "1510726317-tUkxiR1lTlFrTgkF0tJidA", kid: "1510726317-tUkxiR1lTlFrTgkF0tJidA",
k: "TYK6Hmj86PfKiqsk9DZmX61nxk9VsExGrwo94HP-0wU", k: "TYK6Hmj86PfKiqsk9DZmX61nxk9VsExGrwo94HP-0wU",
}, },
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
@ -103,17 +103,17 @@ add_task(function test_check_valid_scoped_keys() {
const keys = new FxAccountsKeys(null); const keys = new FxAccountsKeys(null);
add_task(function test_missing_key_data() { add_task(function test_missing_key_data() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
Assert.equal(keys.validScopedKeys(scopedKeys), false); Assert.equal(keys.validScopedKeys(scopedKeys), false);
}); });
add_task(function test_unexpected_scope() { add_task(function test_unexpected_scope() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
@ -124,59 +124,59 @@ add_task(function test_check_valid_scoped_keys() {
}); });
add_task(function test_not_oct_key() { add_task(function test_not_oct_key() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
// Should be "oct"! // Should be "oct"!
kty: "EC", kty: "EC",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
Assert.equal(keys.validScopedKeys(scopedKeys), false); Assert.equal(keys.validScopedKeys(scopedKeys), false);
}); });
add_task(function test_invalid_kid_not_timestamp() { add_task(function test_invalid_kid_not_timestamp() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
// Does not have the timestamp! // Does not have the timestamp!
kid: "IqQv4onc7VcVE1kTQkyyOw", kid: "IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
Assert.equal(keys.validScopedKeys(scopedKeys), false); Assert.equal(keys.validScopedKeys(scopedKeys), false);
}); });
add_task(function test_invalid_kid_not_valid_timestamp() { add_task(function test_invalid_kid_not_valid_timestamp() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
// foo is not a valid timestamp! // foo is not a valid timestamp!
kid: "foo-IqQv4onc7VcVE1kTQkyyOw", kid: "foo-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
Assert.equal(keys.validScopedKeys(scopedKeys), false); Assert.equal(keys.validScopedKeys(scopedKeys), false);
}); });
add_task(function test_invalid_kid_not_b64_fingerprint() { add_task(function test_invalid_kid_not_b64_fingerprint() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
// fingerprint not a valid base64 encoded string. // fingerprint not a valid base64 encoded string.
kid: "1510726318123-notvalidb64][", kid: "1510726318123-notvalidb64][",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
Assert.equal(keys.validScopedKeys(scopedKeys), false); Assert.equal(keys.validScopedKeys(scopedKeys), false);
}); });
add_task(function test_invalid_k_not_base64() { add_task(function test_invalid_k_not_base64() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "notavalidb64[]", k: "notavalidb64[]",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
Assert.equal(keys.validScopedKeys(scopedKeys), false); Assert.equal(keys.validScopedKeys(scopedKeys), false);
@ -192,11 +192,11 @@ add_task(function test_check_valid_scoped_keys() {
scope: "https://identity.mozilla.com/apps/otherscope", scope: "https://identity.mozilla.com/apps/otherscope",
}, },
// Invalid // Invalid
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "notavalidb64[]", k: "notavalidb64[]",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
Assert.equal(keys.validScopedKeys(scopedKeys), false); Assert.equal(keys.validScopedKeys(scopedKeys), false);
@ -204,11 +204,11 @@ add_task(function test_check_valid_scoped_keys() {
add_task(function test_valid_scopedkeys() { add_task(function test_valid_scopedkeys() {
const scopedKeys = { const scopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
"https://identity.mozilla.com/apps/otherscope": { "https://identity.mozilla.com/apps/otherscope": {
kty: "oct", kty: "oct",

View file

@ -17,7 +17,7 @@ const {
"resource://gre/modules/FxAccountsOAuth.sys.mjs" "resource://gre/modules/FxAccountsOAuth.sys.mjs"
); );
const { SCOPE_PROFILE, FX_OAUTH_CLIENT_ID } = ChromeUtils.importESModule( const { SCOPE_PROFILE, OAUTH_CLIENT_ID } = ChromeUtils.importESModule(
"resource://gre/modules/FxAccountsCommon.sys.mjs" "resource://gre/modules/FxAccountsCommon.sys.mjs"
); );
@ -45,15 +45,15 @@ add_task(function test_begin_oauth_flow() {
} }
}); });
add_task(async function test_begin_oauth_flow_ok() { add_task(async function test_begin_oauth_flow_ok() {
const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
const queryParams = await oauth.beginOAuthFlow(scopes); const queryParams = await oauth.beginOAuthFlow(scopes);
// First verify default query parameters // First verify default query parameters
Assert.equal(queryParams.client_id, FX_OAUTH_CLIENT_ID); Assert.equal(queryParams.client_id, OAUTH_CLIENT_ID);
Assert.equal(queryParams.action, "email"); Assert.equal(queryParams.action, "email");
Assert.equal(queryParams.response_type, "code"); Assert.equal(queryParams.response_type, "code");
Assert.equal(queryParams.access_type, "offline"); Assert.equal(queryParams.access_type, "offline");
Assert.equal(queryParams.scope, [SCOPE_PROFILE, SCOPE_OLD_SYNC].join(" ")); Assert.equal(queryParams.scope, [SCOPE_PROFILE, SCOPE_APP_SYNC].join(" "));
// Then, we verify that the state is a valid Base64 value // Then, we verify that the state is a valid Base64 value
const state = queryParams.state; const state = queryParams.state;
@ -120,7 +120,7 @@ add_task(function test_complete_oauth_flow() {
}), }),
}; };
const oauth = new FxAccountsOAuth(fxaClient); const oauth = new FxAccountsOAuth(fxaClient);
const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
const sessionToken = "01abcef12"; const sessionToken = "01abcef12";
const queryParams = await oauth.beginOAuthFlow(scopes); const queryParams = await oauth.beginOAuthFlow(scopes);
try { try {
@ -133,7 +133,7 @@ add_task(function test_complete_oauth_flow() {
} }
}); });
add_task(async function test_jwe_not_returned() { add_task(async function test_jwe_not_returned() {
const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
const fxaClient = { const fxaClient = {
oauthToken: () => oauthToken: () =>
Promise.resolve({ Promise.resolve({
@ -157,15 +157,15 @@ add_task(function test_complete_oauth_flow() {
add_task(async function test_complete_oauth_ok() { add_task(async function test_complete_oauth_ok() {
// First, we initialize some fake values we would typically get // First, we initialize some fake values we would typically get
// from outside our system // from outside our system
const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
const oauthCode = "fake oauth code"; const oauthCode = "fake oauth code";
const sessionToken = "01abcef12"; const sessionToken = "01abcef12";
const plainTextScopedKeys = { const plainTextScopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kty: "oct", kty: "oct",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
const fakeAccessToken = "fake access token"; const fakeAccessToken = "fake access token";
@ -280,16 +280,16 @@ add_task(function test_complete_oauth_flow() {
add_task(async function test_complete_oauth_invalid_scoped_keys() { add_task(async function test_complete_oauth_invalid_scoped_keys() {
// First, we initialize some fake values we would typically get // First, we initialize some fake values we would typically get
// from outside our system // from outside our system
const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; const scopes = [SCOPE_PROFILE, SCOPE_APP_SYNC];
const oauthCode = "fake oauth code"; const oauthCode = "fake oauth code";
const sessionToken = "01abcef12"; const sessionToken = "01abcef12";
const invalidScopedKeys = { const invalidScopedKeys = {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
// ====== This is an invalid key type! Should be "oct", so we will raise an error once we realize // ====== This is an invalid key type! Should be "oct", so we will raise an error once we realize
kty: "EC", kty: "EC",
kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
scope: "https://identity.mozilla.com/apps/oldsync", scope: SCOPE_APP_SYNC,
}, },
}; };
const fakeAccessToken = "fake access token"; const fakeAccessToken = "fake access token";

View file

@ -72,8 +72,8 @@ const fxAccounts = {
fxAccountsClient: { fxAccountsClient: {
async getScopedKeyData() { async getScopedKeyData() {
return { return {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
identifier: "https://identity.mozilla.com/apps/oldsync", identifier: SCOPE_APP_SYNC,
keyRotationTimestamp: 12345678, keyRotationTimestamp: 12345678,
}, },
}; };
@ -151,7 +151,7 @@ add_task(async function testFullFlow() {
new TextEncoder().encode(JSON.stringify(epk.publicJWK)), new TextEncoder().encode(JSON.stringify(epk.publicJWK)),
{ pad: false } { pad: false }
), ),
scope: "profile https://identity.mozilla.com/apps/oldsync", scope: `profile ${SCOPE_APP_SYNC}`,
code_challenge: "chal", code_challenge: "chal",
code_challenge_method: "S256", code_challenge_method: "S256",
}, },
@ -169,7 +169,9 @@ add_task(async function testFullFlow() {
const oauthUrl = await promiseSwitchToWebContent; const oauthUrl = await promiseSwitchToWebContent;
Assert.equal( Assert.equal(
oauthUrl, oauthUrl,
`${OAUTH_URI}?client_id=client_id_1&scope=profile+https%3A%2F%2Fidentity.mozilla.com%2Fapps%2Foldsync&email=foo%40bar.com&uid=abcd&channel_id=${CHANNEL_ID}&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob%3Apair-auth-webchannel` `${OAUTH_URI}?client_id=client_id_1&scope=profile+${encodeURIComponent(
SCOPE_APP_SYNC
)}&email=foo%40bar.com&uid=abcd&channel_id=${CHANNEL_ID}&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob%3Apair-auth-webchannel`
); );
let pairSuppMetadata = await simulateIncomingWebChannel( let pairSuppMetadata = await simulateIncomingWebChannel(
@ -199,7 +201,7 @@ add_task(async function testFullFlow() {
const generateArgs = generateJWE.firstCall.args; const generateArgs = generateJWE.firstCall.args;
Assert.deepEqual(generateArgs[0], epk.publicJWK); Assert.deepEqual(generateArgs[0], epk.publicJWK);
Assert.deepEqual(JSON.parse(new TextDecoder().decode(generateArgs[1])), { Assert.deepEqual(JSON.parse(new TextDecoder().decode(generateArgs[1])), {
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
kid: "123456", kid: "123456",
k: KSYNC, k: KSYNC,
kty: "oct", kty: "oct",
@ -217,10 +219,7 @@ add_task(async function testFullFlow() {
Assert.equal(oauthCodeArgs.client_id, "client_id_1"); Assert.equal(oauthCodeArgs.client_id, "client_id_1");
Assert.equal(oauthCodeArgs.access_type, "offline"); Assert.equal(oauthCodeArgs.access_type, "offline");
Assert.equal(oauthCodeArgs.state, "mystate"); Assert.equal(oauthCodeArgs.state, "mystate");
Assert.equal( Assert.equal(oauthCodeArgs.scope, `profile ${SCOPE_APP_SYNC}`);
oauthCodeArgs.scope,
"profile https://identity.mozilla.com/apps/oldsync"
);
Assert.equal(oauthCodeArgs.code_challenge, "chal"); Assert.equal(oauthCodeArgs.code_challenge, "chal");
Assert.equal(oauthCodeArgs.code_challenge_method, "S256"); Assert.equal(oauthCodeArgs.code_challenge_method, "S256");
@ -318,7 +317,7 @@ add_task(async function testPairingChannelFailure() {
data: { data: {
client_id: "client_id_1", client_id: "client_id_1",
state: "mystate", state: "mystate",
scope: "profile https://identity.mozilla.com/apps/oldsync", scope: `profile ${SCOPE_APP_SYNC}`,
code_challenge: "chal", code_challenge: "chal",
code_challenge_method: "S256", code_challenge_method: "S256",
}, },

View file

@ -3,8 +3,14 @@
"use strict"; "use strict";
const { ON_PROFILE_CHANGE_NOTIFICATION, WEBCHANNEL_ID, log } = const {
ChromeUtils.importESModule("resource://gre/modules/FxAccountsCommon.sys.mjs"); CLIENT_IS_THUNDERBIRD,
ON_PROFILE_CHANGE_NOTIFICATION,
WEBCHANNEL_ID,
log,
} = ChromeUtils.importESModule(
"resource://gre/modules/FxAccountsCommon.sys.mjs"
);
const { CryptoUtils } = ChromeUtils.importESModule( const { CryptoUtils } = ChromeUtils.importESModule(
"resource://services-crypto/utils.sys.mjs" "resource://services-crypto/utils.sys.mjs"
); );
@ -684,6 +690,7 @@ add_task(async function test_helpers_login_with_customize_sync() {
}); });
add_task( add_task(
{ skip_if: () => CLIENT_IS_THUNDERBIRD },
async function test_helpers_login_with_customize_sync_and_declined_engines() { async function test_helpers_login_with_customize_sync_and_declined_engines() {
let configured = false; let configured = false;
let helpers = new FxAccountsWebChannelHelpers({ let helpers = new FxAccountsWebChannelHelpers({

View file

@ -3,7 +3,7 @@ head = "head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests
firefox-appdir = "browser" firefox-appdir = "browser"
skip-if = [ skip-if = [
"os == 'android'", "os == 'android'",
"appname == 'thunderbird'", "appname == 'thunderbird' && !nightly_build"
] ]
support-files = [ support-files = [
"!/services/common/tests/unit/head_helpers.js", "!/services/common/tests/unit/head_helpers.js",

View file

@ -20,7 +20,7 @@ import {
} from "resource://gre/modules/FxAccounts.sys.mjs"; } from "resource://gre/modules/FxAccounts.sys.mjs";
import { FxAccountsClient } from "resource://gre/modules/FxAccountsClient.sys.mjs"; import { FxAccountsClient } from "resource://gre/modules/FxAccountsClient.sys.mjs";
import { SCOPE_OLD_SYNC } from "resource://gre/modules/FxAccountsCommon.sys.mjs"; import { SCOPE_APP_SYNC } from "resource://gre/modules/FxAccountsCommon.sys.mjs";
// A mock "storage manager" for FxAccounts that doesn't actually write anywhere. // A mock "storage manager" for FxAccounts that doesn't actually write anywhere.
export function MockFxaStorageManager() {} export function MockFxaStorageManager() {}
@ -119,7 +119,7 @@ export var makeIdentityConfig = function (overrides) {
user: { user: {
email: "foo", email: "foo",
scopedKeys: { scopedKeys: {
[SCOPE_OLD_SYNC]: { [SCOPE_APP_SYNC]: {
kid: "1234567890123-u7u7u7u7u7u7u7u7u7u7uw", kid: "1234567890123-u7u7u7u7u7u7u7u7u7u7uw",
k: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg", k: "qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqg",
kty: "oct", kty: "oct",
@ -174,8 +174,8 @@ export var makeFxAccountsInternalMock = function (config) {
keys: { keys: {
getScopedKeys: () => getScopedKeys: () =>
Promise.resolve({ Promise.resolve({
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
identifier: "https://identity.mozilla.com/apps/oldsync", identifier: SCOPE_APP_SYNC,
keyRotationSecret: keyRotationSecret:
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
keyRotationTimestamp: 1510726317123, keyRotationTimestamp: 1510726317123,

View file

@ -48,7 +48,7 @@ XPCOMUtils.defineLazyPreferenceGetter(
// FxAccountsCommon.js doesn't use a "namespace", so create one here. // FxAccountsCommon.js doesn't use a "namespace", so create one here.
import * as fxAccountsCommon from "resource://gre/modules/FxAccountsCommon.sys.mjs"; import * as fxAccountsCommon from "resource://gre/modules/FxAccountsCommon.sys.mjs";
const SCOPE_OLD_SYNC = fxAccountsCommon.SCOPE_OLD_SYNC; const SCOPE_APP_SYNC = fxAccountsCommon.SCOPE_APP_SYNC;
const OBSERVER_TOPICS = [ const OBSERVER_TOPICS = [
fxAccountsCommon.ONLOGIN_NOTIFICATION, fxAccountsCommon.ONLOGIN_NOTIFICATION,
@ -305,7 +305,7 @@ SyncAuthManager.prototype = {
lazy.log.debug("unlockAndVerifyAuthState has an unverified user"); lazy.log.debug("unlockAndVerifyAuthState has an unverified user");
return LOGIN_FAILED_LOGIN_REJECTED; return LOGIN_FAILED_LOGIN_REJECTED;
} }
if (await fxa.keys.canGetKeyForScope(SCOPE_OLD_SYNC)) { if (await fxa.keys.canGetKeyForScope(SCOPE_APP_SYNC)) {
lazy.log.debug( lazy.log.debug(
"unlockAndVerifyAuthState already has (or can fetch) sync keys" "unlockAndVerifyAuthState already has (or can fetch) sync keys"
); );
@ -323,7 +323,7 @@ SyncAuthManager.prototype = {
// without unlocking the MP or cleared the saved logins, so we've now // without unlocking the MP or cleared the saved logins, so we've now
// lost them - the user will need to reauth before continuing. // lost them - the user will need to reauth before continuing.
let result; let result;
if (await fxa.keys.canGetKeyForScope(SCOPE_OLD_SYNC)) { if (await fxa.keys.canGetKeyForScope(SCOPE_APP_SYNC)) {
result = STATUS_OK; result = STATUS_OK;
} else { } else {
result = LOGIN_FAILED_LOGIN_REJECTED; result = LOGIN_FAILED_LOGIN_REJECTED;
@ -377,7 +377,7 @@ SyncAuthManager.prototype = {
// We need keys for things to work. If we don't have them, just // We need keys for things to work. If we don't have them, just
// return null for the token - sync calling unlockAndVerifyAuthState() // return null for the token - sync calling unlockAndVerifyAuthState()
// before actually syncing will setup the error states if necessary. // before actually syncing will setup the error states if necessary.
if (!(await fxa.keys.canGetKeyForScope(SCOPE_OLD_SYNC))) { if (!(await fxa.keys.canGetKeyForScope(SCOPE_APP_SYNC))) {
this._log.info( this._log.info(
"Unable to fetch keys (master-password locked?), so aborting token fetch" "Unable to fetch keys (master-password locked?), so aborting token fetch"
); );
@ -400,7 +400,7 @@ SyncAuthManager.prototype = {
try { try {
this._log.info("Getting sync key"); this._log.info("Getting sync key");
const tokenAndKey = await fxa.getOAuthTokenAndKey({ const tokenAndKey = await fxa.getOAuthTokenAndKey({
scope: SCOPE_OLD_SYNC, scope: SCOPE_APP_SYNC,
ttl, ttl,
}); });
@ -419,7 +419,7 @@ SyncAuthManager.prototype = {
"Token server returned 401, retrying token fetch with fresh credentials" "Token server returned 401, retrying token fetch with fresh credentials"
); );
const tokenAndKey = await fxa.getOAuthTokenAndKey({ const tokenAndKey = await fxa.getOAuthTokenAndKey({
scope: SCOPE_OLD_SYNC, scope: SCOPE_APP_SYNC,
ttl, ttl,
}); });
token = await getToken(tokenAndKey.key, tokenAndKey.token); token = await getToken(tokenAndKey.key, tokenAndKey.token);

View file

@ -24,6 +24,7 @@ const {
ERRNO_INVALID_AUTH_TOKEN, ERRNO_INVALID_AUTH_TOKEN,
ONLOGIN_NOTIFICATION, ONLOGIN_NOTIFICATION,
ONVERIFIED_NOTIFICATION, ONVERIFIED_NOTIFICATION,
SCOPE_APP_SYNC,
} = ChromeUtils.importESModule( } = ChromeUtils.importESModule(
"resource://gre/modules/FxAccountsCommon.sys.mjs" "resource://gre/modules/FxAccountsCommon.sys.mjs"
); );
@ -66,8 +67,8 @@ MockFxAccountsClient.prototype = {
}, },
getScopedKeyData() { getScopedKeyData() {
return Promise.resolve({ return Promise.resolve({
"https://identity.mozilla.com/apps/oldsync": { [SCOPE_APP_SYNC]: {
identifier: "https://identity.mozilla.com/apps/oldsync", identifier: SCOPE_APP_SYNC,
keyRotationSecret: keyRotationSecret:
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
keyRotationTimestamp: 1234567890123, keyRotationTimestamp: 1234567890123,