summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorAscrod <32915892+Ascrod@users.noreply.github.com>2019-04-18 20:35:10 -0400
committerAscrod <32915892+Ascrod@users.noreply.github.com>2019-04-18 20:35:10 -0400
commitaf7e140d4ed8f5bc9a69da2f0338ad3cb1319dec (patch)
tree4aac6c4383fb9e279fccb13c65a4e44595fd4cf6 /services
parent40fc72376411587e7bf9985fb9545eca1c9aaa8e (diff)
parent51722cd4fecb5c8c79a302f2771cad71535df5ea (diff)
downloadUXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar.gz
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar.lz
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.tar.xz
UXP-af7e140d4ed8f5bc9a69da2f0338ad3cb1319dec.zip
Merge branch 'master' into default-pref
Diffstat (limited to 'services')
-rw-r--r--services/cloudsync/CloudSync.jsm89
-rw-r--r--services/cloudsync/CloudSyncAdapters.jsm88
-rw-r--r--services/cloudsync/CloudSyncBookmarks.jsm795
-rw-r--r--services/cloudsync/CloudSyncBookmarksFolderCache.jsm105
-rw-r--r--services/cloudsync/CloudSyncEventSource.jsm65
-rw-r--r--services/cloudsync/CloudSyncLocal.jsm87
-rw-r--r--services/cloudsync/CloudSyncPlacesWrapper.jsm375
-rw-r--r--services/cloudsync/CloudSyncTabs.jsm318
-rw-r--r--services/cloudsync/docs/api.md234
-rw-r--r--services/cloudsync/docs/architecture.rst54
-rw-r--r--services/cloudsync/docs/dataformat.rst77
-rw-r--r--services/cloudsync/docs/example.rst132
-rw-r--r--services/cloudsync/docs/index.rst19
-rw-r--r--services/cloudsync/moz.build21
-rw-r--r--services/cloudsync/tests/mochitest/browser.ini5
-rw-r--r--services/cloudsync/tests/mochitest/browser_tabEvents.js79
-rw-r--r--services/cloudsync/tests/mochitest/other_window.html7
-rw-r--r--services/cloudsync/tests/xpcshell/head.js10
-rw-r--r--services/cloudsync/tests/xpcshell/test_bookmarks.js73
-rw-r--r--services/cloudsync/tests/xpcshell/test_lazyload.js18
-rw-r--r--services/cloudsync/tests/xpcshell/test_module.js19
-rw-r--r--services/cloudsync/tests/xpcshell/test_tabs.js29
-rw-r--r--services/cloudsync/tests/xpcshell/xpcshell.ini10
-rw-r--r--services/fxaccounts/Credentials.jsm136
-rw-r--r--services/fxaccounts/FxAccounts.jsm1725
-rw-r--r--services/fxaccounts/FxAccountsClient.jsm623
-rw-r--r--services/fxaccounts/FxAccountsCommon.js368
-rw-r--r--services/fxaccounts/FxAccountsComponents.manifest4
-rw-r--r--services/fxaccounts/FxAccountsConfig.jsm179
-rw-r--r--services/fxaccounts/FxAccountsOAuthClient.jsm269
-rw-r--r--services/fxaccounts/FxAccountsOAuthGrantClient.jsm241
-rw-r--r--services/fxaccounts/FxAccountsProfile.jsm191
-rw-r--r--services/fxaccounts/FxAccountsProfileClient.jsm260
-rw-r--r--services/fxaccounts/FxAccountsPush.js240
-rw-r--r--services/fxaccounts/FxAccountsStorage.jsm606
-rw-r--r--services/fxaccounts/FxAccountsWebChannel.jsm474
-rw-r--r--services/fxaccounts/interfaces/moz.build11
-rw-r--r--services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl15
-rw-r--r--services/fxaccounts/moz.build32
-rw-r--r--services/fxaccounts/tests/mochitest/chrome.ini7
-rw-r--r--services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs80
-rw-r--r--services/fxaccounts/tests/mochitest/test_invalidEmailCase.html131
-rw-r--r--services/fxaccounts/tests/xpcshell/head.js18
-rw-r--r--services/fxaccounts/tests/xpcshell/test_accounts.js1531
-rw-r--r--services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js526
-rw-r--r--services/fxaccounts/tests/xpcshell/test_client.js917
-rw-r--r--services/fxaccounts/tests/xpcshell/test_credentials.js110
-rw-r--r--services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js214
-rw-r--r--services/fxaccounts/tests/xpcshell/test_oauth_client.js55
-rw-r--r--services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js292
-rw-r--r--services/fxaccounts/tests/xpcshell/test_oauth_grant_client_server.js73
-rw-r--r--services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js165
-rw-r--r--services/fxaccounts/tests/xpcshell/test_oauth_tokens.js251
-rw-r--r--services/fxaccounts/tests/xpcshell/test_profile.js409
-rw-r--r--services/fxaccounts/tests/xpcshell/test_profile_client.js411
-rw-r--r--services/fxaccounts/tests/xpcshell/test_push_service.js236
-rw-r--r--services/fxaccounts/tests/xpcshell/test_storage_manager.js477
-rw-r--r--services/fxaccounts/tests/xpcshell/test_web_channel.js499
-rw-r--r--services/fxaccounts/tests/xpcshell/xpcshell.ini23
-rw-r--r--services/moz.build6
-rw-r--r--services/sync/modules-testing/fxa_utils.js58
-rw-r--r--services/sync/modules-testing/utils.js85
-rw-r--r--services/sync/modules/util.js40
-rw-r--r--services/sync/moz.build1
-rw-r--r--services/sync/tests/unit/test_browserid_identity.js682
-rw-r--r--services/sync/tests/unit/test_errorhandler.js4
-rw-r--r--services/sync/tests/unit/test_fxa_node_reassignment.js321
-rw-r--r--services/sync/tests/unit/test_fxa_service_cluster.js68
-rw-r--r--services/sync/tests/unit/test_fxa_startOver.js63
-rw-r--r--services/sync/tests/unit/test_load_modules.js1
-rw-r--r--services/sync/tests/unit/xpcshell.ini6
-rw-r--r--services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm96
-rw-r--r--services/sync/tps/extensions/tps/resource/tps.jsm4
73 files changed, 4 insertions, 15939 deletions
diff --git a/services/cloudsync/CloudSync.jsm b/services/cloudsync/CloudSync.jsm
deleted file mode 100644
index 2c1057ea9..000000000
--- a/services/cloudsync/CloudSync.jsm
+++ /dev/null
@@ -1,89 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["CloudSync"];
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Adapters",
- "resource://gre/modules/CloudSyncAdapters.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Local",
- "resource://gre/modules/CloudSyncLocal.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Bookmarks",
- "resource://gre/modules/CloudSyncBookmarks.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Tabs",
- "resource://gre/modules/CloudSyncTabs.jsm");
-
-var API_VERSION = 1;
-
-var _CloudSync = function () {
-};
-
-_CloudSync.prototype = {
- _adapters: null,
-
- get adapters () {
- if (!this._adapters) {
- this._adapters = new Adapters();
- }
- return this._adapters;
- },
-
- _bookmarks: null,
-
- get bookmarks () {
- if (!this._bookmarks) {
- this._bookmarks = new Bookmarks();
- }
- return this._bookmarks;
- },
-
- _local: null,
-
- get local () {
- if (!this._local) {
- this._local = new Local();
- }
- return this._local;
- },
-
- _tabs: null,
-
- get tabs () {
- if (!this._tabs) {
- this._tabs = new Tabs();
- }
- return this._tabs;
- },
-
- get tabsReady () {
- return this._tabs ? true: false;
- },
-
- get version () {
- return API_VERSION;
- },
-};
-
-this.CloudSync = function CloudSync () {
- return _cloudSyncInternal.instance;
-};
-
-Object.defineProperty(CloudSync, "ready", {
- get: function () {
- return _cloudSyncInternal.ready;
- }
-});
-
-var _cloudSyncInternal = {
- instance: null,
- ready: false,
-};
-
-XPCOMUtils.defineLazyGetter(_cloudSyncInternal, "instance", function () {
- _cloudSyncInternal.ready = true;
- return new _CloudSync();
-}.bind(this));
diff --git a/services/cloudsync/CloudSyncAdapters.jsm b/services/cloudsync/CloudSyncAdapters.jsm
deleted file mode 100644
index 16264a4f7..000000000
--- a/services/cloudsync/CloudSyncAdapters.jsm
+++ /dev/null
@@ -1,88 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Adapters"];
-
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/CloudSyncEventSource.jsm");
-
-this.Adapters = function () {
- let eventTypes = [
- "sync",
- ];
-
- let suspended = true;
-
- let suspend = function () {
- if (!suspended) {
- Services.obs.removeObserver(observer, "cloudsync:user-sync", false);
- suspended = true;
- }
- }.bind(this);
-
- let resume = function () {
- if (suspended) {
- Services.obs.addObserver(observer, "cloudsync:user-sync", false);
- suspended = false;
- }
- }.bind(this);
-
- let eventSource = new EventSource(eventTypes, suspend, resume);
- let registeredAdapters = new Map();
-
- function register (name, opts) {
- opts = opts || {};
- registeredAdapters.set(name, opts);
- }
-
- function unregister (name) {
- if (!registeredAdapters.has(name)) {
- throw new Error("adapter is not registered: " + name)
- }
- registeredAdapters.delete(name);
- }
-
- function getAdapterNames () {
- let result = [];
- for (let name of registeredAdapters.keys()) {
- result.push(name);
- }
- return result;
- }
-
- function getAdapter (name) {
- if (!registeredAdapters.has(name)) {
- throw new Error("adapter is not registered: " + name)
- }
- return registeredAdapters.get(name);
- }
-
- function countAdapters () {
- return registeredAdapters.size;
- }
-
- let observer = {
- observe: function (subject, topic, data) {
- switch (topic) {
- case "cloudsync:user-sync":
- eventSource.emit("sync");
- break;
- }
- }
- };
-
- this.addEventListener = eventSource.addEventListener;
- this.removeEventListener = eventSource.removeEventListener;
- this.register = register.bind(this);
- this.get = getAdapter.bind(this);
- this.unregister = unregister.bind(this);
- this.__defineGetter__("names", getAdapterNames);
- this.__defineGetter__("count", countAdapters);
-};
-
-Adapters.prototype = {
-
-};
diff --git a/services/cloudsync/CloudSyncBookmarks.jsm b/services/cloudsync/CloudSyncBookmarks.jsm
deleted file mode 100644
index bb2e48d59..000000000
--- a/services/cloudsync/CloudSyncBookmarks.jsm
+++ /dev/null
@@ -1,795 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Bookmarks"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://gre/modules/PlacesUtils.jsm");
-Cu.import("resource:///modules/PlacesUIUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/CloudSyncPlacesWrapper.jsm");
-Cu.import("resource://gre/modules/CloudSyncEventSource.jsm");
-Cu.import("resource://gre/modules/CloudSyncBookmarksFolderCache.jsm");
-
-const ITEM_TYPES = [
- "NULL",
- "BOOKMARK",
- "FOLDER",
- "SEPARATOR",
- "DYNAMIC_CONTAINER", // no longer used by Places, but this ID should not be used for future item types
-];
-
-const CS_UNKNOWN = 0x1;
-const CS_FOLDER = 0x1 << 1;
-const CS_SEPARATOR = 0x1 << 2;
-const CS_QUERY = 0x1 << 3;
-const CS_LIVEMARK = 0x1 << 4;
-const CS_BOOKMARK = 0x1 << 5;
-
-const EXCLUDE_BACKUP_ANNO = "places/excludeFromBackup";
-
-const DATA_VERSION = 1;
-
-function asyncCallback(ctx, func, args) {
- function invoke() {
- func.apply(ctx, args);
- }
- CommonUtils.nextTick(invoke);
-}
-
-var Record = function (params) {
- this.id = params.guid;
- this.parent = params.parent || null;
- this.index = params.position;
- this.title = params.title;
- this.dateAdded = Math.floor(params.dateAdded/1000);
- this.lastModified = Math.floor(params.lastModified/1000);
- this.uri = params.url;
-
- let annos = params.annos || {};
- Object.defineProperty(this, "annos", {
- get: function () {
- return annos;
- },
- enumerable: false
- });
-
- switch (params.type) {
- case PlacesUtils.bookmarks.TYPE_FOLDER:
- if (PlacesUtils.LMANNO_FEEDURI in annos) {
- this.type = CS_LIVEMARK;
- this.feed = annos[PlacesUtils.LMANNO_FEEDURI];
- this.site = annos[PlacesUtils.LMANNO_SITEURI];
- } else {
- this.type = CS_FOLDER;
- }
- break;
- case PlacesUtils.bookmarks.TYPE_BOOKMARK:
- if (this.uri.startsWith("place:")) {
- this.type = CS_QUERY;
- } else {
- this.type = CS_BOOKMARK;
- }
- break;
- case PlacesUtils.bookmarks.TYPE_SEPARATOR:
- this.type = CS_SEPARATOR;
- break;
- default:
- this.type = CS_UNKNOWN;
- }
-};
-
-Record.prototype = {
- version: DATA_VERSION,
-};
-
-var Bookmarks = function () {
- let createRootFolder = function (name) {
- let ROOT_FOLDER_ANNO = "cloudsync/rootFolder/" + name;
- let ROOT_SHORTCUT_ANNO = "cloudsync/rootShortcut/" + name;
-
- let deferred = Promise.defer();
- let placesRootId = PlacesUtils.placesRootId;
- let rootFolderId;
- let rootShortcutId;
-
- function createAdapterShortcut(result) {
- rootFolderId = result;
- let uri = "place:folder=" + rootFolderId;
- return PlacesWrapper.insertBookmark(PlacesUIUtils.allBookmarksFolderId, uri,
- PlacesUtils.bookmarks.DEFAULT_INDEX, name);
- }
-
- function setRootFolderCloudSyncAnnotation(result) {
- rootShortcutId = result;
- return PlacesWrapper.setItemAnnotation(rootFolderId, ROOT_FOLDER_ANNO,
- 1, 0, PlacesUtils.annotations.EXPIRE_NEVER);
- }
-
- function setRootShortcutCloudSyncAnnotation() {
- return PlacesWrapper.setItemAnnotation(rootShortcutId, ROOT_SHORTCUT_ANNO,
- 1, 0, PlacesUtils.annotations.EXPIRE_NEVER);
- }
-
- function setRootFolderExcludeFromBackupAnnotation() {
- return PlacesWrapper.setItemAnnotation(rootFolderId, EXCLUDE_BACKUP_ANNO,
- 1, 0, PlacesUtils.annotations.EXPIRE_NEVER);
- }
-
- function finish() {
- deferred.resolve(rootFolderId);
- }
-
- Promise.resolve(PlacesUtils.bookmarks.createFolder(placesRootId, name, PlacesUtils.bookmarks.DEFAULT_INDEX))
- .then(createAdapterShortcut)
- .then(setRootFolderCloudSyncAnnotation)
- .then(setRootShortcutCloudSyncAnnotation)
- .then(setRootFolderExcludeFromBackupAnnotation)
- .then(finish, deferred.reject);
-
- return deferred.promise;
- };
-
- let getRootFolder = function (name) {
- let ROOT_FOLDER_ANNO = "cloudsync/rootFolder/" + name;
- let ROOT_SHORTCUT_ANNO = "cloudsync/rootShortcut/" + name;
- let deferred = Promise.defer();
-
- function checkRootFolder(folderIds) {
- if (!folderIds.length) {
- return createRootFolder(name);
- }
- return Promise.resolve(folderIds[0]);
- }
-
- function createFolderObject(folderId) {
- return new RootFolder(folderId, name);
- }
-
- PlacesWrapper.getLocalIdsWithAnnotation(ROOT_FOLDER_ANNO)
- .then(checkRootFolder, deferred.reject)
- .then(createFolderObject)
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- };
-
- let deleteRootFolder = function (name) {
- let ROOT_FOLDER_ANNO = "cloudsync/rootFolder/" + name;
- let ROOT_SHORTCUT_ANNO = "cloudsync/rootShortcut/" + name;
-
- let deferred = Promise.defer();
- let placesRootId = PlacesUtils.placesRootId;
-
- function getRootShortcutId() {
- return PlacesWrapper.getLocalIdsWithAnnotation(ROOT_SHORTCUT_ANNO);
- }
-
- function deleteShortcut(shortcutIds) {
- if (!shortcutIds.length) {
- return Promise.resolve();
- }
- return PlacesWrapper.removeItem(shortcutIds[0]);
- }
-
- function getRootFolderId() {
- return PlacesWrapper.getLocalIdsWithAnnotation(ROOT_FOLDER_ANNO);
- }
-
- function deleteFolder(folderIds) {
- let deleteFolderDeferred = Promise.defer();
-
- if (!folderIds.length) {
- return Promise.resolve();
- }
-
- let rootFolderId = folderIds[0];
- PlacesWrapper.removeFolderChildren(rootFolderId).then(
- function () {
- return PlacesWrapper.removeItem(rootFolderId);
- }
- ).then(deleteFolderDeferred.resolve, deleteFolderDeferred.reject);
-
- return deleteFolderDeferred.promise;
- }
-
- getRootShortcutId().then(deleteShortcut)
- .then(getRootFolderId)
- .then(deleteFolder)
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- };
-
- /* PUBLIC API */
- this.getRootFolder = getRootFolder.bind(this);
- this.deleteRootFolder = deleteRootFolder.bind(this);
-
-};
-
-this.Bookmarks = Bookmarks;
-
-var RootFolder = function (rootId, rootName) {
- let suspended = true;
- let ignoreAll = false;
-
- let suspend = function () {
- if (!suspended) {
- PlacesUtils.bookmarks.removeObserver(observer);
- suspended = true;
- }
- }.bind(this);
-
- let resume = function () {
- if (suspended) {
- PlacesUtils.bookmarks.addObserver(observer, false);
- suspended = false;
- }
- }.bind(this);
-
- let eventTypes = [
- "add",
- "remove",
- "change",
- "move",
- ];
-
- let eventSource = new EventSource(eventTypes, suspend, resume);
-
- let folderCache = new FolderCache;
- folderCache.insert(rootId, null);
-
- let getCachedFolderIds = function (cache, roots) {
- let nodes = [...roots];
- let results = [];
-
- while (nodes.length) {
- let node = nodes.shift();
- results.push(node);
- let children = cache.getChildren(node);
- nodes = nodes.concat([...children]);
- }
- return results;
- };
-
- let getLocalItems = function () {
- let deferred = Promise.defer();
-
- let folders = getCachedFolderIds(folderCache, folderCache.getChildren(rootId));
-
- function getFolders(ids) {
- let types = [
- PlacesUtils.bookmarks.TYPE_FOLDER,
- ];
- return PlacesWrapper.getItemsById(ids, types);
- }
-
- function getContents(parents) {
- parents.push(rootId);
- let types = [
- PlacesUtils.bookmarks.TYPE_BOOKMARK,
- PlacesUtils.bookmarks.TYPE_SEPARATOR,
- ];
- return PlacesWrapper.getItemsByParentId(parents, types)
- }
-
- function getParentGuids(results) {
- results = Array.prototype.concat.apply([], results);
- let promises = [];
- results.map(function (result) {
- let promise = PlacesWrapper.localIdToGuid(result.parent).then(
- function (guidResult) {
- result.parent = guidResult;
- return Promise.resolve(result);
- },
- Promise.reject.bind(Promise)
- );
- promises.push(promise);
- });
- return Promise.all(promises);
- }
-
- function getAnnos(results) {
- results = Array.prototype.concat.apply([], results);
- let promises = [];
- results.map(function (result) {
- let promise = PlacesWrapper.getItemAnnotationsForLocalId(result.id).then(
- function (annos) {
- result.annos = annos;
- return Promise.resolve(result);
- },
- Promise.reject.bind(Promise)
- );
- promises.push(promise);
- });
- return Promise.all(promises);
- }
-
- let promises = [
- getFolders(folders),
- getContents(folders),
- ];
-
- Promise.all(promises)
- .then(getParentGuids)
- .then(getAnnos)
- .then(function (results) {
- results = results.map((result) => new Record(result));
- deferred.resolve(results);
- },
- deferred.reject);
-
- return deferred.promise;
- };
-
- let getLocalItemsById = function (guids) {
- let deferred = Promise.defer();
-
- let types = [
- PlacesUtils.bookmarks.TYPE_BOOKMARK,
- PlacesUtils.bookmarks.TYPE_FOLDER,
- PlacesUtils.bookmarks.TYPE_SEPARATOR,
- PlacesUtils.bookmarks.TYPE_DYNAMIC_CONTAINER,
- ];
-
- function getParentGuids(results) {
- let promises = [];
- results.map(function (result) {
- let promise = PlacesWrapper.localIdToGuid(result.parent).then(
- function (guidResult) {
- result.parent = guidResult;
- return Promise.resolve(result);
- },
- Promise.reject.bind(Promise)
- );
- promises.push(promise);
- });
- return Promise.all(promises);
- }
-
- PlacesWrapper.getItemsByGuid(guids, types)
- .then(getParentGuids)
- .then(function (results) {
- results = results.map((result) => new Record(result));
- deferred.resolve(results);
- },
- deferred.reject);
-
- return deferred.promise;
- };
-
- let _createItem = function (item) {
- let deferred = Promise.defer();
-
- function getFolderId() {
- if (item.parent) {
- return PlacesWrapper.guidToLocalId(item.parent);
- }
- return Promise.resolve(rootId);
- }
-
- function create(folderId) {
- let deferred = Promise.defer();
-
- if (!folderId) {
- folderId = rootId;
- }
- let index = item.hasOwnProperty("index") ? item.index : PlacesUtils.bookmarks.DEFAULT_INDEX;
-
- function complete(localId) {
- folderCache.insert(localId, folderId);
- deferred.resolve(localId);
- }
-
- switch (item.type) {
- case CS_BOOKMARK:
- case CS_QUERY:
- PlacesWrapper.insertBookmark(folderId, item.uri, index, item.title, item.id)
- .then(complete, deferred.reject);
- break;
- case CS_FOLDER:
- PlacesWrapper.createFolder(folderId, item.title, index, item.id)
- .then(complete, deferred.reject);
- break;
- case CS_SEPARATOR:
- PlacesWrapper.insertSeparator(folderId, index, item.id)
- .then(complete, deferred.reject);
- break;
- case CS_LIVEMARK:
- let livemark = {
- title: item.title,
- parentId: folderId,
- index: item.index,
- feedURI: item.feed,
- siteURI: item.site,
- guid: item.id,
- };
- PlacesUtils.livemarks.addLivemark(livemark)
- .then(complete, deferred.reject);
- break;
- default:
- deferred.reject("invalid item type: " + item.type);
- }
-
- return deferred.promise;
- }
-
- getFolderId().then(create)
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- };
-
- let _deleteItem = function (item) {
- let deferred = Promise.defer();
-
- PlacesWrapper.guidToLocalId(item.id).then(
- function (localId) {
- folderCache.remove(localId);
- return PlacesWrapper.removeItem(localId);
- }
- ).then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- };
-
- let _updateItem = function (item) {
- let deferred = Promise.defer();
-
- PlacesWrapper.guidToLocalId(item.id).then(
- function (localId) {
- let promises = [];
-
- if (item.hasOwnProperty("dateAdded")) {
- promises.push(PlacesWrapper.setItemDateAdded(localId, item.dateAdded));
- }
-
- if (item.hasOwnProperty("lastModified")) {
- promises.push(PlacesWrapper.setItemLastModified(localId, item.lastModified));
- }
-
- if ((CS_BOOKMARK | CS_FOLDER) & item.type && item.hasOwnProperty("title")) {
- promises.push(PlacesWrapper.setItemTitle(localId, item.title));
- }
-
- if (CS_BOOKMARK & item.type && item.hasOwnProperty("uri")) {
- promises.push(PlacesWrapper.changeBookmarkURI(localId, item.uri));
- }
-
- if (item.hasOwnProperty("parent")) {
- let deferred = Promise.defer();
- PlacesWrapper.guidToLocalId(item.parent)
- .then(
- function (parent) {
- let index = item.hasOwnProperty("index") ? item.index : PlacesUtils.bookmarks.DEFAULT_INDEX;
- if (CS_FOLDER & item.type) {
- folderCache.setParent(localId, parent);
- }
- return PlacesWrapper.moveItem(localId, parent, index);
- }
- )
- .then(deferred.resolve, deferred.reject);
- promises.push(deferred.promise);
- }
-
- if (item.hasOwnProperty("index") && !item.hasOwnProperty("parent")) {
- promises.push(Task.spawn(function* () {
- let localItem = (yield getLocalItemsById([item.id]))[0];
- let parent = yield PlacesWrapper.guidToLocalId(localItem.parent);
- let index = item.index;
- if (CS_FOLDER & item.type) {
- folderCache.setParent(localId, parent);
- }
- yield PlacesWrapper.moveItem(localId, parent, index);
- }));
- }
-
- Promise.all(promises)
- .then(deferred.resolve, deferred.reject);
- }
- );
-
- return deferred.promise;
- };
-
- let mergeRemoteItems = function (items) {
- ignoreAll = true;
- let deferred = Promise.defer();
-
- let newFolders = {};
- let newItems = [];
- let updatedItems = [];
- let deletedItems = [];
-
- let sortItems = function () {
- let promises = [];
-
- let exists = function (item) {
- let existsDeferred = Promise.defer();
- if (!item.id) {
- Object.defineProperty(item, "__exists__", {
- value: false,
- enumerable: false
- });
- existsDeferred.resolve(item);
- } else {
- PlacesWrapper.guidToLocalId(item.id).then(
- function (localId) {
- Object.defineProperty(item, "__exists__", {
- value: localId ? true : false,
- enumerable: false
- });
- existsDeferred.resolve(item);
- },
- existsDeferred.reject
- );
- }
- return existsDeferred.promise;
- }
-
- let handleSortedItem = function (item) {
- if (!item.__exists__ && !item.deleted) {
- if (CS_FOLDER == item.type) {
- newFolders[item.id] = item;
- item._children = [];
- } else {
- newItems.push(item);
- }
- } else if (item.__exists__ && item.deleted) {
- deletedItems.push(item);
- } else if (item.__exists__) {
- updatedItems.push(item);
- }
- }
-
- for (let item of items) {
- if (!item || 'object' !== typeof(item)) {
- continue;
- }
-
- let promise = exists(item).then(handleSortedItem, Promise.reject.bind(Promise));
- promises.push(promise);
- }
-
- return Promise.all(promises);
- }
-
- let processNewFolders = function () {
- let newFolderGuids = Object.keys(newFolders);
- let newFolderRoots = [];
-
- for (let guid of newFolderGuids) {
- let item = newFolders[guid];
- if (item.parent && newFolderGuids.indexOf(item.parent) >= 0) {
- let parent = newFolders[item.parent];
- parent._children.push(item.id);
- } else {
- newFolderRoots.push(guid);
- }
- };
-
- let promises = [];
- for (let guid of newFolderRoots) {
- let root = newFolders[guid];
- let promise = Promise.resolve();
- promise = promise.then(
- function () {
- return _createItem(root);
- },
- Promise.reject.bind(Promise)
- );
- let items = [].concat(root._children);
-
- while (items.length) {
- let item = newFolders[items.shift()];
- items = items.concat(item._children);
- promise = promise.then(
- function () {
- return _createItem(item);
- },
- Promise.reject.bind(Promise)
- );
- }
- promises.push(promise);
- }
-
- return Promise.all(promises);
- }
-
- let processItems = function () {
- let promises = [];
-
- for (let item of newItems) {
- promises.push(_createItem(item));
- }
-
- for (let item of updatedItems) {
- promises.push(_updateItem(item));
- }
-
- for (let item of deletedItems) {
- _deleteItem(item);
- }
-
- return Promise.all(promises);
- }
-
- sortItems().then(processNewFolders)
- .then(processItems)
- .then(function () {
- ignoreAll = false;
- deferred.resolve(items);
- },
- function (err) {
- ignoreAll = false;
- deferred.reject(err);
- });
-
- return deferred.promise;
- };
-
- let ignore = function (id, parent) {
- if (ignoreAll) {
- return true;
- }
-
- if (rootId == parent || folderCache.has(parent)) {
- return false;
- }
-
- return true;
- };
-
- let handleItemAdded = function (id, parent, index, type, uri, title, dateAdded, guid, parentGuid) {
- let deferred = Promise.defer();
-
- if (PlacesUtils.bookmarks.TYPE_FOLDER == type) {
- folderCache.insert(id, parent);
- }
-
- eventSource.emit("add", guid);
- deferred.resolve();
-
- return deferred.promise;
- };
-
- let handleItemRemoved = function (id, parent, index, type, uri, guid, parentGuid) {
- let deferred = Promise.defer();
-
- if (PlacesUtils.bookmarks.TYPE_FOLDER == type) {
- folderCache.remove(id);
- }
-
- eventSource.emit("remove", guid);
- deferred.resolve();
-
- return deferred.promise;
- };
-
- let handleItemChanged = function (id, property, isAnnotation, newValue, lastModified, type, parent, guid, parentGuid) {
- let deferred = Promise.defer();
-
- eventSource.emit('change', guid);
- deferred.resolve();
-
- return deferred.promise;
- };
-
- let handleItemMoved = function (id, oldParent, oldIndex, newParent, newIndex, type, guid, oldParentGuid, newParentGuid) {
- let deferred = Promise.defer();
-
- function complete() {
- eventSource.emit('move', guid);
- deferred.resolve();
- }
-
- if (PlacesUtils.bookmarks.TYPE_FOLDER != type) {
- complete();
- return deferred.promise;
- }
-
- if (folderCache.has(oldParent) && folderCache.has(newParent)) {
- // Folder move inside cloudSync root, so just update parents/children.
- folderCache.setParent(id, newParent);
- complete();
- } else if (!folderCache.has(oldParent)) {
- // Folder moved in from ouside cloudSync root.
- PlacesWrapper.updateCachedFolderIds(folderCache, newParent)
- .then(complete, complete);
- } else if (!folderCache.has(newParent)) {
- // Folder moved out from inside cloudSync root.
- PlacesWrapper.updateCachedFolderIds(folderCache, oldParent)
- .then(complete, complete);
- }
-
- return deferred.promise;
- };
-
- let observer = {
- onBeginBatchUpdate: function () {
- },
-
- onEndBatchUpdate: function () {
- },
-
- onItemAdded: function (id, parent, index, type, uri, title, dateAdded, guid, parentGuid) {
- if (ignore(id, parent)) {
- return;
- }
-
- asyncCallback(this, handleItemAdded, Array.prototype.slice.call(arguments));
- },
-
- onItemRemoved: function (id, parent, index, type, uri, guid, parentGuid) {
- if (ignore(id, parent)) {
- return;
- }
-
- asyncCallback(this, handleItemRemoved, Array.prototype.slice.call(arguments));
- },
-
- onItemChanged: function (id, property, isAnnotation, newValue, lastModified, type, parent, guid, parentGuid) {
- if (ignore(id, parent)) {
- return;
- }
-
- asyncCallback(this, handleItemChanged, Array.prototype.slice.call(arguments));
- },
-
- onItemMoved: function (id, oldParent, oldIndex, newParent, newIndex, type, guid, oldParentGuid, newParentGuid) {
- if (ignore(id, oldParent) && ignore(id, newParent)) {
- return;
- }
-
- asyncCallback(this, handleItemMoved, Array.prototype.slice.call(arguments));
- }
- };
-
- /* PUBLIC API */
- this.addEventListener = eventSource.addEventListener;
- this.removeEventListener = eventSource.removeEventListener;
- this.getLocalItems = getLocalItems.bind(this);
- this.getLocalItemsById = getLocalItemsById.bind(this);
- this.mergeRemoteItems = mergeRemoteItems.bind(this);
-
- let rootGuid = null; // resolved before becoming ready (below)
- this.__defineGetter__("id", function () {
- return rootGuid;
- });
- this.__defineGetter__("name", function () {
- return rootName;
- });
-
- let deferred = Promise.defer();
- let getGuidForRootFolder = function () {
- return PlacesWrapper.localIdToGuid(rootId);
- }
- PlacesWrapper.updateCachedFolderIds(folderCache, rootId)
- .then(getGuidForRootFolder, getGuidForRootFolder)
- .then(function (guid) {
- rootGuid = guid;
- deferred.resolve(this);
- }.bind(this),
- deferred.reject);
- return deferred.promise;
-};
-
-RootFolder.prototype = {
- BOOKMARK: CS_BOOKMARK,
- FOLDER: CS_FOLDER,
- SEPARATOR: CS_SEPARATOR,
- QUERY: CS_QUERY,
- LIVEMARK: CS_LIVEMARK,
-};
diff --git a/services/cloudsync/CloudSyncBookmarksFolderCache.jsm b/services/cloudsync/CloudSyncBookmarksFolderCache.jsm
deleted file mode 100644
index f3c3fc8f2..000000000
--- a/services/cloudsync/CloudSyncBookmarksFolderCache.jsm
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["FolderCache"];
-
-// Cache for bookmarks folder heirarchy.
-var FolderCache = function () {
- this.cache = new Map();
-}
-
-FolderCache.prototype = {
- has: function (id) {
- return this.cache.has(id);
- },
-
- insert: function (id, parentId) {
- if (this.cache.has(id)) {
- return;
- }
-
- if (parentId && !(this.cache.has(parentId))) {
- throw new Error("insert :: parentId not found in cache: " + parentId);
- }
-
- this.cache.set(id, {
- parent: parentId || null,
- children: new Set(),
- });
-
- if (parentId) {
- this.cache.get(parentId).children.add(id);
- }
- },
-
- remove: function (id) {
- if (!(this.cache.has(id))) {
- throw new Error("remote :: id not found in cache: " + id);
- }
-
- let parentId = this.cache.get(id).parent;
- if (parentId) {
- this.cache.get(parentId).children.delete(id);
- }
-
- for (let child of this.cache.get(id).children) {
- this.cache.get(child).parent = null;
- }
-
- this.cache.delete(id);
- },
-
- setParent: function (id, parentId) {
- if (!(this.cache.has(id))) {
- throw new Error("setParent :: id not found in cache: " + id);
- }
-
- if (parentId && !(this.cache.has(parentId))) {
- throw new Error("setParent :: parentId not found in cache: " + parentId);
- }
-
- let oldParent = this.cache.get(id).parent;
- if (oldParent) {
- this.cache.get(oldParent).children.delete(id);
- }
- this.cache.get(id).parent = parentId;
- this.cache.get(parentId).children.add(id);
-
- return true;
- },
-
- getParent: function (id) {
- if (this.cache.has(id)) {
- return this.cache.get(id).parent;
- }
-
- throw new Error("getParent :: id not found in cache: " + id);
- },
-
- getChildren: function (id) {
- if (this.cache.has(id)) {
- return this.cache.get(id).children;
- }
-
- throw new Error("getChildren :: id not found in cache: " + id);
- },
-
- setChildren: function (id, children) {
- for (let child of children) {
- if (!this.cache.has(child)) {
- this.insert(child, id);
- } else {
- this.setParent(child, id);
- }
- }
- },
-
- dump: function () {
- dump("FolderCache: " + JSON.stringify(this.cache) + "\n");
- },
-};
-
-this.FolderCache = FolderCache;
diff --git a/services/cloudsync/CloudSyncEventSource.jsm b/services/cloudsync/CloudSyncEventSource.jsm
deleted file mode 100644
index edb9c426b..000000000
--- a/services/cloudsync/CloudSyncEventSource.jsm
+++ /dev/null
@@ -1,65 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-this.EXPORTED_SYMBOLS = ["EventSource"];
-
-Components.utils.import("resource://services-common/utils.js");
-
-var EventSource = function (types, suspendFunc, resumeFunc) {
- this.listeners = new Map();
- for (let type of types) {
- this.listeners.set(type, new Set());
- }
-
- this.suspend = suspendFunc || function () {};
- this.resume = resumeFunc || function () {};
-
- this.addEventListener = this.addEventListener.bind(this);
- this.removeEventListener = this.removeEventListener.bind(this);
-};
-
-EventSource.prototype = {
- addEventListener: function (type, listener) {
- if (!this.listeners.has(type)) {
- return;
- }
- this.listeners.get(type).add(listener);
- this.resume();
- },
-
- removeEventListener: function (type, listener) {
- if (!this.listeners.has(type)) {
- return;
- }
- this.listeners.get(type).delete(listener);
- if (!this.hasListeners()) {
- this.suspend();
- }
- },
-
- hasListeners: function () {
- for (let l of this.listeners.values()) {
- if (l.size > 0) {
- return true;
- }
- }
- return false;
- },
-
- emit: function (type, arg) {
- if (!this.listeners.has(type)) {
- return;
- }
- CommonUtils.nextTick(
- function () {
- for (let listener of this.listeners.get(type)) {
- listener.call(undefined, arg);
- }
- },
- this
- );
- },
-};
-
-this.EventSource = EventSource;
diff --git a/services/cloudsync/CloudSyncLocal.jsm b/services/cloudsync/CloudSyncLocal.jsm
deleted file mode 100644
index 998c0c3c4..000000000
--- a/services/cloudsync/CloudSyncLocal.jsm
+++ /dev/null
@@ -1,87 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Local"];
-
-const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://services-common/stringbundle.js");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://gre/modules/Preferences.jsm");
-
-function lazyStrings(name) {
- let bundle = "chrome://weave/locale/services/" + name + ".properties";
- return () => new StringBundle(bundle);
-}
-
-this.Str = {};
-XPCOMUtils.defineLazyGetter(Str, "errors", lazyStrings("errors"));
-XPCOMUtils.defineLazyGetter(Str, "sync", lazyStrings("sync"));
-
-function makeGUID() {
- return CommonUtils.encodeBase64URL(CryptoUtils.generateRandomBytes(9));
-}
-
-this.Local = function () {
- let prefs = new Preferences("services.cloudsync.");
- this.__defineGetter__("prefs", function () {
- return prefs;
- });
-};
-
-Local.prototype = {
- get id() {
- let clientId = this.prefs.get("client.GUID", "");
- return clientId == "" ? this.id = makeGUID(): clientId;
- },
-
- set id(value) {
- this.prefs.set("client.GUID", value);
- },
-
- get name() {
- let clientName = this.prefs.get("client.name", "");
-
- if (clientName != "") {
- return clientName;
- }
-
- // Generate a client name if we don't have a useful one yet
- let env = Cc["@mozilla.org/process/environment;1"]
- .getService(Ci.nsIEnvironment);
- let user = env.get("USER") || env.get("USERNAME");
- let appName;
- let brand = new StringBundle("chrome://branding/locale/brand.properties");
- let brandName = brand.get("brandShortName");
-
- try {
- let syncStrings = new StringBundle("chrome://browser/locale/sync.properties");
- appName = syncStrings.getFormattedString("sync.defaultAccountApplication", [brandName]);
- } catch (ex) {
- }
-
- appName = appName || brandName;
-
- let system =
- // 'device' is defined on unix systems
- Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
- // hostname of the system, usually assigned by the user or admin
- Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("host") ||
- // fall back on ua info string
- Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
-
- return this.name = Str.sync.get("client.name2", [user, appName, system]);
- },
-
- set name(value) {
- this.prefs.set("client.name", value);
- },
-};
-
diff --git a/services/cloudsync/CloudSyncPlacesWrapper.jsm b/services/cloudsync/CloudSyncPlacesWrapper.jsm
deleted file mode 100644
index dd8c5c52e..000000000
--- a/services/cloudsync/CloudSyncPlacesWrapper.jsm
+++ /dev/null
@@ -1,375 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["PlacesWrapper"];
-
-const {interfaces: Ci, utils: Cu} = Components;
-const REASON_ERROR = Ci.mozIStorageStatementCallback.REASON_ERROR;
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/PlacesUtils.jsm");
-Cu.import("resource:///modules/PlacesUIUtils.jsm");
-Cu.import("resource://services-common/utils.js");
-
-var PlacesQueries = function () {
-}
-
-PlacesQueries.prototype = {
- cachedStmts: {},
-
- getQuery: function (queryString) {
- if (queryString in this.cachedStmts) {
- return this.cachedStmts[queryString];
- }
-
- let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
- return this.cachedStmts[queryString] = db.createAsyncStatement(queryString);
- }
-};
-
-var PlacesWrapper = function () {
-}
-
-PlacesWrapper.prototype = {
- placesQueries: new PlacesQueries(),
-
- guidToLocalId: function (guid) {
- let deferred = Promise.defer();
-
- let stmt = "SELECT id AS item_id " +
- "FROM moz_bookmarks " +
- "WHERE guid = :guid";
- let query = this.placesQueries.getQuery(stmt);
-
- function getLocalId(results) {
- let result = results[0] && results[0]["item_id"];
- return Promise.resolve(result);
- }
-
- query.params.guid = guid.toString();
-
- this.asyncQuery(query, ["item_id"])
- .then(getLocalId, deferred.reject)
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- },
-
- localIdToGuid: function (id) {
- let deferred = Promise.defer();
-
- let stmt = "SELECT guid " +
- "FROM moz_bookmarks " +
- "WHERE id = :item_id";
- let query = this.placesQueries.getQuery(stmt);
-
- function getGuid(results) {
- let result = results[0] && results[0]["guid"];
- return Promise.resolve(result);
- }
-
- query.params.item_id = id;
-
- this.asyncQuery(query, ["guid"])
- .then(getGuid, deferred.reject)
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- },
-
- getItemsById: function (ids, types) {
- let deferred = Promise.defer();
- let stmt = "SELECT b.id, b.type, b.parent, b.position, b.title, b.guid, b.dateAdded, b.lastModified, p.url " +
- "FROM moz_bookmarks b " +
- "LEFT JOIN moz_places p ON b.fk = p.id " +
- "WHERE b.id in (" + ids.join(",") + ") AND b.type in (" + types.join(",") + ")";
- let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
- let query = db.createAsyncStatement(stmt);
-
- this.asyncQuery(query, ["id", "type", "parent", "position", "title", "guid", "dateAdded", "lastModified", "url"])
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- },
-
- getItemsByParentId: function (parents, types) {
- let deferred = Promise.defer();
- let stmt = "SELECT b.id, b.type, b.parent, b.position, b.title, b.guid, b.dateAdded, b.lastModified, p.url " +
- "FROM moz_bookmarks b " +
- "LEFT JOIN moz_places p ON b.fk = p.id " +
- "WHERE b.parent in (" + parents.join(",") + ") AND b.type in (" + types.join(",") + ")";
- let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
- let query = db.createAsyncStatement(stmt);
-
- this.asyncQuery(query, ["id", "type", "parent", "position", "title", "guid", "dateAdded", "lastModified", "url"])
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- },
-
- getItemsByGuid: function (guids, types) {
- let deferred = Promise.defer();
- guids = guids.map(JSON.stringify);
- let stmt = "SELECT b.id, b.type, b.parent, b.position, b.title, b.guid, b.dateAdded, b.lastModified, p.url " +
- "FROM moz_bookmarks b " +
- "LEFT JOIN moz_places p ON b.fk = p.id " +
- "WHERE b.guid in (" + guids.join(",") + ") AND b.type in (" + types.join(",") + ")";
- let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
- let query = db.createAsyncStatement(stmt);
-
- this.asyncQuery(query, ["id", "type", "parent", "position", "title", "guid", "dateAdded", "lastModified", "url"])
- .then(deferred.resolve, deferred.reject);
-
- return deferred.promise;
- },
-
- updateCachedFolderIds: function (folderCache, folder) {
- let deferred = Promise.defer();
- let stmt = "SELECT id, guid " +
- "FROM moz_bookmarks " +
- "WHERE parent = :parent_id AND type = :item_type";
- let query = this.placesQueries.getQuery(stmt);
-
- query.params.parent_id = folder;
- query.params.item_type = PlacesUtils.bookmarks.TYPE_FOLDER;
-
- this.asyncQuery(query, ["id", "guid"]).then(
- function (items) {
- let previousIds = folderCache.getChildren(folder);
- let currentIds = new Set();
- for (let item of items) {
- currentIds.add(item.id);
- }
- let newIds = new Set();
- let missingIds = new Set();
-
- for (let currentId of currentIds) {
- if (!previousIds.has(currentId)) {
- newIds.add(currentId);
- }
- }
- for (let previousId of previousIds) {
- if (!currentIds.has(previousId)) {
- missingIds.add(previousId);
- }
- }
-
- folderCache.setChildren(folder, currentIds);
-
- let promises = [];
- for (let newId of newIds) {
- promises.push(this.updateCachedFolderIds(folderCache, newId));
- }
- Promise.all(promises)
- .then(deferred.resolve, deferred.reject);
-
- for (let missingId of missingIds) {
- folderCache.remove(missingId);
- }
- }.bind(this)
- );
-
- return deferred.promise;
- },
-
- getLocalIdsWithAnnotation: function (anno) {
- let deferred = Promise.defer();
- let stmt = "SELECT a.item_id " +
- "FROM moz_anno_attributes n " +
- "JOIN moz_items_annos a ON n.id = a.anno_attribute_id " +
- "WHERE n.name = :anno_name";
- let query = this.placesQueries.getQuery(stmt);
-
- query.params.anno_name = anno.toString();
-
- this.asyncQuery(query, ["item_id"])
- .then(function (items) {
- let results = [];
- for (let item of items) {
- results.push(item.item_id);
- }
- deferred.resolve(results);
- },
- deferred.reject);
-
- return deferred.promise;
- },
-
- getItemAnnotationsForLocalId: function (id) {
- let deferred = Promise.defer();
- let stmt = "SELECT a.name, b.content " +
- "FROM moz_anno_attributes a " +
- "JOIN moz_items_annos b ON a.id = b.anno_attribute_id " +
- "WHERE b.item_id = :item_id";
- let query = this.placesQueries.getQuery(stmt);
-
- query.params.item_id = id;
-
- this.asyncQuery(query, ["name", "content"])
- .then(function (results) {
- let annos = {};
- for (let result of results) {
- annos[result.name] = result.content;
- }
- deferred.resolve(annos);
- },
- deferred.reject);
-
- return deferred.promise;
- },
-
- insertBookmark: function (parent, uri, index, title, guid) {
- let parsedURI;
- try {
- parsedURI = CommonUtils.makeURI(uri)
- } catch (e) {
- return Promise.reject("unable to parse URI '" + uri + "': " + e);
- }
-
- try {
- let id = PlacesUtils.bookmarks.insertBookmark(parent, parsedURI, index, title, guid);
- return Promise.resolve(id);
- } catch (e) {
- return Promise.reject("unable to insert bookmark " + JSON.stringify(arguments) + ": " + e);
- }
- },
-
- setItemAnnotation: function (item, anno, value, flags, exp) {
- try {
- return Promise.resolve(PlacesUtils.annotations.setItemAnnotation(item, anno, value, flags, exp));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- itemHasAnnotation: function (item, anno) {
- try {
- return Promise.resolve(PlacesUtils.annotations.itemHasAnnotation(item, anno));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- createFolder: function (parent, name, index, guid) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.createFolder(parent, name, index, guid));
- } catch (e) {
- return Promise.reject("unable to create folder ['" + name + "']: " + e);
- }
- },
-
- removeFolderChildren: function (folder) {
- try {
- PlacesUtils.bookmarks.removeFolderChildren(folder);
- return Promise.resolve();
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- insertSeparator: function (parent, index, guid) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.insertSeparator(parent, index, guid));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- removeItem: function (item) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.removeItem(item));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- setItemDateAdded: function (item, dateAdded) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.setItemDateAdded(item, dateAdded));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- setItemLastModified: function (item, lastModified) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.setItemLastModified(item, lastModified));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- setItemTitle: function (item, title) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.setItemTitle(item, title));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- changeBookmarkURI: function (item, uri) {
- try {
- uri = CommonUtils.makeURI(uri);
- return Promise.resolve(PlacesUtils.bookmarks.changeBookmarkURI(item, uri));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- moveItem: function (item, parent, index) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.moveItem(item, parent, index));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- setItemIndex: function (item, index) {
- try {
- return Promise.resolve(PlacesUtils.bookmarks.setItemIndex(item, index));
- } catch (e) {
- return Promise.reject(e);
- }
- },
-
- asyncQuery: function (query, names) {
- let deferred = Promise.defer();
- let storageCallback = {
- results: [],
- handleResult: function (results) {
- if (!names) {
- return;
- }
-
- let row;
- while ((row = results.getNextRow()) != null) {
- let item = {};
- for (let name of names) {
- item[name] = row.getResultByName(name);
- }
- this.results.push(item);
- }
- },
-
- handleError: function (error) {
- deferred.reject(error);
- },
-
- handleCompletion: function (reason) {
- if (REASON_ERROR == reason) {
- return;
- }
-
- deferred.resolve(this.results);
- }
- };
-
- query.executeAsync(storageCallback);
- return deferred.promise;
- },
-};
-
-this.PlacesWrapper = new PlacesWrapper();
diff --git a/services/cloudsync/CloudSyncTabs.jsm b/services/cloudsync/CloudSyncTabs.jsm
deleted file mode 100644
index 7debc2678..000000000
--- a/services/cloudsync/CloudSyncTabs.jsm
+++ /dev/null
@@ -1,318 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Tabs"];
-
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/CloudSyncEventSource.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/observers.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "Session", "@mozilla.org/browser/sessionstore;1", "nsISessionStore");
-
-const DATA_VERSION = 1;
-
-var ClientRecord = function (params) {
- this.id = params.id;
- this.name = params.name || "?";
- this.tabs = new Set();
-}
-
-ClientRecord.prototype = {
- version: DATA_VERSION,
-
- update: function (params) {
- if (this.id !== params.id) {
- throw new Error("expected " + this.id + " to equal " + params.id);
- }
-
- this.name = params.name;
- }
-};
-
-var TabRecord = function (params) {
- this.url = params.url || "";
- this.update(params);
-};
-
-TabRecord.prototype = {
- version: DATA_VERSION,
-
- update: function (params) {
- if (this.url && this.url !== params.url) {
- throw new Error("expected " + this.url + " to equal " + params.url);
- }
-
- if (params.lastUsed && params.lastUsed < this.lastUsed) {
- return;
- }
-
- this.title = params.title || "";
- this.icon = params.icon || "";
- this.lastUsed = params.lastUsed || 0;
- },
-};
-
-var TabCache = function () {
- this.tabs = new Map();
- this.clients = new Map();
-};
-
-TabCache.prototype = {
- merge: function (client, tabs) {
- if (!client || !client.id) {
- return;
- }
-
- if (!tabs) {
- return;
- }
-
- let cRecord;
- if (this.clients.has(client.id)) {
- try {
- cRecord = this.clients.get(client.id);
- } catch (e) {
- throw new Error("unable to update client: " + e);
- }
- } else {
- cRecord = new ClientRecord(client);
- this.clients.set(cRecord.id, cRecord);
- }
-
- for (let tab of tabs) {
- if (!tab || 'object' !== typeof(tab)) {
- continue;
- }
-
- let tRecord;
- if (this.tabs.has(tab.url)) {
- tRecord = this.tabs.get(tab.url);
- try {
- tRecord.update(tab);
- } catch (e) {
- throw new Error("unable to update tab: " + e);
- }
- } else {
- tRecord = new TabRecord(tab);
- this.tabs.set(tRecord.url, tRecord);
- }
-
- if (tab.deleted) {
- cRecord.tabs.delete(tRecord);
- } else {
- cRecord.tabs.add(tRecord);
- }
- }
- },
-
- clear: function (client) {
- if (client) {
- this.clients.delete(client.id);
- } else {
- this.clients = new Map();
- this.tabs = new Map();
- }
- },
-
- get: function () {
- let results = [];
- for (let client of this.clients.values()) {
- results.push(client);
- }
- return results;
- },
-
- isEmpty: function () {
- return 0 == this.clients.size;
- },
-
-};
-
-this.Tabs = function () {
- let suspended = true;
-
- let topics = [
- "pageshow",
- "TabOpen",
- "TabClose",
- "TabSelect",
- ];
-
- let update = function (event) {
- if (event.originalTarget.linkedBrowser) {
- if (PrivateBrowsingUtils.isBrowserPrivate(event.originalTarget.linkedBrowser) &&
- !PrivateBrowsingUtils.permanentPrivateBrowsing) {
- return;
- }
- }
-
- eventSource.emit("change");
- };
-
- let registerListenersForWindow = function (window) {
- for (let topic of topics) {
- window.addEventListener(topic, update, false);
- }
- window.addEventListener("unload", unregisterListeners, false);
- };
-
- let unregisterListenersForWindow = function (window) {
- window.removeEventListener("unload", unregisterListeners, false);
- for (let topic of topics) {
- window.removeEventListener(topic, update, false);
- }
- };
-
- let unregisterListeners = function (event) {
- unregisterListenersForWindow(event.target);
- };
-
- let observer = {
- observe: function (subject, topic, data) {
- switch (topic) {
- case "domwindowopened":
- let onLoad = () => {
- subject.removeEventListener("load", onLoad, false);
- // Only register after the window is done loading to avoid unloads.
- registerListenersForWindow(subject);
- };
-
- // Add tab listeners now that a window has opened.
- subject.addEventListener("load", onLoad, false);
- break;
- }
- }
- };
-
- let resume = function () {
- if (suspended) {
- Observers.add("domwindowopened", observer);
- let wins = Services.wm.getEnumerator("navigator:browser");
- while (wins.hasMoreElements()) {
- registerListenersForWindow(wins.getNext());
- }
- }
- }.bind(this);
-
- let suspend = function () {
- if (!suspended) {
- Observers.remove("domwindowopened", observer);
- let wins = Services.wm.getEnumerator("navigator:browser");
- while (wins.hasMoreElements()) {
- unregisterListenersForWindow(wins.getNext());
- }
- }
- }.bind(this);
-
- let eventTypes = [
- "change",
- ];
-
- let eventSource = new EventSource(eventTypes, suspend, resume);
-
- let tabCache = new TabCache();
-
- let getWindowEnumerator = function () {
- return Services.wm.getEnumerator("navigator:browser");
- };
-
- let shouldSkipWindow = function (win) {
- return win.closed ||
- PrivateBrowsingUtils.isWindowPrivate(win);
- };
-
- let getTabState = function (tab) {
- return JSON.parse(Session.getTabState(tab));
- };
-
- let getLocalTabs = function (filter) {
- let deferred = Promise.defer();
-
- filter = (undefined === filter) ? true : filter;
- let filteredUrls = new RegExp("^(about:.*|chrome://weave/.*|wyciwyg:.*|file:.*)$"); // FIXME: should be a pref (B#1044304)
-
- let allTabs = [];
-
- let currentState = JSON.parse(Session.getBrowserState());
- currentState.windows.forEach(function (window) {
- if (window.isPrivate) {
- return;
- }
- window.tabs.forEach(function (tab) {
- if (!tab.entries.length) {
- return;
- }
-
- // Get only the latest entry
- // FIXME: support full history (B#1044306)
- let entry = tab.entries[tab.index - 1];
-
- if (!entry.url || filter && filteredUrls.test(entry.url)) {
- return;
- }
-
- allTabs.push(new TabRecord({
- title: entry.title,
- url: entry.url,
- icon: tab.attributes && tab.attributes.image || "",
- lastUsed: tab.lastAccessed,
- }));
- });
- });
-
- deferred.resolve(allTabs);
-
- return deferred.promise;
- };
-
- let mergeRemoteTabs = function (client, tabs) {
- let deferred = Promise.defer();
-
- deferred.resolve(tabCache.merge(client, tabs));
- Observers.notify("cloudsync:tabs:update");
-
- return deferred.promise;
- };
-
- let clearRemoteTabs = function (client) {
- let deferred = Promise.defer();
-
- deferred.resolve(tabCache.clear(client));
- Observers.notify("cloudsync:tabs:update");
-
- return deferred.promise;
- };
-
- let getRemoteTabs = function () {
- let deferred = Promise.defer();
-
- deferred.resolve(tabCache.get());
-
- return deferred.promise;
- };
-
- let hasRemoteTabs = function () {
- return !tabCache.isEmpty();
- };
-
- /* PUBLIC API */
- this.addEventListener = eventSource.addEventListener;
- this.removeEventListener = eventSource.removeEventListener;
- this.getLocalTabs = getLocalTabs.bind(this);
- this.mergeRemoteTabs = mergeRemoteTabs.bind(this);
- this.clearRemoteTabs = clearRemoteTabs.bind(this);
- this.getRemoteTabs = getRemoteTabs.bind(this);
- this.hasRemoteTabs = hasRemoteTabs.bind(this);
-};
-
-Tabs.prototype = {
-};
-this.Tabs = Tabs;
diff --git a/services/cloudsync/docs/api.md b/services/cloudsync/docs/api.md
deleted file mode 100644
index bca3193a4..000000000
--- a/services/cloudsync/docs/api.md
+++ /dev/null
@@ -1,234 +0,0 @@
-### Importing the JS module
-
-````
-Cu.import("resource://gre/modules/CloudSync.jsm");
-
-let cloudSync = CloudSync();
-console.log(cloudSync); // Module is imported
-````
-
-### cloudSync.local
-
-#### id
-
-Local device ID. Is unique.
-
-````
-let localId = cloudSync.local.id;
-````
-
-#### name
-
-Local device name.
-
-````
-let localName = cloudSync.local.name;
-````
-
-### CloudSync.tabs
-
-#### addEventListener(type, callback)
-
-Add an event handler for Tabs events. Valid type is `change`. The callback receives no arguments.
-
-````
-function handleTabChange() {
- // Tabs have changed.
-}
-
-cloudSync.tabs.addEventListener("change", handleTabChange);
-````
-
-Change events are emitted when a tab is opened or closed, when a tab is selected, or when the page changes for an open tab.
-
-#### removeEventListener(type, callback)
-
-Remove an event handler. Pass the type and function that were passed to addEventListener.
-
-````
-cloudSync.tabs.removeEventListener("change", handleTabChange);
-````
-
-#### mergeRemoteTabs(client, tabs)
-
-Merge remote tabs from upstream by updating existing items, adding new tabs, and deleting existing tabs. Accepts a client and a list of tabs. Returns a promise.
-
-````
-let remoteClient = {
- id: "fawe78",
- name: "My Firefox client",
-};
-
-let remoteTabs = [
- {title: "Google",
- url: "https://www.google.com",
- icon: "https://www.google.com/favicon.ico",
- lastUsed: 1400799296192},
- {title: "Reddit",
- url: "http://www.reddit.com",
- icon: "http://www.reddit.com/favicon.ico",
- lastUsed: 1400799296192
- deleted: true},
-];
-
-cloudSync.tabs.mergeRemoteTabs(client, tabs).then(
- function() {
- console.log("merge complete");
- }
-);
-````
-
-#### getLocalTabs()
-
-Returns a promise. Passes a list of local tabs when complete.
-
-````
-cloudSync.tabs.getLocalTabs().then(
- function(tabs) {
- console.log(JSON.stringify(tabs));
- }
-);
-````
-
-#### clearRemoteTabs(client)
-
-Clears all tabs for a remote client.
-
-````
-let remoteClient = {
- id: "fawe78",
- name: "My Firefox client",
-};
-
-cloudSync.tabs.clearRemoteTabs(client);
-````
-
-### cloudSync.bookmarks
-
-#### getRootFolder(name)
-
-Gets the named root folder, creating it if it doesn't exist. The root folder object has a number of methods (see the next section for details).
-
-````
-cloudSync.bookmarks.getRootFolder("My Bookmarks").then(
- function(rootFolder) {
- console.log(rootFolder);
- }
-);
-````
-
-### cloudSync.bookmarks.RootFolder
-
-This is a root folder object for bookmarks, created by `cloudSync.bookmarks.getRootFolder`.
-
-#### BOOKMARK
-
-Bookmark type. Used in results objects.
-
-````
-let bookmarkType = rootFolder.BOOKMARK;
-````
-
-#### FOLDER
-
-Folder type. Used in results objects.
-
-````
-let folderType = rootFolder.FOLDER;
-````
-
-#### SEPARATOR
-
-Separator type. Used in results objects.
-
-````
-let separatorType = rootFolder.SEPARATOR;
-````
-
-#### addEventListener(type, callback)
-
-Add an event handler for Tabs events. Valid types are `add, remove, change, move`. The callback receives an ID corresponding to the target item.
-
-````
-function handleBoookmarkEvent(id) {
- console.log("event for id:", id);
-}
-
-rootFolder.addEventListener("add", handleBookmarkEvent);
-rootFolder.addEventListener("remove", handleBookmarkEvent);
-rootFolder.addEventListener("change", handleBookmarkEvent);
-rootFolder.addEventListener("move", handleBookmarkEvent);
-````
-
-#### removeEventListener(type, callback)
-
-Remove an event handler. Pass the type and function that were passed to addEventListener.
-
-````
-rootFolder.removeEventListener("add", handleBookmarkEvent);
-rootFolder.removeEventListener("remove", handleBookmarkEvent);
-rootFolder.removeEventListener("change", handleBookmarkEvent);
-rootFolder.removeEventListener("move", handleBookmarkEvent);
-````
-
-#### getLocalItems()
-
-Callback receives a list of items on the local client. Results have the following form:
-
-````
-{
- id: "faw8e7f", // item guid
- parent: "f7sydf87y", // parent folder guid
- dateAdded: 1400799296192, // timestamp
- lastModified: 1400799296192, // timestamp
- uri: "https://www.google.ca", // null for FOLDER and SEPARATOR
- title: "Google"
- type: rootFolder.BOOKMARK, // should be one of rootFolder.{BOOKMARK, FOLDER, SEPARATOR},
- index: 0 // must be unique among folder items
-}
-````
-
-````
-rootFolder.getLocalItems().then(
- function(items) {
- console.log(JSON.stringify(items));
- }
-);
-````
-
-#### getLocalItemsById([...])
-
-Callback receives a list of items, specified by ID, on the local client. Results have the same form as `getLocalItems()` above.
-
-````
-rootFolder.getLocalItemsById(["213r23f", "f22fy3f3"]).then(
- function(items) {
- console.log(JSON.stringify(items));
- }
-);
-````
-
-#### mergeRemoteItems([...])
-
-Merge remote items from upstream by updating existing items, adding new items, and deleting existing items. Folders are created first so that subsequent operations will succeed. Items have the same form as `getLocalItems()` above. Items that do not have an ID will have an ID generated for them. The results structure will contain this generated ID.
-
-````
-rootFolder.mergeRemoteItems([
- {
- id: 'f2398f23',
- type: rootFolder.FOLDER,
- title: 'Folder 1',
- parent: '9f8237f928'
- },
- {
- id: '9f8237f928',
- type: rootFolder.FOLDER,
- title: 'Folder 0',
- }
- ]).then(
- function(items) {
- console.log(items); // any generated IDs are filled in now
- console.log("merge completed");
- }
-);
-```` \ No newline at end of file
diff --git a/services/cloudsync/docs/architecture.rst b/services/cloudsync/docs/architecture.rst
deleted file mode 100644
index a7a8aa7ba..000000000
--- a/services/cloudsync/docs/architecture.rst
+++ /dev/null
@@ -1,54 +0,0 @@
-.. _cloudsync_architecture:
-
-============
-Architecture
-============
-
-CloudSync offers functionality similar to Firefox Sync for data sources. Third-party addons
-(sync adapters) consume local data, send and receive updates from the cloud, and merge remote data.
-
-
-Files
-=====
-
-CloudSync.jsm
- Main module; Includes other modules and exposes them.
-
-CloudSyncAdapters.jsm
- Provides an API for addons to register themselves. Will be used to
- list available adapters and to notify adapters when sync operations
- are requested manually by the user.
-
-CloudSyncBookmarks.jsm
- Provides operations for interacting with bookmarks.
-
-CloudSyncBookmarksFolderCache.jsm
- Implements a cache used to store folder hierarchy for filtering bookmark events.
-
-CloudSyncEventSource.jsm
- Implements an event emitter. Used to provide addEventListener and removeEventListener
- for tabs and bookmarks.
-
-CloudSyncLocal.jsm
- Provides information about the local device, such as name and a unique id.
-
-CloudSyncPlacesWrapper.jsm
- Wraps parts of the Places API in promises. Some methods are implemented to be asynchronous
- where they are not in the places API.
-
-CloudSyncTabs.jsm
- Provides operations for fetching local tabs and for populating the about:sync-tabs page.
-
-
-Data Sources
-============
-
-CloudSync provides data for tabs and bookmarks. For tabs, local open pages can be enumerated and
-remote tabs can be merged for displaying in about:sync-tabs. For bookmarks, updates are tracked
-for a named folder (given by each adapter) and handled by callbacks registered using addEventListener,
-and remote changes can be merged into the local database.
-
-Versioning
-==========
-
-The API carries an integer version number (clouySync.version). Data records are versioned separately and individually.
diff --git a/services/cloudsync/docs/dataformat.rst b/services/cloudsync/docs/dataformat.rst
deleted file mode 100644
index 916581459..000000000
--- a/services/cloudsync/docs/dataformat.rst
+++ /dev/null
@@ -1,77 +0,0 @@
-.. _cloudsync_dataformat:
-
-===========
-Data Format
-===========
-
-All fields are required unless noted otherwise.
-
-Bookmarks
-=========
-
-Record
-------
-
-type:
- record type; one of CloudSync.bookmarks.{BOOKMARK, FOLDER, SEPARATOR, QUERY, LIVEMARK}
-
-id:
- GUID for this bookmark item
-
-parent:
- id of parent folder
-
-index:
- item index in parent folder; should be unique and contiguous, or they will be adjusted internally
-
-title:
- bookmark or folder title; not meaningful for separators
-
-dateAdded:
- timestamp (in milliseconds) for item added
-
-lastModified:
- timestamp (in milliseconds) for last modification
-
-uri:
- bookmark URI; not meaningful for folders or separators
-
-version:
- data layout version
-
-Tabs
-====
-
-ClientRecord
-------------
-
-id:
- GUID for this client
-
-name:
- name for this client; not guaranteed to be unique
-
-tabs:
- list of tabs open on this client; see TabRecord
-
-version:
- data layout version
-
-
-TabRecord
----------
-
-title:
- name for this tab
-
-url:
- URL for this tab; only one tab for each URL is stored
-
-icon:
- favicon URL for this tab; optional
-
-lastUsed:
- timetamp (in milliseconds) for last use
-
-version:
- data layout version
diff --git a/services/cloudsync/docs/example.rst b/services/cloudsync/docs/example.rst
deleted file mode 100644
index 33d0f0531..000000000
--- a/services/cloudsync/docs/example.rst
+++ /dev/null
@@ -1,132 +0,0 @@
-.. _cloudsync_example:
-
-=======
-Example
-=======
-
-.. code-block:: javascript
-
- Cu.import("resource://gre/modules/CloudSync.jsm");
-
- let HelloWorld = {
- onLoad: function() {
- let cloudSync = CloudSync();
- console.log("CLOUDSYNC -- hello world", cloudSync.local.id, cloudSync.local.name, cloudSync.adapters);
- cloudSync.adapters.register('helloworld', {});
- console.log("CLOUDSYNC -- " + JSON.stringify(cloudSync.adapters.getAdapterNames()));
-
-
- cloudSync.tabs.addEventListener("change", function() {
- console.log("tab change");
- cloudSync.tabs.getLocalTabs().then(
- function(records) {
- console.log(JSON.stringify(records));
- }
- );
- });
-
- cloudSync.tabs.getLocalTabs().then(
- function(records) {
- console.log(JSON.stringify(records));
- }
- );
-
- let remoteClient = {
- id: "001",
- name: "FakeClient",
- };
- let remoteTabs1 = [
- {url:"https://www.google.ca",title:"Google",icon:"https://www.google.ca/favicon.ico",lastUsed:Date.now()},
- ];
- let remoteTabs2 = [
- {url:"https://www.google.ca",title:"Google Canada",icon:"https://www.google.ca/favicon.ico",lastUsed:Date.now()},
- {url:"http://www.reddit.com",title:"Reddit",icon:"http://www.reddit.com/favicon.ico",lastUsed:Date.now()},
- ];
- cloudSync.tabs.mergeRemoteTabs(remoteClient, remoteTabs1).then(
- function() {
- return cloudSync.tabs.mergeRemoteTabs(remoteClient, remoteTabs2);
- }
- ).then(
- function() {
- return cloudSync.tabs.getRemoteTabs();
- }
- ).then(
- function(tabs) {
- console.log("remote tabs:", tabs);
- }
- );
-
- cloudSync.bookmarks.getRootFolder("Hello World").then(
- function(rootFolder) {
- console.log(rootFolder.name, rootFolder.id);
- rootFolder.addEventListener("add", function(guid) {
- console.log("CLOUDSYNC -- bookmark item added: " + guid);
- rootFolder.getLocalItemsById([guid]).then(
- function(items) {
- console.log("CLOUDSYNC -- items: " + JSON.stringify(items));
- }
- );
- });
- rootFolder.addEventListener("remove", function(guid) {
- console.log("CLOUDSYNC -- bookmark item removed: " + guid);
- rootFolder.getLocalItemsById([guid]).then(
- function(items) {
- console.log("CLOUDSYNC -- items: " + JSON.stringify(items));
- }
- );
- });
- rootFolder.addEventListener("change", function(guid) {
- console.log("CLOUDSYNC -- bookmark item changed: " + guid);
- rootFolder.getLocalItemsById([guid]).then(
- function(items) {
- console.log("CLOUDSYNC -- items: " + JSON.stringify(items));
- }
- );
- });
- rootFolder.addEventListener("move", function(guid) {
- console.log("CLOUDSYNC -- bookmark item moved: " + guid);
- rootFolder.getLocalItemsById([guid]).then(
- function(items) {
- console.log("CLOUDSYNC -- items: " + JSON.stringify(items));
- }
- );
- });
-
- function logLocalItems() {
- return rootFolder.getLocalItems().then(
- function(items) {
- console.log("CLOUDSYNC -- local items: " + JSON.stringify(items));
- }
- );
- }
-
- let items = [
- {"id":"9fdoci2KOME6","type":rootFolder.FOLDER,"parent":rootFolder.id,"title":"My Bookmarks 1"},
- {"id":"1fdoci2KOME5","type":rootFolder.FOLDER,"parent":rootFolder.id,"title":"My Bookmarks 2"},
- {"id":"G_UL4ZhOyX8m","type":rootFolder.BOOKMARK,"parent":"1fdoci2KOME5","title":"reddit: the front page of the internet","uri":"http://www.reddit.com/"},
- ];
- function mergeSomeItems() {
- return rootFolder.mergeRemoteItems(items);
- }
-
- logLocalItems().then(
- mergeSomeItems
- ).then(
- function(processedItems) {
- console.log("!!!", processedItems);
- console.log("merge complete");
- },
- function(error) {
- console.log("merge failed:", error);
- }
- ).then(
- logLocalItems
- );
- }
- );
-
-
- },
- };
-
- window.addEventListener("load", function(e) { HelloWorld.onLoad(e); }, false);
diff --git a/services/cloudsync/docs/index.rst b/services/cloudsync/docs/index.rst
deleted file mode 100644
index d7951776d..000000000
--- a/services/cloudsync/docs/index.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-.. _cloudsync:
-
-=====================
-CloudSync
-=====================
-
-CloudSync is a service that provides access to tabs and bookmarks data
-for third-party sync addons. Addons can read local bookmarks and tabs.
-Bookmarks and tab data can be merged from remote devices.
-
-Addons are responsible for maintaining an upstream representation, as
-well as sending and receiving data over the network.
-
-.. toctree::
- :maxdepth: 1
-
- architecture
- dataformat
- example
diff --git a/services/cloudsync/moz.build b/services/cloudsync/moz.build
deleted file mode 100644
index 93197c9fe..000000000
--- a/services/cloudsync/moz.build
+++ /dev/null
@@ -1,21 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# 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/.
-
-SPHINX_TREES['cloudsync'] = 'docs'
-
-EXTRA_JS_MODULES += [
- 'CloudSync.jsm',
- 'CloudSyncAdapters.jsm',
- 'CloudSyncBookmarks.jsm',
- 'CloudSyncBookmarksFolderCache.jsm',
- 'CloudSyncEventSource.jsm',
- 'CloudSyncLocal.jsm',
- 'CloudSyncPlacesWrapper.jsm',
- 'CloudSyncTabs.jsm',
-]
-
-XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
-BROWSER_CHROME_MANIFESTS += ['tests/mochitest/browser.ini']
diff --git a/services/cloudsync/tests/mochitest/browser.ini b/services/cloudsync/tests/mochitest/browser.ini
deleted file mode 100644
index c9eddbf71..000000000
--- a/services/cloudsync/tests/mochitest/browser.ini
+++ /dev/null
@@ -1,5 +0,0 @@
-[DEFAULT]
-support-files=
- other_window.html
-
-[browser_tabEvents.js] \ No newline at end of file
diff --git a/services/cloudsync/tests/mochitest/browser_tabEvents.js b/services/cloudsync/tests/mochitest/browser_tabEvents.js
deleted file mode 100644
index 9d80090a0..000000000
--- a/services/cloudsync/tests/mochitest/browser_tabEvents.js
+++ /dev/null
@@ -1,79 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-function test() {
-
- let local = {};
-
- Components.utils.import("resource://gre/modules/CloudSync.jsm", local);
- Components.utils.import("resource:///modules/sessionstore/TabStateFlusher.jsm", local);
-
- let cloudSync = local.CloudSync();
- let opentabs = [];
-
- waitForExplicitFinish();
-
- let testURL = "chrome://mochitests/content/browser/services/cloudsync/tests/mochitest/other_window.html";
- let expected = [
- testURL,
- testURL+"?x=1",
- testURL+"?x=%20a",
- // testURL+"?x=Ã¥",
- ];
-
- let nevents = 0;
- let nflushed = 0;
- function handleTabChangeEvent () {
- cloudSync.tabs.removeEventListener("change", handleTabChangeEvent);
- ++ nevents;
- info("tab change event " + nevents);
- next();
- }
-
- function getLocalTabs() {
- cloudSync.tabs.getLocalTabs().then(
- function (tabs) {
- for (let tab of tabs) {
- ok(expected.indexOf(tab.url) >= 0, "found an expected tab");
- }
-
- is(tabs.length, expected.length, "found the right number of tabs");
-
- opentabs.forEach(function (tab) {
- gBrowser.removeTab(tab);
- });
-
- is(nevents, 1, "expected number of change events");
-
- finish();
- }
- )
- }
-
- cloudSync.tabs.addEventListener("change", handleTabChangeEvent);
-
- expected.forEach(function(url) {
- let tab = gBrowser.addTab(url);
-
- function flush() {
- tab.linkedBrowser.removeEventListener("load", flush, true);
- local.TabStateFlusher.flush(tab.linkedBrowser).then(() => {
- ++ nflushed;
- info("flushed " + nflushed);
- next();
- });
- }
-
- tab.linkedBrowser.addEventListener("load", flush, true);
-
- opentabs.push(tab);
- });
-
- function next() {
- if (nevents == 1 && nflushed == expected.length) {
- getLocalTabs();
- }
- }
-
-}
diff --git a/services/cloudsync/tests/mochitest/other_window.html b/services/cloudsync/tests/mochitest/other_window.html
deleted file mode 100644
index a9ded2bd6..000000000
--- a/services/cloudsync/tests/mochitest/other_window.html
+++ /dev/null
@@ -1,7 +0,0 @@
-<!--
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE HTML>
-<html>
-</html>
diff --git a/services/cloudsync/tests/xpcshell/head.js b/services/cloudsync/tests/xpcshell/head.js
deleted file mode 100644
index bd517cafa..000000000
--- a/services/cloudsync/tests/xpcshell/head.js
+++ /dev/null
@@ -1,10 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
-
-"use strict";
-
-(function initCloudSyncTestingInfrastructure () {
- do_get_profile();
-}).call(this);
diff --git a/services/cloudsync/tests/xpcshell/test_bookmarks.js b/services/cloudsync/tests/xpcshell/test_bookmarks.js
deleted file mode 100644
index d4e1d2b75..000000000
--- a/services/cloudsync/tests/xpcshell/test_bookmarks.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/CloudSync.jsm");
-
-function run_test () {
- run_next_test();
-}
-
-function cleanup () {
-
-}
-
-add_task(function* test_merge_bookmarks_flat () {
- try {
- let rootFolder = yield CloudSync().bookmarks.getRootFolder("TEST");
- ok(rootFolder.id, "root folder id is ok");
-
- let items = [
- {"id":"G_UL4ZhOyX8m","type":rootFolder.BOOKMARK,"title":"reddit: the front page of the internet 1","uri":"http://www.reddit.com",index:2},
- {"id":"G_UL4ZhOyX8n","type":rootFolder.BOOKMARK,"title":"reddit: the front page of the internet 2","uri":"http://www.reddit.com?1",index:1},
- ];
- yield rootFolder.mergeRemoteItems(items);
-
- let localItems = yield rootFolder.getLocalItems();
- equal(Object.keys(localItems).length, items.length, "found merged items");
- } finally {
- yield CloudSync().bookmarks.deleteRootFolder("TEST");
- }
-});
-
-add_task(function* test_merge_bookmarks_in_folders () {
- try {
- let rootFolder = yield CloudSync().bookmarks.getRootFolder("TEST");
- ok(rootFolder.id, "root folder id is ok");
-
- let items = [
- {"id":"G_UL4ZhOyX8m","type":rootFolder.BOOKMARK,"title":"reddit: the front page of the internet 1","uri":"http://www.reddit.com",index:2},
- {"id":"G_UL4ZhOyX8n","type":rootFolder.BOOKMARK,parent:"G_UL4ZhOyX8x","title":"reddit: the front page of the internet 2","uri":"http://www.reddit.com/?a=å%20ä%20ö",index:1},
- {"id":"G_UL4ZhOyX8x","type":rootFolder.FOLDER},
- ];
- yield rootFolder.mergeRemoteItems(items);
-
- let localItems = yield rootFolder.getLocalItems();
- equal(localItems.length, items.length, "found merged items");
-
- localItems.forEach(function(item) {
- ok(item.id == "G_UL4ZhOyX8m" ||
- item.id == "G_UL4ZhOyX8n" ||
- item.id == "G_UL4ZhOyX8x");
- if (item.id == "G_UL4ZhOyX8n") {
- equal(item.parent, "G_UL4ZhOyX8x")
- } else {
- equal(item.parent, rootFolder.id);
- }
- });
-
- let folder = (yield rootFolder.getLocalItemsById(["G_UL4ZhOyX8x"]))[0];
- equal(folder.id, "G_UL4ZhOyX8x");
- equal(folder.type, rootFolder.FOLDER);
-
- let bookmark = (yield rootFolder.getLocalItemsById(["G_UL4ZhOyX8n"]))[0];
- equal(bookmark.id, "G_UL4ZhOyX8n");
- equal(bookmark.parent, "G_UL4ZhOyX8x");
- equal(bookmark.title, "reddit: the front page of the internet 2");
- equal(bookmark.index, 0);
- equal(bookmark.uri, "http://www.reddit.com/?a=%C3%A5%20%C3%A4%20%C3%B6");
- } finally {
- yield CloudSync().bookmarks.deleteRootFolder("TEST");
- }
-}); \ No newline at end of file
diff --git a/services/cloudsync/tests/xpcshell/test_lazyload.js b/services/cloudsync/tests/xpcshell/test_lazyload.js
deleted file mode 100644
index 5928875d5..000000000
--- a/services/cloudsync/tests/xpcshell/test_lazyload.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/CloudSync.jsm");
-
-function run_test() {
- run_next_test();
-}
-
-add_task(function test_lazyload() {
- ok(!CloudSync.ready, "CloudSync.ready is false before CloudSync() invoked");
- let cs1 = CloudSync();
- ok(CloudSync.ready, "CloudSync.ready is true after CloudSync() invoked");
- let cs2 = CloudSync();
- ok(cs1 === cs2, "CloudSync() returns the same instance on multiple invocations");
-});
diff --git a/services/cloudsync/tests/xpcshell/test_module.js b/services/cloudsync/tests/xpcshell/test_module.js
deleted file mode 100644
index 6d31345ed..000000000
--- a/services/cloudsync/tests/xpcshell/test_module.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/CloudSync.jsm");
-
-function run_test () {
- run_next_test();
-}
-
-add_task(function test_module_load () {
- ok(CloudSync);
- let cloudSync = CloudSync();
- ok(cloudSync.adapters);
- ok(cloudSync.bookmarks);
- ok(cloudSync.local);
- ok(cloudSync.tabs);
-});
diff --git a/services/cloudsync/tests/xpcshell/test_tabs.js b/services/cloudsync/tests/xpcshell/test_tabs.js
deleted file mode 100644
index 50f7a73de..000000000
--- a/services/cloudsync/tests/xpcshell/test_tabs.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/CloudSync.jsm");
-
-function run_test () {
- run_next_test();
-}
-
-add_task(function* test_get_remote_tabs () {
- let cloudSync = CloudSync();
- let clients = yield cloudSync.tabs.getRemoteTabs();
- equal(clients.length, 0);
-
- yield cloudSync.tabs.mergeRemoteTabs({
- id: "001",
- name: "FakeClient",
- },[
- {url:"https://www.google.ca?a=å%20ä%20ö",title:"Google Canada",icon:"https://www.google.ca/favicon.ico",lastUsed:0},
- {url:"http://www.reddit.com",title:"Reddit",icon:"http://www.reddit.com/favicon.ico",lastUsed:1},
- ]);
- ok(cloudSync.tabs.hasRemoteTabs());
-
- clients = yield cloudSync.tabs.getRemoteTabs();
- equal(clients.length, 1);
- equal(clients[0].tabs.size, 2);
-});
diff --git a/services/cloudsync/tests/xpcshell/xpcshell.ini b/services/cloudsync/tests/xpcshell/xpcshell.ini
deleted file mode 100644
index 08d2eff3a..000000000
--- a/services/cloudsync/tests/xpcshell/xpcshell.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[DEFAULT]
-head = head.js
-tail =
-firefox-appdir = browser
-skip-if = toolkit == 'android'
-
-[test_module.js]
-[test_tabs.js]
-[test_bookmarks.js]
-[test_lazyload.js]
diff --git a/services/fxaccounts/Credentials.jsm b/services/fxaccounts/Credentials.jsm
deleted file mode 100644
index 56e8b3db7..000000000
--- a/services/fxaccounts/Credentials.jsm
+++ /dev/null
@@ -1,136 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-/**
- * This module implements client-side key stretching for use in Firefox
- * Accounts account creation and login.
- *
- * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
- */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["Credentials"];
-
-const {utils: Cu, interfaces: Ci} = Components;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://services-common/utils.js");
-
-const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/";
-const PBKDF2_ROUNDS = 1000;
-const STRETCHED_PW_LENGTH_BYTES = 32;
-const HKDF_SALT = CommonUtils.hexToBytes("00");
-const HKDF_LENGTH = 32;
-const HMAC_ALGORITHM = Ci.nsICryptoHMAC.SHA256;
-const HMAC_LENGTH = 32;
-
-// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
-// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
-// default.
-const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
-try {
- this.LOG_LEVEL =
- Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
- && Services.prefs.getCharPref(PREF_LOG_LEVEL);
-} catch (e) {
- this.LOG_LEVEL = Log.Level.Error;
-}
-
-var log = Log.repository.getLogger("Identity.FxAccounts");
-log.level = LOG_LEVEL;
-log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
-
-this.Credentials = Object.freeze({
- /**
- * Make constants accessible to tests
- */
- constants: {
- PROTOCOL_VERSION: PROTOCOL_VERSION,
- PBKDF2_ROUNDS: PBKDF2_ROUNDS,
- STRETCHED_PW_LENGTH_BYTES: STRETCHED_PW_LENGTH_BYTES,
- HKDF_SALT: HKDF_SALT,
- HKDF_LENGTH: HKDF_LENGTH,
- HMAC_ALGORITHM: HMAC_ALGORITHM,
- HMAC_LENGTH: HMAC_LENGTH,
- },
-
- /**
- * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
- *
- * keyWord derivation for use as a salt.
- *
- *
- * @param {String} context String for use in generating salt
- *
- * @return {bitArray} the salt
- *
- * Note that PROTOCOL_VERSION does not refer in any way to the version of the
- * Firefox Accounts API.
- */
- keyWord: function(context) {
- return CommonUtils.stringToBytes(PROTOCOL_VERSION + context);
- },
-
- /**
- * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
- *
- * keyWord extended with a name and an email.
- *
- * @param {String} name The name of the salt
- * @param {String} email The email of the user.
- *
- * @return {bitArray} the salt combination with the namespace
- *
- * Note that PROTOCOL_VERSION does not refer in any way to the version of the
- * Firefox Accounts API.
- */
- keyWordExtended: function(name, email) {
- return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ':' + email);
- },
-
- setup: function(emailInput, passwordInput, options={}) {
- let deferred = Promise.defer();
- log.debug("setup credentials for " + emailInput);
-
- let hkdfSalt = options.hkdfSalt || HKDF_SALT;
- let hkdfLength = options.hkdfLength || HKDF_LENGTH;
- let hmacLength = options.hmacLength || HMAC_LENGTH;
- let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
- let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
- let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
-
- let result = {};
-
- let password = CommonUtils.encodeUTF8(passwordInput);
- let salt = this.keyWordExtended("quickStretch", emailInput);
-
- let runnable = () => {
- let start = Date.now();
- let quickStretchedPW = CryptoUtils.pbkdf2Generate(
- password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
-
- result.quickStretchedPW = quickStretchedPW;
-
- result.authPW =
- CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("authPW"), hkdfLength);
-
- result.unwrapBKey =
- CryptoUtils.hkdf(quickStretchedPW, hkdfSalt, this.keyWord("unwrapBkey"), hkdfLength);
-
- log.debug("Credentials set up after " + (Date.now() - start) + " ms");
- deferred.resolve(result);
- }
-
- Services.tm.currentThread.dispatch(runnable,
- Ci.nsIThread.DISPATCH_NORMAL);
- log.debug("Dispatched thread for credentials setup crypto work");
-
- return deferred.promise;
- }
-});
-
diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm
deleted file mode 100644
index 0e072ee74..000000000
--- a/services/fxaccounts/FxAccounts.jsm
+++ /dev/null
@@ -1,1725 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-"use strict";
-
-this.EXPORTED_SYMBOLS = ["fxAccounts", "FxAccounts"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-common/rest.js");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Timer.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsClient",
- "resource://gre/modules/FxAccountsClient.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsConfig",
- "resource://gre/modules/FxAccountsConfig.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
- "resource://gre/modules/identity/jwcrypto.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsOAuthGrantClient",
- "resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-
-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",
- "checkVerificationStatus",
- "getAccountsClient",
- "getAssertion",
- "getDeviceId",
- "getKeys",
- "getOAuthToken",
- "getSignedInUser",
- "getSignedInUserProfile",
- "handleDeviceDisconnection",
- "invalidateCertificate",
- "loadAndPoll",
- "localtimeOffsetMsec",
- "notifyDevices",
- "now",
- "promiseAccountsChangeProfileURI",
- "promiseAccountsForceSigninURI",
- "promiseAccountsManageURI",
- "promiseAccountsSignUpURI",
- "promiseAccountsSignInURI",
- "removeCachedOAuthToken",
- "requiresHttps",
- "resendVerificationEmail",
- "resetCredentials",
- "sessionStatus",
- "setSignedInUser",
- "signOut",
- "updateDeviceRegistration",
- "updateUserAccountData",
- "whenVerified",
-];
-
-// An AccountState object holds all state related to one specific account.
-// Only one AccountState is ever "current" in the FxAccountsInternal object -
-// whenever a user logs out or logs in, the current AccountState is discarded,
-// making it impossible for the wrong state or state data to be accidentally
-// used.
-// In addition, it has some promise-related helpers to ensure that if an
-// attempt is made to resolve a promise on a "stale" state (eg, if an
-// operation starts, but a different user logs in before the operation
-// completes), the promise will be rejected.
-// It is intended to be used thusly:
-// somePromiseBasedFunction: function() {
-// let currentState = this.currentAccountState;
-// return someOtherPromiseFunction().then(
-// data => currentState.resolve(data)
-// );
-// }
-// If the state has changed between the function being called and the promise
-// being resolved, the .resolve() call will actually be rejected.
-var AccountState = this.AccountState = function(storageManager) {
- this.storageManager = storageManager;
- this.promiseInitialized = this.storageManager.getAccountData().then(data => {
- this.oauthTokens = data && data.oauthTokens ? data.oauthTokens : {};
- }).catch(err => {
- log.error("Failed to initialize the storage manager", err);
- // Things are going to fall apart, but not much we can do about it here.
- });
-};
-
-AccountState.prototype = {
- oauthTokens: null,
- whenVerifiedDeferred: null,
- whenKeysReadyDeferred: null,
-
- // If the storage manager has been nuked then we are no longer current.
- get isCurrent() {
- return this.storageManager != null;
- },
-
- abort() {
- if (this.whenVerifiedDeferred) {
- this.whenVerifiedDeferred.reject(
- new Error("Verification aborted; Another user signing in"));
- this.whenVerifiedDeferred = null;
- }
-
- if (this.whenKeysReadyDeferred) {
- this.whenKeysReadyDeferred.reject(
- new Error("Verification aborted; Another user signing in"));
- this.whenKeysReadyDeferred = null;
- }
-
- this.cert = null;
- this.keyPair = null;
- this.oauthTokens = null;
- // Avoid finalizing the storageManager multiple times (ie, .signOut()
- // followed by .abort())
- if (!this.storageManager) {
- return Promise.resolve();
- }
- let storageManager = this.storageManager;
- this.storageManager = null;
- return storageManager.finalize();
- },
-
- // Clobber all cached data and write that empty data to storage.
- signOut() {
- this.cert = null;
- this.keyPair = null;
- this.oauthTokens = null;
- let storageManager = this.storageManager;
- this.storageManager = null;
- return storageManager.deleteAccountData().then(() => {
- return storageManager.finalize();
- });
- },
-
- // Get user account data. Optionally specify explicit field names to fetch
- // (and note that if you require an in-memory field you *must* specify the
- // field name(s).)
- getUserAccountData(fieldNames = null) {
- if (!this.isCurrent) {
- return Promise.reject(new Error("Another user has signed in"));
- }
- return this.storageManager.getAccountData(fieldNames).then(result => {
- return this.resolve(result);
- });
- },
-
- updateUserAccountData(updatedFields) {
- if (!this.isCurrent) {
- return Promise.reject(new Error("Another user has signed in"));
- }
- return this.storageManager.updateAccountData(updatedFields);
- },
-
- resolve: function(result) {
- if (!this.isCurrent) {
- log.info("An accountState promise was resolved, but was actually rejected" +
- " due to a different user being signed in. Originally resolved" +
- " with", result);
- return Promise.reject(new Error("A different user signed in"));
- }
- return Promise.resolve(result);
- },
-
- reject: function(error) {
- // It could be argued that we should just let it reject with the original
- // error - but this runs the risk of the error being (eg) a 401, which
- // might cause the consumer to attempt some remediation and cause other
- // problems.
- if (!this.isCurrent) {
- log.info("An accountState promise was rejected, but we are ignoring that" +
- "reason and rejecting it due to a different user being signed in." +
- "Originally rejected with", error);
- return Promise.reject(new Error("A different user signed in"));
- }
- return Promise.reject(error);
- },
-
- // Abstractions for storage of cached tokens - these are all sync, and don't
- // handle revocation etc - it's just storage (and the storage itself is async,
- // but we don't return the storage promises, so it *looks* sync)
- // These functions are sync simply so we can handle "token races" - when there
- // are multiple in-flight requests for the same scope, we can detect this
- // and revoke the redundant token.
-
- // A preamble for the cache helpers...
- _cachePreamble() {
- if (!this.isCurrent) {
- throw new Error("Another user has signed in");
- }
- },
-
- // Set a cached token. |tokenData| must have a 'token' element, but may also
- // have additional fields (eg, it probably specifies the server to revoke
- // from). The 'get' functions below return the entire |tokenData| value.
- setCachedToken(scopeArray, tokenData) {
- this._cachePreamble();
- if (!tokenData.token) {
- throw new Error("No token");
- }
- let key = getScopeKey(scopeArray);
- this.oauthTokens[key] = tokenData;
- // And a background save...
- this._persistCachedTokens();
- },
-
- // Return data for a cached token or null (or throws on bad state etc)
- getCachedToken(scopeArray) {
- this._cachePreamble();
- let key = getScopeKey(scopeArray);
- let result = this.oauthTokens[key];
- if (result) {
- // later we might want to check an expiry date - but we currently
- // have no such concept, so just return it.
- log.trace("getCachedToken returning cached token");
- return result;
- }
- return null;
- },
-
- // Remove a cached token from the cache. Does *not* revoke it from anywhere.
- // Returns the entire token entry if found, null otherwise.
- removeCachedToken(token) {
- this._cachePreamble();
- let data = this.oauthTokens;
- for (let [key, tokenValue] of Object.entries(data)) {
- if (tokenValue.token == token) {
- delete data[key];
- // And a background save...
- this._persistCachedTokens();
- return tokenValue;
- }
- }
- return null;
- },
-
- // A hook-point for tests. Returns a promise that's ignored in most cases
- // (notable exceptions are tests and when we explicitly are saving the entire
- // set of user data.)
- _persistCachedTokens() {
- this._cachePreamble();
- return this.updateUserAccountData({ oauthTokens: this.oauthTokens }).catch(err => {
- log.error("Failed to update cached tokens", err);
- });
- },
-}
-
-/* Given an array of scopes, make a string key by normalizing. */
-function getScopeKey(scopeArray) {
- let normalizedScopes = scopeArray.map(item => item.toLowerCase());
- return normalizedScopes.sort().join("|");
-}
-
-/**
- * Copies properties from a given object to another object.
- *
- * @param from (object)
- * The object we read property descriptors from.
- * @param to (object)
- * The object that we set property descriptors on.
- * @param options (object) (optional)
- * {keys: [...]}
- * Lets the caller pass the names of all properties they want to be
- * copied. Will copy all properties of the given source object by
- * default.
- * {bind: object}
- * Lets the caller specify the object that will be used to .bind()
- * all function properties we find to. Will bind to the given target
- * object by default.
- */
-function copyObjectProperties(from, to, opts = {}) {
- let keys = (opts && opts.keys) || Object.keys(from);
- let thisArg = (opts && opts.bind) || to;
-
- for (let prop of keys) {
- let desc = Object.getOwnPropertyDescriptor(from, prop);
-
- if (typeof(desc.value) == "function") {
- desc.value = desc.value.bind(thisArg);
- }
-
- if (desc.get) {
- desc.get = desc.get.bind(thisArg);
- }
-
- if (desc.set) {
- desc.set = desc.set.bind(thisArg);
- }
-
- Object.defineProperty(to, prop, desc);
- }
-}
-
-function urlsafeBase64Encode(key) {
- return ChromeUtils.base64URLEncode(new Uint8Array(key), { pad: false });
-}
-
-/**
- * The public API's constructor.
- */
-this.FxAccounts = function (mockInternal) {
- let internal = new FxAccountsInternal();
- let external = {};
-
- // Copy all public properties to the 'external' object.
- let prototype = FxAccountsInternal.prototype;
- let options = {keys: publicProperties, bind: internal};
- copyObjectProperties(prototype, external, options);
-
- // Copy all of the mock's properties to the internal object.
- if (mockInternal && !mockInternal.onlySetInternal) {
- copyObjectProperties(mockInternal, internal);
- }
-
- if (mockInternal) {
- // Exposes the internal object for testing only.
- external.internal = internal;
- }
-
- if (!internal.fxaPushService) {
- // internal.fxaPushService option is used in testing.
- // Otherwise we load the service lazily.
- XPCOMUtils.defineLazyGetter(internal, "fxaPushService", function () {
- return Components.classes["@mozilla.org/fxaccounts/push;1"]
- .getService(Components.interfaces.nsISupports)
- .wrappedJSObject;
- });
- }
-
- // wait until after the mocks are setup before initializing.
- internal.initialize();
-
- return Object.freeze(external);
-}
-
-/**
- * The internal API's constructor.
- */
-function FxAccountsInternal() {
- // Make a local copy of this constant so we can mock it in testing
- this.POLL_SESSION = POLL_SESSION;
-
- // All significant initialization should be done in the initialize() method
- // below as it helps with testing.
-}
-
-/**
- * The internal API's prototype.
- */
-FxAccountsInternal.prototype = {
- // The timeout (in ms) we use to poll for a verified mail for the first 2 mins.
- VERIFICATION_POLL_TIMEOUT_INITIAL: 15000, // 15 seconds
- // And how often we poll after the first 2 mins.
- VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 30000, // 30 seconds.
- // The current version of the device registration, we use this to re-register
- // devices after we update what we send on device registration.
- DEVICE_REGISTRATION_VERSION: 2,
-
- _fxAccountsClient: null,
-
- // All significant initialization should be done in this initialize() method,
- // as it's called after this object has been mocked for tests.
- initialize() {
- this.currentTimer = null;
- this.currentAccountState = this.newAccountState();
- },
-
- get fxAccountsClient() {
- if (!this._fxAccountsClient) {
- this._fxAccountsClient = new FxAccountsClient();
- }
- return this._fxAccountsClient;
- },
-
- // The profile object used to fetch the actual user profile.
- _profile: null,
- get profile() {
- if (!this._profile) {
- let profileServerUrl = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.profile.uri");
- this._profile = new FxAccountsProfile({
- fxa: this,
- profileServerUrl: profileServerUrl,
- });
- }
- return this._profile;
- },
-
- // A hook-point for tests who may want a mocked AccountState or mocked storage.
- newAccountState(credentials) {
- let storage = new FxAccountsStorageManager();
- storage.initialize(credentials);
- return new AccountState(storage);
- },
-
- /**
- * Send a message to a set of devices in the same account
- *
- * @return Promise
- */
- notifyDevices: function(deviceIds, payload, TTL) {
- if (!Array.isArray(deviceIds)) {
- deviceIds = [deviceIds];
- }
- return this.currentAccountState.getUserAccountData()
- .then(data => {
- if (!data) {
- throw this._error(ERROR_NO_ACCOUNT);
- }
- if (!data.sessionToken) {
- throw this._error(ERROR_AUTH_ERROR,
- "notifyDevices called without a session token");
- }
- return this.fxAccountsClient.notifyDevices(data.sessionToken, deviceIds,
- payload, TTL);
- });
- },
-
- /**
- * Return the current time in milliseconds as an integer. Allows tests to
- * manipulate the date to simulate certificate expiration.
- */
- now: function() {
- return this.fxAccountsClient.now();
- },
-
- getAccountsClient: function() {
- return this.fxAccountsClient;
- },
-
- /**
- * Return clock offset in milliseconds, as reported by the fxAccountsClient.
- * This can be overridden for testing.
- *
- * The offset is the number of milliseconds that must be added to the client
- * clock to make it equal to the server clock. For example, if the client is
- * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
- */
- get localtimeOffsetMsec() {
- return this.fxAccountsClient.localtimeOffsetMsec;
- },
-
- /**
- * Ask the server whether the user's email has been verified
- */
- checkEmailStatus: function checkEmailStatus(sessionToken, options = {}) {
- if (!sessionToken) {
- return Promise.reject(new Error(
- "checkEmailStatus called without a session token"));
- }
- return this.fxAccountsClient.recoveryEmailStatus(sessionToken,
- options).catch(error => this._handleTokenError(error));
- },
-
- /**
- * Once the user's email is verified, we can request the keys
- */
- fetchKeys: function fetchKeys(keyFetchToken) {
- log.debug("fetchKeys: " + !!keyFetchToken);
- if (logPII) {
- log.debug("fetchKeys - the token is " + keyFetchToken);
- }
- return this.fxAccountsClient.accountKeys(keyFetchToken);
- },
-
- // set() makes sure that polling is happening, if necessary.
- // get() does not wait for verification, and returns an object even if
- // unverified. The caller of get() must check .verified .
- // The "fxaccounts:onverified" event will fire only when the verified
- // state goes from false to true, so callers must register their observer
- // and then call get(). In particular, it will not fire when the account
- // was found to be verified in a previous boot: if our stored state says
- // the account is verified, the event will never fire. So callers must do:
- // register notification observer (go)
- // userdata = get()
- // if (userdata.verified()) {go()}
-
- /**
- * Get the user currently signed in to Firefox Accounts.
- *
- * @return Promise
- * The promise resolves to the credentials object of the signed-in user:
- * {
- * email: The user's email address
- * uid: The user's unique id
- * sessionToken: Session for the FxA server
- * kA: An encryption key from the FxA server
- * kB: An encryption key derived from the user's FxA password
- * verified: email verification status
- * authAt: The time (seconds since epoch) that this record was
- * authenticated
- * }
- * or null if no user is signed in.
- */
- getSignedInUser: function getSignedInUser() {
- let currentState = this.currentAccountState;
- return currentState.getUserAccountData().then(data => {
- if (!data) {
- return null;
- }
- if (!this.isUserEmailVerified(data)) {
- // If the email is not verified, start polling for verification,
- // but return null right away. We don't want to return a promise
- // that might not be fulfilled for a long time.
- this.startVerifiedCheck(data);
- }
- return data;
- }).then(result => currentState.resolve(result));
- },
-
- /**
- * Set the current user signed in to Firefox Accounts.
- *
- * @param credentials
- * The credentials object obtained by logging in or creating
- * an account on the FxA server:
- * {
- * authAt: The time (seconds since epoch) that this record was
- * authenticated
- * email: The users email address
- * keyFetchToken: a keyFetchToken which has not yet been used
- * sessionToken: Session for the FxA server
- * uid: The user's unique id
- * unwrapBKey: used to unwrap kB, derived locally from the
- * password (not revealed to the FxA server)
- * verified: true/false
- * }
- * @return Promise
- * The promise resolves to null when the data is saved
- * successfully and is rejected on error.
- */
- setSignedInUser: function setSignedInUser(credentials) {
- log.debug("setSignedInUser - aborting any existing flows");
- return this.abortExistingFlow().then(() => {
- let currentAccountState = this.currentAccountState = this.newAccountState(
- Cu.cloneInto(credentials, {}) // Pass a clone of the credentials object.
- );
- // This promise waits for storage, but not for verification.
- // 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(() => {
- // Starting point for polling if new user
- if (!this.isUserEmailVerified(credentials)) {
- this.startVerifiedCheck(credentials);
- }
-
- return this.updateDeviceRegistration();
- }).then(() => {
- Services.telemetry.getHistogramById("FXA_CONFIGURED").add(1);
- this.notifyObservers(ONLOGIN_NOTIFICATION);
- }).then(() => {
- return currentAccountState.resolve();
- });
- })
- },
-
- /**
- * Update account data for the currently signed in user.
- *
- * @param credentials
- * The credentials object containing the fields to be updated.
- * This object must contain |email| and |uid| fields and they must
- * match the currently signed in user.
- */
- updateUserAccountData(credentials) {
- log.debug("updateUserAccountData called with fields", Object.keys(credentials));
- if (logPII) {
- log.debug("updateUserAccountData called with data", credentials);
- }
- let currentAccountState = this.currentAccountState;
- return currentAccountState.promiseInitialized.then(() => {
- return currentAccountState.getUserAccountData(["email", "uid"]);
- }).then(existing => {
- if (existing.email != credentials.email || existing.uid != credentials.uid) {
- throw new Error("The specified credentials aren't for the current user");
- }
- // We need to nuke email and uid as storage will complain if we try and
- // update them (even when the value is the same)
- credentials = Cu.cloneInto(credentials, {}); // clone it first
- delete credentials.email;
- delete credentials.uid;
- return currentAccountState.updateUserAccountData(credentials);
- });
- },
-
- /**
- * returns a promise that fires with the assertion. If there is no verified
- * signed-in user, fires with null.
- */
- getAssertion: function getAssertion(audience) {
- return this._getAssertion(audience);
- },
-
- // getAssertion() is "public" so screws with our mock story. This
- // implementation method *can* be (and is) mocked by tests.
- _getAssertion: function _getAssertion(audience) {
- log.debug("enter getAssertion()");
- let currentState = this.currentAccountState;
- return currentState.getUserAccountData().then(data => {
- if (!data) {
- // No signed-in user
- return null;
- }
- if (!this.isUserEmailVerified(data)) {
- // Signed-in user has not verified email
- return null;
- }
- if (!data.sessionToken) {
- // can't get a signed certificate without a session token. This
- // can happen if we request an assertion after clearing an invalid
- // session token from storage.
- throw this._error(ERROR_AUTH_ERROR, "getAssertion called without a session token");
- }
- return this.getKeypairAndCertificate(currentState).then(
- ({keyPair, certificate}) => {
- return this.getAssertionFromCert(data, keyPair, certificate, audience);
- }
- );
- }).catch(err =>
- this._handleTokenError(err)
- ).then(result => currentState.resolve(result));
- },
-
- /**
- * Invalidate the FxA certificate, so that it will be refreshed from the server
- * the next time it is needed.
- */
- invalidateCertificate() {
- return this.currentAccountState.updateUserAccountData({ cert: null });
- },
-
- getDeviceId() {
- return this.currentAccountState.getUserAccountData()
- .then(data => {
- if (data) {
- if (!data.deviceId || !data.deviceRegistrationVersion ||
- data.deviceRegistrationVersion < this.DEVICE_REGISTRATION_VERSION) {
- // There is no device id or the device registration is outdated.
- // 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.
- *
- */
- resendVerificationEmail: function resendVerificationEmail() {
- let currentState = this.currentAccountState;
- return this.getSignedInUser().then(data => {
- // If the caller is asking for verification to be re-sent, and there is
- // no signed-in user to begin with, this is probably best regarded as an
- // error.
- if (data) {
- if (!data.sessionToken) {
- return Promise.reject(new Error(
- "resendVerificationEmail called without a session token"));
- }
- this.pollEmailStatus(currentState, data.sessionToken, "start");
- return this.fxAccountsClient.resendVerificationEmail(
- data.sessionToken).catch(err => this._handleTokenError(err));
- }
- throw new Error("Cannot resend verification email; no signed-in user");
- });
- },
-
- /*
- * Reset state such that any previous flow is canceled.
- */
- abortExistingFlow: function abortExistingFlow() {
- if (this.currentTimer) {
- log.debug("Polling aborted; Another user signing in");
- clearTimeout(this.currentTimer);
- this.currentTimer = 0;
- }
- if (this._profile) {
- this._profile.tearDown();
- this._profile = null;
- }
- // We "abort" the accountState and assume our caller is about to throw it
- // away and replace it with a new one.
- return this.currentAccountState.abort();
- },
-
- accountStatus: function accountStatus() {
- return this.currentAccountState.getUserAccountData().then(data => {
- if (!data) {
- return false;
- }
- return this.fxAccountsClient.accountStatus(data.uid);
- });
- },
-
- checkVerificationStatus: function() {
- log.trace('checkVerificationStatus');
- let currentState = this.currentAccountState;
- return currentState.getUserAccountData().then(data => {
- if (!data) {
- log.trace("checkVerificationStatus - no user data");
- return null;
- }
-
- // Always check the verification status, even if the local state indicates
- // we're already verified. If the user changed their password, the check
- // will fail, and we'll enter the reauth state.
- log.trace("checkVerificationStatus - forcing verification status check");
- return this.pollEmailStatus(currentState, data.sessionToken, "push");
- });
- },
-
- _destroyOAuthToken: function(tokenData) {
- let client = new FxAccountsOAuthGrantClient({
- serverURL: tokenData.server,
- client_id: FX_OAUTH_CLIENT_ID
- });
- return client.destroyToken(tokenData.token)
- },
-
- _destroyAllOAuthTokens: function(tokenInfos) {
- // let's just destroy them all in parallel...
- let promises = [];
- for (let [key, tokenInfo] of Object.entries(tokenInfos || {})) {
- promises.push(this._destroyOAuthToken(tokenInfo));
- }
- return Promise.all(promises);
- },
-
- signOut: function signOut(localOnly) {
- let currentState = this.currentAccountState;
- let sessionToken;
- let tokensToRevoke;
- let deviceId;
- return currentState.getUserAccountData().then(data => {
- // 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
- // to FxAccountsClient.signOut().
- if (!localOnly) {
- // Wrap this in a promise so *any* errors in signOut won't
- // block the local sign out. This is *not* returned.
- Promise.resolve().then(() => {
- // 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.
- if (sessionToken) {
- return this._signOutServer(sessionToken, deviceId);
- }
- log.warn("Missing session token; skipping remote sign out");
- }).catch(err => {
- log.error("Error during remote sign out of Firefox Accounts", err);
- }).then(() => {
- return this._destroyAllOAuthTokens(tokensToRevoke);
- }).catch(err => {
- log.error("Error during destruction of oauth tokens during signout", err);
- }).then(() => {
- FxAccountsConfig.resetConfigURLs();
- // just for testing - notifications are cheap when no observers.
- this.notifyObservers("testhelper-fxa-signout-complete");
- })
- } else {
- // We want to do this either way -- but if we're signing out remotely we
- // need to wait until we destroy the oauth tokens if we want that to succeed.
- FxAccountsConfig.resetConfigURLs();
- }
- }).then(() => {
- this.notifyObservers(ONLOGOUT_NOTIFICATION);
- });
- },
-
- /**
- * This function should be called in conjunction with a server-side
- * signOut via FxAccountsClient.
- */
- _signOutLocal: function signOutLocal() {
- let currentAccountState = this.currentAccountState;
- return currentAccountState.signOut().then(() => {
- // this "aborts" this.currentAccountState but doesn't make a new one.
- return this.abortExistingFlow();
- }).then(() => {
- this.currentAccountState = this.newAccountState();
- return this.currentAccountState.promiseInitialized;
- });
- },
-
- _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);
- }
-
- log.debug("destroying session");
- return this.fxAccountsClient.signOut(sessionToken, options);
- },
-
- /**
- * Check the status of the current session using cached credentials.
- *
- * @return Promise
- * Resolves with a boolean indicating if the session is still valid
- */
- sessionStatus() {
- return this.getSignedInUser().then(data => {
- if (!data.sessionToken) {
- return Promise.reject(new Error(
- "sessionStatus called without a session token"));
- }
- return this.fxAccountsClient.sessionStatus(data.sessionToken);
- });
- },
-
- /**
- * Fetch encryption keys for the signed-in-user from the FxA API server.
- *
- * Not for user consumption. Exists to cause the keys to be fetch.
- *
- * Returns user data so that it can be chained with other methods.
- *
- * @return Promise
- * The promise resolves to the credentials object of the signed-in user:
- * {
- * email: The user's email address
- * uid: The user's unique id
- * sessionToken: Session for the FxA server
- * kA: An encryption key from the FxA server
- * kB: An encryption key derived from the user's FxA password
- * verified: email verification status
- * }
- * or null if no user is signed in
- */
- getKeys: function() {
- let currentState = this.currentAccountState;
- return currentState.getUserAccountData().then((userData) => {
- if (!userData) {
- throw new Error("Can't get keys; User is not signed in");
- }
- if (userData.kA && userData.kB) {
- return userData;
- }
- if (!currentState.whenKeysReadyDeferred) {
- currentState.whenKeysReadyDeferred = Promise.defer();
- if (userData.keyFetchToken) {
- this.fetchAndUnwrapKeys(userData.keyFetchToken).then(
- (dataWithKeys) => {
- if (!dataWithKeys.kA || !dataWithKeys.kB) {
- currentState.whenKeysReadyDeferred.reject(
- new Error("user data missing kA or kB")
- );
- return;
- }
- currentState.whenKeysReadyDeferred.resolve(dataWithKeys);
- },
- (err) => {
- currentState.whenKeysReadyDeferred.reject(err);
- }
- );
- } else {
- currentState.whenKeysReadyDeferred.reject('No keyFetchToken');
- }
- }
- return currentState.whenKeysReadyDeferred.promise;
- }).catch(err =>
- this._handleTokenError(err)
- ).then(result => currentState.resolve(result));
- },
-
- fetchAndUnwrapKeys: function(keyFetchToken) {
- if (logPII) {
- log.debug("fetchAndUnwrapKeys: token: " + keyFetchToken);
- }
- let currentState = this.currentAccountState;
- return Task.spawn(function* task() {
- // Sign out if we don't have a key fetch token.
- if (!keyFetchToken) {
- log.warn("improper fetchAndUnwrapKeys() call: token missing");
- yield this.signOut();
- return null;
- }
-
- let {kA, wrapKB} = yield this.fetchKeys(keyFetchToken);
-
- let data = yield currentState.getUserAccountData();
-
- // Sanity check that the user hasn't changed out from under us
- if (data.keyFetchToken !== keyFetchToken) {
- throw new Error("Signed in user changed while fetching keys!");
- }
-
- // Next statements must be synchronous until we setUserAccountData
- // so that we don't risk getting into a weird state.
- let kB_hex = CryptoUtils.xor(CommonUtils.hexToBytes(data.unwrapBKey),
- wrapKB);
-
- if (logPII) {
- log.debug("kB_hex: " + kB_hex);
- }
- let updateData = {
- kA: CommonUtils.bytesAsHex(kA),
- kB: CommonUtils.bytesAsHex(kB_hex),
- keyFetchToken: null, // null values cause the item to be removed.
- unwrapBKey: null,
- }
-
- log.debug("Keys Obtained: kA=" + !!updateData.kA + ", kB=" + !!updateData.kB);
- if (logPII) {
- log.debug("Keys Obtained: kA=" + updateData.kA + ", kB=" + updateData.kB);
- }
-
- yield currentState.updateUserAccountData(updateData);
- // We are now ready for business. This should only be invoked once
- // per setSignedInUser(), regardless of whether we've rebooted since
- // setSignedInUser() was called.
- this.notifyObservers(ONVERIFIED_NOTIFICATION);
- return currentState.getUserAccountData();
- }.bind(this)).then(result => currentState.resolve(result));
- },
-
- getAssertionFromCert: function(data, keyPair, cert, audience) {
- log.debug("getAssertionFromCert");
- let payload = {};
- let d = Promise.defer();
- let options = {
- duration: ASSERTION_LIFETIME,
- localtimeOffsetMsec: this.localtimeOffsetMsec,
- now: this.now()
- };
- let currentState = this.currentAccountState;
- // "audience" should look like "http://123done.org".
- // The generated assertion will expire in two minutes.
- jwcrypto.generateAssertion(cert, keyPair, audience, options, (err, signed) => {
- if (err) {
- log.error("getAssertionFromCert: " + err);
- d.reject(err);
- } else {
- log.debug("getAssertionFromCert returning signed: " + !!signed);
- if (logPII) {
- log.debug("getAssertionFromCert returning signed: " + signed);
- }
- d.resolve(signed);
- }
- });
- return d.promise.then(result => currentState.resolve(result));
- },
-
- getCertificateSigned: function(sessionToken, serializedPublicKey, lifetime) {
- log.debug("getCertificateSigned: " + !!sessionToken + " " + !!serializedPublicKey);
- if (logPII) {
- log.debug("getCertificateSigned: " + sessionToken + " " + serializedPublicKey);
- }
- return this.fxAccountsClient.signCertificate(
- sessionToken,
- JSON.parse(serializedPublicKey),
- lifetime
- );
- },
-
- /**
- * returns a promise that fires with {keyPair, certificate}.
- */
- getKeypairAndCertificate: Task.async(function* (currentState) {
- // If the debugging pref to ignore cached authentication credentials is set for Sync,
- // then don't use any cached key pair/certificate, i.e., generate a new
- // one and get it signed.
- // The purpose of this pref is to expedite any auth errors as the result of a
- // expired or revoked FxA session token, e.g., from resetting or changing the FxA
- // password.
- let ignoreCachedAuthCredentials = Services.prefs.getBoolPref("services.sync.debug.ignoreCachedAuthCredentials", false);
- let mustBeValidUntil = this.now() + ASSERTION_USE_PERIOD;
- let accountData = yield currentState.getUserAccountData(["cert", "keyPair", "sessionToken"]);
-
- let keyPairValid = !ignoreCachedAuthCredentials &&
- accountData.keyPair &&
- (accountData.keyPair.validUntil > mustBeValidUntil);
- let certValid = !ignoreCachedAuthCredentials &&
- accountData.cert &&
- (accountData.cert.validUntil > mustBeValidUntil);
- // TODO: get the lifetime from the cert's .exp field
- if (keyPairValid && certValid) {
- log.debug("getKeypairAndCertificate: already have keyPair and certificate");
- return {
- keyPair: accountData.keyPair.rawKeyPair,
- certificate: accountData.cert.rawCert
- }
- }
- // We are definately going to generate a new cert, either because it has
- // already expired, or the keyPair has - and a new keyPair means we must
- // generate a new cert.
-
- // A keyPair has a longer lifetime than a cert, so it's possible we will
- // have a valid keypair but an expired cert, which means we can skip
- // keypair generation.
- // Either way, the cert will require hitting the network, so bail now if
- // we know that's going to fail.
- if (Services.io.offline) {
- throw new Error(ERROR_OFFLINE);
- }
-
- let keyPair;
- if (keyPairValid) {
- keyPair = accountData.keyPair;
- } else {
- let keyWillBeValidUntil = this.now() + KEY_LIFETIME;
- keyPair = yield new Promise((resolve, reject) => {
- jwcrypto.generateKeyPair("DS160", (err, kp) => {
- if (err) {
- return reject(err);
- }
- log.debug("got keyPair");
- resolve({
- rawKeyPair: kp,
- validUntil: keyWillBeValidUntil,
- });
- });
- });
- }
-
- // and generate the cert.
- let certWillBeValidUntil = this.now() + CERT_LIFETIME;
- let certificate = yield this.getCertificateSigned(accountData.sessionToken,
- keyPair.rawKeyPair.serializedPublicKey,
- CERT_LIFETIME);
- log.debug("getCertificate got a new one: " + !!certificate);
- if (certificate) {
- // Cache both keypair and cert.
- let toUpdate = {
- keyPair,
- cert: {
- rawCert: certificate,
- validUntil: certWillBeValidUntil,
- },
- };
- yield currentState.updateUserAccountData(toUpdate);
- }
- return {
- keyPair: keyPair.rawKeyPair,
- certificate: certificate,
- }
- }),
-
- getUserAccountData: function() {
- return this.currentAccountState.getUserAccountData();
- },
-
- isUserEmailVerified: function isUserEmailVerified(data) {
- return !!(data && data.verified);
- },
-
- /**
- * Setup for and if necessary do email verification polling.
- */
- loadAndPoll: function() {
- let currentState = this.currentAccountState;
- return currentState.getUserAccountData()
- .then(data => {
- if (data) {
- Services.telemetry.getHistogramById("FXA_CONFIGURED").add(1);
- if (!this.isUserEmailVerified(data)) {
- this.pollEmailStatus(currentState, data.sessionToken, "start");
- }
- }
- return data;
- });
- },
-
- startVerifiedCheck: function(data) {
- log.debug("startVerifiedCheck", data && data.verified);
- if (logPII) {
- log.debug("startVerifiedCheck with user data", data);
- }
-
- // Get us to the verified state, then get the keys. This returns a promise
- // that will fire when we are completely ready.
- //
- // Login is truly complete once keys have been fetched, so once getKeys()
- // obtains and stores kA and kB, it will fire the onverified observer
- // notification.
-
- // The callers of startVerifiedCheck never consume a returned promise (ie,
- // this is simply kicking off a background fetch) so we must add a rejection
- // handler to avoid runtime warnings about the rejection not being handled.
- this.whenVerified(data).then(
- () => this.getKeys(),
- err => log.info("startVerifiedCheck promise was rejected: " + err)
- );
- },
-
- whenVerified: function(data) {
- let currentState = this.currentAccountState;
- if (data.verified) {
- log.debug("already verified");
- return currentState.resolve(data);
- }
- if (!currentState.whenVerifiedDeferred) {
- log.debug("whenVerified promise starts polling for verified email");
- this.pollEmailStatus(currentState, data.sessionToken, "start");
- }
- return currentState.whenVerifiedDeferred.promise.then(
- result => currentState.resolve(result)
- );
- },
-
- notifyObservers: function(topic, data) {
- log.debug("Notifying observers of " + topic);
- Services.obs.notifyObservers(null, topic, data);
- },
-
- // XXX - pollEmailStatus should maybe be on the AccountState object?
- pollEmailStatus: function pollEmailStatus(currentState, sessionToken, why) {
- log.debug("entering pollEmailStatus: " + why);
- if (why == "start" || why == "push") {
- if (this.currentTimer) {
- log.debug("pollEmailStatus starting while existing timer is running");
- clearTimeout(this.currentTimer);
- this.currentTimer = null;
- }
-
- // If we were already polling, stop and start again. This could happen
- // if the user requested the verification email to be resent while we
- // were already polling for receipt of an earlier email.
- this.pollStartDate = Date.now();
- if (!currentState.whenVerifiedDeferred) {
- currentState.whenVerifiedDeferred = Promise.defer();
- // This deferred might not end up with any handlers (eg, if sync
- // is yet to start up.) This might cause "A promise chain failed to
- // handle a rejection" messages, so add an error handler directly
- // on the promise to log the error.
- currentState.whenVerifiedDeferred.promise.then(null, err => {
- log.info("the wait for user verification was stopped: " + err);
- });
- }
- }
-
- // We return a promise for testing only. Other callers can ignore this,
- // since verification polling continues in the background.
- return this.checkEmailStatus(sessionToken, { reason: why })
- .then((response) => {
- log.debug("checkEmailStatus -> " + JSON.stringify(response));
- if (response && response.verified) {
- currentState.updateUserAccountData({ verified: true })
- .then(() => {
- return currentState.getUserAccountData();
- })
- .then(data => {
- // Now that the user is verified, we can proceed to fetch keys
- if (currentState.whenVerifiedDeferred) {
- currentState.whenVerifiedDeferred.resolve(data);
- delete currentState.whenVerifiedDeferred;
- }
- // Tell FxAccountsManager to clear its cache
- this.notifyObservers(ON_FXA_UPDATE_NOTIFICATION, ONVERIFIED_NOTIFICATION);
- });
- } else {
- // Poll email status again after a short delay.
- this.pollEmailStatusAgain(currentState, sessionToken);
- }
- }, error => {
- let timeoutMs = undefined;
- if (error && error.retryAfter) {
- // If the server told us to back off, back off the requested amount.
- timeoutMs = (error.retryAfter + 3) * 1000;
- }
- // The server will return 401 if a request parameter is erroneous or
- // if the session token expired. Let's continue polling otherwise.
- if (!error || !error.code || error.code != 401) {
- this.pollEmailStatusAgain(currentState, sessionToken, timeoutMs);
- } else {
- let error = new Error("Verification status check failed");
- this._rejectWhenVerified(currentState, error);
- }
- });
- },
-
- _rejectWhenVerified(currentState, error) {
- currentState.whenVerifiedDeferred.reject(error);
- delete currentState.whenVerifiedDeferred;
- },
-
- // Poll email status using truncated exponential back-off.
- pollEmailStatusAgain: function (currentState, sessionToken, timeoutMs) {
- let ageMs = Date.now() - this.pollStartDate;
- if (ageMs >= this.POLL_SESSION) {
- if (currentState.whenVerifiedDeferred) {
- let error = new Error("User email verification timed out.");
- this._rejectWhenVerified(currentState, error);
- }
- log.debug("polling session exceeded, giving up");
- return;
- }
- if (timeoutMs === undefined) {
- let currentMinute = Math.ceil(ageMs / 60000);
- timeoutMs = currentMinute <= 2 ? this.VERIFICATION_POLL_TIMEOUT_INITIAL
- : this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT;
- }
- log.debug("polling with timeout = " + timeoutMs);
- this.currentTimer = setTimeout(() => {
- this.pollEmailStatus(currentState, sessionToken, "timer");
- }, timeoutMs);
- },
-
- requiresHttps: function() {
- let allowHttp = Services.prefs.getBoolPref("identity.fxaccounts.allowHttp", false);
- return allowHttp !== true;
- },
-
- promiseAccountsSignUpURI() {
- return FxAccountsConfig.promiseAccountsSignUpURI();
- },
-
- promiseAccountsSignInURI() {
- return FxAccountsConfig.promiseAccountsSignInURI();
- },
-
- // Returns a promise that resolves with the URL to use to force a re-signin
- // of the current account.
- promiseAccountsForceSigninURI: Task.async(function *() {
- yield FxAccountsConfig.ensureConfigured();
- let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.force_auth.uri");
- if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
- throw new Error("Firefox Accounts server must use HTTPS");
- }
- let currentState = this.currentAccountState;
- // but we need to append the email address onto a query string.
- return this.getSignedInUser().then(accountData => {
- if (!accountData) {
- return null;
- }
- let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
- newQueryPortion += "email=" + encodeURIComponent(accountData.email);
- return url + newQueryPortion;
- }).then(result => currentState.resolve(result));
- }),
-
- // Returns a promise that resolves with the URL to use to change
- // the current account's profile image.
- // if settingToEdit is set, the profile page should hightlight that setting
- // for the user to edit.
- promiseAccountsChangeProfileURI: function(entrypoint, settingToEdit = null) {
- let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri");
-
- if (settingToEdit) {
- url += (url.indexOf("?") == -1 ? "?" : "&") +
- "setting=" + encodeURIComponent(settingToEdit);
- }
-
- if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
- throw new Error("Firefox Accounts server must use HTTPS");
- }
- let currentState = this.currentAccountState;
- // but we need to append the email address onto a query string.
- return this.getSignedInUser().then(accountData => {
- if (!accountData) {
- return null;
- }
- let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
- newQueryPortion += "email=" + encodeURIComponent(accountData.email);
- newQueryPortion += "&uid=" + encodeURIComponent(accountData.uid);
- if (entrypoint) {
- newQueryPortion += "&entrypoint=" + encodeURIComponent(entrypoint);
- }
- return url + newQueryPortion;
- }).then(result => currentState.resolve(result));
- },
-
- // Returns a promise that resolves with the URL to use to manage the current
- // user's FxA acct.
- promiseAccountsManageURI: function(entrypoint) {
- let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.settings.uri");
- if (this.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
- throw new Error("Firefox Accounts server must use HTTPS");
- }
- let currentState = this.currentAccountState;
- // but we need to append the uid and email address onto a query string
- // (if the server has no matching uid it will offer to sign in with the
- // email address)
- return this.getSignedInUser().then(accountData => {
- if (!accountData) {
- return null;
- }
- let newQueryPortion = url.indexOf("?") == -1 ? "?" : "&";
- newQueryPortion += "uid=" + encodeURIComponent(accountData.uid) +
- "&email=" + encodeURIComponent(accountData.email);
- if (entrypoint) {
- newQueryPortion += "&entrypoint=" + encodeURIComponent(entrypoint);
- }
- return url + newQueryPortion;
- }).then(result => currentState.resolve(result));
- },
-
- /**
- * Get an OAuth token for the user
- *
- * @param options
- * {
- * scope: (string/array) the oauth scope(s) being requested. As a
- * convenience, you may pass a string if only one scope is
- * required, or an array of strings if multiple are needed.
- * }
- *
- * @return Promise.<string | Error>
- * The promise resolves the oauth token as a string or rejects with
- * an error object ({error: ERROR, details: {}}) of the following:
- * INVALID_PARAMETER
- * NO_ACCOUNT
- * UNVERIFIED_ACCOUNT
- * NETWORK_ERROR
- * AUTH_ERROR
- * UNKNOWN_ERROR
- */
- getOAuthToken: Task.async(function* (options = {}) {
- log.debug("getOAuthToken enter");
- let scope = options.scope;
- if (typeof scope === "string") {
- scope = [scope];
- }
-
- if (!scope || !scope.length) {
- throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'scope' option");
- }
-
- yield this._getVerifiedAccountOrReject();
-
- // Early exit for a cached token.
- let currentState = this.currentAccountState;
- let cached = currentState.getCachedToken(scope);
- if (cached) {
- log.debug("getOAuthToken returning a cached token");
- return cached.token;
- }
-
- // We are going to hit the server - this is the string we pass to it.
- let scopeString = scope.join(" ");
- let client = options.client;
-
- if (!client) {
- try {
- let defaultURL = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.oauth.uri");
- client = new FxAccountsOAuthGrantClient({
- serverURL: defaultURL,
- client_id: FX_OAUTH_CLIENT_ID
- });
- } catch (e) {
- throw this._error(ERROR_INVALID_PARAMETER, e);
- }
- }
- let oAuthURL = client.serverURL.href;
-
- try {
- log.debug("getOAuthToken fetching new token from", oAuthURL);
- let assertion = yield this.getAssertion(oAuthURL);
- let result = yield client.getTokenFromAssertion(assertion, scopeString);
- let token = result.access_token;
- // If we got one, cache it.
- if (token) {
- let entry = {token: token, server: oAuthURL};
- // But before we do, check the cache again - if we find one now, it
- // means someone else concurrently requested the same scope and beat
- // us to the cache write. To be nice to the server, we revoke the one
- // we just got and return the newly cached value.
- let cached = currentState.getCachedToken(scope);
- if (cached) {
- log.debug("Detected a race for this token - revoking the new one.");
- this._destroyOAuthToken(entry);
- return cached.token;
- }
- currentState.setCachedToken(scope, entry);
- }
- return token;
- } catch (err) {
- throw this._errorToErrorClass(err);
- }
- }),
-
- /**
- * Remove an OAuth token from the token cache. Callers should call this
- * after they determine a token is invalid, so a new token will be fetched
- * on the next call to getOAuthToken().
- *
- * @param options
- * {
- * token: (string) A previously fetched token.
- * }
- * @return Promise.<undefined> This function will always resolve, even if
- * an unknown token is passed.
- */
- removeCachedOAuthToken: Task.async(function* (options) {
- if (!options.token || typeof options.token !== "string") {
- throw this._error(ERROR_INVALID_PARAMETER, "Missing or invalid 'token' option");
- }
- let currentState = this.currentAccountState;
- let existing = currentState.removeCachedToken(options.token);
- if (existing) {
- // background destroy.
- this._destroyOAuthToken(existing).catch(err => {
- log.warn("FxA failed to revoke a cached token", err);
- });
- }
- }),
-
- _getVerifiedAccountOrReject: Task.async(function* () {
- let data = yield this.currentAccountState.getUserAccountData();
- if (!data) {
- // No signed-in user
- throw this._error(ERROR_NO_ACCOUNT);
- }
- if (!this.isUserEmailVerified(data)) {
- // Signed-in user has not verified email
- throw this._error(ERROR_UNVERIFIED_ACCOUNT);
- }
- }),
-
- /*
- * Coerce an error into one of the general error cases:
- * NETWORK_ERROR
- * AUTH_ERROR
- * UNKNOWN_ERROR
- *
- * These errors will pass through:
- * INVALID_PARAMETER
- * NO_ACCOUNT
- * UNVERIFIED_ACCOUNT
- */
- _errorToErrorClass: function (aError) {
- if (aError.errno) {
- let error = SERVER_ERRNO_TO_ERROR[aError.errno];
- return this._error(ERROR_TO_GENERAL_ERROR_CLASS[error] || ERROR_UNKNOWN, aError);
- } else if (aError.message &&
- (aError.message === "INVALID_PARAMETER" ||
- aError.message === "NO_ACCOUNT" ||
- aError.message === "UNVERIFIED_ACCOUNT" ||
- aError.message === "AUTH_ERROR")) {
- return aError;
- }
- return this._error(ERROR_UNKNOWN, aError);
- },
-
- _error: function(aError, aDetails) {
- log.error("FxA rejecting with error ${aError}, details: ${aDetails}", {aError, aDetails});
- let reason = new Error(aError);
- if (aDetails) {
- reason.details = aDetails;
- }
- return reason;
- },
-
- /**
- * Get the user's account and profile data
- *
- * @param options
- * {
- * contentUrl: (string) Used by the FxAccountsWebChannel.
- * Defaults to pref identity.fxaccounts.settings.uri
- * profileServerUrl: (string) Used by the FxAccountsWebChannel.
- * Defaults to pref identity.fxaccounts.remote.profile.uri
- * }
- *
- * @return Promise.<object | Error>
- * The promise resolves to an accountData object with extra profile
- * information such as profileImageUrl, or rejects with
- * an error object ({error: ERROR, details: {}}) of the following:
- * INVALID_PARAMETER
- * NO_ACCOUNT
- * UNVERIFIED_ACCOUNT
- * NETWORK_ERROR
- * AUTH_ERROR
- * UNKNOWN_ERROR
- */
- getSignedInUserProfile: function () {
- let currentState = this.currentAccountState;
- return this.profile.getProfile().then(
- profileData => {
- let profile = Cu.cloneInto(profileData, {});
- return currentState.resolve(profile);
- },
- error => {
- log.error("Could not retrieve profile data", error);
- return currentState.reject(error);
- }
- ).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._logErrorAndResetDeviceRegistrationVersion(error));
- },
-
- handleDeviceDisconnection(deviceId) {
- return this.currentAccountState.getUserAccountData()
- .then(data => data ? data.deviceId : null)
- .then(localDeviceId => {
- if (deviceId == localDeviceId) {
- this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, deviceId);
- return this.signOut(true);
- }
- log.error(
- "The device ID to disconnect doesn't match with the local device ID.\n"
- + "Local: " + localDeviceId + ", ID to disconnect: " + deviceId);
- });
- },
-
- /**
- * Delete all the cached persisted credentials we store for FxA.
- *
- * @return Promise resolves when the user data has been persisted
- */
- resetCredentials() {
- // Delete all fields except those required for the user to
- // reauthenticate.
- let updateData = {};
- let clearField = field => {
- if (!FXA_PWDMGR_REAUTH_WHITELIST.has(field)) {
- updateData[field] = null;
- }
- }
- FXA_PWDMGR_PLAINTEXT_FIELDS.forEach(clearField);
- FXA_PWDMGR_SECURE_FIELDS.forEach(clearField);
- FXA_PWDMGR_MEMORY_FIELDS.forEach(clearField);
-
- let currentState = this.currentAccountState;
- return currentState.updateUserAccountData(updateData);
- },
-
- // If you change what we send to the FxA servers during device registration,
- // you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older
- // devices to re-register when Firefox updates
- _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) {}
-
- if (!signedInUser.sessionToken) {
- return Promise.reject(new Error(
- "_registerOrUpdateDevice called without a session token"));
- }
-
- return this.fxaPushService.registerPushEndpoint().then(subscription => {
- const deviceName = this._getDeviceName();
- let deviceOptions = {};
-
- // if we were able to obtain a subscription
- if (subscription && subscription.endpoint) {
- deviceOptions.pushCallback = subscription.endpoint;
- let publicKey = subscription.getKey('p256dh');
- let authKey = subscription.getKey('auth');
- if (publicKey && authKey) {
- deviceOptions.pushPublicKey = urlsafeBase64Encode(publicKey);
- deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey);
- }
- }
-
- if (signedInUser.deviceId) {
- log.debug("updating existing device details");
- return this.fxAccountsClient.updateDevice(
- signedInUser.sessionToken, signedInUser.deviceId, deviceName, deviceOptions);
- }
-
- log.debug("registering new device details");
- return this.fxAccountsClient.registerDevice(
- signedInUser.sessionToken, deviceName, this._getDeviceType(), deviceOptions);
- }).then(device =>
- this.currentAccountState.updateUserAccountData({
- deviceId: device.id,
- deviceRegistrationVersion: this.DEVICE_REGISTRATION_VERSION
- }).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);
- }
- }
-
- // `_handleTokenError` re-throws the error.
- return this._handleTokenError(error);
- }).catch(error =>
- this._logErrorAndResetDeviceRegistrationVersion(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._logErrorAndResetDeviceRegistrationVersion(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 device registration
- // version 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,
- deviceRegistrationVersion: null
- }).then(() => deviceId);
- }
- if (length > 1) {
- log.error("insane server state, " + length + " devices for this session");
- }
- return this._logErrorAndResetDeviceRegistrationVersion(error);
- }).catch(secondError => {
- log.error("failed to recover from device-session conflict", secondError);
- this._logErrorAndResetDeviceRegistrationVersion(error)
- });
- },
-
- _logErrorAndResetDeviceRegistrationVersion(error) {
- // Device registration should never cause other operations to fail.
- // If we've reached this point, just log the error and reset the device
- // registration version 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({
- deviceRegistrationVersion: null
- }).catch(secondError => {
- log.error(
- "failed to reset the device registration version, device registration won't be retried",
- secondError);
- }).then(() => {});
- },
-
- _handleTokenError(err) {
- if (!err || err.code != 401 || err.errno != ERRNO_INVALID_AUTH_TOKEN) {
- throw err;
- }
- log.warn("recovering from invalid token error", err);
- return this.accountStatus().then(exists => {
- if (!exists) {
- // Delete all local account data. Since the account no longer
- // exists, we can skip the remote calls.
- log.info("token invalidated because the account no longer exists");
- return this.signOut(true);
- }
- log.info("clearing credentials to handle invalid token error");
- return this.resetCredentials();
- }).then(() => Promise.reject(err));
- },
-};
-
-
-// A getter for the instance to export
-XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() {
- let a = new FxAccounts();
-
- // XXX Bug 947061 - We need a strategy for resuming email verification after
- // browser restart
- a.loadAndPoll();
-
- return a;
-});
diff --git a/services/fxaccounts/FxAccountsClient.jsm b/services/fxaccounts/FxAccountsClient.jsm
deleted file mode 100644
index fbe8da2fe..000000000
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ /dev/null
@@ -1,623 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsClient"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-common/hawkclient.js");
-Cu.import("resource://services-common/hawkrequest.js");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Credentials.jsm");
-
-const HOST_PREF = "identity.fxaccounts.auth.uri";
-
-const SIGNIN = "/account/login";
-const SIGNUP = "/account/create";
-
-this.FxAccountsClient = function(host = Services.prefs.getCharPref(HOST_PREF)) {
- this.host = host;
-
- // The FxA auth server expects requests to certain endpoints to be authorized
- // using Hawk.
- this.hawk = new HawkClient(host);
- this.hawk.observerPrefix = "FxA:hawk";
-
- // Manage server backoff state. C.f.
- // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#backoff-protocol
- this.backoffError = null;
-};
-
-this.FxAccountsClient.prototype = {
-
- /**
- * Return client clock offset, in milliseconds, as determined by hawk client.
- * Provided because callers should not have to know about hawk
- * implementation.
- *
- * The offset is the number of milliseconds that must be added to the client
- * clock to make it equal to the server clock. For example, if the client is
- * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
- */
- get localtimeOffsetMsec() {
- return this.hawk.localtimeOffsetMsec;
- },
-
- /*
- * Return current time in milliseconds
- *
- * Not used by this module, but made available to the FxAccounts.jsm
- * that uses this client.
- */
- now: function() {
- return this.hawk.now();
- },
-
- /**
- * Common code from signIn and signUp.
- *
- * @param path
- * Request URL path. Can be /account/create or /account/login
- * @param email
- * The email address for the account (utf8)
- * @param password
- * The user's password
- * @param [getKeys=false]
- * If set to true the keyFetchToken will be retrieved
- * @param [retryOK=true]
- * If capitalization of the email is wrong and retryOK is set to true,
- * we will retry with the suggested capitalization from the server
- * @return Promise
- * Returns a promise that resolves to an object:
- * {
- * authAt: authentication time for the session (seconds since epoch)
- * email: the primary email for this account
- * keyFetchToken: a key fetch token (hex)
- * sessionToken: a session token (hex)
- * uid: the user's unique ID (hex)
- * unwrapBKey: used to unwrap kB, derived locally from the
- * password (not revealed to the FxA server)
- * verified (optional): flag indicating verification status of the
- * email
- * }
- */
- _createSession: function(path, email, password, getKeys=false,
- retryOK=true) {
- return Credentials.setup(email, password).then((creds) => {
- let data = {
- authPW: CommonUtils.bytesAsHex(creds.authPW),
- email: email,
- };
- let keys = getKeys ? "?keys=true" : "";
-
- return this._request(path + keys, "POST", null, data).then(
- // Include the canonical capitalization of the email in the response so
- // the caller can set its signed-in user state accordingly.
- result => {
- result.email = data.email;
- result.unwrapBKey = CommonUtils.bytesAsHex(creds.unwrapBKey);
-
- return result;
- },
- error => {
- log.debug("Session creation failed", error);
- // If the user entered an email with different capitalization from
- // what's stored in the database (e.g., Greta.Garbo@gmail.COM as
- // opposed to greta.garbo@gmail.com), the server will respond with a
- // errno 120 (code 400) and the expected capitalization of the email.
- // We retry with this email exactly once. If successful, we use the
- // server's version of the email as the signed-in-user's email. This
- // is necessary because the email also serves as salt; so we must be
- // in agreement with the server on capitalization.
- //
- // API reference:
- // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md
- if (ERRNO_INCORRECT_EMAIL_CASE === error.errno && retryOK) {
- if (!error.email) {
- log.error("Server returned errno 120 but did not provide email");
- throw error;
- }
- return this._createSession(path, error.email, password, getKeys,
- false);
- }
- throw error;
- }
- );
- });
- },
-
- /**
- * Create a new Firefox Account and authenticate
- *
- * @param email
- * The email address for the account (utf8)
- * @param password
- * The user's password
- * @param [getKeys=false]
- * If set to true the keyFetchToken will be retrieved
- * @return Promise
- * Returns a promise that resolves to an object:
- * {
- * uid: the user's unique ID (hex)
- * sessionToken: a session token (hex)
- * keyFetchToken: a key fetch token (hex),
- * unwrapBKey: used to unwrap kB, derived locally from the
- * password (not revealed to the FxA server)
- * }
- */
- signUp: function(email, password, getKeys=false) {
- return this._createSession(SIGNUP, email, password, getKeys,
- false /* no retry */);
- },
-
- /**
- * Authenticate and create a new session with the Firefox Account API server
- *
- * @param email
- * The email address for the account (utf8)
- * @param password
- * The user's password
- * @param [getKeys=false]
- * If set to true the keyFetchToken will be retrieved
- * @return Promise
- * Returns a promise that resolves to an object:
- * {
- * authAt: authentication time for the session (seconds since epoch)
- * email: the primary email for this account
- * keyFetchToken: a key fetch token (hex)
- * sessionToken: a session token (hex)
- * uid: the user's unique ID (hex)
- * unwrapBKey: used to unwrap kB, derived locally from the
- * password (not revealed to the FxA server)
- * verified: flag indicating verification status of the email
- * }
- */
- signIn: function signIn(email, password, getKeys=false) {
- return this._createSession(SIGNIN, email, password, getKeys,
- true /* retry */);
- },
-
- /**
- * Check the status of a session given a session token
- *
- * @param sessionTokenHex
- * The session token encoded in hex
- * @return Promise
- * Resolves with a boolean indicating if the session is still valid
- */
- sessionStatus: function (sessionTokenHex) {
- return this._request("/session/status", "GET",
- deriveHawkCredentials(sessionTokenHex, "sessionToken")).then(
- () => Promise.resolve(true),
- error => {
- if (isInvalidTokenError(error)) {
- return Promise.resolve(false);
- }
- throw error;
- }
- );
- },
-
- /**
- * Destroy the current session with the Firefox Account API server
- *
- * @param sessionTokenHex
- * The session token encoded in hex
- * @return Promise
- */
- signOut: function (sessionTokenHex, options = {}) {
- let path = "/session/destroy";
- if (options.service) {
- path += "?service=" + encodeURIComponent(options.service);
- }
- return this._request(path, "POST",
- deriveHawkCredentials(sessionTokenHex, "sessionToken"));
- },
-
- /**
- * Check the verification status of the user's FxA email address
- *
- * @param sessionTokenHex
- * The current session token encoded in hex
- * @return Promise
- */
- recoveryEmailStatus: function (sessionTokenHex, options = {}) {
- let path = "/recovery_email/status";
- if (options.reason) {
- path += "?reason=" + encodeURIComponent(options.reason);
- }
-
- return this._request(path, "GET",
- deriveHawkCredentials(sessionTokenHex, "sessionToken"));
- },
-
- /**
- * Resend the verification email for the user
- *
- * @param sessionTokenHex
- * The current token encoded in hex
- * @return Promise
- */
- resendVerificationEmail: function(sessionTokenHex) {
- return this._request("/recovery_email/resend_code", "POST",
- deriveHawkCredentials(sessionTokenHex, "sessionToken"));
- },
-
- /**
- * Retrieve encryption keys
- *
- * @param keyFetchTokenHex
- * A one-time use key fetch token encoded in hex
- * @return Promise
- * Returns a promise that resolves to an object:
- * {
- * kA: an encryption key for recevorable data (bytes)
- * wrapKB: an encryption key that requires knowledge of the
- * user's password (bytes)
- * }
- */
- accountKeys: function (keyFetchTokenHex) {
- let creds = deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
- let keyRequestKey = creds.extra.slice(0, 32);
- let morecreds = CryptoUtils.hkdf(keyRequestKey, undefined,
- Credentials.keyWord("account/keys"), 3 * 32);
- let respHMACKey = morecreds.slice(0, 32);
- let respXORKey = morecreds.slice(32, 96);
-
- return this._request("/account/keys", "GET", creds).then(resp => {
- if (!resp.bundle) {
- throw new Error("failed to retrieve keys");
- }
-
- let bundle = CommonUtils.hexToBytes(resp.bundle);
- let mac = bundle.slice(-32);
-
- let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
- CryptoUtils.makeHMACKey(respHMACKey));
-
- let bundleMAC = CryptoUtils.digestBytes(bundle.slice(0, -32), hasher);
- if (mac !== bundleMAC) {
- throw new Error("error unbundling encryption keys");
- }
-
- let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));
-
- return {
- kA: keyAWrapB.slice(0, 32),
- wrapKB: keyAWrapB.slice(32)
- };
- });
- },
-
- /**
- * Sends a public key to the FxA API server and returns a signed certificate
- *
- * @param sessionTokenHex
- * The current session token encoded in hex
- * @param serializedPublicKey
- * A public key (usually generated by jwcrypto)
- * @param lifetime
- * The lifetime of the certificate
- * @return Promise
- * Returns a promise that resolves to the signed certificate.
- * The certificate can be used to generate a Persona assertion.
- * @throws a new Error
- * wrapping any of these HTTP code/errno pairs:
- * https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-12
- */
- signCertificate: function (sessionTokenHex, serializedPublicKey, lifetime) {
- let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
-
- let body = { publicKey: serializedPublicKey,
- duration: lifetime };
- return Promise.resolve()
- .then(_ => this._request("/certificate/sign", "POST", creds, body))
- .then(resp => resp.cert,
- err => {
- log.error("HAWK.signCertificate error: " + JSON.stringify(err));
- throw err;
- });
- },
-
- /**
- * Determine if an account exists
- *
- * @param email
- * The email address to check
- * @return Promise
- * The promise resolves to true if the account exists, or false
- * if it doesn't. The promise is rejected on other errors.
- */
- accountExists: function (email) {
- return this.signIn(email, "").then(
- (cantHappen) => {
- throw new Error("How did I sign in with an empty password?");
- },
- (expectedError) => {
- switch (expectedError.errno) {
- case ERRNO_ACCOUNT_DOES_NOT_EXIST:
- return false;
- break;
- case ERRNO_INCORRECT_PASSWORD:
- return true;
- break;
- default:
- // not so expected, any more ...
- throw expectedError;
- break;
- }
- }
- );
- },
-
- /**
- * Given the uid of an existing account (not an arbitrary email), ask
- * the server if it still exists via /account/status.
- *
- * Used for differentiating between password change and account deletion.
- */
- accountStatus: function(uid) {
- return this._request("/account/status?uid="+uid, "GET").then(
- (result) => {
- return result.exists;
- },
- (error) => {
- log.error("accountStatus failed with: " + error);
- return Promise.reject(error);
- }
- );
- },
-
- /**
- * Register a new device
- *
- * @method registerDevice
- * @param sessionTokenHex
- * Session token obtained from signIn
- * @param name
- * Device name
- * @param type
- * Device type (mobile|desktop)
- * @param [options]
- * Extra device options
- * @param [options.pushCallback]
- * `pushCallback` push endpoint callback
- * @param [options.pushPublicKey]
- * `pushPublicKey` push public key (URLSafe Base64 string)
- * @param [options.pushAuthKey]
- * `pushAuthKey` push auth secret (URLSafe Base64 string)
- * @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, options = {}) {
- let path = "/account/device";
-
- let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
- let body = { name, type };
-
- if (options.pushCallback) {
- body.pushCallback = options.pushCallback;
- }
- if (options.pushPublicKey && options.pushAuthKey) {
- body.pushPublicKey = options.pushPublicKey;
- body.pushAuthKey = options.pushAuthKey;
- }
-
- return this._request(path, "POST", creds, body);
- },
-
- /**
- * Sends a message to other devices. Must conform with the push payload schema:
- * https://github.com/mozilla/fxa-auth-server/blob/master/docs/pushpayloads.schema.json
- *
- * @method notifyDevice
- * @param sessionTokenHex
- * Session token obtained from signIn
- * @param deviceIds
- * Devices to send the message to
- * @param payload
- * Data to send with the message
- * @return Promise
- * Resolves to an empty object:
- * {}
- */
- notifyDevices(sessionTokenHex, deviceIds, payload, TTL = 0) {
- const body = {
- to: deviceIds,
- payload,
- TTL
- };
- return this._request("/account/devices/notify", "POST",
- deriveHawkCredentials(sessionTokenHex, "sessionToken"), 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
- * @param [options]
- * Extra device options
- * @param [options.pushCallback]
- * `pushCallback` push endpoint callback
- * @param [options.pushPublicKey]
- * `pushPublicKey` push public key (URLSafe Base64 string)
- * @param [options.pushAuthKey]
- * `pushAuthKey` push auth secret (URLSafe Base64 string)
- * @return Promise
- * Resolves to an object:
- * {
- * id: Device identifier
- * name: Device name
- * }
- */
- updateDevice(sessionTokenHex, id, name, options = {}) {
- let path = "/account/device";
-
- let creds = deriveHawkCredentials(sessionTokenHex, "sessionToken");
- let body = { id, name };
- if (options.pushCallback) {
- body.pushCallback = options.pushCallback;
- }
- if (options.pushPublicKey && options.pushAuthKey) {
- body.pushPublicKey = options.pushPublicKey;
- body.pushAuthKey = options.pushAuthKey;
- }
-
- 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;
- },
-
- /**
- * A general method for sending raw API calls to the FxA auth server.
- * All request bodies and responses are JSON.
- *
- * @param path
- * API endpoint path
- * @param method
- * The HTTP request method
- * @param credentials
- * Hawk credentials
- * @param jsonPayload
- * A JSON payload
- * @return Promise
- * Returns a promise that resolves to the JSON response of the API call,
- * or is rejected with an error. Error responses have the following properties:
- * {
- * "code": 400, // matches the HTTP status code
- * "errno": 107, // stable application-level error number
- * "error": "Bad Request", // string description of the error type
- * "message": "the value of salt is not allowed to be undefined",
- * "info": "https://docs.dev.lcip.og/errors/1234" // link to more info on the error
- * }
- */
- _request: function hawkRequest(path, method, credentials, jsonPayload) {
- let deferred = Promise.defer();
-
- // We were asked to back off.
- if (this.backoffError) {
- log.debug("Received new request during backoff, re-rejecting.");
- deferred.reject(this.backoffError);
- return deferred.promise;
- }
-
- this.hawk.request(path, method, credentials, jsonPayload).then(
- (response) => {
- try {
- let responseObj = JSON.parse(response.body);
- deferred.resolve(responseObj);
- } catch (err) {
- log.error("json parse error on response: " + response.body);
- deferred.reject({error: err});
- }
- },
-
- (error) => {
- log.error("error " + method + "ing " + path + ": " + JSON.stringify(error));
- if (error.retryAfter) {
- log.debug("Received backoff response; caching error as flag.");
- this.backoffError = error;
- // Schedule clearing of cached-error-as-flag.
- CommonUtils.namedTimer(
- this._clearBackoff,
- error.retryAfter * 1000,
- this,
- "fxaBackoffTimer"
- );
- }
- deferred.reject(error);
- }
- );
-
- return deferred.promise;
- },
-};
-
-function isInvalidTokenError(error) {
- if (error.code != 401) {
- return false;
- }
- switch (error.errno) {
- case ERRNO_INVALID_AUTH_TOKEN:
- case ERRNO_INVALID_AUTH_TIMESTAMP:
- case ERRNO_INVALID_AUTH_NONCE:
- return true;
- }
- return false;
-}
diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js
deleted file mode 100644
index 71fe78a50..000000000
--- a/services/fxaccounts/FxAccountsCommon.js
+++ /dev/null
@@ -1,368 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-var { interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-
-// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config",
-// "Debug", "Trace" or "All". If none is specified, "Debug" will be used by
-// default. Note "Debug" is usually appropriate so that when this log is
-// included in the Sync file logs we get verbose output.
-const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
-// The level of messages that will be dumped to the console. If not specified,
-// "Error" will be used.
-const PREF_LOG_LEVEL_DUMP = "identity.fxaccounts.log.appender.dump";
-
-// A pref that can be set so "sensitive" information (eg, personally
-// identifiable info, credentials, etc) will be logged.
-const PREF_LOG_SENSITIVE_DETAILS = "identity.fxaccounts.log.sensitive";
-
-var exports = Object.create(null);
-
-XPCOMUtils.defineLazyGetter(exports, 'log', function() {
- let log = Log.repository.getLogger("FirefoxAccounts");
- // We set the log level to debug, but the default dump appender is set to
- // the level reflected in the pref. Other code that consumes FxA may then
- // choose to add another appender at a different level.
- log.level = Log.Level.Debug;
- let appender = new Log.DumpAppender();
- appender.level = Log.Level.Error;
-
- log.addAppender(appender);
- try {
- // The log itself.
- let level =
- Services.prefs.getPrefType(PREF_LOG_LEVEL) == Ci.nsIPrefBranch.PREF_STRING
- && Services.prefs.getCharPref(PREF_LOG_LEVEL);
- log.level = Log.Level[level] || Log.Level.Debug;
-
- // The appender.
- level =
- Services.prefs.getPrefType(PREF_LOG_LEVEL_DUMP) == Ci.nsIPrefBranch.PREF_STRING
- && Services.prefs.getCharPref(PREF_LOG_LEVEL_DUMP);
- appender.level = Log.Level[level] || Log.Level.Error;
- } catch (e) {
- log.error(e);
- }
-
- return log;
-});
-
-// A boolean to indicate if personally identifiable information (or anything
-// else sensitive, such as credentials) should be logged.
-XPCOMUtils.defineLazyGetter(exports, 'logPII', function() {
- try {
- return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS);
- } catch (_) {
- return false;
- }
-});
-
-exports.FXACCOUNTS_PERMISSION = "firefox-accounts";
-
-exports.DATA_FORMAT_VERSION = 1;
-exports.DEFAULT_STORAGE_FILENAME = "signedInUser.json";
-
-// Token life times.
-// Having this parameter be short has limited security value and can cause
-// spurious authentication values if the client's clock is skewed and
-// we fail to adjust. See Bug 983256.
-exports.ASSERTION_LIFETIME = 1000 * 3600 * 24 * 365 * 25; // 25 years
-// This is a time period we want to guarantee that the assertion will be
-// valid after we generate it (e.g., the signed cert won't expire in this
-// period).
-exports.ASSERTION_USE_PERIOD = 1000 * 60 * 5; // 5 minutes
-exports.CERT_LIFETIME = 1000 * 3600 * 6; // 6 hours
-exports.KEY_LIFETIME = 1000 * 3600 * 12; // 12 hours
-
-// After we start polling for account verification, we stop polling when this
-// many milliseconds have elapsed.
-exports.POLL_SESSION = 1000 * 60 * 20; // 20 minutes
-
-// Observer notifications.
-exports.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
-exports.ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
-exports.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
-// Internal to services/fxaccounts only
-exports.ON_FXA_UPDATE_NOTIFICATION = "fxaccounts:update";
-exports.ON_DEVICE_DISCONNECTED_NOTIFICATION = "fxaccounts:device_disconnected";
-exports.ON_PASSWORD_CHANGED_NOTIFICATION = "fxaccounts:password_changed";
-exports.ON_PASSWORD_RESET_NOTIFICATION = "fxaccounts:password_reset";
-exports.ON_COLLECTION_CHANGED_NOTIFICATION = "sync:collection_changed";
-
-exports.FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";
-
-exports.ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange";
-exports.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange";
-
-// UI Requests.
-exports.UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
-exports.UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";
-
-// The OAuth client ID for Firefox Desktop
-exports.FX_OAUTH_CLIENT_ID = "5882386c6d801776";
-
-// Firefox Accounts WebChannel ID
-exports.WEBCHANNEL_ID = "account_updates";
-
-// Server errno.
-// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
-exports.ERRNO_ACCOUNT_ALREADY_EXISTS = 101;
-exports.ERRNO_ACCOUNT_DOES_NOT_EXIST = 102;
-exports.ERRNO_INCORRECT_PASSWORD = 103;
-exports.ERRNO_UNVERIFIED_ACCOUNT = 104;
-exports.ERRNO_INVALID_VERIFICATION_CODE = 105;
-exports.ERRNO_NOT_VALID_JSON_BODY = 106;
-exports.ERRNO_INVALID_BODY_PARAMETERS = 107;
-exports.ERRNO_MISSING_BODY_PARAMETERS = 108;
-exports.ERRNO_INVALID_REQUEST_SIGNATURE = 109;
-exports.ERRNO_INVALID_AUTH_TOKEN = 110;
-exports.ERRNO_INVALID_AUTH_TIMESTAMP = 111;
-exports.ERRNO_MISSING_CONTENT_LENGTH = 112;
-exports.ERRNO_REQUEST_BODY_TOO_LARGE = 113;
-exports.ERRNO_TOO_MANY_CLIENT_REQUESTS = 114;
-exports.ERRNO_INVALID_AUTH_NONCE = 115;
-exports.ERRNO_ENDPOINT_NO_LONGER_SUPPORTED = 116;
-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;
-exports.ERRNO_UNKNOWN_ERROR = 999;
-
-// Offset oauth server errnos so they don't conflict with auth server errnos
-exports.OAUTH_SERVER_ERRNO_OFFSET = 1000;
-
-// OAuth Server errno.
-exports.ERRNO_UNKNOWN_CLIENT_ID = 101 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INCORRECT_CLIENT_SECRET = 102 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INCORRECT_REDIRECT_URI = 103 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_FXA_ASSERTION = 104 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_UNKNOWN_CODE = 105 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INCORRECT_CODE = 106 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_EXPIRED_CODE = 107 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_OAUTH_INVALID_TOKEN = 108 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_REQUEST_PARAM = 109 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_RESPONSE_TYPE = 110 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_UNAUTHORIZED = 111 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_FORBIDDEN = 112 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-exports.ERRNO_INVALID_CONTENT_TYPE = 113 + exports.OAUTH_SERVER_ERRNO_OFFSET;
-
-// 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";
-exports.ERROR_INCORRECT_KEY_RETRIEVAL_METHOD = "INCORRECT_KEY_RETRIEVAL_METHOD";
-exports.ERROR_INCORRECT_LOGIN_METHOD = "INCORRECT_LOGIN_METHOD";
-exports.ERROR_INVALID_EMAIL = "INVALID_EMAIL";
-exports.ERROR_INVALID_AUDIENCE = "INVALID_AUDIENCE";
-exports.ERROR_INVALID_AUTH_TOKEN = "INVALID_AUTH_TOKEN";
-exports.ERROR_INVALID_AUTH_TIMESTAMP = "INVALID_AUTH_TIMESTAMP";
-exports.ERROR_INVALID_AUTH_NONCE = "INVALID_AUTH_NONCE";
-exports.ERROR_INVALID_BODY_PARAMETERS = "INVALID_BODY_PARAMETERS";
-exports.ERROR_INVALID_PASSWORD = "INVALID_PASSWORD";
-exports.ERROR_INVALID_VERIFICATION_CODE = "INVALID_VERIFICATION_CODE";
-exports.ERROR_INVALID_REFRESH_AUTH_VALUE = "INVALID_REFRESH_AUTH_VALUE";
-exports.ERROR_INVALID_REQUEST_SIGNATURE = "INVALID_REQUEST_SIGNATURE";
-exports.ERROR_INTERNAL_INVALID_USER = "INTERNAL_ERROR_INVALID_USER";
-exports.ERROR_MISSING_BODY_PARAMETERS = "MISSING_BODY_PARAMETERS";
-exports.ERROR_MISSING_CONTENT_LENGTH = "MISSING_CONTENT_LENGTH";
-exports.ERROR_NO_TOKEN_SESSION = "NO_TOKEN_SESSION";
-exports.ERROR_NO_SILENT_REFRESH_AUTH = "NO_SILENT_REFRESH_AUTH";
-exports.ERROR_NOT_VALID_JSON_BODY = "NOT_VALID_JSON_BODY";
-exports.ERROR_OFFLINE = "OFFLINE";
-exports.ERROR_PERMISSION_DENIED = "PERMISSION_DENIED";
-exports.ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE";
-exports.ERROR_SERVER_ERROR = "SERVER_ERROR";
-exports.ERROR_SYNC_DISABLED = "SYNC_DISABLED";
-exports.ERROR_TOO_MANY_CLIENT_REQUESTS = "TOO_MANY_CLIENT_REQUESTS";
-exports.ERROR_SERVICE_TEMP_UNAVAILABLE = "SERVICE_TEMPORARY_UNAVAILABLE";
-exports.ERROR_UI_ERROR = "UI_ERROR";
-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.
-exports.ERROR_UNKNOWN_CLIENT_ID = "UNKNOWN_CLIENT_ID";
-exports.ERROR_INCORRECT_CLIENT_SECRET = "INCORRECT_CLIENT_SECRET";
-exports.ERROR_INCORRECT_REDIRECT_URI = "INCORRECT_REDIRECT_URI";
-exports.ERROR_INVALID_FXA_ASSERTION = "INVALID_FXA_ASSERTION";
-exports.ERROR_UNKNOWN_CODE = "UNKNOWN_CODE";
-exports.ERROR_INCORRECT_CODE = "INCORRECT_CODE";
-exports.ERROR_EXPIRED_CODE = "EXPIRED_CODE";
-exports.ERROR_OAUTH_INVALID_TOKEN = "OAUTH_INVALID_TOKEN";
-exports.ERROR_INVALID_REQUEST_PARAM = "INVALID_REQUEST_PARAM";
-exports.ERROR_INVALID_RESPONSE_TYPE = "INVALID_RESPONSE_TYPE";
-exports.ERROR_UNAUTHORIZED = "UNAUTHORIZED";
-exports.ERROR_FORBIDDEN = "FORBIDDEN";
-exports.ERROR_INVALID_CONTENT_TYPE = "INVALID_CONTENT_TYPE";
-
-// Additional generic error classes for external consumers
-exports.ERROR_NO_ACCOUNT = "NO_ACCOUNT";
-exports.ERROR_AUTH_ERROR = "AUTH_ERROR";
-exports.ERROR_INVALID_PARAMETER = "INVALID_PARAMETER";
-
-// Status code errors
-exports.ERROR_CODE_METHOD_NOT_ALLOWED = 405;
-exports.ERROR_MSG_METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED";
-
-// FxAccounts has the ability to "split" the credentials between a plain-text
-// JSON file in the profile dir and in the login manager.
-// In order to prevent new fields accidentally ending up in the "wrong" place,
-// all fields stored are listed here.
-
-// 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",
- "deviceId", "deviceRegistrationVersion"]);
-
-// Fields we store in secure storage if it exists.
-exports.FXA_PWDMGR_SECURE_FIELDS = new Set(
- ["kA", "kB", "keyFetchToken", "unwrapBKey", "assertion"]);
-
-// Fields we keep in memory and don't persist anywhere.
-exports.FXA_PWDMGR_MEMORY_FIELDS = new Set(
- ["cert", "keyPair"]);
-
-// A whitelist of fields that remain in storage when the user needs to
-// reauthenticate. All other fields will be removed.
-exports.FXA_PWDMGR_REAUTH_WHITELIST = new Set(
- ["email", "uid", "profile", "deviceId", "deviceRegistrationVersion", "verified"]);
-
-// The pseudo-host we use in the login manager
-exports.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
-// The realm we use in the login manager.
-exports.FXA_PWDMGR_REALM = "Firefox Accounts credentials";
-
-// Error matching.
-exports.SERVER_ERRNO_TO_ERROR = {};
-
-// Error mapping
-exports.ERROR_TO_GENERAL_ERROR_CLASS = {};
-
-for (let id in exports) {
- this[id] = exports[id];
-}
-
-// Allow this file to be imported via Components.utils.import().
-this.EXPORTED_SYMBOLS = Object.keys(exports);
-
-// Set these up now that everything has been loaded into |this|.
-SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_ALREADY_EXISTS] = ERROR_ACCOUNT_ALREADY_EXISTS;
-SERVER_ERRNO_TO_ERROR[ERRNO_ACCOUNT_DOES_NOT_EXIST] = ERROR_ACCOUNT_DOES_NOT_EXIST;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_PASSWORD] = ERROR_INVALID_PASSWORD;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNVERIFIED_ACCOUNT] = ERROR_UNVERIFIED_ACCOUNT;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_VERIFICATION_CODE] = ERROR_INVALID_VERIFICATION_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_NOT_VALID_JSON_BODY] = ERROR_NOT_VALID_JSON_BODY;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_BODY_PARAMETERS] = ERROR_INVALID_BODY_PARAMETERS;
-SERVER_ERRNO_TO_ERROR[ERRNO_MISSING_BODY_PARAMETERS] = ERROR_MISSING_BODY_PARAMETERS;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_REQUEST_SIGNATURE] = ERROR_INVALID_REQUEST_SIGNATURE;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_TOKEN] = ERROR_INVALID_AUTH_TOKEN;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_TIMESTAMP] = ERROR_INVALID_AUTH_TIMESTAMP;
-SERVER_ERRNO_TO_ERROR[ERRNO_MISSING_CONTENT_LENGTH] = ERROR_MISSING_CONTENT_LENGTH;
-SERVER_ERRNO_TO_ERROR[ERRNO_REQUEST_BODY_TOO_LARGE] = ERROR_REQUEST_BODY_TOO_LARGE;
-SERVER_ERRNO_TO_ERROR[ERRNO_TOO_MANY_CLIENT_REQUESTS] = ERROR_TOO_MANY_CLIENT_REQUESTS;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_NONCE] = ERROR_INVALID_AUTH_NONCE;
-SERVER_ERRNO_TO_ERROR[ERRNO_ENDPOINT_NO_LONGER_SUPPORTED] = ERROR_ENDPOINT_NO_LONGER_SUPPORTED;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_LOGIN_METHOD] = ERROR_INCORRECT_LOGIN_METHOD;
-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;
-
-// oauth
-SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_CLIENT_ID] = ERROR_UNKNOWN_CLIENT_ID;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_CLIENT_SECRET] = ERROR_INCORRECT_CLIENT_SECRET;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_REDIRECT_URI] = ERROR_INCORRECT_REDIRECT_URI;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_FXA_ASSERTION] = ERROR_INVALID_FXA_ASSERTION;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_CODE] = ERROR_UNKNOWN_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_CODE] = ERROR_INCORRECT_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_EXPIRED_CODE] = ERROR_EXPIRED_CODE;
-SERVER_ERRNO_TO_ERROR[ERRNO_OAUTH_INVALID_TOKEN] = ERROR_OAUTH_INVALID_TOKEN;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_REQUEST_PARAM] = ERROR_INVALID_REQUEST_PARAM;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_RESPONSE_TYPE] = ERROR_INVALID_RESPONSE_TYPE;
-SERVER_ERRNO_TO_ERROR[ERRNO_UNAUTHORIZED] = ERROR_UNAUTHORIZED;
-SERVER_ERRNO_TO_ERROR[ERRNO_FORBIDDEN] = ERROR_FORBIDDEN;
-SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_CONTENT_TYPE] = ERROR_INVALID_CONTENT_TYPE;
-
-
-// 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;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_KEY_RETRIEVAL_METHOD] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_LOGIN_METHOD] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_EMAIL] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUDIENCE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUTH_TOKEN] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUTH_TIMESTAMP] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_AUTH_NONCE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_BODY_PARAMETERS] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_PASSWORD] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_VERIFICATION_CODE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_REFRESH_AUTH_VALUE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_REQUEST_SIGNATURE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INTERNAL_INVALID_USER] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_MISSING_BODY_PARAMETERS] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_MISSING_CONTENT_LENGTH] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NO_TOKEN_SESSION] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NO_SILENT_REFRESH_AUTH] = ERROR_AUTH_ERROR;
-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;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_OFFLINE] = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_SERVER_ERROR] = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_TOO_MANY_CLIENT_REQUESTS] = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_SERVICE_TEMP_UNAVAILABLE] = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_PARSE] = ERROR_NETWORK;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_NETWORK] = ERROR_NETWORK;
-
-// oauth
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_CLIENT_SECRET] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_REDIRECT_URI] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_FXA_ASSERTION] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNKNOWN_CODE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INCORRECT_CODE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_EXPIRED_CODE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_OAUTH_INVALID_TOKEN] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_REQUEST_PARAM] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_RESPONSE_TYPE] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_UNAUTHORIZED] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_FORBIDDEN] = ERROR_AUTH_ERROR;
-ERROR_TO_GENERAL_ERROR_CLASS[ERROR_INVALID_CONTENT_TYPE] = ERROR_AUTH_ERROR;
diff --git a/services/fxaccounts/FxAccountsComponents.manifest b/services/fxaccounts/FxAccountsComponents.manifest
deleted file mode 100644
index 5069755bc..000000000
--- a/services/fxaccounts/FxAccountsComponents.manifest
+++ /dev/null
@@ -1,4 +0,0 @@
-# FxAccountsPush.js
-component {1b7db999-2ecd-4abf-bb95-a726896798ca} FxAccountsPush.js process=main
-contract @mozilla.org/fxaccounts/push;1 {1b7db999-2ecd-4abf-bb95-a726896798ca}
-category push chrome://fxa-device-update @mozilla.org/fxaccounts/push;1
diff --git a/services/fxaccounts/FxAccountsConfig.jsm b/services/fxaccounts/FxAccountsConfig.jsm
deleted file mode 100644
index 9dcf532ab..000000000
--- a/services/fxaccounts/FxAccountsConfig.jsm
+++ /dev/null
@@ -1,179 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-"use strict";
-this.EXPORTED_SYMBOLS = ["FxAccountsConfig"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://services-common/rest.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
- "resource://gre/modules/FxAccounts.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
- "resource://gre/modules/FxAccountsWebChannel.jsm");
-
-const CONFIG_PREFS = [
- "identity.fxaccounts.auth.uri",
- "identity.fxaccounts.remote.oauth.uri",
- "identity.fxaccounts.remote.profile.uri",
- "identity.sync.tokenserver.uri",
- "identity.fxaccounts.remote.webchannel.uri",
- "identity.fxaccounts.settings.uri",
- "identity.fxaccounts.remote.signup.uri",
- "identity.fxaccounts.remote.signin.uri",
- "identity.fxaccounts.remote.force_auth.uri",
-];
-
-this.FxAccountsConfig = {
-
- // Returns a promise that resolves with the URI of the remote UI flows.
- promiseAccountsSignUpURI: Task.async(function*() {
- yield this.ensureConfigured();
- let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signup.uri");
- if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
- throw new Error("Firefox Accounts server must use HTTPS");
- }
- return url;
- }),
-
- // Returns a promise that resolves with the URI of the remote UI flows.
- promiseAccountsSignInURI: Task.async(function*() {
- yield this.ensureConfigured();
- let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signin.uri");
- if (fxAccounts.requiresHttps() && !/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
- throw new Error("Firefox Accounts server must use HTTPS");
- }
- return url;
- }),
-
- resetConfigURLs() {
- let autoconfigURL = this.getAutoConfigURL();
- if (!autoconfigURL) {
- return;
- }
- // They have the autoconfig uri pref set, so we clear all the prefs that we
- // will have initialized, which will leave them pointing at production.
- for (let pref of CONFIG_PREFS) {
- Services.prefs.clearUserPref(pref);
- }
- // Reset the webchannel.
- EnsureFxAccountsWebChannel();
- if (!Services.prefs.prefHasUserValue("webchannel.allowObject.urlWhitelist")) {
- return;
- }
- let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
- if (whitelistValue.startsWith(autoconfigURL + " ")) {
- whitelistValue = whitelistValue.slice(autoconfigURL.length + 1);
- // Check and see if the value will be the default, and just clear the pref if it would
- // to avoid it showing up as changed in about:config.
- let defaultWhitelist;
- try {
- defaultWhitelist = Services.prefs.getDefaultBranch("webchannel.allowObject.").getCharPref("urlWhitelist");
- } catch (e) {
- // No default value ...
- }
-
- if (defaultWhitelist === whitelistValue) {
- Services.prefs.clearUserPref("webchannel.allowObject.urlWhitelist");
- } else {
- Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
- }
- }
- },
-
- getAutoConfigURL() {
- let pref;
- try {
- pref = Services.prefs.getCharPref("identity.fxaccounts.autoconfig.uri");
- } catch (e) { /* no pref */ }
- if (!pref) {
- // no pref / empty pref means we don't bother here.
- return "";
- }
- let rootURL = Services.urlFormatter.formatURL(pref);
- if (rootURL.endsWith("/")) {
- rootURL.slice(0, -1);
- }
- return rootURL;
- },
-
- ensureConfigured: Task.async(function*() {
- let isSignedIn = !!(yield fxAccounts.getSignedInUser());
- if (!isSignedIn) {
- yield this.fetchConfigURLs();
- }
- }),
-
- // Read expected client configuration from the fxa auth server
- // (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
- // and replace all the relevant our prefs with the information found there.
- // This is only done before sign-in and sign-up, and even then only if the
- // `identity.fxaccounts.autoconfig.uri` preference is set.
- fetchConfigURLs: Task.async(function*() {
- let rootURL = this.getAutoConfigURL();
- if (!rootURL) {
- return;
- }
- let configURL = rootURL + "/.well-known/fxa-client-configuration";
- let jsonStr = yield new Promise((resolve, reject) => {
- let request = new RESTRequest(configURL);
- request.setHeader("Accept", "application/json");
- request.get(error => {
- if (error) {
- log.error(`Failed to get configuration object from "${configURL}"`, error);
- return reject(error);
- }
- if (!request.response.success) {
- log.error(`Received HTTP response code ${request.response.status} from configuration object request`);
- if (request.response && request.response.body) {
- log.debug("Got error response", request.response.body);
- }
- return reject(request.response.status);
- }
- resolve(request.response.body);
- });
- });
-
- log.debug("Got successful configuration response", jsonStr);
- try {
- // Update the prefs directly specified by the config.
- let config = JSON.parse(jsonStr)
- let authServerBase = config.auth_server_base_url;
- if (!authServerBase.endsWith("/v1")) {
- authServerBase += "/v1";
- }
- Services.prefs.setCharPref("identity.fxaccounts.auth.uri", authServerBase);
- Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", config.oauth_server_base_url + "/v1");
- Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", config.profile_server_base_url + "/v1");
- Services.prefs.setCharPref("identity.sync.tokenserver.uri", config.sync_tokenserver_base_url + "/1.0/sync/1.5");
- // Update the prefs that are based off of the autoconfig url
-
- let contextParam = encodeURIComponent(
- Services.prefs.getCharPref("identity.fxaccounts.contextParam"));
-
- Services.prefs.setCharPref("identity.fxaccounts.remote.webchannel.uri", rootURL);
- Services.prefs.setCharPref("identity.fxaccounts.settings.uri", rootURL + "/settings?service=sync&context=" + contextParam);
- Services.prefs.setCharPref("identity.fxaccounts.remote.signup.uri", rootURL + "/signup?service=sync&context=" + contextParam);
- Services.prefs.setCharPref("identity.fxaccounts.remote.signin.uri", rootURL + "/signin?service=sync&context=" + contextParam);
- Services.prefs.setCharPref("identity.fxaccounts.remote.force_auth.uri", rootURL + "/force_auth?service=sync&context=" + contextParam);
-
- let whitelistValue = Services.prefs.getCharPref("webchannel.allowObject.urlWhitelist");
- if (!whitelistValue.includes(rootURL)) {
- whitelistValue = `${rootURL} ${whitelistValue}`;
- Services.prefs.setCharPref("webchannel.allowObject.urlWhitelist", whitelistValue);
- }
- // Ensure the webchannel is pointed at the correct uri
- EnsureFxAccountsWebChannel();
- } catch (e) {
- log.error("Failed to initialize configuration preferences from autoconfig object", e);
- throw e;
- }
- }),
-
-};
diff --git a/services/fxaccounts/FxAccountsOAuthClient.jsm b/services/fxaccounts/FxAccountsOAuthClient.jsm
deleted file mode 100644
index c59f1a869..000000000
--- a/services/fxaccounts/FxAccountsOAuthClient.jsm
+++ /dev/null
@@ -1,269 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-/**
- * Firefox Accounts OAuth browser login helper.
- * Uses the WebChannel component to receive OAuth messages and complete login flows.
- */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsOAuthClient"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
- "resource://gre/modules/WebChannel.jsm");
-Cu.importGlobalProperties(["URL"]);
-
-/**
- * Create a new FxAccountsOAuthClient for browser some service.
- *
- * @param {Object} options Options
- * @param {Object} options.parameters
- * Opaque alphanumeric token to be included in verification links
- * @param {String} options.parameters.client_id
- * OAuth id returned from client registration
- * @param {String} options.parameters.state
- * A value that will be returned to the client as-is upon redirection
- * @param {String} options.parameters.oauth_uri
- * The FxA OAuth server uri
- * @param {String} options.parameters.content_uri
- * The FxA Content server uri
- * @param {String} [options.parameters.scope]
- * Optional. A colon-separated list of scopes that the user has authorized
- * @param {String} [options.parameters.action]
- * Optional. If provided, should be either signup, signin or force_auth.
- * @param {String} [options.parameters.email]
- * Optional. Required if options.paramters.action is 'force_auth'.
- * @param {Boolean} [options.parameters.keys]
- * Optional. If true then relier-specific encryption keys will be
- * available in the second argument to onComplete.
- * @param [authorizationEndpoint] {String}
- * Optional authorization endpoint for the OAuth server
- * @constructor
- */
-this.FxAccountsOAuthClient = function(options) {
- this._validateOptions(options);
- this.parameters = options.parameters;
- this._configureChannel();
-
- let authorizationEndpoint = options.authorizationEndpoint || "/authorization";
-
- try {
- this._fxaOAuthStartUrl = new URL(this.parameters.oauth_uri + authorizationEndpoint + "?");
- } catch (e) {
- throw new Error("Invalid OAuth Url");
- }
-
- let params = this._fxaOAuthStartUrl.searchParams;
- params.append("client_id", this.parameters.client_id);
- params.append("state", this.parameters.state);
- params.append("scope", this.parameters.scope || "");
- params.append("action", this.parameters.action || "signin");
- params.append("webChannelId", this._webChannelId);
- if (this.parameters.keys) {
- params.append("keys", "true");
- }
- // Only append if we actually have a value.
- if (this.parameters.email) {
- params.append("email", this.parameters.email);
- }
-};
-
-this.FxAccountsOAuthClient.prototype = {
- /**
- * Function that gets called once the OAuth flow is complete.
- * The callback will receive an object with code and state properties.
- * If the keys parameter was specified and true, the callback will receive
- * a second argument with kAr and kBr properties.
- */
- onComplete: null,
- /**
- * Function that gets called if there is an error during the OAuth flow,
- * for example due to a state mismatch.
- * The callback will receive an Error object as its argument.
- */
- onError: null,
- /**
- * Configuration object that stores all OAuth parameters.
- */
- parameters: null,
- /**
- * WebChannel that is used to communicate with content page.
- */
- _channel: null,
- /**
- * Boolean to indicate if this client has completed an OAuth flow.
- */
- _complete: false,
- /**
- * The url that opens the Firefox Accounts OAuth flow.
- */
- _fxaOAuthStartUrl: null,
- /**
- * WebChannel id.
- */
- _webChannelId: null,
- /**
- * WebChannel origin, used to validate origin of messages.
- */
- _webChannelOrigin: null,
- /**
- * Opens a tab at "this._fxaOAuthStartUrl".
- * Registers a WebChannel listener and sets up a callback if needed.
- */
- launchWebFlow: function () {
- if (!this._channelCallback) {
- this._registerChannel();
- }
-
- if (this._complete) {
- throw new Error("This client already completed the OAuth flow");
- } else {
- let opener = Services.wm.getMostRecentWindow("navigator:browser").gBrowser;
- opener.selectedTab = opener.addTab(this._fxaOAuthStartUrl.href);
- }
- },
-
- /**
- * Release all resources that are in use.
- */
- tearDown: function() {
- this.onComplete = null;
- this.onError = null;
- this._complete = true;
- this._channel.stopListening();
- this._channel = null;
- },
-
- /**
- * Configures WebChannel id and origin
- *
- * @private
- */
- _configureChannel: function() {
- this._webChannelId = "oauth_" + this.parameters.client_id;
-
- // if this.parameters.content_uri is present but not a valid URI, then this will throw an error.
- try {
- this._webChannelOrigin = Services.io.newURI(this.parameters.content_uri, null, null);
- } catch (e) {
- throw e;
- }
- },
-
- /**
- * Create a new channel with the WebChannelBroker, setup a callback listener
- * @private
- */
- _registerChannel: function() {
- /**
- * Processes messages that are called back from the FxAccountsChannel
- *
- * @param webChannelId {String}
- * Command webChannelId
- * @param message {Object}
- * Command message
- * @param sendingContext {Object}
- * Channel message event sendingContext
- * @private
- */
- let listener = function (webChannelId, message, sendingContext) {
- if (message) {
- let command = message.command;
- let data = message.data;
- let target = sendingContext && sendingContext.browser;
-
- switch (command) {
- case "oauth_complete":
- // validate the returned state and call onComplete or onError
- let result = null;
- let err = null;
-
- if (this.parameters.state !== data.state) {
- err = new Error("OAuth flow failed. State doesn't match");
- } else if (this.parameters.keys && !data.keys) {
- err = new Error("OAuth flow failed. Keys were not returned");
- } else {
- result = {
- code: data.code,
- state: data.state
- };
- }
-
- // if the message asked to close the tab
- if (data.closeWindow && target) {
- // for e10s reasons the best way is to use the TabBrowser to close the tab.
- let tabbrowser = target.getTabBrowser();
-
- if (tabbrowser) {
- let tab = tabbrowser.getTabForBrowser(target);
-
- if (tab) {
- tabbrowser.removeTab(tab);
- log.debug("OAuth flow closed the tab.");
- } else {
- log.debug("OAuth flow failed to close the tab. Tab not found in TabBrowser.");
- }
- } else {
- log.debug("OAuth flow failed to close the tab. TabBrowser not found.");
- }
- }
-
- if (err) {
- log.debug(err.message);
- if (this.onError) {
- this.onError(err);
- }
- } else {
- log.debug("OAuth flow completed.");
- if (this.onComplete) {
- if (this.parameters.keys) {
- this.onComplete(result, data.keys);
- } else {
- this.onComplete(result);
- }
- }
- }
-
- // onComplete will be called for this client only once
- // calling onComplete again will result in a failure of the OAuth flow
- this.tearDown();
- break;
- }
- }
- };
-
- this._channelCallback = listener.bind(this);
- this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
- this._channel.listen(this._channelCallback);
- log.debug("Channel registered: " + this._webChannelId + " with origin " + this._webChannelOrigin.prePath);
- },
-
- /**
- * Validates the required FxA OAuth parameters
- *
- * @param options {Object}
- * OAuth client options
- * @private
- */
- _validateOptions: function (options) {
- if (!options || !options.parameters) {
- throw new Error("Missing 'parameters' configuration option");
- }
-
- ["oauth_uri", "client_id", "content_uri", "state"].forEach(option => {
- if (!options.parameters[option]) {
- throw new Error("Missing 'parameters." + option + "' parameter");
- }
- });
-
- if (options.parameters.action == "force_auth" && !options.parameters.email) {
- throw new Error("parameters.email is required for action 'force_auth'");
- }
- },
-};
diff --git a/services/fxaccounts/FxAccountsOAuthGrantClient.jsm b/services/fxaccounts/FxAccountsOAuthGrantClient.jsm
deleted file mode 100644
index 4319a07ab..000000000
--- a/services/fxaccounts/FxAccountsOAuthGrantClient.jsm
+++ /dev/null
@@ -1,241 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-/**
- * Firefox Accounts OAuth Grant Client allows clients to obtain
- * an OAuth token from a BrowserID assertion. Only certain client
- * IDs support this privilage.
- */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://services-common/rest.js");
-
-Cu.importGlobalProperties(["URL"]);
-
-const AUTH_ENDPOINT = "/authorization";
-const DESTROY_ENDPOINT = "/destroy";
-
-/**
- * Create a new FxAccountsOAuthClient for browser some service.
- *
- * @param {Object} options Options
- * @param {Object} options.parameters
- * @param {String} options.parameters.client_id
- * OAuth id returned from client registration
- * @param {String} options.parameters.serverURL
- * The FxA OAuth server URL
- * @param [authorizationEndpoint] {String}
- * Optional authorization endpoint for the OAuth server
- * @constructor
- */
-this.FxAccountsOAuthGrantClient = function(options) {
-
- this._validateOptions(options);
- this.parameters = options;
-
- try {
- this.serverURL = new URL(this.parameters.serverURL);
- } catch (e) {
- throw new Error("Invalid 'serverURL'");
- }
-
- log.debug("FxAccountsOAuthGrantClient Initialized");
-};
-
-this.FxAccountsOAuthGrantClient.prototype = {
-
- /**
- * Retrieves an OAuth access token for the signed in user
- *
- * @param {Object} assertion BrowserID assertion
- * @param {String} scope OAuth scope
- * @return Promise
- * Resolves: {Object} Object with access_token property
- */
- getTokenFromAssertion: function (assertion, scope) {
- if (!assertion) {
- throw new Error("Missing 'assertion' parameter");
- }
- if (!scope) {
- throw new Error("Missing 'scope' parameter");
- }
- let params = {
- scope: scope,
- client_id: this.parameters.client_id,
- assertion: assertion,
- response_type: "token"
- };
-
- return this._createRequest(AUTH_ENDPOINT, "POST", params);
- },
-
- /**
- * Destroys a previously fetched OAuth access token.
- *
- * @param {String} token The previously fetched token
- * @return Promise
- * Resolves: {Object} with the server response, which is typically
- * ignored.
- */
- destroyToken: function (token) {
- if (!token) {
- throw new Error("Missing 'token' parameter");
- }
- let params = {
- token: token,
- };
-
- return this._createRequest(DESTROY_ENDPOINT, "POST", params);
- },
-
- /**
- * Validates the required FxA OAuth parameters
- *
- * @param options {Object}
- * OAuth client options
- * @private
- */
- _validateOptions: function (options) {
- if (!options) {
- throw new Error("Missing configuration options");
- }
-
- ["serverURL", "client_id"].forEach(option => {
- if (!options[option]) {
- throw new Error("Missing '" + option + "' parameter");
- }
- });
- },
-
- /**
- * Interface for making remote requests.
- */
- _Request: RESTRequest,
-
- /**
- * Remote request helper
- *
- * @param {String} path
- * Profile server path, i.e "/profile".
- * @param {String} [method]
- * Type of request, i.e "GET".
- * @return Promise
- * Resolves: {Object} Successful response from the Profile server.
- * Rejects: {FxAccountsOAuthGrantClientError} Profile client error.
- * @private
- */
- _createRequest: function(path, method = "POST", params) {
- return new Promise((resolve, reject) => {
- let profileDataUrl = this.serverURL + path;
- let request = new this._Request(profileDataUrl);
- method = method.toUpperCase();
-
- request.setHeader("Accept", "application/json");
- request.setHeader("Content-Type", "application/json");
-
- request.onComplete = function (error) {
- if (error) {
- return reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- message: error.toString(),
- }));
- }
-
- let body = null;
- try {
- body = JSON.parse(request.response.body);
- } catch (e) {
- return reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_PARSE,
- errno: ERRNO_PARSE,
- code: request.response.status,
- message: request.response.body,
- }));
- }
-
- // "response.success" means status code is 200
- if (request.response.success) {
- return resolve(body);
- }
-
- if (typeof body.errno === 'number') {
- // Offset oauth server errnos to avoid conflict with other FxA server errnos
- body.errno += OAUTH_SERVER_ERRNO_OFFSET;
- } else if (body.errno) {
- body.errno = ERRNO_UNKNOWN_ERROR;
- }
- return reject(new FxAccountsOAuthGrantClientError(body));
- };
-
- if (method === "POST") {
- request.post(params);
- } else {
- // method not supported
- return reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- code: ERROR_CODE_METHOD_NOT_ALLOWED,
- message: ERROR_MSG_METHOD_NOT_ALLOWED,
- }));
- }
- });
- },
-
-};
-
-/**
- * Normalized profile client errors
- * @param {Object} [details]
- * Error details object
- * @param {number} [details.code]
- * Error code
- * @param {number} [details.errno]
- * Error number
- * @param {String} [details.error]
- * Error description
- * @param {String|null} [details.message]
- * Error message
- * @constructor
- */
-this.FxAccountsOAuthGrantClientError = function(details) {
- details = details || {};
-
- this.name = "FxAccountsOAuthGrantClientError";
- this.code = details.code || null;
- this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
- this.error = details.error || ERROR_UNKNOWN;
- this.message = details.message || null;
-};
-
-/**
- * Returns error object properties
- *
- * @returns {{name: *, code: *, errno: *, error: *, message: *}}
- * @private
- */
-FxAccountsOAuthGrantClientError.prototype._toStringFields = function() {
- return {
- name: this.name,
- code: this.code,
- errno: this.errno,
- error: this.error,
- message: this.message,
- };
-};
-
-/**
- * String representation of a oauth grant client error
- *
- * @returns {String}
- */
-FxAccountsOAuthGrantClientError.prototype.toString = function() {
- return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
-};
diff --git a/services/fxaccounts/FxAccountsProfile.jsm b/services/fxaccounts/FxAccountsProfile.jsm
deleted file mode 100644
index b63cd64c1..000000000
--- a/services/fxaccounts/FxAccountsProfile.jsm
+++ /dev/null
@@ -1,191 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-/**
- * Firefox Accounts Profile helper.
- *
- * This class abstracts interaction with the profile server for an account.
- * It will handle things like fetching profile data, listening for updates to
- * the user's profile in open browser tabs, and cacheing/invalidating profile data.
- */
-
-this.EXPORTED_SYMBOLS = ["FxAccountsProfile"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsProfileClient",
- "resource://gre/modules/FxAccountsProfileClient.jsm");
-
-// Based off of deepEqual from Assert.jsm
-function deepEqual(actual, expected) {
- if (actual === expected) {
- return true;
- } else if (typeof actual != "object" && typeof expected != "object") {
- return actual == expected;
- } else {
- return objEquiv(actual, expected);
- }
-}
-
-function isUndefinedOrNull(value) {
- return value === null || value === undefined;
-}
-
-function objEquiv(a, b) {
- if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
- return false;
- }
- if (a.prototype !== b.prototype) {
- return false;
- }
- let ka, kb, key, i;
- try {
- ka = Object.keys(a);
- kb = Object.keys(b);
- } catch (e) {
- return false;
- }
- if (ka.length != kb.length) {
- return false;
- }
- ka.sort();
- kb.sort();
- for (i = ka.length - 1; i >= 0; i--) {
- key = ka[i];
- if (!deepEqual(a[key], b[key])) {
- return false;
- }
- }
- return true;
-}
-
-function hasChanged(oldData, newData) {
- return !deepEqual(oldData, newData);
-}
-
-this.FxAccountsProfile = function (options = {}) {
- this._cachedProfile = null;
- this._cachedAt = 0; // when we saved the cached version.
- this._currentFetchPromise = null;
- this._isNotifying = false; // are we sending a notification?
- this.fxa = options.fxa || fxAccounts;
- this.client = options.profileClient || new FxAccountsProfileClient({
- fxa: this.fxa,
- serverURL: options.profileServerUrl,
- });
-
- // An observer to invalidate our _cachedAt optimization. We use a weak-ref
- // just incase this.tearDown isn't called in some cases.
- Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true);
- // for testing
- if (options.channel) {
- this.channel = options.channel;
- }
-}
-
-this.FxAccountsProfile.prototype = {
- // If we get subsequent requests for a profile within this period, don't bother
- // making another request to determine if it is fresh or not.
- PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes
-
- observe(subject, topic, data) {
- // If we get a profile change notification from our webchannel it means
- // the user has just changed their profile via the web, so we want to
- // ignore our "freshness threshold"
- if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) {
- log.debug("FxAccountsProfile observed profile change");
- this._cachedAt = 0;
- }
- },
-
- tearDown: function () {
- this.fxa = null;
- this.client = null;
- this._cachedProfile = null;
- Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION);
- },
-
- _getCachedProfile: function () {
- // The cached profile will end up back in the generic accountData
- // once bug 1157529 is fixed.
- return Promise.resolve(this._cachedProfile);
- },
-
- _notifyProfileChange: function (uid) {
- this._isNotifying = true;
- Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid);
- this._isNotifying = false;
- },
-
- // Cache fetched data if it is different from what's in the cache.
- // Send out a notification if it has changed so that UI can update.
- _cacheProfile: function (profileData) {
- if (!hasChanged(this._cachedProfile, profileData)) {
- log.debug("fetched profile matches cached copy");
- return Promise.resolve(null); // indicates no change (but only tests care)
- }
- this._cachedProfile = profileData;
- this._cachedAt = Date.now();
- return this.fxa.getSignedInUser()
- .then(userData => {
- log.debug("notifying profile changed for user ${uid}", userData);
- this._notifyProfileChange(userData.uid);
- return profileData;
- });
- },
-
- _fetchAndCacheProfile: function () {
- if (!this._currentFetchPromise) {
- this._currentFetchPromise = this.client.fetchProfile().then(profile => {
- return this._cacheProfile(profile).then(() => {
- return profile;
- });
- }).then(profile => {
- this._currentFetchPromise = null;
- return profile;
- }, err => {
- this._currentFetchPromise = null;
- throw err;
- });
- }
- return this._currentFetchPromise
- },
-
- // Returns cached data right away if available, then fetches the latest profile
- // data in the background. After data is fetched a notification will be sent
- // out if the profile has changed.
- getProfile: function () {
- return this._getCachedProfile()
- .then(cachedProfile => {
- if (cachedProfile) {
- if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) {
- // Note that _fetchAndCacheProfile isn't returned, so continues
- // in the background.
- this._fetchAndCacheProfile().catch(err => {
- log.error("Background refresh of profile failed", err);
- });
- } else {
- log.trace("not checking freshness of profile as it remains recent");
- }
- return cachedProfile;
- }
- return this._fetchAndCacheProfile();
- })
- .then(profile => {
- return profile;
- });
- },
-
- QueryInterface: XPCOMUtils.generateQI([
- Ci.nsIObserver,
- Ci.nsISupportsWeakReference,
- ]),
-};
diff --git a/services/fxaccounts/FxAccountsProfileClient.jsm b/services/fxaccounts/FxAccountsProfileClient.jsm
deleted file mode 100644
index 1e5edc634..000000000
--- a/services/fxaccounts/FxAccountsProfileClient.jsm
+++ /dev/null
@@ -1,260 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-/**
- * A client to fetch profile information for a Firefox Account.
- */
- "use strict;"
-
-this.EXPORTED_SYMBOLS = ["FxAccountsProfileClient", "FxAccountsProfileClientError"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://services-common/rest.js");
-
-Cu.importGlobalProperties(["URL"]);
-
-/**
- * Create a new FxAccountsProfileClient to be able to fetch Firefox Account profile information.
- *
- * @param {Object} options Options
- * @param {String} options.serverURL
- * The URL of the profile server to query.
- * Example: https://profile.accounts.firefox.com/v1
- * @param {String} options.token
- * The bearer token to access the profile server
- * @constructor
- */
-this.FxAccountsProfileClient = function(options) {
- if (!options || !options.serverURL) {
- throw new Error("Missing 'serverURL' configuration option");
- }
-
- this.fxa = options.fxa || fxAccounts;
- // This is a work-around for loop that manages its own oauth tokens.
- // * If |token| is in options we use it and don't attempt any token refresh
- // on 401. This is for loop.
- // * If |token| doesn't exist we will fetch our own token. This is for the
- // normal FxAccounts methods for obtaining the profile.
- // We should nuke all |this.token| support once loop moves closer to FxAccounts.
- this.token = options.token;
-
- try {
- this.serverURL = new URL(options.serverURL);
- } catch (e) {
- throw new Error("Invalid 'serverURL'");
- }
- this.oauthOptions = {
- scope: "profile",
- };
- log.debug("FxAccountsProfileClient: Initialized");
-};
-
-this.FxAccountsProfileClient.prototype = {
- /**
- * {nsIURI}
- * The server to fetch profile information from.
- */
- serverURL: null,
-
- /**
- * Interface for making remote requests.
- */
- _Request: RESTRequest,
-
- /**
- * Remote request helper which abstracts authentication away.
- *
- * @param {String} path
- * Profile server path, i.e "/profile".
- * @param {String} [method]
- * Type of request, i.e "GET".
- * @return Promise
- * Resolves: {Object} Successful response from the Profile server.
- * Rejects: {FxAccountsProfileClientError} Profile client error.
- * @private
- */
- _createRequest: Task.async(function* (path, method = "GET") {
- let token = this.token;
- if (!token) {
- // tokens are cached, so getting them each request is cheap.
- token = yield this.fxa.getOAuthToken(this.oauthOptions);
- }
- try {
- return (yield this._rawRequest(path, method, token));
- } catch (ex) {
- if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
- throw ex;
- }
- // If this object was instantiated with a token then we don't refresh it.
- if (this.token) {
- throw ex;
- }
- // it's an auth error - assume our token expired and retry.
- log.info("Fetching the profile returned a 401 - revoking our token and retrying");
- yield this.fxa.removeCachedOAuthToken({token});
- token = yield this.fxa.getOAuthToken(this.oauthOptions);
- // and try with the new token - if that also fails then we fail after
- // revoking the token.
- try {
- return (yield this._rawRequest(path, method, token));
- } catch (ex) {
- if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
- throw ex;
- }
- log.info("Retry fetching the profile still returned a 401 - revoking our token and failing");
- yield this.fxa.removeCachedOAuthToken({token});
- throw ex;
- }
- }
- }),
-
- /**
- * Remote "raw" request helper - doesn't handle auth errors and tokens.
- *
- * @param {String} path
- * Profile server path, i.e "/profile".
- * @param {String} method
- * Type of request, i.e "GET".
- * @param {String} token
- * @return Promise
- * Resolves: {Object} Successful response from the Profile server.
- * Rejects: {FxAccountsProfileClientError} Profile client error.
- * @private
- */
- _rawRequest: function(path, method, token) {
- return new Promise((resolve, reject) => {
- let profileDataUrl = this.serverURL + path;
- let request = new this._Request(profileDataUrl);
- method = method.toUpperCase();
-
- request.setHeader("Authorization", "Bearer " + token);
- request.setHeader("Accept", "application/json");
-
- request.onComplete = function (error) {
- if (error) {
- return reject(new FxAccountsProfileClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- message: error.toString(),
- }));
- }
-
- let body = null;
- try {
- body = JSON.parse(request.response.body);
- } catch (e) {
- return reject(new FxAccountsProfileClientError({
- error: ERROR_PARSE,
- errno: ERRNO_PARSE,
- code: request.response.status,
- message: request.response.body,
- }));
- }
-
- // "response.success" means status code is 200
- if (request.response.success) {
- return resolve(body);
- } else {
- return reject(new FxAccountsProfileClientError({
- error: body.error || ERROR_UNKNOWN,
- errno: body.errno || ERRNO_UNKNOWN_ERROR,
- code: request.response.status,
- message: body.message || body,
- }));
- }
- };
-
- if (method === "GET") {
- request.get();
- } else {
- // method not supported
- return reject(new FxAccountsProfileClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK,
- code: ERROR_CODE_METHOD_NOT_ALLOWED,
- message: ERROR_MSG_METHOD_NOT_ALLOWED,
- }));
- }
- });
- },
-
- /**
- * Retrieve user's profile from the server
- *
- * @return Promise
- * Resolves: {Object} Successful response from the '/profile' endpoint.
- * Rejects: {FxAccountsProfileClientError} profile client error.
- */
- fetchProfile: function () {
- log.debug("FxAccountsProfileClient: Requested profile");
- return this._createRequest("/profile", "GET");
- },
-
- /**
- * Retrieve user's profile from the server
- *
- * @return Promise
- * Resolves: {Object} Successful response from the '/avatar' endpoint.
- * Rejects: {FxAccountsProfileClientError} profile client error.
- */
- fetchProfileImage: function () {
- log.debug("FxAccountsProfileClient: Requested avatar");
- return this._createRequest("/avatar", "GET");
- }
-};
-
-/**
- * Normalized profile client errors
- * @param {Object} [details]
- * Error details object
- * @param {number} [details.code]
- * Error code
- * @param {number} [details.errno]
- * Error number
- * @param {String} [details.error]
- * Error description
- * @param {String|null} [details.message]
- * Error message
- * @constructor
- */
-this.FxAccountsProfileClientError = function(details) {
- details = details || {};
-
- this.name = "FxAccountsProfileClientError";
- this.code = details.code || null;
- this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
- this.error = details.error || ERROR_UNKNOWN;
- this.message = details.message || null;
-};
-
-/**
- * Returns error object properties
- *
- * @returns {{name: *, code: *, errno: *, error: *, message: *}}
- * @private
- */
-FxAccountsProfileClientError.prototype._toStringFields = function() {
- return {
- name: this.name,
- code: this.code,
- errno: this.errno,
- error: this.error,
- message: this.message,
- };
-};
-
-/**
- * String representation of a profile client error
- *
- * @returns {String}
- */
-FxAccountsProfileClientError.prototype.toString = function() {
- return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
-};
diff --git a/services/fxaccounts/FxAccountsPush.js b/services/fxaccounts/FxAccountsPush.js
deleted file mode 100644
index 358be06ee..000000000
--- a/services/fxaccounts/FxAccountsPush.js
+++ /dev/null
@@ -1,240 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Task.jsm");
-
-/**
- * FxAccountsPushService manages Push notifications for Firefox Accounts in the browser
- *
- * @param [options]
- * Object, custom options that used for testing
- * @constructor
- */
-function FxAccountsPushService(options = {}) {
- this.log = log;
-
- if (options.log) {
- // allow custom log for testing purposes
- this.log = options.log;
- }
-
- this.log.debug("FxAccountsPush loading service");
- this.wrappedJSObject = this;
- this.initialize(options);
-}
-
-FxAccountsPushService.prototype = {
- /**
- * Helps only initialize observers once.
- */
- _initialized: false,
- /**
- * Instance of the nsIPushService or a mocked object.
- */
- pushService: null,
- /**
- * Instance of FxAccounts or a mocked object.
- */
- fxAccounts: null,
- /**
- * Component ID of this service, helps register this component.
- */
- classID: Components.ID("{1b7db999-2ecd-4abf-bb95-a726896798ca}"),
- /**
- * Register used interfaces in this service
- */
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
- /**
- * Initialize the service and register all the required observers.
- *
- * @param [options]
- */
- initialize(options) {
- if (this._initialized) {
- return false;
- }
-
- this._initialized = true;
-
- if (options.pushService) {
- this.pushService = options.pushService;
- } else {
- this.pushService = Cc["@mozilla.org/push/Service;1"].getService(Ci.nsIPushService);
- }
-
- if (options.fxAccounts) {
- this.fxAccounts = options.fxAccounts;
- } else {
- XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
- "resource://gre/modules/FxAccounts.jsm");
- }
-
- // listen to new push messages, push changes and logout events
- Services.obs.addObserver(this, this.pushService.pushTopic, false);
- Services.obs.addObserver(this, this.pushService.subscriptionChangeTopic, false);
- Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
-
- this.log.debug("FxAccountsPush initialized");
- },
- /**
- * Registers a new endpoint with the Push Server
- *
- * @returns {Promise}
- * Promise always resolves with a subscription or a null if failed to subscribe.
- */
- registerPushEndpoint() {
- this.log.trace("FxAccountsPush registerPushEndpoint");
-
- return new Promise((resolve) => {
- this.pushService.subscribe(FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
- Services.scriptSecurityManager.getSystemPrincipal(),
- (result, subscription) => {
- if (Components.isSuccessCode(result)) {
- this.log.debug("FxAccountsPush got subscription");
- resolve(subscription);
- } else {
- this.log.warn("FxAccountsPush failed to subscribe", result);
- resolve(null);
- }
- });
- });
- },
- /**
- * Standard observer interface to listen to push messages, changes and logout.
- *
- * @param subject
- * @param topic
- * @param data
- * @returns {Promise}
- */
- _observe(subject, topic, data) {
- this.log.trace(`observed topic=${topic}, data=${data}, subject=${subject}`);
- switch (topic) {
- case this.pushService.pushTopic:
- if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
- let message = subject.QueryInterface(Ci.nsIPushMessage);
- return this._onPushMessage(message);
- }
- break;
- case this.pushService.subscriptionChangeTopic:
- if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
- return this._onPushSubscriptionChange();
- }
- break;
- case ONLOGOUT_NOTIFICATION:
- // user signed out, we need to stop polling the Push Server
- return this.unsubscribe().catch(err => {
- this.log.error("Error during unsubscribe", err);
- });
- break;
- default:
- break;
- }
- },
- /**
- * Wrapper around _observe that catches errors
- */
- observe(subject, topic, data) {
- Promise.resolve()
- .then(() => this._observe(subject, topic, data))
- .catch(err => this.log.error(err));
- },
- /**
- * Fired when the Push server sends a notification.
- *
- * @private
- * @returns {Promise}
- */
- _onPushMessage(message) {
- this.log.trace("FxAccountsPushService _onPushMessage");
- if (!message.data) {
- // Use the empty signal to check the verification state of the account right away
- this.log.debug("empty push message - checking account status");
- return this.fxAccounts.checkVerificationStatus();
- }
- let payload = message.data.json();
- this.log.debug(`push command: ${payload.command}`);
- switch (payload.command) {
- case ON_DEVICE_DISCONNECTED_NOTIFICATION:
- return this.fxAccounts.handleDeviceDisconnection(payload.data.id);
- break;
- case ON_PASSWORD_CHANGED_NOTIFICATION:
- case ON_PASSWORD_RESET_NOTIFICATION:
- return this._onPasswordChanged();
- break;
- case ON_COLLECTION_CHANGED_NOTIFICATION:
- Services.obs.notifyObservers(null, ON_COLLECTION_CHANGED_NOTIFICATION, payload.data.collections);
- default:
- this.log.warn("FxA Push command unrecognized: " + payload.command);
- }
- },
- /**
- * Check the FxA session status after a password change/reset event.
- * If the session is invalid, reset credentials and notify listeners of
- * ON_ACCOUNT_STATE_CHANGE_NOTIFICATION that the account may have changed
- *
- * @returns {Promise}
- * @private
- */
- _onPasswordChanged: Task.async(function* () {
- if (!(yield this.fxAccounts.sessionStatus())) {
- yield this.fxAccounts.resetCredentials();
- Services.obs.notifyObservers(null, ON_ACCOUNT_STATE_CHANGE_NOTIFICATION, null);
- }
- }),
- /**
- * Fired when the Push server drops a subscription, or the subscription identifier changes.
- *
- * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#Receiving_Push_Messages
- *
- * @returns {Promise}
- * @private
- */
- _onPushSubscriptionChange() {
- this.log.trace("FxAccountsPushService _onPushSubscriptionChange");
- return this.fxAccounts.updateDeviceRegistration();
- },
- /**
- * Unsubscribe from the Push server
- *
- * Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#unsubscribe()
- *
- * @returns {Promise}
- * @private
- */
- unsubscribe() {
- this.log.trace("FxAccountsPushService unsubscribe");
- return new Promise((resolve) => {
- this.pushService.unsubscribe(FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
- Services.scriptSecurityManager.getSystemPrincipal(),
- (result, ok) => {
- if (Components.isSuccessCode(result)) {
- if (ok === true) {
- this.log.debug("FxAccountsPushService unsubscribed");
- } else {
- this.log.debug("FxAccountsPushService had no subscription to unsubscribe");
- }
- } else {
- this.log.warn("FxAccountsPushService failed to unsubscribe", result);
- }
- return resolve(ok);
- });
- });
- },
-};
-
-// Service registration below registers with FxAccountsComponents.manifest
-const components = [FxAccountsPushService];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
-
-// The following registration below helps with testing this service.
-this.EXPORTED_SYMBOLS=["FxAccountsPushService"];
diff --git a/services/fxaccounts/FxAccountsStorage.jsm b/services/fxaccounts/FxAccountsStorage.jsm
deleted file mode 100644
index 4362cdf5b..000000000
--- a/services/fxaccounts/FxAccountsStorage.jsm
+++ /dev/null
@@ -1,606 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
- "FxAccountsStorageManagerCanStoreField",
- "FxAccountsStorageManager",
-];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/osfile.jsm");
-Cu.import("resource://services-common/utils.js");
-
-var haveLoginManager = true;
-
-// A helper function so code can check what fields are able to be stored by
-// the storage manager without having a reference to a manager instance.
-function FxAccountsStorageManagerCanStoreField(fieldName) {
- return FXA_PWDMGR_MEMORY_FIELDS.has(fieldName) ||
- FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName) ||
- FXA_PWDMGR_SECURE_FIELDS.has(fieldName);
-}
-
-// The storage manager object.
-this.FxAccountsStorageManager = function(options = {}) {
- this.options = {
- filename: options.filename || DEFAULT_STORAGE_FILENAME,
- baseDir: options.baseDir || OS.Constants.Path.profileDir,
- }
- this.plainStorage = new JSONStorage(this.options);
- // On b2g we have no loginManager for secure storage, and tests may want
- // to pretend secure storage isn't available.
- let useSecure = 'useSecure' in options ? options.useSecure : haveLoginManager;
- if (useSecure) {
- this.secureStorage = new LoginManagerStorage();
- } else {
- this.secureStorage = null;
- }
- this._clearCachedData();
- // See .initialize() below - this protects against it not being called.
- this._promiseInitialized = Promise.reject("initialize not called");
- // A promise to avoid storage races - see _queueStorageOperation
- this._promiseStorageComplete = Promise.resolve();
-}
-
-this.FxAccountsStorageManager.prototype = {
- _initialized: false,
- _needToReadSecure: true,
-
- // An initialization routine that *looks* synchronous to the callers, but
- // is actually async as everything else waits for it to complete.
- initialize(accountData) {
- if (this._initialized) {
- throw new Error("already initialized");
- }
- this._initialized = true;
- // If we just throw away our pre-rejected promise it is reported as an
- // unhandled exception when it is GCd - so add an empty .catch handler here
- // to prevent this.
- this._promiseInitialized.catch(() => {});
- this._promiseInitialized = this._initialize(accountData);
- },
-
- _initialize: Task.async(function* (accountData) {
- log.trace("initializing new storage manager");
- try {
- if (accountData) {
- // If accountData is passed we don't need to read any storage.
- this._needToReadSecure = false;
- // split it into the 2 parts, write it and we are done.
- for (let [name, val] of Object.entries(accountData)) {
- if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
- this.cachedPlain[name] = val;
- } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
- this.cachedSecure[name] = val;
- } else {
- // Hopefully it's an "in memory" field. If it's not we log a warning
- // but still treat it as such (so it will still be available in this
- // session but isn't persisted anywhere.)
- if (!FXA_PWDMGR_MEMORY_FIELDS.has(name)) {
- log.warn("Unknown FxA field name in user data, treating as in-memory", name);
- }
- this.cachedMemory[name] = val;
- }
- }
- // write it out and we are done.
- yield this._write();
- return;
- }
- // So we were initialized without account data - that means we need to
- // read the state from storage. We try and read plain storage first and
- // only attempt to read secure storage if the plain storage had a user.
- this._needToReadSecure = yield this._readPlainStorage();
- if (this._needToReadSecure && this.secureStorage) {
- yield this._doReadAndUpdateSecure();
- }
- } finally {
- log.trace("initializing of new storage manager done");
- }
- }),
-
- finalize() {
- // We can't throw this instance away while it is still writing or we may
- // end up racing with the newly created one.
- log.trace("StorageManager finalizing");
- return this._promiseInitialized.then(() => {
- return this._promiseStorageComplete;
- }).then(() => {
- this._promiseStorageComplete = null;
- this._promiseInitialized = null;
- this._clearCachedData();
- log.trace("StorageManager finalized");
- })
- },
-
- // We want to make sure we don't end up doing multiple storage requests
- // concurrently - which has a small window for reads if the master-password
- // is locked at initialization time and becomes unlocked later, and always
- // has an opportunity for updates.
- // We also want to make sure we finished writing when finalizing, so we
- // can't accidentally end up with the previous user's write finishing after
- // a signOut attempts to clear it.
- // So all such operations "queue" themselves via this.
- _queueStorageOperation(func) {
- // |result| is the promise we return - it has no .catch handler, so callers
- // of the storage operation still see failure as a normal rejection.
- let result = this._promiseStorageComplete.then(func);
- // But the promise we assign to _promiseStorageComplete *does* have a catch
- // handler so that rejections in one storage operation does not prevent
- // future operations from starting (ie, _promiseStorageComplete must never
- // be in a rejected state)
- this._promiseStorageComplete = result.catch(err => {
- log.error("${func} failed: ${err}", {func, err});
- });
- return result;
- },
-
- // Get the account data by combining the plain and secure storage.
- // If fieldNames is specified, it may be a string or an array of strings,
- // and only those fields are returned. If not specified the entire account
- // data is returned except for "in memory" fields. Note that not specifying
- // field names will soon be deprecated/removed - we want all callers to
- // specify the fields they care about.
- getAccountData: Task.async(function* (fieldNames = null) {
- yield this._promiseInitialized;
- // We know we are initialized - this means our .cachedPlain is accurate
- // and doesn't need to be read (it was read if necessary by initialize).
- // So if there's no uid, there's no user signed in.
- if (!('uid' in this.cachedPlain)) {
- return null;
- }
- let result = {};
- if (fieldNames === null) {
- // The "old" deprecated way of fetching a logged in user.
- for (let [name, value] of Object.entries(this.cachedPlain)) {
- result[name] = value;
- }
- // But the secure data may not have been read, so try that now.
- yield this._maybeReadAndUpdateSecure();
- // .cachedSecure now has as much as it possibly can (which is possibly
- // nothing if (a) secure storage remains locked and (b) we've never updated
- // a field to be stored in secure storage.)
- for (let [name, value] of Object.entries(this.cachedSecure)) {
- result[name] = value;
- }
- // Note we don't return cachedMemory fields here - they must be explicitly
- // requested.
- return result;
- }
- // The new explicit way of getting attributes.
- if (!Array.isArray(fieldNames)) {
- fieldNames = [fieldNames];
- }
- let checkedSecure = false;
- for (let fieldName of fieldNames) {
- if (FXA_PWDMGR_MEMORY_FIELDS.has(fieldName)) {
- if (this.cachedMemory[fieldName] !== undefined) {
- result[fieldName] = this.cachedMemory[fieldName];
- }
- } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName)) {
- if (this.cachedPlain[fieldName] !== undefined) {
- result[fieldName] = this.cachedPlain[fieldName];
- }
- } else if (FXA_PWDMGR_SECURE_FIELDS.has(fieldName)) {
- // We may not have read secure storage yet.
- if (!checkedSecure) {
- yield this._maybeReadAndUpdateSecure();
- checkedSecure = true;
- }
- if (this.cachedSecure[fieldName] !== undefined) {
- result[fieldName] = this.cachedSecure[fieldName];
- }
- } else {
- throw new Error("unexpected field '" + name + "'");
- }
- }
- return result;
- }),
-
- // Update just the specified fields. This DOES NOT allow you to change to
- // a different user, nor to set the user as signed-out.
- updateAccountData: Task.async(function* (newFields) {
- yield this._promiseInitialized;
- if (!('uid' in this.cachedPlain)) {
- // If this storage instance shows no logged in user, then you can't
- // update fields.
- throw new Error("No user is logged in");
- }
- if (!newFields || 'uid' in newFields || 'email' in newFields) {
- // Once we support
- // user changing email address this may need to change, but it's not
- // clear how we would be told of such a change anyway...
- throw new Error("Can't change uid or email address");
- }
- log.debug("_updateAccountData with items", Object.keys(newFields));
- // work out what bucket.
- for (let [name, value] of Object.entries(newFields)) {
- if (FXA_PWDMGR_MEMORY_FIELDS.has(name)) {
- if (value == null) {
- delete this.cachedMemory[name];
- } else {
- this.cachedMemory[name] = value;
- }
- } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
- if (value == null) {
- delete this.cachedPlain[name];
- } else {
- this.cachedPlain[name] = value;
- }
- } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
- // don't do the "delete on null" thing here - we need to keep it until
- // we have managed to read so we can nuke it on write.
- this.cachedSecure[name] = value;
- } else {
- // Throwing seems reasonable here as some client code has explicitly
- // specified the field name, so it's either confused or needs to update
- // how this field is to be treated.
- throw new Error("unexpected field '" + name + "'");
- }
- }
- // If we haven't yet read the secure data, do so now, else we may write
- // out partial data.
- yield this._maybeReadAndUpdateSecure();
- // Now save it - but don't wait on the _write promise - it's queued up as
- // a storage operation, so .finalize() will wait for completion, but no need
- // for us to.
- this._write();
- }),
-
- _clearCachedData() {
- this.cachedMemory = {};
- this.cachedPlain = {};
- // If we don't have secure storage available we have cachedPlain and
- // cachedSecure be the same object.
- this.cachedSecure = this.secureStorage == null ? this.cachedPlain : {};
- },
-
- /* Reads the plain storage and caches the read values in this.cachedPlain.
- Only ever called once and unlike the "secure" storage, is expected to never
- fail (ie, plain storage is considered always available, whereas secure
- storage may be unavailable if it is locked).
-
- Returns a promise that resolves with true if valid account data was found,
- false otherwise.
-
- Note: _readPlainStorage is only called during initialize, so isn't
- protected via _queueStorageOperation() nor _promiseInitialized.
- */
- _readPlainStorage: Task.async(function* () {
- let got;
- try {
- got = yield this.plainStorage.get();
- } catch(err) {
- // File hasn't been created yet. That will be done
- // when write is called.
- if (!(err instanceof OS.File.Error) || !err.becauseNoSuchFile) {
- log.error("Failed to read plain storage", err);
- }
- // either way, we return null.
- got = null;
- }
- if (!got || !got.accountData || !got.accountData.uid ||
- got.version != DATA_FORMAT_VERSION) {
- return false;
- }
- // We need to update our .cachedPlain, but can't just assign to it as
- // it may need to be the exact same object as .cachedSecure
- // As a sanity check, .cachedPlain must be empty (as we are called by init)
- // XXX - this would be a good use-case for a RuntimeAssert or similar, as
- // being added in bug 1080457.
- if (Object.keys(this.cachedPlain).length != 0) {
- throw new Error("should be impossible to have cached data already.")
- }
- for (let [name, value] of Object.entries(got.accountData)) {
- this.cachedPlain[name] = value;
- }
- return true;
- }),
-
- /* If we haven't managed to read the secure storage, try now, so
- we can merge our cached data with the data that's already been set.
- */
- _maybeReadAndUpdateSecure: Task.async(function* () {
- if (this.secureStorage == null || !this._needToReadSecure) {
- return;
- }
- return this._queueStorageOperation(() => {
- if (this._needToReadSecure) { // we might have read it by now!
- return this._doReadAndUpdateSecure();
- }
- });
- }),
-
- /* Unconditionally read the secure storage and merge our cached data (ie, data
- which has already been set while the secure storage was locked) with
- the read data
- */
- _doReadAndUpdateSecure: Task.async(function* () {
- let { uid, email } = this.cachedPlain;
- try {
- log.debug("reading secure storage with existing", Object.keys(this.cachedSecure));
- // If we already have anything in .cachedSecure it means something has
- // updated cachedSecure before we've read it. That means that after we do
- // manage to read we must write back the merged data.
- let needWrite = Object.keys(this.cachedSecure).length != 0;
- let readSecure = yield this.secureStorage.get(uid, email);
- // and update our cached data with it - anything already in .cachedSecure
- // wins (including the fact it may be null or undefined, the latter
- // which means it will be removed from storage.
- if (readSecure && readSecure.version != DATA_FORMAT_VERSION) {
- log.warn("got secure data but the data format version doesn't match");
- readSecure = null;
- }
- if (readSecure && readSecure.accountData) {
- log.debug("secure read fetched items", Object.keys(readSecure.accountData));
- for (let [name, value] of Object.entries(readSecure.accountData)) {
- if (!(name in this.cachedSecure)) {
- this.cachedSecure[name] = value;
- }
- }
- if (needWrite) {
- log.debug("successfully read secure data; writing updated data back")
- yield this._doWriteSecure();
- }
- }
- this._needToReadSecure = false;
- } catch (ex) {
- if (ex instanceof this.secureStorage.STORAGE_LOCKED) {
- log.debug("setAccountData: secure storage is locked trying to read");
- } else {
- log.error("failed to read secure storage", ex);
- throw ex;
- }
- }
- }),
-
- _write() {
- // We don't want multiple writes happening concurrently, and we also need to
- // know when an "old" storage manager is done (this.finalize() waits for this)
- return this._queueStorageOperation(() => this.__write());
- },
-
- __write: Task.async(function* () {
- // Write everything back - later we could track what's actually dirty,
- // but for now we write it all.
- log.debug("writing plain storage", Object.keys(this.cachedPlain));
- let toWritePlain = {
- version: DATA_FORMAT_VERSION,
- accountData: this.cachedPlain,
- }
- yield this.plainStorage.set(toWritePlain);
-
- // If we have no secure storage manager we are done.
- if (this.secureStorage == null) {
- return;
- }
- // and only attempt to write to secure storage if we've managed to read it,
- // otherwise we might clobber data that's already there.
- if (!this._needToReadSecure) {
- yield this._doWriteSecure();
- }
- }),
-
- /* Do the actual write of secure data. Caller is expected to check if we actually
- need to write and to ensure we are in a queued storage operation.
- */
- _doWriteSecure: Task.async(function* () {
- // We need to remove null items here.
- for (let [name, value] of Object.entries(this.cachedSecure)) {
- if (value == null) {
- delete this.cachedSecure[name];
- }
- }
- log.debug("writing secure storage", Object.keys(this.cachedSecure));
- let toWriteSecure = {
- version: DATA_FORMAT_VERSION,
- accountData: this.cachedSecure,
- }
- try {
- yield this.secureStorage.set(this.cachedPlain.uid, toWriteSecure);
- } catch (ex) {
- if (!(ex instanceof this.secureStorage.STORAGE_LOCKED)) {
- throw ex;
- }
- // This shouldn't be possible as once it is unlocked it can't be
- // re-locked, and we can only be here if we've previously managed to
- // read.
- log.error("setAccountData: secure storage is locked trying to write");
- }
- }),
-
- // Delete the data for an account - ie, called on "sign out".
- deleteAccountData() {
- return this._queueStorageOperation(() => this._deleteAccountData());
- },
-
- _deleteAccountData: Task.async(function* () {
- log.debug("removing account data");
- yield this._promiseInitialized;
- yield this.plainStorage.set(null);
- if (this.secureStorage) {
- yield this.secureStorage.set(null);
- }
- this._clearCachedData();
- log.debug("account data reset");
- }),
-}
-
-/**
- * JSONStorage constructor that creates instances that may set/get
- * to a specified file, in a directory that will be created if it
- * doesn't exist.
- *
- * @param options {
- * filename: of the file to write to
- * baseDir: directory where the file resides
- * }
- * @return instance
- */
-function JSONStorage(options) {
- this.baseDir = options.baseDir;
- this.path = OS.Path.join(options.baseDir, options.filename);
-};
-
-JSONStorage.prototype = {
- set: function(contents) {
- log.trace("starting write of json user data", contents ? Object.keys(contents.accountData) : "null");
- let start = Date.now();
- return OS.File.makeDir(this.baseDir, {ignoreExisting: true})
- .then(CommonUtils.writeJSON.bind(null, contents, this.path))
- .then(result => {
- log.trace("finished write of json user data - took", Date.now()-start);
- return result;
- });
- },
-
- get: function() {
- log.trace("starting fetch of json user data");
- let start = Date.now();
- return CommonUtils.readJSON(this.path).then(result => {
- log.trace("finished fetch of json user data - took", Date.now()-start);
- return result;
- });
- },
-};
-
-function StorageLockedError() {
-}
-/**
- * LoginManagerStorage constructor that creates instances that set/get
- * data stored securely in the nsILoginManager.
- *
- * @return instance
- */
-
-function LoginManagerStorage() {
-}
-
-LoginManagerStorage.prototype = {
- STORAGE_LOCKED: StorageLockedError,
- // The fields in the credentials JSON object that are stored in plain-text
- // in the profile directory. All other fields are stored in the login manager,
- // and thus are only available when the master-password is unlocked.
-
- // a hook point for testing.
- get _isLoggedIn() {
- return Services.logins.isLoggedIn;
- },
-
- // Clear any data from the login manager. Returns true if the login manager
- // was unlocked (even if no existing logins existed) or false if it was
- // locked (meaning we don't even know if it existed or not.)
- _clearLoginMgrData: Task.async(function* () {
- try { // Services.logins might be third-party and broken...
- yield Services.logins.initializationPromise;
- if (!this._isLoggedIn) {
- return false;
- }
- let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
- for (let login of logins) {
- Services.logins.removeLogin(login);
- }
- return true;
- } catch (ex) {
- log.error("Failed to clear login data: ${}", ex);
- return false;
- }
- }),
-
- set: Task.async(function* (uid, contents) {
- if (!contents) {
- // Nuke it from the login manager.
- let cleared = yield this._clearLoginMgrData();
- if (!cleared) {
- // just log a message - we verify that the uid matches when
- // we reload it, so having a stale entry doesn't really hurt.
- log.info("not removing credentials from login manager - not logged in");
- }
- log.trace("storage set finished clearing account data");
- return;
- }
-
- // We are saving actual data.
- log.trace("starting write of user data to the login manager");
- try { // Services.logins might be third-party and broken...
- // and the stuff into the login manager.
- yield Services.logins.initializationPromise;
- // If MP is locked we silently fail - the user may need to re-auth
- // next startup.
- if (!this._isLoggedIn) {
- log.info("not saving credentials to login manager - not logged in");
- throw new this.STORAGE_LOCKED();
- }
- // write the data to the login manager.
- let loginInfo = new Components.Constructor(
- "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
- let login = new loginInfo(FXA_PWDMGR_HOST,
- null, // aFormSubmitURL,
- FXA_PWDMGR_REALM, // aHttpRealm,
- uid, // aUsername
- JSON.stringify(contents), // aPassword
- "", // aUsernameField
- "");// aPasswordField
-
- let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null,
- FXA_PWDMGR_REALM);
- if (existingLogins.length) {
- Services.logins.modifyLogin(existingLogins[0], login);
- } else {
- Services.logins.addLogin(login);
- }
- log.trace("finished write of user data to the login manager");
- } catch (ex) {
- if (ex instanceof this.STORAGE_LOCKED) {
- throw ex;
- }
- // just log and consume the error here - it may be a 3rd party login
- // manager replacement that's simply broken.
- log.error("Failed to save data to the login manager", ex);
- }
- }),
-
- get: Task.async(function* (uid, email) {
- log.trace("starting fetch of user data from the login manager");
-
- try { // Services.logins might be third-party and broken...
- // read the data from the login manager and merge it for return.
- yield Services.logins.initializationPromise;
-
- if (!this._isLoggedIn) {
- log.info("returning partial account data as the login manager is locked.");
- throw new this.STORAGE_LOCKED();
- }
-
- let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
- if (logins.length == 0) {
- // This could happen if the MP was locked when we wrote the data.
- log.info("Can't find any credentials in the login manager");
- return null;
- }
- let login = logins[0];
- // Support either the uid or the email as the username - as of bug 1183951
- // we store the uid, but we support having either for b/w compat.
- if (login.username == uid || login.username == email) {
- return JSON.parse(login.password);
- }
- log.info("username in the login manager doesn't match - ignoring it");
- yield this._clearLoginMgrData();
- } catch (ex) {
- if (ex instanceof this.STORAGE_LOCKED) {
- throw ex;
- }
- // just log and consume the error here - it may be a 3rd party login
- // manager replacement that's simply broken.
- log.error("Failed to get data from the login manager", ex);
- }
- return null;
- }),
-}
-
diff --git a/services/fxaccounts/FxAccountsWebChannel.jsm b/services/fxaccounts/FxAccountsWebChannel.jsm
deleted file mode 100644
index 810d93c65..000000000
--- a/services/fxaccounts/FxAccountsWebChannel.jsm
+++ /dev/null
@@ -1,474 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-/**
- * Firefox Accounts Web Channel.
- *
- * Uses the WebChannel component to receive messages
- * about account state changes.
- */
-
-this.EXPORTED_SYMBOLS = ["EnsureFxAccountsWebChannel"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Services",
- "resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
- "resource://gre/modules/WebChannel.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
- "resource://gre/modules/FxAccounts.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsStorageManagerCanStoreField",
- "resource://gre/modules/FxAccountsStorage.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Weave",
- "resource://services-sync/main.js");
-
-const COMMAND_PROFILE_CHANGE = "profile:change";
-const COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account";
-const COMMAND_LOGIN = "fxaccounts:login";
-const COMMAND_LOGOUT = "fxaccounts:logout";
-const COMMAND_DELETE = "fxaccounts:delete";
-const COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences";
-const COMMAND_CHANGE_PASSWORD = "fxaccounts:change_password";
-
-const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
-const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync-setup.ui.showCustomizationDialog";
-
-/**
- * A helper function that extracts the message and stack from an error object.
- * Returns a `{ message, stack }` tuple. `stack` will be null if the error
- * doesn't have a stack trace.
- */
-function getErrorDetails(error) {
- let details = { message: String(error), stack: null };
-
- // Adapted from Console.jsm.
- if (error.stack) {
- let frames = [];
- for (let frame = error.stack; frame; frame = frame.caller) {
- frames.push(String(frame).padStart(4));
- }
- details.stack = frames.join("\n");
- }
-
- return details;
-}
-
-/**
- * Create a new FxAccountsWebChannel to listen for account updates
- *
- * @param {Object} options Options
- * @param {Object} options
- * @param {String} options.content_uri
- * The FxA Content server uri
- * @param {String} options.channel_id
- * The ID of the WebChannel
- * @param {String} options.helpers
- * Helpers functions. Should only be passed in for testing.
- * @constructor
- */
-this.FxAccountsWebChannel = function(options) {
- if (!options) {
- throw new Error("Missing configuration options");
- }
- if (!options["content_uri"]) {
- throw new Error("Missing 'content_uri' option");
- }
- this._contentUri = options.content_uri;
-
- if (!options["channel_id"]) {
- throw new Error("Missing 'channel_id' option");
- }
- this._webChannelId = options.channel_id;
-
- // options.helpers is only specified by tests.
- this._helpers = options.helpers || new FxAccountsWebChannelHelpers(options);
-
- this._setupChannel();
-};
-
-this.FxAccountsWebChannel.prototype = {
- /**
- * WebChannel that is used to communicate with content page
- */
- _channel: null,
-
- /**
- * Helpers interface that does the heavy lifting.
- */
- _helpers: null,
-
- /**
- * WebChannel ID.
- */
- _webChannelId: null,
- /**
- * WebChannel origin, used to validate origin of messages
- */
- _webChannelOrigin: null,
-
- /**
- * Release all resources that are in use.
- */
- tearDown() {
- this._channel.stopListening();
- this._channel = null;
- this._channelCallback = null;
- },
-
- /**
- * Configures and registers a new WebChannel
- *
- * @private
- */
- _setupChannel() {
- // if this.contentUri is present but not a valid URI, then this will throw an error.
- try {
- this._webChannelOrigin = Services.io.newURI(this._contentUri, null, null);
- this._registerChannel();
- } catch (e) {
- log.error(e);
- throw e;
- }
- },
-
- _receiveMessage(message, sendingContext) {
- let command = message.command;
- let data = message.data;
-
- switch (command) {
- case COMMAND_PROFILE_CHANGE:
- Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, data.uid);
- break;
- case COMMAND_LOGIN:
- this._helpers.login(data).catch(error =>
- this._sendError(error, message, sendingContext));
- break;
- case COMMAND_LOGOUT:
- case COMMAND_DELETE:
- this._helpers.logout(data.uid).catch(error =>
- this._sendError(error, message, sendingContext));
- break;
- case COMMAND_CAN_LINK_ACCOUNT:
- let canLinkAccount = this._helpers.shouldAllowRelink(data.email);
-
- let response = {
- command: command,
- messageId: message.messageId,
- data: { ok: canLinkAccount }
- };
-
- log.debug("FxAccountsWebChannel response", response);
- this._channel.send(response, sendingContext);
- break;
- case COMMAND_SYNC_PREFERENCES:
- this._helpers.openSyncPreferences(sendingContext.browser, data.entryPoint);
- break;
- case COMMAND_CHANGE_PASSWORD:
- this._helpers.changePassword(data).catch(error =>
- this._sendError(error, message, sendingContext));
- break;
- default:
- log.warn("Unrecognized FxAccountsWebChannel command", command);
- break;
- }
- },
-
- _sendError(error, incomingMessage, sendingContext) {
- log.error("Failed to handle FxAccountsWebChannel message", error);
- this._channel.send({
- command: incomingMessage.command,
- messageId: incomingMessage.messageId,
- data: {
- error: getErrorDetails(error),
- },
- }, sendingContext);
- },
-
- /**
- * Create a new channel with the WebChannelBroker, setup a callback listener
- * @private
- */
- _registerChannel() {
- /**
- * Processes messages that are called back from the FxAccountsChannel
- *
- * @param webChannelId {String}
- * Command webChannelId
- * @param message {Object}
- * Command message
- * @param sendingContext {Object}
- * Message sending context.
- * @param sendingContext.browser {browser}
- * The <browser> object that captured the
- * WebChannelMessageToChrome.
- * @param sendingContext.eventTarget {EventTarget}
- * The <EventTarget> where the message was sent.
- * @param sendingContext.principal {Principal}
- * The <Principal> of the EventTarget where the message was sent.
- * @private
- *
- */
- let listener = (webChannelId, message, sendingContext) => {
- if (message) {
- log.debug("FxAccountsWebChannel message received", message.command);
- if (logPII) {
- log.debug("FxAccountsWebChannel message details", message);
- }
- try {
- this._receiveMessage(message, sendingContext);
- } catch (error) {
- this._sendError(error, message, sendingContext);
- }
- }
- };
-
- this._channelCallback = listener;
- this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
- this._channel.listen(listener);
- log.debug("FxAccountsWebChannel registered: " + this._webChannelId + " with origin " + this._webChannelOrigin.prePath);
- }
-};
-
-this.FxAccountsWebChannelHelpers = function(options) {
- options = options || {};
-
- this._fxAccounts = options.fxAccounts || fxAccounts;
-};
-
-this.FxAccountsWebChannelHelpers.prototype = {
- // If the last fxa account used for sync isn't this account, we display
- // a modal dialog checking they really really want to do this...
- // (This is sync-specific, so ideally would be in sync's identity module,
- // but it's a little more seamless to do here, and sync is currently the
- // only fxa consumer, so...
- shouldAllowRelink(acctName) {
- return !this._needRelinkWarning(acctName) ||
- this._promptForRelink(acctName);
- },
-
- /**
- * New users are asked in the content server whether they want to
- * customize which data should be synced. The user is only shown
- * the dialog listing the possible data types upon verification.
- *
- * Save a bit into prefs that is read on verification to see whether
- * to show the list of data types that can be saved.
- */
- setShowCustomizeSyncPref(showCustomizeSyncPref) {
- Services.prefs.setBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION, showCustomizeSyncPref);
- },
-
- getShowCustomizeSyncPref() {
- return Services.prefs.getBoolPref(PREF_SYNC_SHOW_CUSTOMIZATION);
- },
-
- /**
- * stores sync login info it in the fxaccounts service
- *
- * @param accountData the user's account data and credentials
- */
- login(accountData) {
- if (accountData.customizeSync) {
- this.setShowCustomizeSyncPref(true);
- delete accountData.customizeSync;
- }
-
- if (accountData.declinedSyncEngines) {
- let declinedSyncEngines = accountData.declinedSyncEngines;
- log.debug("Received declined engines", declinedSyncEngines);
- Weave.Service.engineManager.setDeclined(declinedSyncEngines);
- declinedSyncEngines.forEach(engine => {
- Services.prefs.setBoolPref("services.sync.engine." + engine, false);
- });
-
- // if we got declinedSyncEngines that means we do not need to show the customize screen.
- this.setShowCustomizeSyncPref(false);
- delete accountData.declinedSyncEngines;
- }
-
- // the user has already been shown the "can link account"
- // screen. No need to keep this data around.
- delete accountData.verifiedCanLinkAccount;
-
- // Remember who it was so we can log out next time.
- this.setPreviousAccountNameHashPref(accountData.email);
-
- // A sync-specific hack - we want to ensure sync has been initialized
- // before we set the signed-in user.
- let xps = Cc["@mozilla.org/weave/service;1"]
- .getService(Ci.nsISupports)
- .wrappedJSObject;
- return xps.whenLoaded().then(() => {
- return this._fxAccounts.setSignedInUser(accountData);
- });
- },
-
- /**
- * logout the fxaccounts service
- *
- * @param the uid of the account which have been logged out
- */
- logout(uid) {
- return fxAccounts.getSignedInUser().then(userData => {
- if (userData.uid === uid) {
- // true argument is `localOnly`, because server-side stuff
- // has already been taken care of by the content server
- return fxAccounts.signOut(true);
- }
- });
- },
-
- changePassword(credentials) {
- // If |credentials| has fields that aren't handled by accounts storage,
- // updateUserAccountData will throw - mainly to prevent errors in code
- // that hard-codes field names.
- // However, in this case the field names aren't really in our control.
- // We *could* still insist the server know what fields names are valid,
- // but that makes life difficult for the server when Firefox adds new
- // features (ie, new fields) - forcing the server to track a map of
- // versions to supported field names doesn't buy us much.
- // So we just remove field names we know aren't handled.
- let newCredentials = {
- deviceId: null
- };
- for (let name of Object.keys(credentials)) {
- if (name == "email" || name == "uid" || FxAccountsStorageManagerCanStoreField(name)) {
- newCredentials[name] = credentials[name];
- } else {
- log.info("changePassword ignoring unsupported field", name);
- }
- }
- return this._fxAccounts.updateUserAccountData(newCredentials)
- .then(() => this._fxAccounts.updateDeviceRegistration());
- },
-
- /**
- * Get the hash of account name of the previously signed in account
- */
- getPreviousAccountNameHashPref() {
- try {
- return Services.prefs.getComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString).data;
- } catch (_) {
- return "";
- }
- },
-
- /**
- * Given an account name, set the hash of the previously signed in account
- *
- * @param acctName the account name of the user's account.
- */
- setPreviousAccountNameHashPref(acctName) {
- let string = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- string.data = this.sha256(acctName);
- Services.prefs.setComplexValue(PREF_LAST_FXA_USER, Ci.nsISupportsString, string);
- },
-
- /**
- * Given a string, returns the SHA265 hash in base64
- */
- sha256(str) {
- let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = "UTF-8";
- // Data is an array of bytes.
- let data = converter.convertToByteArray(str, {});
- let hasher = Cc["@mozilla.org/security/hash;1"]
- .createInstance(Ci.nsICryptoHash);
- hasher.init(hasher.SHA256);
- hasher.update(data, data.length);
-
- return hasher.finish(true);
- },
-
- /**
- * Open Sync Preferences in the current tab of the browser
- *
- * @param {Object} browser the browser in which to open preferences
- * @param {String} [entryPoint] entryPoint to use for logging
- */
- openSyncPreferences(browser, entryPoint) {
- let uri = "about:preferences";
- if (entryPoint) {
- uri += "?entrypoint=" + encodeURIComponent(entryPoint);
- }
- uri += "#sync";
-
- browser.loadURI(uri);
- },
-
- /**
- * If a user signs in using a different account, the data from the
- * previous account and the new account will be merged. Ask the user
- * if they want to continue.
- *
- * @private
- */
- _needRelinkWarning(acctName) {
- let prevAcctHash = this.getPreviousAccountNameHashPref();
- return prevAcctHash && prevAcctHash != this.sha256(acctName);
- },
-
- /**
- * Show the user a warning dialog that the data from the previous account
- * and the new account will be merged.
- *
- * @private
- */
- _promptForRelink(acctName) {
- let sb = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
- let continueLabel = sb.GetStringFromName("continue.label");
- let title = sb.GetStringFromName("relinkVerify.title");
- let description = sb.formatStringFromName("relinkVerify.description",
- [acctName], 1);
- let body = sb.GetStringFromName("relinkVerify.heading") +
- "\n\n" + description;
- let ps = Services.prompt;
- let buttonFlags = (ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING) +
- (ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL) +
- ps.BUTTON_POS_1_DEFAULT;
-
- // If running in context of the browser chrome, window does not exist.
- var targetWindow = typeof window === 'undefined' ? null : window;
- let pressed = Services.prompt.confirmEx(targetWindow, title, body, buttonFlags,
- continueLabel, null, null, null,
- {});
- return pressed === 0; // 0 is the "continue" button
- }
-};
-
-var singleton;
-// The entry-point for this module, which ensures only one of our channels is
-// ever created - we require this because the WebChannel is global in scope
-// (eg, it uses the observer service to tell interested parties of interesting
-// things) and allowing multiple channels would cause such notifications to be
-// sent multiple times.
-this.EnsureFxAccountsWebChannel = function() {
- let contentUri = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.webchannel.uri");
- if (singleton && singleton._contentUri !== contentUri) {
- singleton.tearDown();
- singleton = null;
- }
- if (!singleton) {
- try {
- if (contentUri) {
- // The FxAccountsWebChannel listens for events and updates
- // the state machine accordingly.
- singleton = new this.FxAccountsWebChannel({
- content_uri: contentUri,
- channel_id: WEBCHANNEL_ID,
- });
- } else {
- log.warn("FxA WebChannel functionaly is disabled due to no URI pref.");
- }
- } catch (ex) {
- log.error("Failed to create FxA WebChannel", ex);
- }
- }
-}
diff --git a/services/fxaccounts/interfaces/moz.build b/services/fxaccounts/interfaces/moz.build
deleted file mode 100644
index ac80b3e93..000000000
--- a/services/fxaccounts/interfaces/moz.build
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# 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/.
-
-XPIDL_SOURCES += [
- 'nsIFxAccountsUIGlue.idl'
-]
-
-XPIDL_MODULE = 'services_fxaccounts'
diff --git a/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl b/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl
deleted file mode 100644
index 950fdbc25..000000000
--- a/services/fxaccounts/interfaces/nsIFxAccountsUIGlue.idl
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-#include "nsISupports.idl"
-
-[scriptable, uuid(ab8d0700-9577-11e3-a5e2-0800200c9a66)]
-interface nsIFxAccountsUIGlue : nsISupports
-{
- // Returns a Promise.
- jsval signInFlow();
-
- // Returns a Promise.
- jsval refreshAuthentication(in DOMString email);
-};
diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build
deleted file mode 100644
index b1cd3b59c..000000000
--- a/services/fxaccounts/moz.build
+++ /dev/null
@@ -1,32 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# 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/.
-
-DIRS += ['interfaces']
-
-MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
-
-XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
-
-EXTRA_COMPONENTS += [
- 'FxAccountsComponents.manifest',
- 'FxAccountsPush.js',
-]
-
-EXTRA_JS_MODULES += [
- 'Credentials.jsm',
- 'FxAccounts.jsm',
- 'FxAccountsClient.jsm',
- 'FxAccountsCommon.js',
- 'FxAccountsConfig.jsm',
- 'FxAccountsOAuthClient.jsm',
- 'FxAccountsOAuthGrantClient.jsm',
- 'FxAccountsProfile.jsm',
- 'FxAccountsProfileClient.jsm',
- 'FxAccountsPush.js',
- 'FxAccountsStorage.jsm',
- 'FxAccountsWebChannel.jsm',
-]
-
diff --git a/services/fxaccounts/tests/mochitest/chrome.ini b/services/fxaccounts/tests/mochitest/chrome.ini
deleted file mode 100644
index ab2e77053..000000000
--- a/services/fxaccounts/tests/mochitest/chrome.ini
+++ /dev/null
@@ -1,7 +0,0 @@
-[DEFAULT]
-skip-if = os == 'android'
-support-files=
- file_invalidEmailCase.sjs
-
-[test_invalidEmailCase.html]
-
diff --git a/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs b/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs
deleted file mode 100644
index 9d97ac70c..000000000
--- a/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs
+++ /dev/null
@@ -1,80 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * This server simulates the behavior of /account/login on the Firefox Accounts
- * auth server in the case where the user is trying to sign in with an email
- * with the wrong capitalization.
- *
- * https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountlogin
- *
- * The expected behavior is that on the first attempt, with the wrong email,
- * the server will respond with a 400 and the canonical email capitalization
- * that the client should use. The client then has one chance to sign in with
- * this different capitalization.
- *
- * In this test, the user with the account id "Greta.Garbo@gmail.COM" initially
- * tries to sign in as "greta.garbo@gmail.com".
- *
- * On success, the client is responsible for updating its sign-in user state
- * and recording the proper email capitalization.
- */
-
-const CC = Components.Constructor;
-const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
- "nsIBinaryInputStream",
- "setInputStream");
-
-const goodEmail = "Greta.Garbo@gmail.COM";
-const badEmail = "greta.garbo@gmail.com";
-
-function handleRequest(request, response) {
- let body = new BinaryInputStream(request.bodyInputStream);
- let bytes = [];
- let available;
- while ((available = body.available()) > 0) {
- Array.prototype.push.apply(bytes, body.readByteArray(available));
- }
-
- let data = JSON.parse(String.fromCharCode.apply(null, bytes));
- let message;
-
- switch (data.email) {
- case badEmail:
- // Almost - try again with fixed email case
- message = {
- code: 400,
- errno: 120,
- error: "Incorrect email case",
- email: goodEmail,
- };
- response.setStatusLine(request.httpVersion, 400, "Almost");
- break;
-
- case goodEmail:
- // Successful login.
- message = {
- uid: "your-uid",
- sessionToken: "your-sessionToken",
- keyFetchToken: "your-keyFetchToken",
- verified: true,
- authAt: 1392144866,
- };
- response.setStatusLine(request.httpVersion, 200, "Yay");
- break;
-
- default:
- // Anything else happening in this test is a failure.
- message = {
- code: 400,
- errno: 999,
- error: "What happened!?",
- };
- response.setStatusLine(request.httpVersion, 400, "Ouch");
- break;
- }
-
- messageStr = JSON.stringify(message);
- response.bodyOutputStream.write(messageStr, messageStr.length);
-}
-
diff --git a/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html b/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html
deleted file mode 100644
index 52866cc4b..000000000
--- a/services/fxaccounts/tests/mochitest/test_invalidEmailCase.html
+++ /dev/null
@@ -1,131 +0,0 @@
-<!--
- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/
--->
-<!DOCTYPE HTML>
-<html>
-<!--
-Tests for Firefox Accounts signin with invalid email case
-https://bugzilla.mozilla.org/show_bug.cgi?id=963835
--->
-<head>
- <title>Test for Firefox Accounts (Bug 963835)</title>
- <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
- <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-</head>
-<body>
-
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=963835">Mozilla Bug 963835</a>
-<p id="display"></p>
-<div id="content" style="display: none">
- Test for correction of invalid email case in Fx Accounts signIn
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript;version=1.8">
-
-SimpleTest.waitForExplicitFinish();
-
-Components.utils.import("resource://gre/modules/Promise.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-Components.utils.import("resource://gre/modules/FxAccounts.jsm");
-Components.utils.import("resource://gre/modules/FxAccountsClient.jsm");
-Components.utils.import("resource://services-common/hawkclient.js");
-
-const TEST_SERVER =
- "http://mochi.test:8888/chrome/services/fxaccounts/tests/mochitest/file_invalidEmailCase.sjs?path=";
-
-let MockStorage = function() {
- this.data = null;
-};
-MockStorage.prototype = Object.freeze({
- set: function (contents) {
- this.data = contents;
- return Promise.resolve(null);
- },
- get: function () {
- return Promise.resolve(this.data);
- },
- getOAuthTokens() {
- return Promise.resolve(null);
- },
- setOAuthTokens(contents) {
- return Promise.resolve();
- },
-});
-
-function MockFxAccounts() {
- return new FxAccounts({
- _now_is: new Date(),
-
- now: function() {
- return this._now_is;
- },
-
- signedInUserStorage: new MockStorage(),
-
- fxAccountsClient: new FxAccountsClient(TEST_SERVER),
- });
-}
-
-let wrongEmail = "greta.garbo@gmail.com";
-let rightEmail = "Greta.Garbo@gmail.COM";
-let password = "123456";
-
-function runTest() {
- is(Services.prefs.getCharPref("identity.fxaccounts.auth.uri"), TEST_SERVER,
- "Pref for auth.uri should be set to test server");
-
- let fxa = new MockFxAccounts();
- let client = fxa.internal.fxAccountsClient;
-
- ok(true, !!fxa, "Couldn't mock fxa");
- ok(true, !!client, "Couldn't mock fxa client");
- is(client.host, TEST_SERVER, "Should be using the test auth server uri");
-
- // First try to sign in using the email with the wrong capitalization. The
- // FxAccountsClient will receive a 400 from the server with the corrected email.
- // It will automatically try to sign in again. We expect this to succeed.
- client.signIn(wrongEmail, password).then(
- user => {
-
- // Now store the signed-in user state. This will include the correct
- // email capitalization.
- fxa.setSignedInUser(user).then(
- () => {
-
- // Confirm that the correct email got stored.
- fxa.getSignedInUser().then(
- data => {
- is(data.email, rightEmail);
- SimpleTest.finish();
- },
- getUserError => {
- ok(false, JSON.stringify(getUserError));
- }
- );
- },
- setSignedInUserError => {
- ok(false, JSON.stringify(setSignedInUserError));
- }
- );
- },
- signInError => {
- ok(false, JSON.stringify(signInError));
- }
- );
-};
-
-SpecialPowers.pushPrefEnv({"set": [
- ["identity.fxaccounts.enabled", true], // fx accounts
- ["identity.fxaccounts.auth.uri", TEST_SERVER], // our sjs server
- ["toolkit.identity.debug", true], // verbose identity logging
- ["browser.dom.window.dump.enabled", true],
- ]},
- function () { runTest(); }
-);
-
-</script>
-</pre>
-</body>
-</html>
-
diff --git a/services/fxaccounts/tests/xpcshell/head.js b/services/fxaccounts/tests/xpcshell/head.js
deleted file mode 100644
index ed70fdac5..000000000
--- a/services/fxaccounts/tests/xpcshell/head.js
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-var {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
-
-"use strict";
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-(function initFxAccountsTestingInfrastructure() {
- do_get_profile();
-
- let ns = {};
- Cu.import("resource://testing-common/services/common/logging.js", ns);
-
- ns.initTestLogging("Trace");
-}).call(this);
-
diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js
deleted file mode 100644
index d6139a076..000000000
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ /dev/null
@@ -1,1531 +0,0 @@
-/* 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/FxAccountsOAuthGrantClient.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/Log.jsm");
-
-// We grab some additional stuff via backstage passes.
-var {AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
-
-const ONE_HOUR_MS = 1000 * 60 * 60;
-const ONE_DAY_MS = ONE_HOUR_MS * 24;
-const TWO_MINUTES_MS = 1000 * 60 * 2;
-
-initTestLogging("Trace");
-
-// XXX until bug 937114 is fixed
-Cu.importGlobalProperties(['atob']);
-
-var log = Log.repository.getLogger("Services.FxAccounts.test");
-log.level = Log.Level.Debug;
-
-// See verbose logging from FxAccounts.jsm
-Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
-Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace;
-
-// The oauth server is mocked, but set these prefs to pass param checks
-Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
-Services.prefs.setCharPref("identity.fxaccounts.oauth.client_id", "abc123");
-
-
-const PROFILE_SERVER_URL = "http://example.com/v1";
-const CONTENT_URL = "http://accounts.example.com/";
-
-Services.prefs.setCharPref("identity.fxaccounts.remote.profile.uri", PROFILE_SERVER_URL);
-Services.prefs.setCharPref("identity.fxaccounts.settings.uri", CONTENT_URL);
-
-/*
- * The FxAccountsClient communicates with the remote Firefox
- * Accounts auth server. Mock the server calls, with a little
- * lag time to simulate some latency.
- *
- * We add the _verified attribute to mock the change in verification
- * state on the FXA server.
- */
-
-function MockStorageManager() {
-}
-
-MockStorageManager.prototype = {
- promiseInitialized: Promise.resolve(),
-
- 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() {
- 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;
- };
-
- this.accountKeys = function (keyFetchToken) {
- let deferred = Promise.defer();
-
- do_timeout(50, () => {
- let response = {
- kA: expandBytes("11"),
- wrapKB: expandBytes("22")
- };
- deferred.resolve(response);
- });
- return deferred.promise;
- };
-
- this.resendVerificationEmail = function(sessionToken) {
- // Return the session token to show that we received it in the first place
- return Promise.resolve(sessionToken);
- };
-
- this.signCertificate = function() { throw "no" };
-
- this.signOut = () => Promise.resolve();
- this.signOutAndDestroyDevice = () => Promise.resolve({});
-
- FxAccountsClient.apply(this);
-}
-MockFxAccountsClient.prototype = {
- __proto__: FxAccountsClient.prototype
-}
-
-/*
- * We need to mock the FxAccounts module's interfaces to external
- * services, such as storage and the FxAccounts client. We also
- * mock the now() method, so that we can simulate the passing of
- * time and verify that signatures expire correctly.
- */
-function MockFxAccounts() {
- return new FxAccounts({
- VERIFICATION_POLL_TIMEOUT_INITIAL: 100, // 100ms
-
- _getCertificateSigned_calls: [],
- _d_signCertificate: Promise.defer(),
- _now_is: new Date(),
- now: function () {
- return this._now_is;
- },
- newAccountState(credentials) {
- // we use a real accountState but mocked storage.
- let storage = new MockStorageManager();
- storage.initialize(credentials);
- return new AccountState(storage);
- },
- getCertificateSigned: function (sessionToken, serializedPublicKey) {
- _("mock getCertificateSigned\n");
- this._getCertificateSigned_calls.push([sessionToken, serializedPublicKey]);
- return this._d_signCertificate.promise;
- },
- _registerOrUpdateDevice() {
- return Promise.resolve();
- },
- fxAccountsClient: new MockFxAccountsClient()
- });
-}
-
-/*
- * Some tests want a "real" fxa instance - however, we still mock the storage
- * to keep the tests fast on b2g.
- */
-function MakeFxAccounts(internal = {}) {
- if (!internal.newAccountState) {
- // we use a real accountState but mocked storage.
- internal.newAccountState = function(credentials) {
- let storage = new MockStorageManager();
- storage.initialize(credentials);
- return new AccountState(storage);
- };
- }
- if (!internal._signOutServer) {
- internal._signOutServer = () => Promise.resolve();
- }
- if (!internal._registerOrUpdateDevice) {
- internal._registerOrUpdateDevice = () => Promise.resolve();
- }
- return new FxAccounts(internal);
-}
-
-add_task(function* test_non_https_remote_server_uri_with_requireHttps_false() {
- Services.prefs.setBoolPref(
- "identity.fxaccounts.allowHttp",
- true);
- Services.prefs.setCharPref(
- "identity.fxaccounts.remote.signup.uri",
- "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
- do_check_eq(yield fxAccounts.promiseAccountsSignUpURI(),
- "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
-
- Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
- Services.prefs.clearUserPref("identity.fxaccounts.allowHttp");
-});
-
-add_task(function* test_non_https_remote_server_uri() {
- Services.prefs.setCharPref(
- "identity.fxaccounts.remote.signup.uri",
- "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
- rejects(fxAccounts.promiseAccountsSignUpURI(), null, "Firefox Accounts server must use HTTPS");
- Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
-});
-
-add_task(function* test_get_signed_in_user_initially_unset() {
- _("Check getSignedInUser initially and after signout reports no user");
- let account = MakeFxAccounts();
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- assertion: "foobar",
- sessionToken: "dead",
- kA: "beef",
- kB: "cafe",
- verified: true
- };
- let result = yield account.getSignedInUser();
- do_check_eq(result, null);
-
- yield account.setSignedInUser(credentials);
- let histogram = Services.telemetry.getHistogramById("FXA_CONFIGURED");
- do_check_eq(histogram.snapshot().sum, 1);
- histogram.clear();
-
- result = yield account.getSignedInUser();
- do_check_eq(result.email, credentials.email);
- do_check_eq(result.assertion, credentials.assertion);
- do_check_eq(result.kB, credentials.kB);
-
- // Delete the memory cache and force the user
- // to be read and parsed from storage (e.g. disk via JSONStorage).
- delete account.internal.signedInUser;
- result = yield account.getSignedInUser();
- do_check_eq(result.email, credentials.email);
- do_check_eq(result.assertion, credentials.assertion);
- do_check_eq(result.kB, credentials.kB);
-
- // sign out
- let localOnly = true;
- yield account.signOut(localOnly);
-
- // user should be undefined after sign out
- result = yield account.getSignedInUser();
- do_check_eq(result, null);
-});
-
-add_task(function* test_update_account_data() {
- _("Check updateUserAccountData does the right thing.");
- let account = MakeFxAccounts();
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- assertion: "foobar",
- sessionToken: "dead",
- kA: "beef",
- kB: "cafe",
- verified: true
- };
- yield account.setSignedInUser(credentials);
-
- let newCreds = {
- email: credentials.email,
- uid: credentials.uid,
- assertion: "new_assertion",
- }
- yield account.updateUserAccountData(newCreds);
- do_check_eq((yield account.getSignedInUser()).assertion, "new_assertion",
- "new field value was saved");
-
- // but we should fail attempting to change email or uid.
- newCreds = {
- email: "someoneelse@example.com",
- uid: credentials.uid,
- assertion: "new_assertion",
- }
- yield Assert.rejects(account.updateUserAccountData(newCreds));
- newCreds = {
- email: credentials.email,
- uid: "another_uid",
- assertion: "new_assertion",
- }
- yield Assert.rejects(account.updateUserAccountData(newCreds));
-
- // should fail without email or uid.
- newCreds = {
- assertion: "new_assertion",
- }
- yield Assert.rejects(account.updateUserAccountData(newCreds));
-
- // and should fail with a field name that's not known by storage.
- newCreds = {
- email: credentials.email,
- uid: "another_uid",
- foo: "bar",
- }
- yield Assert.rejects(account.updateUserAccountData(newCreds));
-});
-
-add_task(function* test_getCertificateOffline() {
- _("getCertificateOffline()");
- let fxa = MakeFxAccounts();
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- sessionToken: "dead",
- verified: true,
- };
-
- yield fxa.setSignedInUser(credentials);
-
- // Test that an expired cert throws if we're offline.
- let offline = Services.io.offline;
- Services.io.offline = true;
- yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState).then(
- result => {
- Services.io.offline = offline;
- do_throw("Unexpected success");
- },
- err => {
- Services.io.offline = offline;
- // ... so we have to check the error string.
- do_check_eq(err, "Error: OFFLINE");
- }
- );
- yield fxa.signOut(/*localOnly = */true);
-});
-
-add_task(function* test_getCertificateCached() {
- _("getCertificateCached()");
- let fxa = MakeFxAccounts();
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- sessionToken: "dead",
- verified: true,
- // A cached keypair and cert that remain valid.
- keyPair: {
- validUntil: Date.now() + KEY_LIFETIME + 10000,
- rawKeyPair: "good-keypair",
- },
- cert: {
- validUntil: Date.now() + CERT_LIFETIME + 10000,
- rawCert: "good-cert",
- },
- };
-
- yield fxa.setSignedInUser(credentials);
- let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
- // should have the same keypair and cert.
- do_check_eq(keyPair, credentials.keyPair.rawKeyPair);
- do_check_eq(certificate, credentials.cert.rawCert);
- yield fxa.signOut(/*localOnly = */true);
-});
-
-add_task(function* test_getCertificateExpiredCert() {
- _("getCertificateExpiredCert()");
- let fxa = MakeFxAccounts({
- getCertificateSigned() {
- return "new cert";
- }
- });
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- sessionToken: "dead",
- verified: true,
- // A cached keypair that remains valid.
- keyPair: {
- validUntil: Date.now() + KEY_LIFETIME + 10000,
- rawKeyPair: "good-keypair",
- },
- // A cached certificate which has expired.
- cert: {
- validUntil: Date.parse("Mon, 13 Jan 2000 21:45:06 GMT"),
- rawCert: "expired-cert",
- },
- };
- yield fxa.setSignedInUser(credentials);
- let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
- // should have the same keypair but a new cert.
- do_check_eq(keyPair, credentials.keyPair.rawKeyPair);
- do_check_neq(certificate, credentials.cert.rawCert);
- yield fxa.signOut(/*localOnly = */true);
-});
-
-add_task(function* test_getCertificateExpiredKeypair() {
- _("getCertificateExpiredKeypair()");
- let fxa = MakeFxAccounts({
- getCertificateSigned() {
- return "new cert";
- },
- });
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- sessionToken: "dead",
- verified: true,
- // A cached keypair that has expired.
- keyPair: {
- validUntil: Date.now() - 1000,
- rawKeyPair: "expired-keypair",
- },
- // A cached certificate which remains valid.
- cert: {
- validUntil: Date.now() + CERT_LIFETIME + 10000,
- rawCert: "expired-cert",
- },
- };
-
- yield fxa.setSignedInUser(credentials);
- let {keyPair, certificate} = yield fxa.internal.getKeypairAndCertificate(fxa.internal.currentAccountState);
- // even though the cert was valid, the fact the keypair was not means we
- // should have fetched both.
- do_check_neq(keyPair, credentials.keyPair.rawKeyPair);
- do_check_neq(certificate, credentials.cert.rawCert);
- yield fxa.signOut(/*localOnly = */true);
-});
-
-// Sanity-check that our mocked client is working correctly
-add_test(function test_client_mock() {
- let fxa = new MockFxAccounts();
- let client = fxa.internal.fxAccountsClient;
- do_check_eq(client._verified, false);
- do_check_eq(typeof client.signIn, "function");
-
- // The recoveryEmailStatus function eventually fulfills its promise
- client.recoveryEmailStatus()
- .then(response => {
- do_check_eq(response.verified, false);
- run_next_test();
- });
-});
-
-// Sign in a user, and after a little while, verify the user's email.
-// Right after signing in the user, we should get the 'onlogin' notification.
-// Polling should detect that the email is verified, and eventually
-// 'onverified' should be observed
-add_test(function test_verification_poll() {
- let fxa = new MockFxAccounts();
- let test_user = getTestUser("francine");
- let login_notification_received = false;
-
- makeObserver(ONVERIFIED_NOTIFICATION, function() {
- log.debug("test_verification_poll observed onverified");
- // Once email verification is complete, we will observe onverified
- fxa.internal.getUserAccountData().then(user => {
- // And confirm that the user's state has changed
- do_check_eq(user.verified, true);
- do_check_eq(user.email, test_user.email);
- do_check_true(login_notification_received);
- run_next_test();
- });
- });
-
- makeObserver(ONLOGIN_NOTIFICATION, function() {
- log.debug("test_verification_poll observer onlogin");
- login_notification_received = true;
- });
-
- fxa.setSignedInUser(test_user).then(() => {
- fxa.internal.getUserAccountData().then(user => {
- // The user is signing in, but email has not been verified yet
- do_check_eq(user.verified, false);
- do_timeout(200, function() {
- log.debug("Mocking verification of francine's email");
- fxa.internal.fxAccountsClient._email = test_user.email;
- fxa.internal.fxAccountsClient._verified = true;
- });
- });
- });
-});
-
-// Sign in the user, but never verify the email. The check-email
-// poll should time out. No verifiedlogin event should be observed, and the
-// internal whenVerified promise should be rejected
-add_test(function test_polling_timeout() {
- // This test could be better - the onverified observer might fire on
- // somebody else's stack, and we're not making sure that we're not receiving
- // such a message. In other words, this tests either failure, or success, but
- // not both.
-
- let fxa = new MockFxAccounts();
- let test_user = getTestUser("carol");
-
- let removeObserver = makeObserver(ONVERIFIED_NOTIFICATION, function() {
- do_throw("We should not be getting a login event!");
- });
-
- fxa.internal.POLL_SESSION = 1;
-
- let p = fxa.internal.whenVerified({});
-
- fxa.setSignedInUser(test_user).then(() => {
- p.then(
- (success) => {
- do_throw("this should not succeed");
- },
- (fail) => {
- removeObserver();
- fxa.signOut().then(run_next_test);
- }
- );
- });
-});
-
-add_test(function test_getKeys() {
- let fxa = new MockFxAccounts();
- let user = getTestUser("eusebius");
-
- // Once email has been verified, we will be able to get keys
- user.verified = true;
-
- fxa.setSignedInUser(user).then(() => {
- fxa.getSignedInUser().then((user) => {
- // Before getKeys, we have no keys
- do_check_eq(!!user.kA, false);
- do_check_eq(!!user.kB, false);
- // And we still have a key-fetch token and unwrapBKey to use
- do_check_eq(!!user.keyFetchToken, true);
- do_check_eq(!!user.unwrapBKey, true);
-
- fxa.internal.getKeys().then(() => {
- fxa.getSignedInUser().then((user) => {
- // Now we should have keys
- do_check_eq(fxa.internal.isUserEmailVerified(user), true);
- do_check_eq(!!user.verified, true);
- do_check_eq(user.kA, expandHex("11"));
- do_check_eq(user.kB, expandHex("66"));
- do_check_eq(user.keyFetchToken, undefined);
- do_check_eq(user.unwrapBKey, undefined);
- run_next_test();
- });
- });
- });
- });
-});
-
-add_task(function* test_getKeys_nonexistent_account() {
- let fxa = new MockFxAccounts();
- let bismarck = getTestUser("bismarck");
-
- let client = fxa.internal.fxAccountsClient;
- client.accountStatus = () => Promise.resolve(false);
- client.accountKeys = () => {
- return Promise.reject({
- code: 401,
- errno: ERRNO_INVALID_AUTH_TOKEN,
- });
- };
-
- yield fxa.setSignedInUser(bismarck);
-
- let promiseLogout = new Promise(resolve => {
- makeObserver(ONLOGOUT_NOTIFICATION, function() {
- log.debug("test_getKeys_nonexistent_account observed logout");
- resolve();
- });
- });
-
- try {
- yield fxa.internal.getKeys();
- do_check_true(false);
- } catch (err) {
- do_check_eq(err.code, 401);
- do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
- }
-
- yield promiseLogout;
-
- let user = yield fxa.internal.getUserAccountData();
- do_check_eq(user, null);
-});
-
-// getKeys with invalid keyFetchToken should delete keyFetchToken from storage
-add_task(function* test_getKeys_invalid_token() {
- let fxa = new MockFxAccounts();
- let yusuf = getTestUser("yusuf");
-
- let client = fxa.internal.fxAccountsClient;
- client.accountStatus = () => Promise.resolve(true);
- client.accountKeys = () => {
- return Promise.reject({
- code: 401,
- errno: ERRNO_INVALID_AUTH_TOKEN,
- });
- };
-
- yield fxa.setSignedInUser(yusuf);
-
- try {
- yield fxa.internal.getKeys();
- do_check_true(false);
- } catch (err) {
- do_check_eq(err.code, 401);
- do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
- }
-
- let user = yield fxa.internal.getUserAccountData();
- do_check_eq(user.email, yusuf.email);
- do_check_eq(user.keyFetchToken, null);
-});
-
-// fetchAndUnwrapKeys with no keyFetchToken should trigger signOut
-add_test(function test_fetchAndUnwrapKeys_no_token() {
- let fxa = new MockFxAccounts();
- let user = getTestUser("lettuce.protheroe");
- delete user.keyFetchToken
-
- makeObserver(ONLOGOUT_NOTIFICATION, function() {
- log.debug("test_fetchAndUnwrapKeys_no_token observed logout");
- fxa.internal.getUserAccountData().then(user => {
- run_next_test();
- });
- });
-
- fxa.setSignedInUser(user).then(
- user => {
- return fxa.internal.fetchAndUnwrapKeys();
- }
- ).then(
- null,
- error => {
- log.info("setSignedInUser correctly rejected");
- }
- )
-});
-
-// Alice (User A) signs up but never verifies her email. Then Bob (User B)
-// signs in with a verified email. Ensure that no sign-in events are triggered
-// on Alice's behalf. In the end, Bob should be the signed-in user.
-add_test(function test_overlapping_signins() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- let bob = getTestUser("bob");
-
- makeObserver(ONVERIFIED_NOTIFICATION, function() {
- log.debug("test_overlapping_signins observed onverified");
- // Once email verification is complete, we will observe onverified
- fxa.internal.getUserAccountData().then(user => {
- do_check_eq(user.email, bob.email);
- do_check_eq(user.verified, true);
- run_next_test();
- });
- });
-
- // Alice is the user signing in; her email is unverified.
- fxa.setSignedInUser(alice).then(() => {
- log.debug("Alice signing in ...");
- fxa.internal.getUserAccountData().then(user => {
- do_check_eq(user.email, alice.email);
- do_check_eq(user.verified, false);
- log.debug("Alice has not verified her email ...");
-
- // Now Bob signs in instead and actually verifies his email
- log.debug("Bob signing in ...");
- fxa.setSignedInUser(bob).then(() => {
- do_timeout(200, function() {
- // Mock email verification ...
- log.debug("Bob verifying his email ...");
- fxa.internal.fxAccountsClient._verified = true;
- });
- });
- });
- });
-});
-
-add_task(function* test_getAssertion_invalid_token() {
- let fxa = new MockFxAccounts();
-
- let client = fxa.internal.fxAccountsClient;
- client.accountStatus = () => Promise.resolve(true);
-
- let creds = {
- sessionToken: "sessionToken",
- kA: expandHex("11"),
- kB: expandHex("66"),
- verified: true,
- email: "sonia@example.com",
- };
- yield fxa.setSignedInUser(creds);
-
- try {
- let promiseAssertion = fxa.getAssertion("audience.example.com");
- fxa.internal._d_signCertificate.reject({
- code: 401,
- errno: ERRNO_INVALID_AUTH_TOKEN,
- });
- yield promiseAssertion;
- do_check_true(false, "getAssertion should reject invalid session token");
- } catch (err) {
- do_check_eq(err.code, 401);
- do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
- }
-
- let user = yield fxa.internal.getUserAccountData();
- do_check_eq(user.email, creds.email);
- do_check_eq(user.sessionToken, null);
-});
-
-add_task(function* test_getAssertion() {
- let fxa = new MockFxAccounts();
-
- do_check_throws(function* () {
- yield fxa.getAssertion("nonaudience");
- });
-
- let creds = {
- sessionToken: "sessionToken",
- kA: expandHex("11"),
- kB: expandHex("66"),
- verified: true
- };
- // By putting kA/kB/verified in "creds", we skip ahead
- // to the "we're ready" stage.
- yield fxa.setSignedInUser(creds);
-
- _("== ready to go\n");
- // Start with a nice arbitrary but realistic date. Here we use a nice RFC
- // 1123 date string like we would get from an HTTP header. Over the course of
- // the test, we will update 'now', but leave 'start' where it is.
- let now = Date.parse("Mon, 13 Jan 2014 21:45:06 GMT");
- let start = now;
- fxa.internal._now_is = now;
-
- let d = fxa.getAssertion("audience.example.com");
- // At this point, a thread has been spawned to generate the keys.
- _("-- back from fxa.getAssertion\n");
- fxa.internal._d_signCertificate.resolve("cert1");
- let assertion = yield d;
- do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1);
- do_check_eq(fxa.internal._getCertificateSigned_calls[0][0], "sessionToken");
- do_check_neq(assertion, null);
- _("ASSERTION: " + assertion + "\n");
- let pieces = assertion.split("~");
- do_check_eq(pieces[0], "cert1");
- let userData = yield fxa.getSignedInUser();
- let keyPair = userData.keyPair;
- let cert = userData.cert;
- do_check_neq(keyPair, undefined);
- _(keyPair.validUntil + "\n");
- let p2 = pieces[1].split(".");
- let header = JSON.parse(atob(p2[0]));
- _("HEADER: " + JSON.stringify(header) + "\n");
- do_check_eq(header.alg, "DS128");
- let payload = JSON.parse(atob(p2[1]));
- _("PAYLOAD: " + JSON.stringify(payload) + "\n");
- do_check_eq(payload.aud, "audience.example.com");
- do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
- do_check_eq(cert.validUntil, start + CERT_LIFETIME);
- _("delta: " + Date.parse(payload.exp - start) + "\n");
- let exp = Number(payload.exp);
-
- do_check_eq(exp, now + ASSERTION_LIFETIME);
-
- // Reset for next call.
- fxa.internal._d_signCertificate = Promise.defer();
-
- // Getting a new assertion "soon" (i.e., w/o incrementing "now"), even for
- // a new audience, should not provoke key generation or a signing request.
- assertion = yield fxa.getAssertion("other.example.com");
-
- // There were no additional calls - same number of getcert calls as before
- do_check_eq(fxa.internal._getCertificateSigned_calls.length, 1);
-
- // Wait an hour; assertion use period expires, but not the certificate
- now += ONE_HOUR_MS;
- fxa.internal._now_is = now;
-
- // This won't block on anything - will make an assertion, but not get a
- // new certificate.
- assertion = yield fxa.getAssertion("third.example.com");
-
- // Test will time out if that failed (i.e., if that had to go get a new cert)
- pieces = assertion.split("~");
- do_check_eq(pieces[0], "cert1");
- p2 = pieces[1].split(".");
- header = JSON.parse(atob(p2[0]));
- payload = JSON.parse(atob(p2[1]));
- do_check_eq(payload.aud, "third.example.com");
-
- // The keypair and cert should have the same validity as before, but the
- // expiration time of the assertion should be different. We compare this to
- // the initial start time, to which they are relative, not the current value
- // of "now".
- userData = yield fxa.getSignedInUser();
-
- keyPair = userData.keyPair;
- cert = userData.cert;
- do_check_eq(keyPair.validUntil, start + KEY_LIFETIME);
- do_check_eq(cert.validUntil, start + CERT_LIFETIME);
- exp = Number(payload.exp);
- do_check_eq(exp, now + ASSERTION_LIFETIME);
-
- // Now we wait even longer, and expect both assertion and cert to expire. So
- // we will have to get a new keypair and cert.
- now += ONE_DAY_MS;
- fxa.internal._now_is = now;
- d = fxa.getAssertion("fourth.example.com");
- fxa.internal._d_signCertificate.resolve("cert2");
- assertion = yield d;
- do_check_eq(fxa.internal._getCertificateSigned_calls.length, 2);
- do_check_eq(fxa.internal._getCertificateSigned_calls[1][0], "sessionToken");
- pieces = assertion.split("~");
- do_check_eq(pieces[0], "cert2");
- p2 = pieces[1].split(".");
- header = JSON.parse(atob(p2[0]));
- payload = JSON.parse(atob(p2[1]));
- do_check_eq(payload.aud, "fourth.example.com");
- userData = yield fxa.getSignedInUser();
- keyPair = userData.keyPair;
- cert = userData.cert;
- do_check_eq(keyPair.validUntil, now + KEY_LIFETIME);
- do_check_eq(cert.validUntil, now + CERT_LIFETIME);
- exp = Number(payload.exp);
-
- do_check_eq(exp, now + ASSERTION_LIFETIME);
- _("----- DONE ----\n");
-});
-
-add_task(function* test_resend_email_not_signed_in() {
- let fxa = new MockFxAccounts();
-
- try {
- yield fxa.resendVerificationEmail();
- } catch(err) {
- do_check_eq(err.message,
- "Cannot resend verification email; no signed-in user");
- return;
- }
- do_throw("Should not be able to resend email when nobody is signed in");
-});
-
-add_test(function test_accountStatus() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
-
- // If we have no user, we have no account server-side
- fxa.accountStatus().then(
- (result) => {
- do_check_false(result);
- }
- ).then(
- () => {
- fxa.setSignedInUser(alice).then(
- () => {
- fxa.accountStatus().then(
- (result) => {
- // FxAccounts.accountStatus() should match Client.accountStatus()
- do_check_true(result);
- fxa.internal.fxAccountsClient._deletedOnServer = true;
- fxa.accountStatus().then(
- (result) => {
- do_check_false(result);
- fxa.internal.fxAccountsClient._deletedOnServer = false;
- fxa.signOut().then(run_next_test);
- }
- );
- }
- )
- }
- );
- }
- );
-});
-
-add_task(function* test_resend_email_invalid_token() {
- let fxa = new MockFxAccounts();
- let sophia = getTestUser("sophia");
- do_check_neq(sophia.sessionToken, null);
-
- let client = fxa.internal.fxAccountsClient;
- client.resendVerificationEmail = () => {
- return Promise.reject({
- code: 401,
- errno: ERRNO_INVALID_AUTH_TOKEN,
- });
- };
- client.accountStatus = () => Promise.resolve(true);
-
- yield fxa.setSignedInUser(sophia);
- let user = yield fxa.internal.getUserAccountData();
- do_check_eq(user.email, sophia.email);
- do_check_eq(user.verified, false);
- log.debug("Sophia wants verification email resent");
-
- try {
- yield fxa.resendVerificationEmail();
- do_check_true(false, "resendVerificationEmail should reject invalid session token");
- } catch (err) {
- do_check_eq(err.code, 401);
- do_check_eq(err.errno, ERRNO_INVALID_AUTH_TOKEN);
- }
-
- user = yield fxa.internal.getUserAccountData();
- do_check_eq(user.email, sophia.email);
- do_check_eq(user.sessionToken, null);
-});
-
-add_test(function test_resend_email() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
-
- let initialState = fxa.internal.currentAccountState;
-
- // Alice is the user signing in; her email is unverified.
- fxa.setSignedInUser(alice).then(() => {
- log.debug("Alice signing in");
-
- // We're polling for the first email
- do_check_true(fxa.internal.currentAccountState !== initialState);
- let aliceState = fxa.internal.currentAccountState;
-
- // The polling timer is ticking
- do_check_true(fxa.internal.currentTimer > 0);
-
- fxa.internal.getUserAccountData().then(user => {
- do_check_eq(user.email, alice.email);
- do_check_eq(user.verified, false);
- log.debug("Alice wants verification email resent");
-
- fxa.resendVerificationEmail().then((result) => {
- // Mock server response; ensures that the session token actually was
- // passed to the client to make the hawk call
- do_check_eq(result, "alice's session token");
-
- // Timer was not restarted
- do_check_true(fxa.internal.currentAccountState === aliceState);
-
- // Timer is still ticking
- do_check_true(fxa.internal.currentTimer > 0);
-
- // Ok abort polling before we go on to the next test
- fxa.internal.abortExistingFlow();
- 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();
- });
- });
- });
-
- 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_task(function* test_sign_out_with_remote_error() {
- let fxa = new MockFxAccounts();
- let client = fxa.internal.fxAccountsClient;
- let remoteSignOutCalled = false;
- // Force remote sign out to trigger an error
- client.signOutAndDestroyDevice = function() { remoteSignOutCalled = true; throw "Remote sign out error"; };
- let promiseLogout = new Promise(resolve => {
- makeObserver(ONLOGOUT_NOTIFICATION, function() {
- log.debug("test_sign_out_with_remote_error observed onlogout");
- resolve();
- });
- });
-
- let jane = getTestUser("jane");
- yield fxa.setSignedInUser(jane);
- yield fxa.signOut();
- yield promiseLogout;
-
- let user = yield fxa.internal.getUserAccountData();
- do_check_eq(user, null);
- do_check_true(remoteSignOutCalled);
-});
-
-add_test(function test_getOAuthToken() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
- let getTokenFromAssertionCalled = false;
-
- fxa.internal._d_signCertificate.resolve("cert1");
-
- // create a mock oauth client
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://example.com/v1",
- client_id: "abc123"
- });
- client.getTokenFromAssertion = function () {
- getTokenFromAssertionCalled = true;
- return Promise.resolve({ access_token: "token" });
- };
-
- fxa.setSignedInUser(alice).then(
- () => {
- fxa.getOAuthToken({ scope: "profile", client: client }).then(
- (result) => {
- do_check_true(getTokenFromAssertionCalled);
- do_check_eq(result, "token");
- run_next_test();
- }
- )
- }
- );
-
-});
-
-add_test(function test_getOAuthTokenScoped() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
- let getTokenFromAssertionCalled = false;
-
- fxa.internal._d_signCertificate.resolve("cert1");
-
- // create a mock oauth client
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://example.com/v1",
- client_id: "abc123"
- });
- client.getTokenFromAssertion = function (assertion, scopeString) {
- equal(scopeString, "foo bar");
- getTokenFromAssertionCalled = true;
- return Promise.resolve({ access_token: "token" });
- };
-
- fxa.setSignedInUser(alice).then(
- () => {
- fxa.getOAuthToken({ scope: ["foo", "bar"], client: client }).then(
- (result) => {
- do_check_true(getTokenFromAssertionCalled);
- do_check_eq(result, "token");
- run_next_test();
- }
- )
- }
- );
-
-});
-
-add_task(function* test_getOAuthTokenCached() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
- let numTokenFromAssertionCalls = 0;
-
- fxa.internal._d_signCertificate.resolve("cert1");
-
- // create a mock oauth client
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://example.com/v1",
- client_id: "abc123"
- });
- client.getTokenFromAssertion = function () {
- numTokenFromAssertionCalls += 1;
- return Promise.resolve({ access_token: "token" });
- };
-
- yield fxa.setSignedInUser(alice);
- let result = yield fxa.getOAuthToken({ scope: "profile", client: client, service: "test-service" });
- do_check_eq(numTokenFromAssertionCalls, 1);
- do_check_eq(result, "token");
-
- // requesting it again should not re-fetch the token.
- result = yield fxa.getOAuthToken({ scope: "profile", client: client, service: "test-service" });
- do_check_eq(numTokenFromAssertionCalls, 1);
- do_check_eq(result, "token");
- // But requesting the same service and a different scope *will* get a new one.
- result = yield fxa.getOAuthToken({ scope: "something-else", client: client, service: "test-service" });
- do_check_eq(numTokenFromAssertionCalls, 2);
- do_check_eq(result, "token");
-});
-
-add_task(function* test_getOAuthTokenCachedScopeNormalization() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
- let numTokenFromAssertionCalls = 0;
-
- fxa.internal._d_signCertificate.resolve("cert1");
-
- // create a mock oauth client
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://example.com/v1",
- client_id: "abc123"
- });
- client.getTokenFromAssertion = function () {
- numTokenFromAssertionCalls += 1;
- return Promise.resolve({ access_token: "token" });
- };
-
- yield fxa.setSignedInUser(alice);
- let result = yield fxa.getOAuthToken({ scope: ["foo", "bar"], client: client, service: "test-service" });
- do_check_eq(numTokenFromAssertionCalls, 1);
- do_check_eq(result, "token");
-
- // requesting it again with the scope array in a different order not re-fetch the token.
- result = yield fxa.getOAuthToken({ scope: ["bar", "foo"], client: client, service: "test-service" });
- do_check_eq(numTokenFromAssertionCalls, 1);
- do_check_eq(result, "token");
- // requesting it again with the scope array in different case not re-fetch the token.
- result = yield fxa.getOAuthToken({ scope: ["Bar", "Foo"], client: client, service: "test-service" });
- do_check_eq(numTokenFromAssertionCalls, 1);
- do_check_eq(result, "token");
- // But requesting with a new entry in the array does fetch one.
- result = yield fxa.getOAuthToken({ scope: ["foo", "bar", "etc"], client: client, service: "test-service" });
- do_check_eq(numTokenFromAssertionCalls, 2);
- do_check_eq(result, "token");
-});
-
-Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
-add_test(function test_getOAuthToken_invalid_param() {
- let fxa = new MockFxAccounts();
-
- fxa.getOAuthToken()
- .then(null, err => {
- do_check_eq(err.message, "INVALID_PARAMETER");
- fxa.signOut().then(run_next_test);
- });
-});
-
-add_test(function test_getOAuthToken_invalid_scope_array() {
- let fxa = new MockFxAccounts();
-
- fxa.getOAuthToken({scope: []})
- .then(null, err => {
- do_check_eq(err.message, "INVALID_PARAMETER");
- fxa.signOut().then(run_next_test);
- });
-});
-
-add_test(function test_getOAuthToken_misconfigure_oauth_uri() {
- let fxa = new MockFxAccounts();
-
- Services.prefs.deleteBranch("identity.fxaccounts.remote.oauth.uri");
-
- fxa.getOAuthToken()
- .then(null, err => {
- do_check_eq(err.message, "INVALID_PARAMETER");
- // revert the pref
- Services.prefs.setCharPref("identity.fxaccounts.remote.oauth.uri", "https://example.com/v1");
- fxa.signOut().then(run_next_test);
- });
-});
-
-add_test(function test_getOAuthToken_no_account() {
- let fxa = new MockFxAccounts();
-
- fxa.internal.currentAccountState.getUserAccountData = function () {
- return Promise.resolve(null);
- };
-
- fxa.getOAuthToken({ scope: "profile" })
- .then(null, err => {
- do_check_eq(err.message, "NO_ACCOUNT");
- fxa.signOut().then(run_next_test);
- });
-});
-
-add_test(function test_getOAuthToken_unverified() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
-
- fxa.setSignedInUser(alice).then(() => {
- fxa.getOAuthToken({ scope: "profile" })
- .then(null, err => {
- do_check_eq(err.message, "UNVERIFIED_ACCOUNT");
- fxa.signOut().then(run_next_test);
- });
- });
-});
-
-add_test(function test_getOAuthToken_network_error() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
-
- fxa.internal._d_signCertificate.resolve("cert1");
-
- // create a mock oauth client
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://example.com/v1",
- client_id: "abc123"
- });
- client.getTokenFromAssertion = function () {
- return Promise.reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_NETWORK,
- errno: ERRNO_NETWORK
- }));
- };
-
- fxa.setSignedInUser(alice).then(() => {
- fxa.getOAuthToken({ scope: "profile", client: client })
- .then(null, err => {
- do_check_eq(err.message, "NETWORK_ERROR");
- do_check_eq(err.details.errno, ERRNO_NETWORK);
- run_next_test();
- });
- });
-});
-
-add_test(function test_getOAuthToken_auth_error() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
-
- fxa.internal._d_signCertificate.resolve("cert1");
-
- // create a mock oauth client
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://example.com/v1",
- client_id: "abc123"
- });
- client.getTokenFromAssertion = function () {
- return Promise.reject(new FxAccountsOAuthGrantClientError({
- error: ERROR_INVALID_FXA_ASSERTION,
- errno: ERRNO_INVALID_FXA_ASSERTION
- }));
- };
-
- fxa.setSignedInUser(alice).then(() => {
- fxa.getOAuthToken({ scope: "profile", client: client })
- .then(null, err => {
- do_check_eq(err.message, "AUTH_ERROR");
- do_check_eq(err.details.errno, ERRNO_INVALID_FXA_ASSERTION);
- run_next_test();
- });
- });
-});
-
-add_test(function test_getOAuthToken_unknown_error() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
-
- fxa.internal._d_signCertificate.resolve("cert1");
-
- // create a mock oauth client
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://example.com/v1",
- client_id: "abc123"
- });
- client.getTokenFromAssertion = function () {
- return Promise.reject("boom");
- };
-
- fxa.setSignedInUser(alice).then(() => {
- fxa.getOAuthToken({ scope: "profile", client: client })
- .then(null, err => {
- do_check_eq(err.message, "UNKNOWN_ERROR");
- run_next_test();
- });
- });
-});
-
-add_test(function test_getSignedInUserProfile() {
- let alice = getTestUser("alice");
- alice.verified = true;
-
- let mockProfile = {
- getProfile: function () {
- return Promise.resolve({ avatar: "image" });
- },
- tearDown: function() {},
- };
- let fxa = new FxAccounts({
- _signOutServer() { return Promise.resolve(); },
- _registerOrUpdateDevice() { return Promise.resolve(); }
- });
-
- fxa.setSignedInUser(alice).then(() => {
- fxa.internal._profile = mockProfile;
- fxa.getSignedInUserProfile()
- .then(result => {
- do_check_true(!!result);
- do_check_eq(result.avatar, "image");
- run_next_test();
- });
- });
-});
-
-add_test(function test_getSignedInUserProfile_error_uses_account_data() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
-
- fxa.internal.getSignedInUser = function () {
- return Promise.resolve({ email: "foo@bar.com" });
- };
-
- let teardownCalled = false;
- fxa.setSignedInUser(alice).then(() => {
- fxa.internal._profile = {
- getProfile: function () {
- return Promise.reject("boom");
- },
- tearDown: function() {
- teardownCalled = true;
- }
- };
-
- fxa.getSignedInUserProfile()
- .catch(error => {
- do_check_eq(error.message, "UNKNOWN_ERROR");
- fxa.signOut().then(() => {
- do_check_true(teardownCalled);
- run_next_test();
- });
- });
- });
-});
-
-add_test(function test_getSignedInUserProfile_unverified_account() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
-
- fxa.setSignedInUser(alice).then(() => {
- fxa.getSignedInUserProfile()
- .catch(error => {
- do_check_eq(error.message, "UNVERIFIED_ACCOUNT");
- fxa.signOut().then(run_next_test);
- });
- });
-
-});
-
-add_test(function test_getSignedInUserProfile_no_account_data() {
- let fxa = new MockFxAccounts();
-
- fxa.internal.getSignedInUser = function () {
- return Promise.resolve(null);
- };
-
- fxa.getSignedInUserProfile()
- .catch(error => {
- do_check_eq(error.message, "NO_ACCOUNT");
- fxa.signOut().then(run_next_test);
- });
-
-});
-
-add_task(function* test_checkVerificationStatusFailed() {
- let fxa = new MockFxAccounts();
- let alice = getTestUser("alice");
- alice.verified = true;
-
- let client = fxa.internal.fxAccountsClient;
- client.recoveryEmailStatus = () => {
- return Promise.reject({
- code: 401,
- errno: ERRNO_INVALID_AUTH_TOKEN,
- });
- };
- client.accountStatus = () => Promise.resolve(true);
-
- yield fxa.setSignedInUser(alice);
- let user = yield fxa.internal.getUserAccountData();
- do_check_neq(alice.sessionToken, null);
- do_check_eq(user.email, alice.email);
- do_check_eq(user.verified, true);
-
- yield fxa.checkVerificationStatus();
-
- user = yield fxa.internal.getUserAccountData();
- do_check_eq(user.email, alice.email);
- do_check_eq(user.sessionToken, null);
-});
-
-/*
- * End of tests.
- * Utility functions follow.
- */
-
-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
- };
-}
-
-function makeObserver(aObserveTopic, aObserveFunc) {
- let observer = {
- // nsISupports provides type management in C++
- // nsIObserver is to be an observer
- QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIObserver]),
-
- observe: function (aSubject, aTopic, aData) {
- log.debug("observed " + aTopic + " " + aData);
- if (aTopic == aObserveTopic) {
- removeMe();
- aObserveFunc(aSubject, aTopic, aData);
- }
- }
- };
-
- function removeMe() {
- log.debug("removing observer for " + aObserveTopic);
- Services.obs.removeObserver(observer, aObserveTopic);
- }
-
- Services.obs.addObserver(observer, aObserveTopic, false);
- return removeMe;
-}
-
-function do_check_throws(func, result, stack)
-{
- if (!stack)
- stack = Components.stack.caller;
-
- try {
- func();
- } catch (ex) {
- if (ex.name == result) {
- return;
- }
- do_throw("Expected result " + result + ", caught " + ex.name, stack);
- }
-
- if (result) {
- do_throw("Expected result " + result + ", none thrown", stack);
- }
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js b/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
deleted file mode 100644
index 9a2d2c127..000000000
--- a/services/fxaccounts/tests/xpcshell/test_accounts_device_registration.js
+++ /dev/null
@@ -1,526 +0,0 @@
-/* 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;
-
-const BOGUS_PUBLICKEY = "BBXOKjUb84pzws1wionFpfCBjDuCh4-s_1b52WA46K5wYL2gCWEOmFKWn_NkS5nmJwTBuO8qxxdjAIDtNeklvQc";
-const BOGUS_AUTHKEY = "GSsIiaD2Mr83iPqwFNK4rw";
-
-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/");
-
-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";
- 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),
- fxaPushService: {
- registerPushEndpoint() {
- return new Promise((resolve) => {
- resolve({
- endpoint: "http://mochi.test:8888",
- getKey: function(type) {
- return ChromeUtils.base64URLDecode(
- type === "auth" ? BOGUS_AUTHKEY : BOGUS_PUBLICKEY,
- { padding: "ignore" });
- }
- });
- });
- },
- },
- DEVICE_REGISTRATION_VERSION
- });
-}
-
-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, 4);
- 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");
- do_check_eq(spy.registerDevice.args[0][3].pushCallback, "http://mochi.test:8888");
- do_check_eq(spy.registerDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
- do_check_eq(spy.registerDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
-
- const state = fxa.internal.currentAccountState;
- const data = yield state.getUserAccountData();
-
- do_check_eq(data.deviceId, "newly-generated device id");
- do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-});
-
-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, 4);
- 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.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
- do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
- do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
-
- const state = fxa.internal.currentAccountState;
- const data = yield state.getUserAccountData();
-
- do_check_eq(data.deviceId, credentials.deviceId);
- do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-});
-
-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, 4);
- 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.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
- do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
- do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
-
-
- const state = fxa.internal.currentAccountState;
- const data = yield state.getUserAccountData();
-
- do_check_null(data.deviceId);
- do_check_eq(data.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
-});
-
-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, 4);
- 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.updateDevice.args[0][3].pushCallback, "http://mochi.test:8888");
- do_check_eq(spy.updateDevice.args[0][3].pushPublicKey, BOGUS_PUBLICKEY);
- do_check_eq(spy.updateDevice.args[0][3].pushAuthKey, BOGUS_AUTHKEY);
- 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_eq(data.deviceRegistrationVersion, null);
-});
-
-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, 4);
-
- 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.currentAccountState.getUserAccountData =
- () => Promise.resolve({ email: credentials.email,
- deviceRegistrationVersion: DEVICE_REGISTRATION_VERSION });
- 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_registration_version_outdated_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, deviceRegistrationVersion: 0 });
- 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_uptodate_registration_version_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.currentAccountState.getUserAccountData =
- () => Promise.resolve({ deviceId: credentials.deviceId, deviceRegistrationVersion: DEVICE_REGISTRATION_VERSION });
- 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");
-});
-
-add_task(function* test_getDeviceId_with_device_id_and_with_no_registration_version_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 });
- 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");
-});
-
-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
- };
-}
-
diff --git a/services/fxaccounts/tests/xpcshell/test_client.js b/services/fxaccounts/tests/xpcshell/test_client.js
deleted file mode 100644
index 83f42bdf5..000000000
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ /dev/null
@@ -1,917 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-common/hawkrequest.js");
-Cu.import("resource://services-crypto/utils.js");
-
-const FAKE_SESSION_TOKEN = "a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf";
-
-function run_test() {
- run_next_test();
-}
-
-// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#.2Faccount.2Fkeys
-var ACCOUNT_KEYS = {
- keyFetch: h("8081828384858687 88898a8b8c8d8e8f"+
- "9091929394959697 98999a9b9c9d9e9f"),
-
- response: h("ee5c58845c7c9412 b11bbd20920c2fdd"+
- "d83c33c9cd2c2de2 d66b222613364636"+
- "c2c0f8cfbb7c6304 72c0bd88451342c6"+
- "c05b14ce342c5ad4 6ad89e84464c993c"+
- "3927d30230157d08 17a077eef4b20d97"+
- "6f7a97363faf3f06 4c003ada7d01aa70"),
-
- kA: h("2021222324252627 28292a2b2c2d2e2f"+
- "3031323334353637 38393a3b3c3d3e3f"),
-
- wrapKB: h("4041424344454647 48494a4b4c4d4e4f"+
- "5051525354555657 58595a5b5c5d5e5f"),
-};
-
-function deferredStop(server) {
- let deferred = Promise.defer();
- server.stop(deferred.resolve);
- return deferred.promise;
-}
-
-add_task(function* test_authenticated_get_request() {
- let message = "{\"msg\": \"Great Success!\"}";
- let credentials = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- let method = "GET";
-
- let server = httpd_setup({"/foo": function(request, response) {
- do_check_true(request.hasHeader("Authorization"));
-
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(message, message.length);
- }
- });
-
- let client = new FxAccountsClient(server.baseURI);
-
- let result = yield client._request("/foo", method, credentials);
- do_check_eq("Great Success!", result.msg);
-
- yield deferredStop(server);
-});
-
-add_task(function* test_authenticated_post_request() {
- let credentials = {
- id: "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x",
- key: "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=",
- algorithm: "sha256"
- };
- let method = "POST";
-
- let server = httpd_setup({"/foo": function(request, response) {
- do_check_true(request.hasHeader("Authorization"));
-
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "application/json");
- response.bodyOutputStream.writeFrom(request.bodyInputStream, request.bodyInputStream.available());
- }
- });
-
- let client = new FxAccountsClient(server.baseURI);
-
- let result = yield client._request("/foo", method, credentials, {foo: "bar"});
- do_check_eq("bar", result.foo);
-
- yield deferredStop(server);
-});
-
-add_task(function* test_500_error() {
- let message = "<h1>Ooops!</h1>";
- let method = "GET";
-
- let server = httpd_setup({"/foo": function(request, response) {
- response.setStatusLine(request.httpVersion, 500, "Internal Server Error");
- response.bodyOutputStream.write(message, message.length);
- }
- });
-
- let client = new FxAccountsClient(server.baseURI);
-
- try {
- yield client._request("/foo", method);
- do_throw("Expected to catch an exception");
- } catch (e) {
- do_check_eq(500, e.code);
- do_check_eq("Internal Server Error", e.message);
- }
-
- yield deferredStop(server);
-});
-
-add_task(function* test_backoffError() {
- let method = "GET";
- let server = httpd_setup({
- "/retryDelay": function(request, response) {
- response.setHeader("Retry-After", "30");
- response.setStatusLine(request.httpVersion, 429, "Client has sent too many requests");
- let message = "<h1>Ooops!</h1>";
- response.bodyOutputStream.write(message, message.length);
- },
- "/duringDelayIShouldNotBeCalled": function(request, response) {
- response.setStatusLine(request.httpVersion, 200, "OK");
- let jsonMessage = "{\"working\": \"yes\"}";
- response.bodyOutputStream.write(jsonMessage, jsonMessage.length);
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
-
- // Retry-After header sets client.backoffError
- do_check_eq(client.backoffError, null);
- try {
- yield client._request("/retryDelay", method);
- } catch (e) {
- do_check_eq(429, e.code);
- do_check_eq(30, e.retryAfter);
- do_check_neq(typeof(client.fxaBackoffTimer), "undefined");
- do_check_neq(client.backoffError, null);
- }
- // While delay is in effect, client short-circuits any requests
- // and re-rejects with previous error.
- try {
- yield client._request("/duringDelayIShouldNotBeCalled", method);
- throw new Error("I should not be reached");
- } catch (e) {
- do_check_eq(e.retryAfter, 30);
- do_check_eq(e.message, "Client has sent too many requests");
- do_check_neq(client.backoffError, null);
- }
- // Once timer fires, client nulls error out and HTTP calls work again.
- client._clearBackoff();
- let result = yield client._request("/duringDelayIShouldNotBeCalled", method);
- do_check_eq(client.backoffError, null);
- do_check_eq(result.working, "yes");
-
- yield deferredStop(server);
-});
-
-add_task(function* test_signUp() {
- let creationMessage_noKey = JSON.stringify({
- uid: "uid",
- sessionToken: "sessionToken"
- });
- let creationMessage_withKey = JSON.stringify({
- uid: "uid",
- sessionToken: "sessionToken",
- keyFetchToken: "keyFetchToken"
- });
- let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"});
- let created = false;
-
- // Note these strings must be unicode and not already utf-8 encoded.
- let unicodeUsername = "andr\xe9@example.org"; // 'andré@example.org'
- let unicodePassword = "p\xe4ssw\xf6rd"; // 'pässwörd'
- let server = httpd_setup({
- "/account/create": function(request, response) {
- let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
- body = CommonUtils.decodeUTF8(body);
- let jsonBody = JSON.parse(body);
-
- // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
-
- if (created) {
- // Error trying to create same account a second time
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage, errorMessage.length);
- return;
- }
-
- if (jsonBody.email == unicodeUsername) {
- do_check_eq("", request._queryString);
- do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375");
-
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(creationMessage_noKey,
- creationMessage_noKey.length);
- return;
- }
-
- if (jsonBody.email == "you@example.org") {
- do_check_eq("keys=true", request._queryString);
- do_check_eq(jsonBody.authPW, "e5c1cdfdaa5fcee06142db865b212cc8ba8abee2a27d639d42c139f006cdb930");
- created = true;
-
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(creationMessage_withKey,
- creationMessage_withKey.length);
- return;
- }
- // just throwing here doesn't make any log noise, so have an assertion
- // fail instead.
- do_check_true(false, "unexpected email: " + jsonBody.email);
- },
- });
-
- // Try to create an account without retrieving optional keys.
- let client = new FxAccountsClient(server.baseURI);
- let result = yield client.signUp(unicodeUsername, unicodePassword);
- do_check_eq("uid", result.uid);
- do_check_eq("sessionToken", result.sessionToken);
- do_check_eq(undefined, result.keyFetchToken);
- do_check_eq(result.unwrapBKey,
- "de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28");
-
- // Try to create an account retrieving optional keys.
- result = yield client.signUp('you@example.org', 'pässwörd', true);
- do_check_eq("uid", result.uid);
- do_check_eq("sessionToken", result.sessionToken);
- do_check_eq("keyFetchToken", result.keyFetchToken);
- do_check_eq(result.unwrapBKey,
- "f589225b609e56075d76eb74f771ff9ab18a4dc0e901e131ba8f984c7fb0ca8c");
-
- // Try to create an existing account. Triggers error path.
- try {
- result = yield client.signUp(unicodeUsername, unicodePassword);
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(101, expectedError.errno);
- }
-
- yield deferredStop(server);
-});
-
-add_task(function* test_signIn() {
- let sessionMessage_noKey = JSON.stringify({
- sessionToken: FAKE_SESSION_TOKEN
- });
- let sessionMessage_withKey = JSON.stringify({
- sessionToken: FAKE_SESSION_TOKEN,
- keyFetchToken: "keyFetchToken"
- });
- let errorMessage_notExistent = JSON.stringify({
- code: 400,
- errno: 102,
- error: "doesn't exist"
- });
- let errorMessage_wrongCap = JSON.stringify({
- code: 400,
- errno: 120,
- error: "Incorrect email case",
- email: "you@example.com"
- });
-
- // Note this strings must be unicode and not already utf-8 encoded.
- let unicodeUsername = "m\xe9@example.com" // 'mé@example.com'
- let server = httpd_setup({
- "/account/login": function(request, response) {
- let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
- body = CommonUtils.decodeUTF8(body);
- let jsonBody = JSON.parse(body);
-
- if (jsonBody.email == unicodeUsername) {
- do_check_eq("", request._queryString);
- do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(sessionMessage_noKey,
- sessionMessage_noKey.length);
- return;
- }
- else if (jsonBody.email == "you@example.com") {
- do_check_eq("keys=true", request._queryString);
- do_check_eq(jsonBody.authPW, "93d20ec50304d496d0707ec20d7e8c89459b6396ec5dd5b9e92809c5e42856c7");
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(sessionMessage_withKey,
- sessionMessage_withKey.length);
- return;
- }
- else if (jsonBody.email == "You@example.com") {
- // Error trying to sign in with a wrong capitalization
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage_wrongCap,
- errorMessage_wrongCap.length);
- return;
- }
- else {
- // Error trying to sign in to nonexistent account
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage_notExistent,
- errorMessage_notExistent.length);
- return;
- }
- },
- });
-
- // Login without retrieving optional keys
- let client = new FxAccountsClient(server.baseURI);
- let result = yield client.signIn(unicodeUsername, 'bigsecret');
- do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
- do_check_eq(result.unwrapBKey,
- "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8");
- do_check_eq(undefined, result.keyFetchToken);
-
- // Login with retrieving optional keys
- result = yield client.signIn('you@example.com', 'bigsecret', true);
- do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
- do_check_eq(result.unwrapBKey,
- "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624");
- do_check_eq("keyFetchToken", result.keyFetchToken);
-
- // Retry due to wrong email capitalization
- result = yield client.signIn('You@example.com', 'bigsecret', true);
- do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
- do_check_eq(result.unwrapBKey,
- "65970516211062112e955d6420bebe020269d6b6a91ebd288319fc8d0cb49624");
- do_check_eq("keyFetchToken", result.keyFetchToken);
-
- // Trigger error path
- try {
- result = yield client.signIn("yøü@bad.example.org", "nofear");
- do_throw("Expected to catch an exception");
- } catch (expectedError) {
- do_check_eq(102, expectedError.errno);
- }
-
- yield deferredStop(server);
-});
-
-add_task(function* test_signOut() {
- let signoutMessage = JSON.stringify({});
- let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
- let signedOut = false;
-
- let server = httpd_setup({
- "/session/destroy": function(request, response) {
- if (!signedOut) {
- signedOut = true;
- do_check_true(request.hasHeader("Authorization"));
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(signoutMessage, signoutMessage.length);
- return;
- }
-
- // Error trying to sign out of nonexistent account
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage, errorMessage.length);
- return;
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
- let result = yield client.signOut("FakeSession");
- do_check_eq(typeof result, "object");
-
- // Trigger error path
- try {
- result = yield client.signOut("FakeSession");
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(102, expectedError.errno);
- }
-
- yield deferredStop(server);
-});
-
-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;
-
- let server = httpd_setup({
- "/recovery_email/status": function(request, response) {
- do_check_true(request.hasHeader("Authorization"));
- do_check_eq("", request._queryString);
-
- if (tries === 0) {
- tries += 1;
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(emailStatus, emailStatus.length);
- return;
- }
-
- // Second call gets an error trying to query a nonexistent account
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage, errorMessage.length);
- return;
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
- let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN);
- do_check_eq(result.verified, true);
-
- // Trigger error path
- try {
- result = yield client.recoveryEmailStatus("some bogus session");
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(102, expectedError.errno);
- }
-
- yield deferredStop(server);
-});
-
-add_task(function* test_recoveryEmailStatusWithReason() {
- let emailStatus = JSON.stringify({verified: true});
- let server = httpd_setup({
- "/recovery_email/status": function(request, response) {
- do_check_true(request.hasHeader("Authorization"));
- // if there is a query string then it will have a reason
- do_check_eq("reason=push", request._queryString);
-
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(emailStatus, emailStatus.length);
- return;
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
- let result = yield client.recoveryEmailStatus(FAKE_SESSION_TOKEN, {
- reason: "push",
- });
- do_check_eq(result.verified, true);
- yield deferredStop(server);
-});
-
-add_task(function* test_resendVerificationEmail() {
- let emptyMessage = "{}";
- let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
- let tries = 0;
-
- let server = httpd_setup({
- "/recovery_email/resend_code": function(request, response) {
- do_check_true(request.hasHeader("Authorization"));
- if (tries === 0) {
- tries += 1;
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
- return;
- }
-
- // Second call gets an error trying to query a nonexistent account
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage, errorMessage.length);
- return;
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
- let result = yield client.resendVerificationEmail(FAKE_SESSION_TOKEN);
- do_check_eq(JSON.stringify(result), emptyMessage);
-
- // Trigger error path
- try {
- result = yield client.resendVerificationEmail("some bogus session");
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(102, expectedError.errno);
- }
-
- yield deferredStop(server);
-});
-
-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
- // should all trigger separate error paths.
- let responseMessage = JSON.stringify({bundle: ACCOUNT_KEYS.response});
- let errorMessage = JSON.stringify({code: 400, errno: 102, error: "doesn't exist"});
- let emptyMessage = "{}";
- let attempt = 0;
-
- let server = httpd_setup({
- "/account/keys": function(request, response) {
- do_check_true(request.hasHeader("Authorization"));
- attempt += 1;
-
- switch(attempt) {
- case 1:
- // First time succeeds
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(responseMessage, responseMessage.length);
- break;
-
- case 2:
- // Second time, return no bundle to trigger client error
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
- break;
-
- case 3:
- // Return gibberish to trigger client MAC error
- // Tweak a byte
- let garbageResponse = JSON.stringify({
- bundle: ACCOUNT_KEYS.response.slice(0, -1) + "1"
- });
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(garbageResponse, garbageResponse.length);
- break;
-
- case 4:
- // Trigger error for nonexistent account
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage, errorMessage.length);
- break;
- }
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
-
- // First try, all should be good
- let result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
- do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.kA), result.kA);
- do_check_eq(CommonUtils.hexToBytes(ACCOUNT_KEYS.wrapKB), result.wrapKB);
-
- // Second try, empty bundle should trigger error
- try {
- result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(expectedError.message, "failed to retrieve keys");
- }
-
- // Third try, bad bundle results in MAC error
- try {
- result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(expectedError.message, "error unbundling encryption keys");
- }
-
- // Fourth try, pretend account doesn't exist
- try {
- result = yield client.accountKeys(ACCOUNT_KEYS.keyFetch);
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(102, expectedError.errno);
- }
-
- yield deferredStop(server);
-});
-
-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;
-
- let server = httpd_setup({
- "/certificate/sign": function(request, response) {
- do_check_true(request.hasHeader("Authorization"));
-
- if (tries === 0) {
- tries += 1;
- let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
- let jsonBody = JSON.parse(body);
- do_check_eq(JSON.parse(jsonBody.publicKey).foo, "bar");
- do_check_eq(jsonBody.duration, 600);
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.bodyOutputStream.write(certSignMessage, certSignMessage.length);
- return;
- }
-
- // Second attempt, trigger error
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(errorMessage, errorMessage.length);
- return;
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
- let result = yield client.signCertificate(FAKE_SESSION_TOKEN, JSON.stringify({foo: "bar"}), 600);
- do_check_eq("baz", result.bar);
-
- // Account doesn't exist
- try {
- result = yield client.signCertificate("bogus", JSON.stringify({foo: "bar"}), 600);
- do_throw("Expected to catch an exception");
- } catch(expectedError) {
- do_check_eq(102, expectedError.errno);
- }
-
- yield deferredStop(server);
-});
-
-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});
- let emptyMessage = "{}";
-
- let server = httpd_setup({
- "/account/login": function(request, response) {
- let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
- let jsonBody = JSON.parse(body);
-
- switch (jsonBody.email) {
- // We'll test that these users' accounts exist
- case "i.exist@example.com":
- case "i.also.exist@example.com":
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(existsMessage, existsMessage.length);
- break;
-
- // This user's account doesn't exist
- case "i.dont.exist@example.com":
- response.setStatusLine(request.httpVersion, 400, "Bad request");
- response.bodyOutputStream.write(doesntExistMessage, doesntExistMessage.length);
- break;
-
- // This user throws an unexpected response
- // This will reject the client signIn promise
- case "i.break.things@example.com":
- response.setStatusLine(request.httpVersion, 500, "Alas");
- response.bodyOutputStream.write(emptyMessage, emptyMessage.length);
- break;
-
- default:
- throw new Error("Unexpected login from " + jsonBody.email);
- break;
- }
- },
- });
-
- let client = new FxAccountsClient(server.baseURI);
- let result;
-
- result = yield client.accountExists("i.exist@example.com");
- do_check_true(result);
-
- result = yield client.accountExists("i.also.exist@example.com");
- do_check_true(result);
-
- result = yield client.accountExists("i.dont.exist@example.com");
- do_check_false(result);
-
- try {
- result = yield client.accountExists("i.break.things@example.com");
- do_throw("Expected to catch an exception");
- } catch(unexpectedError) {
- do_check_eq(unexpectedError.code, 500);
- }
-
- 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() {
- function writeResp(response, msg) {
- if (typeof msg === "object") {
- msg = JSON.stringify(msg);
- }
- response.bodyOutputStream.write(msg, msg.length);
- }
-
- let server = httpd_setup(
- {
- "/session/destroy": function(request, response) {
- response.setHeader("Content-Type", "application/json; charset=utf-8");
- response.setStatusLine(request.httpVersion, 401, "Unauthorized");
- writeResp(response, {
- error: "invalid authentication timestamp",
- code: 401,
- errno: 111,
- });
- },
- }
- );
-
- let client = new FxAccountsClient(server.baseURI);
-
- yield rejects(client.signOut(FAKE_SESSION_TOKEN, {
- service: "sync",
- }), function(err) {
- return err.errno == 111;
- });
-
- yield deferredStop(server);
-});
-
-add_task(function* test_email_case() {
- let canonicalEmail = "greta.garbo@gmail.com";
- let clientEmail = "Greta.Garbo@gmail.COM";
- let attempts = 0;
-
- function writeResp(response, msg) {
- if (typeof msg === "object") {
- msg = JSON.stringify(msg);
- }
- response.bodyOutputStream.write(msg, msg.length);
- }
-
- let server = httpd_setup(
- {
- "/account/login": function(request, response) {
- response.setHeader("Content-Type", "application/json; charset=utf-8");
- attempts += 1;
- if (attempts > 2) {
- response.setStatusLine(request.httpVersion, 429, "Sorry, you had your chance");
- return writeResp(response, "");
- }
-
- let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
- let jsonBody = JSON.parse(body);
- let email = jsonBody.email;
-
- // If the client has the wrong case on the email, we return a 400, with
- // the capitalization of the email as saved in the accounts database.
- if (email == canonicalEmail) {
- response.setStatusLine(request.httpVersion, 200, "Yay");
- return writeResp(response, {areWeHappy: "yes"});
- }
-
- response.setStatusLine(request.httpVersion, 400, "Incorrect email case");
- return writeResp(response, {
- code: 400,
- errno: 120,
- error: "Incorrect email case",
- email: canonicalEmail
- });
- },
- }
- );
-
- let client = new FxAccountsClient(server.baseURI);
-
- let result = yield client.signIn(clientEmail, "123456");
- do_check_eq(result.areWeHappy, "yes");
- do_check_eq(attempts, 2);
-
- yield deferredStop(server);
-});
-
-// turn formatted test vectors into normal hex strings
-function h(hexStr) {
- return hexStr.replace(/\s+/g, "");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_credentials.js b/services/fxaccounts/tests/xpcshell/test_credentials.js
deleted file mode 100644
index cbd9e4c7a..000000000
--- a/services/fxaccounts/tests/xpcshell/test_credentials.js
+++ /dev/null
@@ -1,110 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-Cu.import("resource://gre/modules/Credentials.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-crypto/utils.js");
-
-var {hexToBytes: h2b,
- hexAsString: h2s,
- stringAsHex: s2h,
- bytesAsHex: b2h} = CommonUtils;
-
-// Test vectors for the "onepw" protocol:
-// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
-var vectors = {
- "client stretch-KDF": {
- email:
- h("616e6472c3a94065 78616d706c652e6f 7267"),
- password:
- h("70c3a4737377c3b6 7264"),
- quickStretchedPW:
- h("e4e8889bd8bd61ad 6de6b95c059d56e7 b50dacdaf62bd846 44af7e2add84345d"),
- authPW:
- h("247b675ffb4c4631 0bc87e26d712153a be5e1c90ef00a478 4594f97ef54f2375"),
- authSalt:
- h("00f0000000000000 0000000000000000 0000000000000000 0000000000000000"),
- },
-};
-
-// A simple test suite with no utf8 encoding madness.
-add_task(function* test_onepw_setup_credentials() {
- let email = "francine@example.org";
- let password = CommonUtils.encodeUTF8("i like pie");
-
- let pbkdf2 = CryptoUtils.pbkdf2Generate;
- let hkdf = CryptoUtils.hkdf;
-
- // quickStretch the email
- let saltyEmail = Credentials.keyWordExtended("quickStretch", email);
-
- do_check_eq(b2h(saltyEmail), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a6672616e63696e65406578616d706c652e6f7267");
-
- let pbkdf2Rounds = 1000;
- let pbkdf2Len = 32;
-
- let quickStretchedPW = pbkdf2(password, saltyEmail, pbkdf2Rounds, pbkdf2Len, Ci.nsICryptoHMAC.SHA256, 32);
- let quickStretchedActual = "6b88094c1c73bbf133223f300d101ed70837af48d9d2c1b6e7d38804b20cdde4";
- do_check_eq(b2h(quickStretchedPW), quickStretchedActual);
-
- // obtain hkdf info
- let authKeyInfo = Credentials.keyWord('authPW');
- do_check_eq(b2h(authKeyInfo), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f617574685057");
-
- // derive auth password
- let hkdfSalt = h2b("00");
- let hkdfLen = 32;
- let authPW = hkdf(quickStretchedPW, hkdfSalt, authKeyInfo, hkdfLen);
-
- do_check_eq(b2h(authPW), "4b8dec7f48e7852658163601ff766124c312f9392af6c3d4e1a247eb439be342");
-
- // derive unwrap key
- let unwrapKeyInfo = Credentials.keyWord('unwrapBkey');
- let unwrapKey = hkdf(quickStretchedPW, hkdfSalt, unwrapKeyInfo, hkdfLen);
-
- do_check_eq(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9");
-});
-
-add_task(function* test_client_stretch_kdf() {
- let pbkdf2 = CryptoUtils.pbkdf2Generate;
- let hkdf = CryptoUtils.hkdf;
- let expected = vectors["client stretch-KDF"];
-
- let email = h2s(expected.email);
- let password = h2s(expected.password);
-
- // Intermediate value from sjcl implementation in fxa-js-client
- // The key thing is the c3a9 sequence in "andré"
- let salt = Credentials.keyWordExtended("quickStretch", email);
- do_check_eq(b2h(salt), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a616e6472c3a9406578616d706c652e6f7267");
-
- let options = {
- stretchedPassLength: 32,
- pbkdf2Rounds: 1000,
- hmacAlgorithm: Ci.nsICryptoHMAC.SHA256,
- hmacLength: 32,
- hkdfSalt: h2b("00"),
- hkdfLength: 32,
- };
-
- let results = yield Credentials.setup(email, password, options);
-
- do_check_eq(expected.quickStretchedPW, b2h(results.quickStretchedPW),
- "quickStretchedPW is wrong");
-
- do_check_eq(expected.authPW, b2h(results.authPW),
- "authPW is wrong");
-});
-
-// End of tests
-// Utility functions follow
-
-function run_test() {
- run_next_test();
-}
-
-// turn formatted test vectors into normal hex strings
-function h(hexStr) {
- return hexStr.replace(/\s+/g, "");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
deleted file mode 100644
index 64ddb1fd1..000000000
--- a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests for FxAccounts, storage and the master password.
-
-// Stop us hitting the real auth server.
-Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost");
-// See verbose logging from FxAccounts.jsm
-Services.prefs.setCharPref("identity.fxaccounts.loglevel", "Trace");
-
-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/osfile.jsm");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-
-// Use a backstage pass to get at our LoginManagerStorage object, so we can
-// mock the prototype.
-var {LoginManagerStorage} = Cu.import("resource://gre/modules/FxAccountsStorage.jsm", {});
-var isLoggedIn = true;
-LoginManagerStorage.prototype.__defineGetter__("_isLoggedIn", () => isLoggedIn);
-
-function setLoginMgrLoggedInState(loggedIn) {
- isLoggedIn = loggedIn;
-}
-
-
-initTestLogging("Trace");
-
-function run_test() {
- run_next_test();
-}
-
-function getLoginMgrData() {
- let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
- if (logins.length == 0) {
- return null;
- }
- Assert.equal(logins.length, 1, "only 1 login available");
- return logins[0];
-}
-
-function createFxAccounts() {
- return new FxAccounts({
- _getDeviceName() {
- return "mock device name";
- },
- fxaPushService: {
- registerPushEndpoint() {
- return new Promise((resolve) => {
- resolve({
- endpoint: "http://mochi.test:8888"
- });
- });
- },
- }
- });
-}
-
-add_task(function* test_simple() {
- let fxa = createFxAccounts();
-
- let creds = {
- uid: "abcd",
- email: "test@example.com",
- sessionToken: "sessionToken",
- kA: "the kA value",
- kB: "the kB value",
- verified: true
- };
- yield fxa.setSignedInUser(creds);
-
- // This should have stored stuff in both the .json file in the profile
- // dir, and the login dir.
- let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
- let data = yield CommonUtils.readJSON(path);
-
- Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
- Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
- Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
-
- Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
- Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
-
- let login = getLoginMgrData();
- Assert.strictEqual(login.username, creds.uid, "uid used for username");
- let loginData = JSON.parse(login.password);
- Assert.strictEqual(loginData.version, data.version, "same version flag in both places");
- Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr");
- Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr");
-
- Assert.ok(!("email" in loginData), "email not stored in the login mgr json");
- Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json");
- Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json");
-
- yield fxa.signOut(/* localOnly = */ true);
- Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
-});
-
-add_task(function* test_MPLocked() {
- let fxa = createFxAccounts();
-
- let creds = {
- uid: "abcd",
- email: "test@example.com",
- sessionToken: "sessionToken",
- kA: "the kA value",
- kB: "the kB value",
- verified: true
- };
-
- Assert.strictEqual(getLoginMgrData(), null, "no login mgr at the start");
- // tell the storage that the MP is locked.
- setLoginMgrLoggedInState(false);
- yield fxa.setSignedInUser(creds);
-
- // This should have stored stuff in the .json, and the login manager stuff
- // will not exist.
- let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
- let data = yield CommonUtils.readJSON(path);
-
- Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
- Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
- Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
-
- Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
- Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
-
- Assert.strictEqual(getLoginMgrData(), null, "login mgr data doesn't exist");
- yield fxa.signOut(/* localOnly = */ true)
-});
-
-
-add_task(function* test_consistentWithMPEdgeCases() {
- setLoginMgrLoggedInState(true);
-
- let fxa = createFxAccounts();
-
- let creds1 = {
- uid: "uid1",
- email: "test@example.com",
- sessionToken: "sessionToken",
- kA: "the kA value",
- kB: "the kB value",
- verified: true
- };
-
- let creds2 = {
- uid: "uid2",
- email: "test2@example.com",
- sessionToken: "sessionToken2",
- kA: "the kA value2",
- kB: "the kB value2",
- verified: false,
- };
-
- // Log a user in while MP is unlocked.
- yield fxa.setSignedInUser(creds1);
-
- // tell the storage that the MP is locked - this will prevent logout from
- // being able to clear the data.
- setLoginMgrLoggedInState(false);
-
- // now set the second credentials.
- yield fxa.setSignedInUser(creds2);
-
- // We should still have creds1 data in the login manager.
- let login = getLoginMgrData();
- Assert.strictEqual(login.username, creds1.uid);
- // and that we do have the first kA in the login manager.
- Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds1.kA,
- "stale data still in login mgr");
-
- // 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 = createFxAccounts();
-
- let accountData = yield fxa.getSignedInUser();
- Assert.strictEqual(accountData.email, creds2.email);
- // we should have no kA at all.
- Assert.strictEqual(accountData.kA, undefined, "stale kA wasn't used");
- yield fxa.signOut(/* localOnly = */ true)
-});
-
-// A test for the fact we will accept either a UID or email when looking in
-// the login manager.
-add_task(function* test_uidMigration() {
- setLoginMgrLoggedInState(true);
- Assert.strictEqual(getLoginMgrData(), null, "expect no logins at the start");
-
- // create the login entry using email as a key.
- let contents = {kA: "kA"};
-
- let loginInfo = new Components.Constructor(
- "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
- let login = new loginInfo(FXA_PWDMGR_HOST,
- null, // aFormSubmitURL,
- FXA_PWDMGR_REALM, // aHttpRealm,
- "foo@bar.com", // aUsername
- JSON.stringify(contents), // aPassword
- "", // aUsernameField
- "");// aPasswordField
- Services.logins.addLogin(login);
-
- // ensure we read it.
- let storage = new LoginManagerStorage();
- let got = yield storage.get("uid", "foo@bar.com");
- Assert.deepEqual(got, contents);
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_client.js b/services/fxaccounts/tests/xpcshell/test_oauth_client.js
deleted file mode 100644
index 9bcb1b1ab..000000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_client.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
-
-function run_test() {
- validationHelper(undefined,
- "Error: Missing 'parameters' configuration option");
-
- validationHelper({},
- "Error: Missing 'parameters' configuration option");
-
- validationHelper({ parameters: {} },
- "Error: Missing 'parameters.oauth_uri' parameter");
-
- validationHelper({ parameters: {
- oauth_uri: "http://oauth.test/v1"
- }},
- "Error: Missing 'parameters.client_id' parameter");
-
- validationHelper({ parameters: {
- oauth_uri: "http://oauth.test/v1",
- client_id: "client_id"
- }},
- "Error: Missing 'parameters.content_uri' parameter");
-
- validationHelper({ parameters: {
- oauth_uri: "http://oauth.test/v1",
- client_id: "client_id",
- content_uri: "http://content.test"
- }},
- "Error: Missing 'parameters.state' parameter");
-
- validationHelper({ parameters: {
- oauth_uri: "http://oauth.test/v1",
- client_id: "client_id",
- content_uri: "http://content.test",
- state: "complete",
- action: "force_auth"
- }},
- "Error: parameters.email is required for action 'force_auth'");
-
- run_next_test();
-}
-
-function validationHelper(params, expected) {
- try {
- new FxAccountsOAuthClient(params);
- } catch (e) {
- return do_check_eq(e.toString(), expected);
- }
- throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js b/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
deleted file mode 100644
index 710a65ee5..000000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client.js
+++ /dev/null
@@ -1,292 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-const CLIENT_OPTIONS = {
- serverURL: "http://127.0.0.1:9010/v1",
- client_id: 'abc123'
-};
-
-const STATUS_SUCCESS = 200;
-
-/**
- * Mock request responder
- * @param {String} response
- * Mocked raw response from the server
- * @returns {Function}
- */
-var mockResponse = function (response) {
- return function () {
- return {
- setHeader: function () {},
- post: function () {
- this.response = response;
- this.onComplete();
- }
- };
- };
-};
-
-/**
- * Mock request error responder
- * @param {Error} error
- * Error object
- * @returns {Function}
- */
-var mockResponseError = function (error) {
- return function () {
- return {
- setHeader: function () {},
- post: function () {
- this.onComplete(error);
- }
- };
- };
-};
-
-add_test(function missingParams () {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
- try {
- client.getTokenFromAssertion()
- } catch (e) {
- do_check_eq(e.message, "Missing 'assertion' parameter");
- }
-
- try {
- client.getTokenFromAssertion("assertion")
- } catch (e) {
- do_check_eq(e.message, "Missing 'scope' parameter");
- }
-
- run_next_test();
-});
-
-add_test(function successfulResponse () {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
- let response = {
- success: true,
- status: STATUS_SUCCESS,
- body: "{\"access_token\":\"http://example.com/image.jpeg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
- };
-
- client._Request = new mockResponse(response);
- client.getTokenFromAssertion("assertion", "scope")
- .then(
- function (result) {
- do_check_eq(result.access_token, "http://example.com/image.jpeg");
- run_next_test();
- }
- );
-});
-
-add_test(function successfulDestroy () {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
- let response = {
- success: true,
- status: STATUS_SUCCESS,
- body: "{}",
- };
-
- client._Request = new mockResponse(response);
- client.destroyToken("deadbeef").then(run_next_test);
-});
-
-add_test(function parseErrorResponse () {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
- let response = {
- success: true,
- status: STATUS_SUCCESS,
- body: "unexpected",
- };
-
- client._Request = new mockResponse(response);
- client.getTokenFromAssertion("assertion", "scope")
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(e.code, STATUS_SUCCESS);
- do_check_eq(e.errno, ERRNO_PARSE);
- do_check_eq(e.error, ERROR_PARSE);
- do_check_eq(e.message, "unexpected");
- run_next_test();
- }
- );
-});
-
-add_test(function serverErrorResponse () {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
- let response = {
- status: 400,
- body: "{ \"code\": 400, \"errno\": 104, \"error\": \"Bad Request\", \"message\": \"Unauthorized\", \"reason\": \"Invalid fxa assertion\" }",
- };
-
- client._Request = new mockResponse(response);
- client.getTokenFromAssertion("blah", "scope")
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(e.code, 400);
- do_check_eq(e.errno, ERRNO_INVALID_FXA_ASSERTION);
- do_check_eq(e.error, "Bad Request");
- do_check_eq(e.message, "Unauthorized");
- run_next_test();
- }
- );
-});
-
-add_test(function networkErrorResponse () {
- let client = new FxAccountsOAuthGrantClient({
- serverURL: "http://domain.dummy",
- client_id: "abc123"
- });
- Services.prefs.setBoolPref("identity.fxaccounts.skipDeviceRegistration", true);
- client.getTokenFromAssertion("assertion", "scope")
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(e.code, null);
- do_check_eq(e.errno, ERRNO_NETWORK);
- do_check_eq(e.error, ERROR_NETWORK);
- run_next_test();
- }
- ).catch(() => {}).then(() =>
- Services.prefs.clearUserPref("identity.fxaccounts.skipDeviceRegistration"));
-});
-
-add_test(function unsupportedMethod () {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
-
- return client._createRequest("/", "PUT")
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(e.code, ERROR_CODE_METHOD_NOT_ALLOWED);
- do_check_eq(e.errno, ERRNO_NETWORK);
- do_check_eq(e.error, ERROR_NETWORK);
- do_check_eq(e.message, ERROR_MSG_METHOD_NOT_ALLOWED);
- run_next_test();
- }
- );
-});
-
-add_test(function onCompleteRequestError () {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
- client._Request = new mockResponseError(new Error("onComplete error"));
- client.getTokenFromAssertion("assertion", "scope")
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(e.code, null);
- do_check_eq(e.errno, ERRNO_NETWORK);
- do_check_eq(e.error, ERROR_NETWORK);
- do_check_eq(e.message, "Error: onComplete error");
- run_next_test();
- }
- );
-});
-
-add_test(function incorrectErrno() {
- let client = new FxAccountsOAuthGrantClient(CLIENT_OPTIONS);
- let response = {
- status: 400,
- body: "{ \"code\": 400, \"errno\": \"bad errno\", \"error\": \"Bad Request\", \"message\": \"Unauthorized\", \"reason\": \"Invalid fxa assertion\" }",
- };
-
- client._Request = new mockResponse(response);
- client.getTokenFromAssertion("blah", "scope")
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(e.code, 400);
- do_check_eq(e.errno, ERRNO_UNKNOWN_ERROR);
- do_check_eq(e.error, "Bad Request");
- do_check_eq(e.message, "Unauthorized");
- run_next_test();
- }
- );
-});
-
-add_test(function constructorTests() {
- validationHelper(undefined,
- "Error: Missing configuration options");
-
- validationHelper({},
- "Error: Missing 'serverURL' parameter");
-
- validationHelper({ serverURL: "http://example.com" },
- "Error: Missing 'client_id' parameter");
-
- validationHelper({ client_id: "123ABC" },
- "Error: Missing 'serverURL' parameter");
-
- validationHelper({ client_id: "123ABC", serverURL: "badUrl" },
- "Error: Invalid 'serverURL'");
-
- run_next_test();
-});
-
-add_test(function errorTests() {
- let error1 = new FxAccountsOAuthGrantClientError();
- do_check_eq(error1.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(error1.code, null);
- do_check_eq(error1.errno, ERRNO_UNKNOWN_ERROR);
- do_check_eq(error1.error, ERROR_UNKNOWN);
- do_check_eq(error1.message, null);
-
- let error2 = new FxAccountsOAuthGrantClientError({
- code: STATUS_SUCCESS,
- errno: 1,
- error: "Error",
- message: "Something",
- });
- let fields2 = error2._toStringFields();
- let statusCode = 1;
-
- do_check_eq(error2.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(error2.code, STATUS_SUCCESS);
- do_check_eq(error2.errno, statusCode);
- do_check_eq(error2.error, "Error");
- do_check_eq(error2.message, "Something");
-
- do_check_eq(fields2.name, "FxAccountsOAuthGrantClientError");
- do_check_eq(fields2.code, STATUS_SUCCESS);
- do_check_eq(fields2.errno, statusCode);
- do_check_eq(fields2.error, "Error");
- do_check_eq(fields2.message, "Something");
-
- do_check_true(error2.toString().indexOf("Something") >= 0);
- run_next_test();
-});
-
-function run_test() {
- run_next_test();
-}
-
-/**
- * Quick way to test the "FxAccountsOAuthGrantClient" constructor.
- *
- * @param {Object} options
- * FxAccountsOAuthGrantClient constructor options
- * @param {String} expected
- * Expected error message
- * @returns {*}
- */
-function validationHelper(options, expected) {
- try {
- new FxAccountsOAuthGrantClient(options);
- } catch (e) {
- return do_check_eq(e.toString(), expected);
- }
- throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client_server.js b/services/fxaccounts/tests/xpcshell/test_oauth_grant_client_server.js
deleted file mode 100644
index bd446513e..000000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_grant_client_server.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// A test of FxAccountsOAuthGrantClient but using a real server it can
-// hit.
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsOAuthGrantClient.jsm");
-
-// handlers for our server.
-var numTokenFetches;
-var activeTokens;
-
-function authorize(request, response) {
- response.setStatusLine("1.1", 200, "OK");
- let token = "token" + numTokenFetches;
- numTokenFetches += 1;
- activeTokens.add(token);
- response.write(JSON.stringify({access_token: token}));
-}
-
-function destroy(request, response) {
- // Getting the body seems harder than it should be!
- let sis = Cc["@mozilla.org/scriptableinputstream;1"]
- .createInstance(Ci.nsIScriptableInputStream);
- sis.init(request.bodyInputStream);
- let body = JSON.parse(sis.read(sis.available()));
- sis.close();
- let token = body.token;
- ok(activeTokens.delete(token));
- print("after destroy have", activeTokens.size, "tokens left.")
- response.setStatusLine("1.1", 200, "OK");
- response.write('{}');
-}
-
-function startServer() {
- numTokenFetches = 0;
- activeTokens = new Set();
- let srv = new HttpServer();
- srv.registerPathHandler("/v1/authorization", authorize);
- srv.registerPathHandler("/v1/destroy", destroy);
- srv.start(-1);
- return srv;
-}
-
-function promiseStopServer(server) {
- return new Promise(resolve => {
- server.stop(resolve);
- });
-}
-
-add_task(function* getAndRevokeToken () {
- let server = startServer();
- let clientOptions = {
- serverURL: "http://localhost:" + server.identity.primaryPort + "/v1",
- client_id: 'abc123',
- }
-
- let client = new FxAccountsOAuthGrantClient(clientOptions);
- let result = yield client.getTokenFromAssertion("assertion", "scope");
- equal(result.access_token, "token0");
- equal(numTokenFetches, 1, "we hit the server to fetch a token");
- yield client.destroyToken("token0");
- equal(activeTokens.size, 0, "We hit the server to revoke it");
- yield promiseStopServer(server);
-});
-
-// XXX - TODO - we should probably add more tests for unexpected responses etc.
-
-function run_test() {
- run_next_test();
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js b/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js
deleted file mode 100644
index 08642846b..000000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_token_storage.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-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/osfile.jsm");
-
-// We grab some additional stuff via backstage passes.
-var {AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
-
-function promiseNotification(topic) {
- return new Promise(resolve => {
- let observe = () => {
- Services.obs.removeObserver(observe, topic);
- resolve();
- }
- Services.obs.addObserver(observe, topic, false);
- });
-}
-
-// A storage manager that doesn't actually write anywhere.
-function MockStorageManager() {
-}
-
-MockStorageManager.prototype = {
- promiseInitialized: Promise.resolve(),
-
- 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();
- }
-}
-
-
-// Just enough mocks so we can avoid hawk etc.
-function MockFxAccountsClient() {
- this._email = "nobody@example.com";
- this._verified = false;
-
- this.accountStatus = function(uid) {
- let deferred = Promise.defer();
- deferred.resolve(!!uid && (!this._deletedOnServer));
- return deferred.promise;
- };
-
- 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);
-}
-
-MockFxAccountsClient.prototype = {
- __proto__: FxAccountsClient.prototype
-}
-
-function MockFxAccounts(device={}) {
- return new FxAccounts({
- fxAccountsClient: new MockFxAccountsClient(),
- newAccountState(credentials) {
- // we use a real accountState but mocked storage.
- let storage = new MockStorageManager();
- storage.initialize(credentials);
- return new AccountState(storage);
- },
- _getDeviceName() {
- return "mock device name";
- },
- fxaPushService: {
- registerPushEndpoint() {
- return new Promise((resolve) => {
- resolve({
- endpoint: "http://mochi.test:8888"
- });
- });
- },
- },
- });
-}
-
-function* createMockFxA() {
- let fxa = new MockFxAccounts();
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- assertion: "foobar",
- sessionToken: "dead",
- kA: "beef",
- kB: "cafe",
- verified: true
- };
- yield fxa.setSignedInUser(credentials);
- return fxa;
-}
-
-// The tests.
-function run_test() {
- run_next_test();
-}
-
-add_task(function* testCacheStorage() {
- let fxa = yield createMockFxA();
-
- // Hook what the impl calls to save to disk.
- let cas = fxa.internal.currentAccountState;
- let origPersistCached = cas._persistCachedTokens.bind(cas)
- cas._persistCachedTokens = function() {
- return origPersistCached().then(() => {
- Services.obs.notifyObservers(null, "testhelper-fxa-cache-persist-done", null);
- });
- };
-
- let promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done");
- let tokenData = {token: "token1", somethingelse: "something else"};
- let scopeArray = ["foo", "bar"];
- cas.setCachedToken(scopeArray, tokenData);
- deepEqual(cas.getCachedToken(scopeArray), tokenData);
-
- deepEqual(cas.oauthTokens, {"bar|foo": tokenData});
- // wait for background write to complete.
- yield promiseWritten;
-
- // Check the token cache made it to our mocked storage.
- deepEqual(cas.storageManager.accountData.oauthTokens, {"bar|foo": tokenData});
-
- // Drop the token from the cache and ensure it is removed from the json.
- promiseWritten = promiseNotification("testhelper-fxa-cache-persist-done");
- yield cas.removeCachedToken("token1");
- deepEqual(cas.oauthTokens, {});
- yield promiseWritten;
- deepEqual(cas.storageManager.accountData.oauthTokens, {});
-
- // sign out and the token storage should end up with null.
- let storageManager = cas.storageManager; // .signOut() removes the attribute.
- yield fxa.signOut( /* localOnly = */ true);
- deepEqual(storageManager.accountData, null);
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js b/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
deleted file mode 100644
index f758bf405..000000000
--- a/services/fxaccounts/tests/xpcshell/test_oauth_tokens.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-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/FxAccountsOAuthGrantClient.jsm");
-Cu.import("resource://services-common/utils.js");
-var {AccountState} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
-
-function promiseNotification(topic) {
- return new Promise(resolve => {
- let observe = () => {
- Services.obs.removeObserver(observe, topic);
- resolve();
- }
- Services.obs.addObserver(observe, topic, false);
- });
-}
-
-// Just enough mocks so we can avoid hawk and storage.
-function MockStorageManager() {
-}
-
-MockStorageManager.prototype = {
- promiseInitialized: Promise.resolve(),
-
- 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() {
- this._email = "nobody@example.com";
- this._verified = false;
-
- this.accountStatus = function(uid) {
- let deferred = Promise.defer();
- deferred.resolve(!!uid && (!this._deletedOnServer));
- return deferred.promise;
- };
-
- 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);
-}
-
-MockFxAccountsClient.prototype = {
- __proto__: FxAccountsClient.prototype
-}
-
-function MockFxAccounts(mockGrantClient) {
- return new FxAccounts({
- fxAccountsClient: new MockFxAccountsClient(),
- getAssertion: () => Promise.resolve("assertion"),
- newAccountState(credentials) {
- // we use a real accountState but mocked storage.
- let storage = new MockStorageManager();
- storage.initialize(credentials);
- return new AccountState(storage);
- },
- _destroyOAuthToken: function(tokenData) {
- // somewhat sad duplication of _destroyOAuthToken, but hard to avoid.
- return mockGrantClient.destroyToken(tokenData.token).then( () => {
- Services.obs.notifyObservers(null, "testhelper-fxa-revoke-complete", null);
- });
- },
- _getDeviceName() {
- return "mock device name";
- },
- fxaPushService: {
- registerPushEndpoint() {
- return new Promise((resolve) => {
- resolve({
- endpoint: "http://mochi.test:8888"
- });
- });
- },
- },
- });
-}
-
-function* createMockFxA(mockGrantClient) {
- let fxa = new MockFxAccounts(mockGrantClient);
- let credentials = {
- email: "foo@example.com",
- uid: "1234@lcip.org",
- assertion: "foobar",
- sessionToken: "dead",
- kA: "beef",
- kB: "cafe",
- verified: true
- };
-
- yield fxa.setSignedInUser(credentials);
- return fxa;
-}
-
-// The tests.
-function run_test() {
- run_next_test();
-}
-
-function MockFxAccountsOAuthGrantClient() {
- this.activeTokens = new Set();
-}
-
-MockFxAccountsOAuthGrantClient.prototype = {
- serverURL: {href: "http://localhost"},
- getTokenFromAssertion(assertion, scope) {
- let token = "token" + this.numTokenFetches;
- this.numTokenFetches += 1;
- this.activeTokens.add(token);
- print("getTokenFromAssertion returning token", token);
- return Promise.resolve({access_token: token});
- },
- destroyToken(token) {
- ok(this.activeTokens.delete(token));
- print("after destroy have", this.activeTokens.size, "tokens left.");
- return Promise.resolve({});
- },
- // and some stuff used only for tests.
- numTokenFetches: 0,
- activeTokens: null,
-}
-
-add_task(function* testRevoke() {
- let client = new MockFxAccountsOAuthGrantClient();
- let tokenOptions = { scope: "test-scope", client: client };
- let fxa = yield createMockFxA(client);
-
- // get our first token and check we hit the mock.
- let token1 = yield fxa.getOAuthToken(tokenOptions);
- equal(client.numTokenFetches, 1);
- equal(client.activeTokens.size, 1);
- ok(token1, "got a token");
- equal(token1, "token0");
-
- // drop the new token from our cache.
- yield fxa.removeCachedOAuthToken({token: token1});
-
- // FxA fires an observer when the "background" revoke is complete.
- yield promiseNotification("testhelper-fxa-revoke-complete");
- // the revoke should have been successful.
- equal(client.activeTokens.size, 0);
- // fetching it again hits the server.
- let token2 = yield fxa.getOAuthToken(tokenOptions);
- equal(client.numTokenFetches, 2);
- equal(client.activeTokens.size, 1);
- ok(token2, "got a token");
- notEqual(token1, token2, "got a different token");
-});
-
-add_task(function* testSignOutDestroysTokens() {
- let client = new MockFxAccountsOAuthGrantClient();
- let fxa = yield createMockFxA(client);
-
- // get our first token and check we hit the mock.
- let token1 = yield fxa.getOAuthToken({ scope: "test-scope", client: client });
- equal(client.numTokenFetches, 1);
- equal(client.activeTokens.size, 1);
- ok(token1, "got a token");
-
- // get another
- let token2 = yield fxa.getOAuthToken({ scope: "test-scope-2", client: client });
- equal(client.numTokenFetches, 2);
- equal(client.activeTokens.size, 2);
- ok(token2, "got a token");
- notEqual(token1, token2, "got a different token");
-
- // now sign out - they should be removed.
- yield fxa.signOut();
- // FxA fires an observer when the "background" signout is complete.
- yield promiseNotification("testhelper-fxa-signout-complete");
- // No active tokens left.
- equal(client.activeTokens.size, 0);
-});
-
-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
- // handle and detect that leaving us with one of the fetch tokens being
- // revoked and the same token value returned to both calls.
- let client = new MockFxAccountsOAuthGrantClient();
- let fxa = yield createMockFxA(client);
-
- // We should see 2 notifications as part of this - set up the listeners
- // now (and wait on them later)
- let notifications = Promise.all([
- promiseNotification("testhelper-fxa-revoke-complete"),
- promiseNotification("testhelper-fxa-revoke-complete"),
- ]);
- let results = yield Promise.all([
- fxa.getOAuthToken({scope: "test-scope", client: client}),
- fxa.getOAuthToken({scope: "test-scope", client: client}),
- fxa.getOAuthToken({scope: "test-scope-2", client: client}),
- fxa.getOAuthToken({scope: "test-scope-2", client: client}),
- ]);
-
- equal(client.numTokenFetches, 4, "should have fetched 4 tokens.");
- // We should see 2 of the 4 revoked due to the race.
- yield notifications;
-
- // Should have 2 unique tokens
- results.sort();
- equal(results[0], results[1]);
- equal(results[2], results[3]);
- // should be 2 active.
- equal(client.activeTokens.size, 2);
- // Which can each be revoked.
- notifications = Promise.all([
- promiseNotification("testhelper-fxa-revoke-complete"),
- promiseNotification("testhelper-fxa-revoke-complete"),
- ]);
- yield fxa.removeCachedOAuthToken({token: results[0]});
- equal(client.activeTokens.size, 1);
- yield fxa.removeCachedOAuthToken({token: results[2]});
- equal(client.activeTokens.size, 0);
- yield notifications;
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_profile.js b/services/fxaccounts/tests/xpcshell/test_profile.js
deleted file mode 100644
index 13adf8cbb..000000000
--- a/services/fxaccounts/tests/xpcshell/test_profile.js
+++ /dev/null
@@ -1,409 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsProfileClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsProfile.jsm");
-
-const URL_STRING = "https://example.com";
-Services.prefs.setCharPref("identity.fxaccounts.settings.uri", "https://example.com/settings");
-
-const STATUS_SUCCESS = 200;
-
-/**
- * Mock request responder
- * @param {String} response
- * Mocked raw response from the server
- * @returns {Function}
- */
-var mockResponse = function (response) {
- let Request = function (requestUri) {
- // Store the request uri so tests can inspect it
- Request._requestUri = requestUri;
- return {
- setHeader: function () {},
- head: function () {
- this.response = response;
- this.onComplete();
- }
- };
- };
-
- return Request;
-};
-
-/**
- * Mock request error responder
- * @param {Error} error
- * Error object
- * @returns {Function}
- */
-var mockResponseError = function (error) {
- return function () {
- return {
- setHeader: function () {},
- head: function () {
- this.onComplete(error);
- }
- };
- };
-};
-
-var mockClient = function (fxa) {
- let options = {
- serverURL: "http://127.0.0.1:1111/v1",
- fxa: fxa,
- }
- return new FxAccountsProfileClient(options);
-};
-
-const ACCOUNT_DATA = {
- uid: "abc123"
-};
-
-function FxaMock() {
-}
-FxaMock.prototype = {
- currentAccountState: {
- profile: null,
- get isCurrent() {
- return true;
- }
- },
-
- getSignedInUser: function () {
- return Promise.resolve(ACCOUNT_DATA);
- }
-};
-
-var mockFxa = function() {
- return new FxaMock();
-};
-
-function CreateFxAccountsProfile(fxa = null, client = null) {
- if (!fxa) {
- fxa = mockFxa();
- }
- let options = {
- fxa: fxa,
- profileServerUrl: "http://127.0.0.1:1111/v1"
- }
- if (client) {
- options.profileClient = client;
- }
- return new FxAccountsProfile(options);
-}
-
-add_test(function getCachedProfile() {
- let profile = CreateFxAccountsProfile();
- // a little pointless until bug 1157529 is fixed...
- profile._cachedProfile = { avatar: "myurl" };
-
- return profile._getCachedProfile()
- .then(function (cached) {
- do_check_eq(cached.avatar, "myurl");
- run_next_test();
- });
-});
-
-add_test(function cacheProfile_change() {
- let fxa = mockFxa();
-/* Saving profile data disabled - bug 1157529
- let setUserAccountDataCalled = false;
- fxa.setUserAccountData = function (data) {
- setUserAccountDataCalled = true;
- do_check_eq(data.profile.avatar, "myurl");
- return Promise.resolve();
- };
-*/
- let profile = CreateFxAccountsProfile(fxa);
-
- makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
- do_check_eq(data, ACCOUNT_DATA.uid);
-// do_check_true(setUserAccountDataCalled); - bug 1157529
- run_next_test();
- });
-
- return profile._cacheProfile({ avatar: "myurl" });
-});
-
-add_test(function cacheProfile_no_change() {
- let fxa = mockFxa();
- let profile = CreateFxAccountsProfile(fxa)
- profile._cachedProfile = { avatar: "myurl" };
-// XXX - saving is disabled (but we can leave that in for now as we are
-// just checking it is *not* called)
- fxa.setSignedInUser = function (data) {
- throw new Error("should not update account data");
- };
-
- return profile._cacheProfile({ avatar: "myurl" })
- .then((result) => {
- do_check_false(!!result);
- run_next_test();
- });
-});
-
-add_test(function fetchAndCacheProfile_ok() {
- let client = mockClient(mockFxa());
- client.fetchProfile = function () {
- return Promise.resolve({ avatar: "myimg"});
- };
- let profile = CreateFxAccountsProfile(null, client);
-
- profile._cacheProfile = function (toCache) {
- do_check_eq(toCache.avatar, "myimg");
- return Promise.resolve();
- };
-
- return profile._fetchAndCacheProfile()
- .then(result => {
- do_check_eq(result.avatar, "myimg");
- run_next_test();
- });
-});
-
-// Check that a second profile request when one is already in-flight reuses
-// the in-flight one.
-add_task(function* fetchAndCacheProfileOnce() {
- // A promise that remains unresolved while we fire off 2 requests for
- // a profile.
- let resolveProfile;
- let promiseProfile = new Promise(resolve => {
- resolveProfile = resolve;
- });
- let numFetches = 0;
- let client = mockClient(mockFxa());
- client.fetchProfile = function () {
- numFetches += 1;
- return promiseProfile;
- };
- let profile = CreateFxAccountsProfile(null, client);
-
- let request1 = profile._fetchAndCacheProfile();
- let request2 = profile._fetchAndCacheProfile();
-
- // should be one request made to fetch the profile (but the promise returned
- // by it remains unresolved)
- do_check_eq(numFetches, 1);
-
- // resolve the promise.
- resolveProfile({ avatar: "myimg"});
-
- // both requests should complete with the same data.
- let got1 = yield request1;
- do_check_eq(got1.avatar, "myimg");
- let got2 = yield request1;
- do_check_eq(got2.avatar, "myimg");
-
- // and still only 1 request was made.
- do_check_eq(numFetches, 1);
-});
-
-// Check that sharing a single fetch promise works correctly when the promise
-// is rejected.
-add_task(function* fetchAndCacheProfileOnce() {
- // A promise that remains unresolved while we fire off 2 requests for
- // a profile.
- let rejectProfile;
- let promiseProfile = new Promise((resolve,reject) => {
- rejectProfile = reject;
- });
- let numFetches = 0;
- let client = mockClient(mockFxa());
- client.fetchProfile = function () {
- numFetches += 1;
- return promiseProfile;
- };
- let profile = CreateFxAccountsProfile(null, client);
-
- let request1 = profile._fetchAndCacheProfile();
- let request2 = profile._fetchAndCacheProfile();
-
- // should be one request made to fetch the profile (but the promise returned
- // by it remains unresolved)
- do_check_eq(numFetches, 1);
-
- // reject the promise.
- rejectProfile("oh noes");
-
- // both requests should reject.
- try {
- yield request1;
- throw new Error("should have rejected");
- } catch (ex) {
- if (ex != "oh noes") {
- throw ex;
- }
- }
- try {
- yield request2;
- throw new Error("should have rejected");
- } catch (ex) {
- if (ex != "oh noes") {
- throw ex;
- }
- }
-
- // but a new request should work.
- client.fetchProfile = function () {
- return Promise.resolve({ avatar: "myimg"});
- };
-
- let got = yield profile._fetchAndCacheProfile();
- do_check_eq(got.avatar, "myimg");
-});
-
-// 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() {
- let numFetches = 0;
- let client = mockClient(mockFxa());
- client.fetchProfile = function () {
- numFetches += 1;
- return Promise.resolve({ avatar: "myimg"});
- };
- let profile = CreateFxAccountsProfile(null, client);
- profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
-
- yield profile.getProfile();
- do_check_eq(numFetches, 1);
-
- yield profile.getProfile();
- do_check_eq(numFetches, 1);
-
- yield new Promise(resolve => {
- do_timeout(1000, resolve);
- });
-
- yield profile.getProfile();
- do_check_eq(numFetches, 2);
-});
-
-// Check that a new profile request within PROFILE_FRESHNESS_THRESHOLD of the
-// last one *does* kick off a new request if ON_PROFILE_CHANGE_NOTIFICATION
-// is sent.
-add_task(function* fetchAndCacheProfileBeforeThresholdOnNotification() {
- let numFetches = 0;
- let client = mockClient(mockFxa());
- client.fetchProfile = function () {
- numFetches += 1;
- return Promise.resolve({ avatar: "myimg"});
- };
- let profile = CreateFxAccountsProfile(null, client);
- profile.PROFILE_FRESHNESS_THRESHOLD = 1000;
-
- yield profile.getProfile();
- do_check_eq(numFetches, 1);
-
- Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, null);
-
- yield profile.getProfile();
- do_check_eq(numFetches, 2);
-});
-
-add_test(function tearDown_ok() {
- let profile = CreateFxAccountsProfile();
-
- do_check_true(!!profile.client);
- do_check_true(!!profile.fxa);
-
- profile.tearDown();
- do_check_null(profile.fxa);
- do_check_null(profile.client);
-
- run_next_test();
-});
-
-add_test(function getProfile_ok() {
- let cachedUrl = "myurl";
- let didFetch = false;
-
- let profile = CreateFxAccountsProfile();
- profile._getCachedProfile = function () {
- return Promise.resolve({ avatar: cachedUrl });
- };
-
- profile._fetchAndCacheProfile = function () {
- didFetch = true;
- return Promise.resolve();
- };
-
- return profile.getProfile()
- .then(result => {
- do_check_eq(result.avatar, cachedUrl);
- do_check_true(didFetch);
- run_next_test();
- });
-});
-
-add_test(function getProfile_no_cache() {
- let fetchedUrl = "newUrl";
- let profile = CreateFxAccountsProfile();
- profile._getCachedProfile = function () {
- return Promise.resolve();
- };
-
- profile._fetchAndCacheProfile = function () {
- return Promise.resolve({ avatar: fetchedUrl });
- };
-
- return profile.getProfile()
- .then(result => {
- do_check_eq(result.avatar, fetchedUrl);
- run_next_test();
- });
-});
-
-add_test(function getProfile_has_cached_fetch_deleted() {
- let cachedUrl = "myurl";
-
- let fxa = mockFxa();
- let client = mockClient(fxa);
- client.fetchProfile = function () {
- return Promise.resolve({ avatar: null });
- };
-
- let profile = CreateFxAccountsProfile(fxa, client);
- profile._cachedProfile = { avatar: cachedUrl };
-
-// instead of checking this in a mocked "save" function, just check after the
-// observer
- makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
- profile.getProfile()
- .then(profileData => {
- do_check_null(profileData.avatar);
- run_next_test();
- });
- });
-
- return profile.getProfile()
- .then(result => {
- do_check_eq(result.avatar, "myurl");
- });
-});
-
-function run_test() {
- run_next_test();
-}
-
-function makeObserver(aObserveTopic, aObserveFunc) {
- let callback = function (aSubject, aTopic, aData) {
- log.debug("observed " + aTopic + " " + aData);
- if (aTopic == aObserveTopic) {
- removeMe();
- aObserveFunc(aSubject, aTopic, aData);
- }
- };
-
- function removeMe() {
- log.debug("removing observer for " + aObserveTopic);
- Services.obs.removeObserver(callback, aObserveTopic);
- }
-
- Services.obs.addObserver(callback, aObserveTopic, false);
- return removeMe;
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_profile_client.js b/services/fxaccounts/tests/xpcshell/test_profile_client.js
deleted file mode 100644
index 20ff6efc6..000000000
--- a/services/fxaccounts/tests/xpcshell/test_profile_client.js
+++ /dev/null
@@ -1,411 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsProfileClient.jsm");
-
-const STATUS_SUCCESS = 200;
-
-/**
- * Mock request responder
- * @param {String} response
- * Mocked raw response from the server
- * @returns {Function}
- */
-var mockResponse = function (response) {
- let Request = function (requestUri) {
- // Store the request uri so tests can inspect it
- Request._requestUri = requestUri;
- return {
- setHeader: function () {},
- get: function () {
- this.response = response;
- this.onComplete();
- }
- };
- };
-
- return Request;
-};
-
-// A simple mock FxA that hands out tokens without checking them and doesn't
-// expect tokens to be revoked. We have specific token tests further down that
-// has more checks here.
-var mockFxa = {
- getOAuthToken(options) {
- do_check_eq(options.scope, "profile");
- return "token";
- }
-}
-
-const PROFILE_OPTIONS = {
- serverURL: "http://127.0.0.1:1111/v1",
- fxa: mockFxa,
-};
-
-/**
- * Mock request error responder
- * @param {Error} error
- * Error object
- * @returns {Function}
- */
-var mockResponseError = function (error) {
- return function () {
- return {
- setHeader: function () {},
- get: function () {
- this.onComplete(error);
- }
- };
- };
-};
-
-add_test(function successfulResponse () {
- let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
- let response = {
- success: true,
- status: STATUS_SUCCESS,
- body: "{\"email\":\"someone@restmail.net\",\"uid\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
- };
-
- client._Request = new mockResponse(response);
- client.fetchProfile()
- .then(
- function (result) {
- do_check_eq(client._Request._requestUri, "http://127.0.0.1:1111/v1/profile");
- do_check_eq(result.email, "someone@restmail.net");
- do_check_eq(result.uid, "0d5c1a89b8c54580b8e3e8adadae864a");
- run_next_test();
- }
- );
-});
-
-add_test(function parseErrorResponse () {
- let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
- let response = {
- success: true,
- status: STATUS_SUCCESS,
- body: "unexpected",
- };
-
- client._Request = new mockResponse(response);
- client.fetchProfile()
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsProfileClientError");
- do_check_eq(e.code, STATUS_SUCCESS);
- do_check_eq(e.errno, ERRNO_PARSE);
- do_check_eq(e.error, ERROR_PARSE);
- do_check_eq(e.message, "unexpected");
- run_next_test();
- }
- );
-});
-
-add_test(function serverErrorResponse () {
- let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
- let response = {
- status: 500,
- body: "{ \"code\": 500, \"errno\": 100, \"error\": \"Bad Request\", \"message\": \"Something went wrong\", \"reason\": \"Because the internet\" }",
- };
-
- client._Request = new mockResponse(response);
- client.fetchProfile()
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsProfileClientError");
- do_check_eq(e.code, 500);
- do_check_eq(e.errno, 100);
- do_check_eq(e.error, "Bad Request");
- do_check_eq(e.message, "Something went wrong");
- run_next_test();
- }
- );
-});
-
-// Test that we get a token, then if we get a 401 we revoke it, get a new one
-// and retry.
-add_test(function server401ResponseThenSuccess () {
- // The last token we handed out.
- let lastToken = -1;
- // The number of times our removeCachedOAuthToken function was called.
- let numTokensRemoved = 0;
-
- let mockFxa = {
- getOAuthToken(options) {
- do_check_eq(options.scope, "profile");
- return "" + ++lastToken; // tokens are strings.
- },
- removeCachedOAuthToken(options) {
- // This test never has more than 1 token alive at once, so the token
- // being revoked must always be the last token we handed out.
- do_check_eq(parseInt(options.token), lastToken);
- ++numTokensRemoved;
- }
- }
- let profileOptions = {
- serverURL: "http://127.0.0.1:1111/v1",
- fxa: mockFxa,
- };
- let client = new FxAccountsProfileClient(profileOptions);
-
- // 2 responses - first one implying the token has expired, second works.
- let responses = [
- {
- status: 401,
- body: "{ \"code\": 401, \"errno\": 100, \"error\": \"Token expired\", \"message\": \"That token is too old\", \"reason\": \"Because security\" }",
- },
- {
- success: true,
- status: STATUS_SUCCESS,
- body: "{\"avatar\":\"http://example.com/image.jpg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
- },
- ];
-
- let numRequests = 0;
- let numAuthHeaders = 0;
- // Like mockResponse but we want access to headers etc.
- client._Request = function(requestUri) {
- return {
- setHeader: function (name, value) {
- if (name == "Authorization") {
- numAuthHeaders++;
- do_check_eq(value, "Bearer " + lastToken);
- }
- },
- get: function () {
- this.response = responses[numRequests];
- ++numRequests;
- this.onComplete();
- }
- };
- }
-
- client.fetchProfile()
- .then(result => {
- do_check_eq(result.avatar, "http://example.com/image.jpg");
- do_check_eq(result.id, "0d5c1a89b8c54580b8e3e8adadae864a");
- // should have been exactly 2 requests and exactly 2 auth headers.
- do_check_eq(numRequests, 2);
- do_check_eq(numAuthHeaders, 2);
- // and we should have seen one token revoked.
- do_check_eq(numTokensRemoved, 1);
-
- run_next_test();
- }
- );
-});
-
-// Test that we get a token, then if we get a 401 we revoke it, get a new one
-// and retry - but we *still* get a 401 on the retry, so the caller sees that.
-add_test(function server401ResponsePersists () {
- // The last token we handed out.
- let lastToken = -1;
- // The number of times our removeCachedOAuthToken function was called.
- let numTokensRemoved = 0;
-
- let mockFxa = {
- getOAuthToken(options) {
- do_check_eq(options.scope, "profile");
- return "" + ++lastToken; // tokens are strings.
- },
- removeCachedOAuthToken(options) {
- // This test never has more than 1 token alive at once, so the token
- // being revoked must always be the last token we handed out.
- do_check_eq(parseInt(options.token), lastToken);
- ++numTokensRemoved;
- }
- }
- let profileOptions = {
- serverURL: "http://127.0.0.1:1111/v1",
- fxa: mockFxa,
- };
- let client = new FxAccountsProfileClient(profileOptions);
-
- let response = {
- status: 401,
- body: "{ \"code\": 401, \"errno\": 100, \"error\": \"It's not your token, it's you!\", \"message\": \"I don't like you\", \"reason\": \"Because security\" }",
- };
-
- let numRequests = 0;
- let numAuthHeaders = 0;
- client._Request = function(requestUri) {
- return {
- setHeader: function (name, value) {
- if (name == "Authorization") {
- numAuthHeaders++;
- do_check_eq(value, "Bearer " + lastToken);
- }
- },
- get: function () {
- this.response = response;
- ++numRequests;
- this.onComplete();
- }
- };
- }
-
- client.fetchProfile().then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsProfileClientError");
- do_check_eq(e.code, 401);
- do_check_eq(e.errno, 100);
- do_check_eq(e.error, "It's not your token, it's you!");
- // should have been exactly 2 requests and exactly 2 auth headers.
- do_check_eq(numRequests, 2);
- do_check_eq(numAuthHeaders, 2);
- // and we should have seen both tokens revoked.
- do_check_eq(numTokensRemoved, 2);
- run_next_test();
- }
- );
-});
-
-add_test(function networkErrorResponse () {
- let client = new FxAccountsProfileClient({
- serverURL: "http://domain.dummy",
- fxa: mockFxa,
- });
- client.fetchProfile()
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsProfileClientError");
- do_check_eq(e.code, null);
- do_check_eq(e.errno, ERRNO_NETWORK);
- do_check_eq(e.error, ERROR_NETWORK);
- run_next_test();
- }
- );
-});
-
-add_test(function unsupportedMethod () {
- let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
-
- return client._createRequest("/profile", "PUT")
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsProfileClientError");
- do_check_eq(e.code, ERROR_CODE_METHOD_NOT_ALLOWED);
- do_check_eq(e.errno, ERRNO_NETWORK);
- do_check_eq(e.error, ERROR_NETWORK);
- do_check_eq(e.message, ERROR_MSG_METHOD_NOT_ALLOWED);
- run_next_test();
- }
- );
-});
-
-add_test(function onCompleteRequestError () {
- let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
- client._Request = new mockResponseError(new Error("onComplete error"));
- client.fetchProfile()
- .then(
- null,
- function (e) {
- do_check_eq(e.name, "FxAccountsProfileClientError");
- do_check_eq(e.code, null);
- do_check_eq(e.errno, ERRNO_NETWORK);
- do_check_eq(e.error, ERROR_NETWORK);
- do_check_eq(e.message, "Error: onComplete error");
- run_next_test();
- }
- );
-});
-
-add_test(function fetchProfileImage_successfulResponse () {
- let client = new FxAccountsProfileClient(PROFILE_OPTIONS);
- let response = {
- success: true,
- status: STATUS_SUCCESS,
- body: "{\"avatar\":\"http://example.com/image.jpg\",\"id\":\"0d5c1a89b8c54580b8e3e8adadae864a\"}",
- };
-
- client._Request = new mockResponse(response);
- client.fetchProfileImage()
- .then(
- function (result) {
- do_check_eq(client._Request._requestUri, "http://127.0.0.1:1111/v1/avatar");
- do_check_eq(result.avatar, "http://example.com/image.jpg");
- do_check_eq(result.id, "0d5c1a89b8c54580b8e3e8adadae864a");
- run_next_test();
- }
- );
-});
-
-add_test(function constructorTests() {
- validationHelper(undefined,
- "Error: Missing 'serverURL' configuration option");
-
- validationHelper({},
- "Error: Missing 'serverURL' configuration option");
-
- validationHelper({ serverURL: "badUrl" },
- "Error: Invalid 'serverURL'");
-
- run_next_test();
-});
-
-add_test(function errorTests() {
- let error1 = new FxAccountsProfileClientError();
- do_check_eq(error1.name, "FxAccountsProfileClientError");
- do_check_eq(error1.code, null);
- do_check_eq(error1.errno, ERRNO_UNKNOWN_ERROR);
- do_check_eq(error1.error, ERROR_UNKNOWN);
- do_check_eq(error1.message, null);
-
- let error2 = new FxAccountsProfileClientError({
- code: STATUS_SUCCESS,
- errno: 1,
- error: "Error",
- message: "Something",
- });
- let fields2 = error2._toStringFields();
- let statusCode = 1;
-
- do_check_eq(error2.name, "FxAccountsProfileClientError");
- do_check_eq(error2.code, STATUS_SUCCESS);
- do_check_eq(error2.errno, statusCode);
- do_check_eq(error2.error, "Error");
- do_check_eq(error2.message, "Something");
-
- do_check_eq(fields2.name, "FxAccountsProfileClientError");
- do_check_eq(fields2.code, STATUS_SUCCESS);
- do_check_eq(fields2.errno, statusCode);
- do_check_eq(fields2.error, "Error");
- do_check_eq(fields2.message, "Something");
-
- do_check_true(error2.toString().indexOf("Something") >= 0);
- run_next_test();
-});
-
-function run_test() {
- run_next_test();
-}
-
-/**
- * Quick way to test the "FxAccountsProfileClient" constructor.
- *
- * @param {Object} options
- * FxAccountsProfileClient constructor options
- * @param {String} expected
- * Expected error message
- * @returns {*}
- */
-function validationHelper(options, expected) {
- // add fxa to options - that missing isn't what we are testing here.
- if (options) {
- options.fxa = mockFxa;
- }
- try {
- new FxAccountsProfileClient(options);
- } catch (e) {
- return do_check_eq(e.toString(), expected);
- }
- throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_push_service.js b/services/fxaccounts/tests/xpcshell/test_push_service.js
deleted file mode 100644
index 8d66f6fa8..000000000
--- a/services/fxaccounts/tests/xpcshell/test_push_service.js
+++ /dev/null
@@ -1,236 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests for the FxA push service.
-
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Promise.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/FxAccountsPush.js");
-Cu.import("resource://gre/modules/Log.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "pushService",
- "@mozilla.org/push/Service;1", "nsIPushService");
-
-initTestLogging("Trace");
-log.level = Log.Level.Trace;
-
-const MOCK_ENDPOINT = "http://mochi.test:8888";
-
-// tests do not allow external connections, mock the PushService
-let mockPushService = {
- pushTopic: this.pushService.pushTopic,
- subscriptionChangeTopic: this.pushService.subscriptionChangeTopic,
- subscribe(scope, principal, cb) {
- cb(Components.results.NS_OK, {
- endpoint: MOCK_ENDPOINT
- });
- },
- unsubscribe(scope, principal, cb) {
- cb(Components.results.NS_OK, true);
- }
-};
-
-let mockFxAccounts = {
- checkVerificationStatus() {},
- updateDeviceRegistration() {}
-};
-
-let mockLog = {
- trace() {},
- debug() {},
- warn() {},
- error() {}
-};
-
-
-add_task(function* initialize() {
- let pushService = new FxAccountsPushService();
- equal(pushService.initialize(), false);
-});
-
-add_task(function* registerPushEndpointSuccess() {
- let pushService = new FxAccountsPushService({
- pushService: mockPushService,
- fxAccounts: mockFxAccounts,
- });
-
- let subscription = yield pushService.registerPushEndpoint();
- equal(subscription.endpoint, MOCK_ENDPOINT);
-});
-
-add_task(function* registerPushEndpointFailure() {
- let failPushService = Object.assign(mockPushService, {
- subscribe(scope, principal, cb) {
- cb(Components.results.NS_ERROR_ABORT);
- }
- });
-
- let pushService = new FxAccountsPushService({
- pushService: failPushService,
- fxAccounts: mockFxAccounts,
- });
-
- let subscription = yield pushService.registerPushEndpoint();
- equal(subscription, null);
-});
-
-add_task(function* unsubscribeSuccess() {
- let pushService = new FxAccountsPushService({
- pushService: mockPushService,
- fxAccounts: mockFxAccounts,
- });
-
- let result = yield pushService.unsubscribe();
- equal(result, true);
-});
-
-add_task(function* unsubscribeFailure() {
- let failPushService = Object.assign(mockPushService, {
- unsubscribe(scope, principal, cb) {
- cb(Components.results.NS_ERROR_ABORT);
- }
- });
-
- let pushService = new FxAccountsPushService({
- pushService: failPushService,
- fxAccounts: mockFxAccounts,
- });
-
- let result = yield pushService.unsubscribe();
- equal(result, null);
-});
-
-add_test(function observeLogout() {
- let customLog = Object.assign(mockLog, {
- trace: function (msg) {
- if (msg === "FxAccountsPushService unsubscribe") {
- // logout means we unsubscribe
- run_next_test();
- }
- }
- });
-
- let pushService = new FxAccountsPushService({
- pushService: mockPushService,
- log: customLog
- });
-
- pushService.observe(null, ONLOGOUT_NOTIFICATION);
-});
-
-add_test(function observePushTopicVerify() {
- let emptyMsg = {
- QueryInterface: function() {
- return this;
- }
- };
- let customAccounts = Object.assign(mockFxAccounts, {
- checkVerificationStatus: function () {
- // checking verification status on push messages without data
- run_next_test();
- }
- });
-
- let pushService = new FxAccountsPushService({
- pushService: mockPushService,
- fxAccounts: customAccounts,
- });
-
- pushService.observe(emptyMsg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observePushTopicDeviceDisconnected() {
- const deviceId = "bogusid";
- let msg = {
- data: {
- json: () => ({
- command: ON_DEVICE_DISCONNECTED_NOTIFICATION,
- data: {
- id: deviceId
- }
- })
- },
- QueryInterface: function() {
- return this;
- }
- };
- let customAccounts = Object.assign(mockFxAccounts, {
- handleDeviceDisconnection: function () {
- // checking verification status on push messages without data
- run_next_test();
- }
- });
-
- let pushService = new FxAccountsPushService({
- pushService: mockPushService,
- fxAccounts: customAccounts,
- });
-
- pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observePushTopicPasswordChanged() {
- let msg = {
- data: {
- json: () => ({
- command: ON_PASSWORD_CHANGED_NOTIFICATION
- })
- },
- QueryInterface: function() {
- return this;
- }
- };
-
- let pushService = new FxAccountsPushService({
- pushService: mockPushService,
- });
-
- pushService._onPasswordChanged = function () {
- run_next_test();
- }
-
- pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observePushTopicPasswordReset() {
- let msg = {
- data: {
- json: () => ({
- command: ON_PASSWORD_RESET_NOTIFICATION
- })
- },
- QueryInterface: function() {
- return this;
- }
- };
-
- let pushService = new FxAccountsPushService({
- pushService: mockPushService
- });
-
- pushService._onPasswordChanged = function () {
- run_next_test();
- }
-
- pushService.observe(msg, mockPushService.pushTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
-
-add_test(function observeSubscriptionChangeTopic() {
- let customAccounts = Object.assign(mockFxAccounts, {
- updateDeviceRegistration: function () {
- // subscription change means updating the device registration
- run_next_test();
- }
- });
-
- let pushService = new FxAccountsPushService({
- pushService: mockPushService,
- fxAccounts: customAccounts,
- });
-
- pushService.observe(null, mockPushService.subscriptionChangeTopic, FXA_PUSH_SCOPE_ACCOUNT_UPDATE);
-});
diff --git a/services/fxaccounts/tests/xpcshell/test_storage_manager.js b/services/fxaccounts/tests/xpcshell/test_storage_manager.js
deleted file mode 100644
index 6a293a0ff..000000000
--- a/services/fxaccounts/tests/xpcshell/test_storage_manager.js
+++ /dev/null
@@ -1,477 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Tests for the FxA storage manager.
-
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/FxAccountsStorage.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://gre/modules/Log.jsm");
-
-initTestLogging("Trace");
-log.level = Log.Level.Trace;
-
-const DEVICE_REGISTRATION_VERSION = 42;
-
-// A couple of mocks we can use.
-function MockedPlainStorage(accountData) {
- let data = null;
- if (accountData) {
- data = {
- version: DATA_FORMAT_VERSION,
- accountData: accountData,
- }
- }
- this.data = data;
- this.numReads = 0;
-}
-MockedPlainStorage.prototype = {
- get: Task.async(function* () {
- this.numReads++;
- Assert.equal(this.numReads, 1, "should only ever be 1 read of acct data");
- return this.data;
- }),
-
- set: Task.async(function* (data) {
- this.data = data;
- }),
-};
-
-function MockedSecureStorage(accountData) {
- let data = null;
- if (accountData) {
- data = {
- version: DATA_FORMAT_VERSION,
- accountData: accountData,
- }
- }
- this.data = data;
- this.numReads = 0;
-}
-
-MockedSecureStorage.prototype = {
- fetchCount: 0,
- locked: false,
- STORAGE_LOCKED: function() {},
- get: Task.async(function* (uid, email) {
- this.fetchCount++;
- if (this.locked) {
- throw new this.STORAGE_LOCKED();
- }
- this.numReads++;
- Assert.equal(this.numReads, 1, "should only ever be 1 read of unlocked data");
- return this.data;
- }),
-
- set: Task.async(function* (uid, contents) {
- this.data = contents;
- }),
-}
-
-function add_storage_task(testFunction) {
- add_task(function* () {
- print("Starting test with secure storage manager");
- yield testFunction(new FxAccountsStorageManager());
- });
- add_task(function* () {
- print("Starting test with simple storage manager");
- yield testFunction(new FxAccountsStorageManager({useSecure: false}));
- });
-}
-
-// initialized without account data and there's nothing to read. Not logged in.
-add_storage_task(function* checkInitializedEmpty(sm) {
- if (sm.secureStorage) {
- sm.secureStorage = new MockedSecureStorage(null);
- }
- yield sm.initialize();
- Assert.strictEqual((yield sm.getAccountData()), null);
- Assert.rejects(sm.updateAccountData({kA: "kA"}), "No user is logged in")
-});
-
-// Initialized with account data (ie, simulating a new user being logged in).
-// Should reflect the initial data and be written to storage.
-add_storage_task(function* checkNewUser(sm) {
- let initialAccountData = {
- uid: "uid",
- email: "someone@somewhere.com",
- kA: "kA",
- deviceId: "device id"
- };
- sm.plainStorage = new MockedPlainStorage()
- if (sm.secureStorage) {
- sm.secureStorage = new MockedSecureStorage(null);
- }
- yield sm.initialize(initialAccountData);
- let accountData = yield sm.getAccountData();
- 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);
- } else {
- Assert.equal(sm.plainStorage.data.accountData.kA, initialAccountData.kA);
- }
-});
-
-// 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",
- deviceId: "wibble",
- deviceRegistrationVersion: null
- });
- if (sm.secureStorage) {
- sm.secureStorage = new MockedSecureStorage(null);
- }
- yield sm.initialize();
- let accountData = yield sm.getAccountData();
- 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.deviceRegistrationVersion, null);
- // 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",
- deviceRegistrationVersion: DEVICE_REGISTRATION_VERSION
- });
- accountData = yield sm.getAccountData();
- Assert.equal(accountData.kB, "kB");
- Assert.equal(accountData.kA, "kA");
- Assert.equal(accountData.deviceId, "wibble");
- Assert.equal(accountData.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
- // Check the new value was written to storage.
- yield sm._promiseStorageComplete; // storage is written in the background.
- // "verified", "deviceId" and "deviceRegistrationVersion" 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.deviceRegistrationVersion, DEVICE_REGISTRATION_VERSION);
- // "kA" and "foo" are secure
- if (sm.secureStorage) {
- Assert.equal(sm.secureStorage.data.accountData.kA, "kA");
- Assert.equal(sm.secureStorage.data.accountData.kB, "kB");
- } else {
- Assert.equal(sm.plainStorage.data.accountData.kA, "kA");
- Assert.equal(sm.plainStorage.data.accountData.kB, "kB");
- }
-});
-
-add_storage_task(function* checkInvalidUpdates(sm) {
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- if (sm.secureStorage) {
- sm.secureStorage = new MockedSecureStorage(null);
- }
- Assert.rejects(sm.updateAccountData({uid: "another"}), "Can't change");
- Assert.rejects(sm.updateAccountData({email: "someoneelse"}), "Can't change");
-});
-
-add_storage_task(function* checkNullUpdatesRemovedUnlocked(sm) {
- if (sm.secureStorage) {
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA", kB: "kB"});
- } else {
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com",
- kA: "kA", kB: "kB"});
- }
- yield sm.initialize();
-
- yield sm.updateAccountData({kA: null});
- let accountData = yield sm.getAccountData();
- Assert.ok(!accountData.kA);
- Assert.equal(accountData.kB, "kB");
-});
-
-add_storage_task(function* checkDelete(sm) {
- if (sm.secureStorage) {
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA", kB: "kB"});
- } else {
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com",
- kA: "kA", kB: "kB"});
- }
- yield sm.initialize();
-
- yield sm.deleteAccountData();
- // Storage should have been reset to null.
- Assert.equal(sm.plainStorage.data, null);
- if (sm.secureStorage) {
- Assert.equal(sm.secureStorage.data, null);
- }
- // And everything should reflect no user.
- Assert.equal((yield sm.getAccountData()), null);
-});
-
-// Some tests only for the secure storage manager.
-add_task(function* checkNullUpdatesRemovedLocked() {
- let sm = new FxAccountsStorageManager();
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA", kB: "kB"});
- sm.secureStorage.locked = true;
- yield sm.initialize();
-
- yield sm.updateAccountData({kA: null});
- let accountData = yield sm.getAccountData();
- Assert.ok(!accountData.kA);
- // still no kB as we are locked.
- Assert.ok(!accountData.kB);
-
- // now unlock - should still be no kA but kB should appear.
- sm.secureStorage.locked = false;
- accountData = yield sm.getAccountData();
- Assert.ok(!accountData.kA);
- Assert.equal(accountData.kB, "kB");
- // And secure storage should have been written with our previously-cached
- // data.
- Assert.strictEqual(sm.secureStorage.data.accountData.kA, undefined);
- Assert.strictEqual(sm.secureStorage.data.accountData.kB, "kB");
-});
-
-add_task(function* checkEverythingReadSecure() {
- let sm = new FxAccountsStorageManager();
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA"});
- yield sm.initialize();
-
- let accountData = yield sm.getAccountData();
- Assert.ok(accountData, "read account data");
- Assert.equal(accountData.uid, "uid");
- Assert.equal(accountData.email, "someone@somewhere.com");
- Assert.equal(accountData.kA, "kA");
-});
-
-add_task(function* checkMemoryFieldsNotReturnedByDefault() {
- let sm = new FxAccountsStorageManager();
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA"});
- yield sm.initialize();
-
- // keyPair is a memory field.
- yield sm.updateAccountData({keyPair: "the keypair value"});
- let accountData = yield sm.getAccountData();
-
- // Requesting everything should *not* return in memory fields.
- Assert.strictEqual(accountData.keyPair, undefined);
- // But requesting them specifically does get them.
- accountData = yield sm.getAccountData("keyPair");
- Assert.strictEqual(accountData.keyPair, "the keypair value");
-});
-
-add_task(function* checkExplicitGet() {
- let sm = new FxAccountsStorageManager();
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA"});
- yield sm.initialize();
-
- let accountData = yield sm.getAccountData(["uid", "kA"]);
- Assert.ok(accountData, "read account data");
- Assert.equal(accountData.uid, "uid");
- Assert.equal(accountData.kA, "kA");
- // We didn't ask for email so shouldn't have got it.
- Assert.strictEqual(accountData.email, undefined);
-});
-
-add_task(function* checkExplicitGetNoSecureRead() {
- let sm = new FxAccountsStorageManager();
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA"});
- yield sm.initialize();
-
- Assert.equal(sm.secureStorage.fetchCount, 0);
- // request 2 fields in secure storage - it should have caused a single fetch.
- let accountData = yield sm.getAccountData(["email", "uid"]);
- Assert.ok(accountData, "read account data");
- Assert.equal(accountData.uid, "uid");
- Assert.equal(accountData.email, "someone@somewhere.com");
- Assert.strictEqual(accountData.kA, undefined);
- Assert.equal(sm.secureStorage.fetchCount, 1);
-});
-
-add_task(function* checkLockedUpdates() {
- let sm = new FxAccountsStorageManager();
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "old-kA", kB: "kB"});
- sm.secureStorage.locked = true;
- yield sm.initialize();
-
- let accountData = yield sm.getAccountData();
- // requesting kA and kB will fail as storage is locked.
- Assert.ok(!accountData.kA);
- Assert.ok(!accountData.kB);
- // While locked we can still update it and see the updated value.
- sm.updateAccountData({kA: "new-kA"});
- accountData = yield sm.getAccountData();
- Assert.equal(accountData.kA, "new-kA");
- // unlock.
- sm.secureStorage.locked = false;
- accountData = yield sm.getAccountData();
- // should reflect the value we updated and the one we didn't.
- Assert.equal(accountData.kA, "new-kA");
- Assert.equal(accountData.kB, "kB");
- // And storage should also reflect it.
- Assert.strictEqual(sm.secureStorage.data.accountData.kA, "new-kA");
- Assert.strictEqual(sm.secureStorage.data.accountData.kB, "kB");
-});
-
-// Some tests for the "storage queue" functionality.
-
-// A helper for our queued tests. It creates a StorageManager and then queues
-// an unresolved promise. The tests then do additional setup and checks, then
-// resolves or rejects the blocked promise.
-var setupStorageManagerForQueueTest = Task.async(function* () {
- let sm = new FxAccountsStorageManager();
- sm.plainStorage = new MockedPlainStorage({uid: "uid", email: "someone@somewhere.com"})
- sm.secureStorage = new MockedSecureStorage({kA: "kA"});
- sm.secureStorage.locked = true;
- yield sm.initialize();
-
- let resolveBlocked, rejectBlocked;
- let blockedPromise = new Promise((resolve, reject) => {
- resolveBlocked = resolve;
- rejectBlocked = reject;
- });
-
- sm._queueStorageOperation(() => blockedPromise);
- return {sm, blockedPromise, resolveBlocked, rejectBlocked}
-});
-
-// First the general functionality.
-add_task(function* checkQueueSemantics() {
- let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
-
- // We've one unresolved promise in the queue - add another promise.
- let resolveSubsequent;
- let subsequentPromise = new Promise(resolve => {
- resolveSubsequent = resolve;
- });
- let subsequentCalled = false;
-
- sm._queueStorageOperation(() => {
- subsequentCalled = true;
- resolveSubsequent();
- return subsequentPromise;
- });
-
- // Our "subsequent" function should not have been called yet.
- Assert.ok(!subsequentCalled);
-
- // Release our blocked promise.
- resolveBlocked();
-
- // Our subsequent promise should end up resolved.
- yield subsequentPromise;
- Assert.ok(subsequentCalled);
- yield sm.finalize();
-});
-
-// Check that a queued promise being rejected works correctly.
-add_task(function* checkQueueSemanticsOnError() {
- let { sm, blockedPromise, rejectBlocked } = yield setupStorageManagerForQueueTest();
-
- let resolveSubsequent;
- let subsequentPromise = new Promise(resolve => {
- resolveSubsequent = resolve;
- });
- let subsequentCalled = false;
-
- sm._queueStorageOperation(() => {
- subsequentCalled = true;
- resolveSubsequent();
- return subsequentPromise;
- });
-
- // Our "subsequent" function should not have been called yet.
- Assert.ok(!subsequentCalled);
-
- // Reject our blocked promise - the subsequent operations should still work
- // correctly.
- rejectBlocked("oh no");
-
- // Our subsequent promise should end up resolved.
- yield subsequentPromise;
- Assert.ok(subsequentCalled);
-
- // But the first promise should reflect the rejection.
- try {
- yield blockedPromise;
- Assert.ok(false, "expected this promise to reject");
- } catch (ex) {
- Assert.equal(ex, "oh no");
- }
- yield sm.finalize();
-});
-
-
-// And some tests for the specific operations that are queued.
-add_task(function* checkQueuedReadAndUpdate() {
- let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
- // Mock the underlying operations
- // _doReadAndUpdateSecure is queued by _maybeReadAndUpdateSecure
- let _doReadCalled = false;
- sm._doReadAndUpdateSecure = () => {
- _doReadCalled = true;
- return Promise.resolve();
- }
-
- let resultPromise = sm._maybeReadAndUpdateSecure();
- Assert.ok(!_doReadCalled);
-
- resolveBlocked();
- yield resultPromise;
- Assert.ok(_doReadCalled);
- yield sm.finalize();
-});
-
-add_task(function* checkQueuedWrite() {
- let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
- // Mock the underlying operations
- let __writeCalled = false;
- sm.__write = () => {
- __writeCalled = true;
- return Promise.resolve();
- }
-
- let writePromise = sm._write();
- Assert.ok(!__writeCalled);
-
- resolveBlocked();
- yield writePromise;
- Assert.ok(__writeCalled);
- yield sm.finalize();
-});
-
-add_task(function* checkQueuedDelete() {
- let { sm, resolveBlocked } = yield setupStorageManagerForQueueTest();
- // Mock the underlying operations
- let _deleteCalled = false;
- sm._deleteAccountData = () => {
- _deleteCalled = true;
- return Promise.resolve();
- }
-
- let resultPromise = sm.deleteAccountData();
- Assert.ok(!_deleteCalled);
-
- resolveBlocked();
- yield resultPromise;
- Assert.ok(_deleteCalled);
- yield sm.finalize();
-});
-
-function run_test() {
- run_next_test();
-}
diff --git a/services/fxaccounts/tests/xpcshell/test_web_channel.js b/services/fxaccounts/tests/xpcshell/test_web_channel.js
deleted file mode 100644
index 3cf566278..000000000
--- a/services/fxaccounts/tests/xpcshell/test_web_channel.js
+++ /dev/null
@@ -1,499 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-const { FxAccountsWebChannel, FxAccountsWebChannelHelpers } =
- Cu.import("resource://gre/modules/FxAccountsWebChannel.jsm");
-
-const URL_STRING = "https://example.com";
-
-const mockSendingContext = {
- browser: {},
- principal: {},
- eventTarget: {}
-};
-
-add_test(function () {
- validationHelper(undefined,
- "Error: Missing configuration options");
-
- validationHelper({
- channel_id: WEBCHANNEL_ID
- },
- "Error: Missing 'content_uri' option");
-
- validationHelper({
- content_uri: 'bad uri',
- channel_id: WEBCHANNEL_ID
- },
- /NS_ERROR_MALFORMED_URI/);
-
- validationHelper({
- content_uri: URL_STRING
- },
- 'Error: Missing \'channel_id\' option');
-
- run_next_test();
-});
-
-add_task(function* test_rejection_reporting() {
- let mockMessage = {
- command: 'fxaccounts:login',
- messageId: '1234',
- data: { email: 'testuser@testuser.com' },
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING,
- helpers: {
- login(accountData) {
- equal(accountData.email, 'testuser@testuser.com',
- 'Should forward incoming message data to the helper');
- return Promise.reject(new Error('oops'));
- },
- },
- });
-
- let promiseSend = new Promise(resolve => {
- channel._channel.send = (message, context) => {
- resolve({ message, context });
- };
- });
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-
- let { message, context } = yield promiseSend;
-
- equal(context, mockSendingContext, 'Should forward the original context');
- equal(message.command, 'fxaccounts:login',
- 'Should include the incoming command');
- equal(message.messageId, '1234', 'Should include the message ID');
- equal(message.data.error.message, 'Error: oops',
- 'Should convert the error message to a string');
- notStrictEqual(message.data.error.stack, null,
- 'Should include the stack for JS error rejections');
-});
-
-add_test(function test_exception_reporting() {
- let mockMessage = {
- command: 'fxaccounts:sync_preferences',
- messageId: '5678',
- data: { entryPoint: 'fxa:verification_complete' }
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING,
- helpers: {
- openSyncPreferences(browser, entryPoint) {
- equal(entryPoint, 'fxa:verification_complete',
- 'Should forward incoming message data to the helper');
- throw new TypeError('splines not reticulated');
- },
- },
- });
-
- channel._channel.send = (message, context) => {
- equal(context, mockSendingContext, 'Should forward the original context');
- equal(message.command, 'fxaccounts:sync_preferences',
- 'Should include the incoming command');
- equal(message.messageId, '5678', 'Should include the message ID');
- equal(message.data.error.message, 'TypeError: splines not reticulated',
- 'Should convert the exception to a string');
- notStrictEqual(message.data.error.stack, null,
- 'Should include the stack for JS exceptions');
-
- run_next_test();
- };
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_profile_image_change_message() {
- var mockMessage = {
- command: "profile:change",
- data: { uid: "foo" }
- };
-
- makeObserver(ON_PROFILE_CHANGE_NOTIFICATION, function (subject, topic, data) {
- do_check_eq(data, "foo");
- run_next_test();
- });
-
- var channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING
- });
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_login_message() {
- let mockMessage = {
- command: 'fxaccounts:login',
- data: { email: 'testuser@testuser.com' }
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING,
- helpers: {
- login: function (accountData) {
- do_check_eq(accountData.email, 'testuser@testuser.com');
- run_next_test();
- return Promise.resolve();
- }
- }
- });
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_logout_message() {
- let mockMessage = {
- command: 'fxaccounts:logout',
- data: { uid: "foo" }
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING,
- helpers: {
- logout: function (uid) {
- do_check_eq(uid, 'foo');
- run_next_test();
- return Promise.resolve();
- }
- }
- });
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_delete_message() {
- let mockMessage = {
- command: 'fxaccounts:delete',
- data: { uid: "foo" }
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING,
- helpers: {
- logout: function (uid) {
- do_check_eq(uid, 'foo');
- run_next_test();
- return Promise.resolve();
- }
- }
- });
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_can_link_account_message() {
- let mockMessage = {
- command: 'fxaccounts:can_link_account',
- data: { email: 'testuser@testuser.com' }
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING,
- helpers: {
- shouldAllowRelink: function (email) {
- do_check_eq(email, 'testuser@testuser.com');
- run_next_test();
- }
- }
- });
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_sync_preferences_message() {
- let mockMessage = {
- command: 'fxaccounts:sync_preferences',
- data: { entryPoint: 'fxa:verification_complete' }
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING,
- helpers: {
- openSyncPreferences: function (browser, entryPoint) {
- do_check_eq(entryPoint, 'fxa:verification_complete');
- do_check_eq(browser, mockSendingContext.browser);
- run_next_test();
- }
- }
- });
-
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
-});
-
-add_test(function test_unrecognized_message() {
- let mockMessage = {
- command: 'fxaccounts:unrecognized',
- data: {}
- };
-
- let channel = new FxAccountsWebChannel({
- channel_id: WEBCHANNEL_ID,
- content_uri: URL_STRING
- });
-
- // no error is expected.
- channel._channelCallback(WEBCHANNEL_ID, mockMessage, mockSendingContext);
- run_next_test();
-});
-
-
-add_test(function test_helpers_should_allow_relink_same_email() {
- let helpers = new FxAccountsWebChannelHelpers();
-
- helpers.setPreviousAccountNameHashPref('testuser@testuser.com');
- do_check_true(helpers.shouldAllowRelink('testuser@testuser.com'));
-
- run_next_test();
-});
-
-add_test(function test_helpers_should_allow_relink_different_email() {
- let helpers = new FxAccountsWebChannelHelpers();
-
- helpers.setPreviousAccountNameHashPref('testuser@testuser.com');
-
- helpers._promptForRelink = (acctName) => {
- return acctName === 'allowed_to_relink@testuser.com';
- };
-
- do_check_true(helpers.shouldAllowRelink('allowed_to_relink@testuser.com'));
- do_check_false(helpers.shouldAllowRelink('not_allowed_to_relink@testuser.com'));
-
- run_next_test();
-});
-
-add_task(function* test_helpers_login_without_customize_sync() {
- let helpers = new FxAccountsWebChannelHelpers({
- fxAccounts: {
- setSignedInUser: function(accountData) {
- return new Promise(resolve => {
- // ensure fxAccounts is informed of the new user being signed in.
- do_check_eq(accountData.email, 'testuser@testuser.com');
-
- // verifiedCanLinkAccount should be stripped in the data.
- do_check_false('verifiedCanLinkAccount' in accountData);
-
- // the customizeSync pref should not update
- do_check_false(helpers.getShowCustomizeSyncPref());
-
- // previously signed in user preference is updated.
- do_check_eq(helpers.getPreviousAccountNameHashPref(), helpers.sha256('testuser@testuser.com'));
-
- resolve();
- });
- }
- }
- });
-
- // the show customize sync pref should stay the same
- helpers.setShowCustomizeSyncPref(false);
-
- // ensure the previous account pref is overwritten.
- helpers.setPreviousAccountNameHashPref('lastuser@testuser.com');
-
- yield helpers.login({
- email: 'testuser@testuser.com',
- verifiedCanLinkAccount: true,
- customizeSync: false
- });
-});
-
-add_task(function* test_helpers_login_with_customize_sync() {
- let helpers = new FxAccountsWebChannelHelpers({
- fxAccounts: {
- setSignedInUser: function(accountData) {
- return new Promise(resolve => {
- // ensure fxAccounts is informed of the new user being signed in.
- do_check_eq(accountData.email, 'testuser@testuser.com');
-
- // customizeSync should be stripped in the data.
- do_check_false('customizeSync' in accountData);
-
- // the customizeSync pref should not update
- do_check_true(helpers.getShowCustomizeSyncPref());
-
- resolve();
- });
- }
- }
- });
-
- // the customize sync pref should be overwritten
- helpers.setShowCustomizeSyncPref(false);
-
- yield helpers.login({
- email: 'testuser@testuser.com',
- verifiedCanLinkAccount: true,
- customizeSync: true
- });
-});
-
-add_task(function* test_helpers_login_with_customize_sync_and_declined_engines() {
- let helpers = new FxAccountsWebChannelHelpers({
- fxAccounts: {
- setSignedInUser: function(accountData) {
- return new Promise(resolve => {
- // ensure fxAccounts is informed of the new user being signed in.
- do_check_eq(accountData.email, 'testuser@testuser.com');
-
- // customizeSync should be stripped in the data.
- do_check_false('customizeSync' in accountData);
- do_check_false('declinedSyncEngines' in accountData);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), false);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), false);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true);
-
- // the customizeSync pref should be disabled
- do_check_false(helpers.getShowCustomizeSyncPref());
-
- resolve();
- });
- }
- }
- });
-
- // the customize sync pref should be overwritten
- helpers.setShowCustomizeSyncPref(true);
-
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.addons"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.bookmarks"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.history"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.passwords"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.prefs"), true);
- do_check_eq(Services.prefs.getBoolPref("services.sync.engine.tabs"), true);
- yield helpers.login({
- email: 'testuser@testuser.com',
- verifiedCanLinkAccount: true,
- customizeSync: true,
- declinedSyncEngines: ['addons', 'prefs']
- });
-});
-
-add_test(function test_helpers_open_sync_preferences() {
- let helpers = new FxAccountsWebChannelHelpers({
- fxAccounts: {
- }
- });
-
- let mockBrowser = {
- loadURI(uri) {
- do_check_eq(uri, "about:preferences?entrypoint=fxa%3Averification_complete#sync");
- run_next_test();
- }
- };
-
- helpers.openSyncPreferences(mockBrowser, "fxa:verification_complete");
-});
-
-add_task(function* test_helpers_change_password() {
- let wasCalled = {
- updateUserAccountData: false,
- updateDeviceRegistration: false
- };
- let helpers = new FxAccountsWebChannelHelpers({
- fxAccounts: {
- updateUserAccountData(credentials) {
- return new Promise(resolve => {
- do_check_true(credentials.hasOwnProperty("email"));
- do_check_true(credentials.hasOwnProperty("uid"));
- do_check_true(credentials.hasOwnProperty("kA"));
- do_check_true(credentials.hasOwnProperty("deviceId"));
- do_check_null(credentials.deviceId);
- // "foo" isn't a field known by storage, so should be dropped.
- do_check_false(credentials.hasOwnProperty("foo"));
- wasCalled.updateUserAccountData = true;
-
- resolve();
- });
- },
-
- updateDeviceRegistration() {
- do_check_eq(arguments.length, 0);
- wasCalled.updateDeviceRegistration = true;
- return Promise.resolve()
- }
- }
- });
- yield helpers.changePassword({ email: "email", uid: "uid", kA: "kA", foo: "foo" });
- do_check_true(wasCalled.updateUserAccountData);
- do_check_true(wasCalled.updateDeviceRegistration);
-});
-
-add_task(function* test_helpers_change_password_with_error() {
- let wasCalled = {
- updateUserAccountData: false,
- updateDeviceRegistration: false
- };
- let helpers = new FxAccountsWebChannelHelpers({
- fxAccounts: {
- updateUserAccountData() {
- wasCalled.updateUserAccountData = true;
- return Promise.reject();
- },
-
- updateDeviceRegistration() {
- wasCalled.updateDeviceRegistration = true;
- return Promise.resolve()
- }
- }
- });
- try {
- yield helpers.changePassword({});
- do_check_false('changePassword should have rejected');
- } catch (_) {
- do_check_true(wasCalled.updateUserAccountData);
- do_check_false(wasCalled.updateDeviceRegistration);
- }
-});
-
-function run_test() {
- run_next_test();
-}
-
-function makeObserver(aObserveTopic, aObserveFunc) {
- let callback = function (aSubject, aTopic, aData) {
- log.debug("observed " + aTopic + " " + aData);
- if (aTopic == aObserveTopic) {
- removeMe();
- aObserveFunc(aSubject, aTopic, aData);
- }
- };
-
- function removeMe() {
- log.debug("removing observer for " + aObserveTopic);
- Services.obs.removeObserver(callback, aObserveTopic);
- }
-
- Services.obs.addObserver(callback, aObserveTopic, false);
- return removeMe;
-}
-
-function validationHelper(params, expected) {
- try {
- new FxAccountsWebChannel(params);
- } catch (e) {
- if (typeof expected === 'string') {
- return do_check_eq(e.toString(), expected);
- } else {
- return do_check_true(e.toString().match(expected));
- }
- }
- throw new Error("Validation helper error");
-}
diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.ini b/services/fxaccounts/tests/xpcshell/xpcshell.ini
deleted file mode 100644
index 56a3d2947..000000000
--- a/services/fxaccounts/tests/xpcshell/xpcshell.ini
+++ /dev/null
@@ -1,23 +0,0 @@
-[DEFAULT]
-head = head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests/unit/head_http.js
-tail =
-skip-if = (toolkit == 'android' || appname == 'thunderbird')
-support-files =
- !/services/common/tests/unit/head_helpers.js
- !/services/common/tests/unit/head_http.js
-
-[test_accounts.js]
-[test_accounts_device_registration.js]
-[test_client.js]
-[test_credentials.js]
-[test_loginmgr_storage.js]
-[test_oauth_client.js]
-[test_oauth_grant_client.js]
-[test_oauth_grant_client_server.js]
-[test_oauth_tokens.js]
-[test_oauth_token_storage.js]
-[test_profile_client.js]
-[test_push_service.js]
-[test_web_channel.js]
-[test_profile.js]
-[test_storage_manager.js]
diff --git a/services/moz.build b/services/moz.build
index 2109d512a..e98d15275 100644
--- a/services/moz.build
+++ b/services/moz.build
@@ -9,11 +9,5 @@ DIRS += [
'crypto',
]
-if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'android':
- DIRS += ['fxaccounts']
-
if CONFIG['MOZ_SERVICES_SYNC']:
DIRS += ['sync']
-
-if CONFIG['MOZ_SERVICES_CLOUDSYNC']:
- DIRS += ['cloudsync']
diff --git a/services/sync/modules-testing/fxa_utils.js b/services/sync/modules-testing/fxa_utils.js
deleted file mode 100644
index 70aa17b03..000000000
--- a/services/sync/modules-testing/fxa_utils.js
+++ /dev/null
@@ -1,58 +0,0 @@
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
- "initializeIdentityWithTokenServerResponse",
-];
-
-var {utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://services-sync/main.js");
-Cu.import("resource://services-sync/browserid_identity.js");
-Cu.import("resource://services-common/tokenserverclient.js");
-Cu.import("resource://testing-common/services/common/logging.js");
-Cu.import("resource://testing-common/services/sync/utils.js");
-
-// Create a new browserid_identity object and initialize it with a
-// mocked TokenServerClient which always receives the specified response.
-this.initializeIdentityWithTokenServerResponse = function(response) {
- // First create a mock "request" object that well' hack into the token server.
- // A log for it
- let requestLog = Log.repository.getLogger("testing.mock-rest");
- if (!requestLog.appenders.length) { // might as well see what it says :)
- requestLog.addAppender(new Log.DumpAppender());
- requestLog.level = Log.Level.Trace;
- }
-
- // A mock request object.
- function MockRESTRequest(url) {};
- MockRESTRequest.prototype = {
- _log: requestLog,
- setHeader: function() {},
- get: function(callback) {
- this.response = response;
- callback.call(this);
- }
- }
- // The mocked TokenServer client which will get the response.
- function MockTSC() { }
- MockTSC.prototype = new TokenServerClient();
- MockTSC.prototype.constructor = MockTSC;
- MockTSC.prototype.newRESTRequest = function(url) {
- return new MockRESTRequest(url);
- }
- // Arrange for the same observerPrefix as browserid_identity uses.
- MockTSC.prototype.observerPrefix = "weave:service";
-
- // tie it all together.
- Weave.Status.__authManager = Weave.Service.identity = new BrowserIDManager();
- Weave.Service._clusterManager = Weave.Service.identity.createClusterManager(Weave.Service);
- let browseridManager = Weave.Service.identity;
- // a sanity check
- if (!(browseridManager instanceof BrowserIDManager)) {
- throw new Error("sync isn't configured for browserid_identity");
- }
- let mockTSC = new MockTSC()
- configureFxAccountIdentity(browseridManager);
- browseridManager._tokenServerClient = mockTSC;
-}
diff --git a/services/sync/modules-testing/utils.js b/services/sync/modules-testing/utils.js
index fc14f2fbd..64c9b163d 100644
--- a/services/sync/modules-testing/utils.js
+++ b/services/sync/modules-testing/utils.js
@@ -28,8 +28,6 @@ Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/browserid_identity.js");
Cu.import("resource://testing-common/services/common/logging.js");
Cu.import("resource://testing-common/services/sync/fakeservices.js");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
Cu.import("resource://gre/modules/Promise.jsm");
/**
@@ -77,27 +75,8 @@ this.setBasicCredentials =
this.makeIdentityConfig = function(overrides) {
// first setup the defaults.
let result = {
- // Username used in both fxaccount and sync identity configs.
+ // Username used in sync identity config.
username: "foo",
- // fxaccount specific credentials.
- fxaccount: {
- user: {
- assertion: 'assertion',
- email: 'email',
- kA: 'kA',
- kB: 'kB',
- sessionToken: 'sessionToken',
- uid: 'user_uid',
- verified: true,
- },
- token: {
- endpoint: Svc.Prefs.get("tokenServerURI"),
- duration: 300,
- id: "id",
- key: "key",
- // uid will be set to the username.
- }
- },
sync: {
// username will come from the top-level username
password: "whatever",
@@ -114,64 +93,15 @@ this.makeIdentityConfig = function(overrides) {
// TODO: allow just some attributes to be specified
result.sync = overrides.sync;
}
- if (overrides.fxaccount) {
- // TODO: allow just some attributes to be specified
- result.fxaccount = overrides.fxaccount;
- }
}
return result;
}
-// Configure an instance of an FxAccount identity provider with the specified
-// config (or the default config if not specified).
-this.configureFxAccountIdentity = function(authService,
- config = makeIdentityConfig()) {
- let MockInternal = {};
- let fxa = new FxAccounts(MockInternal);
-
- // until we get better test infrastructure for bid_identity, we set the
- // signedin user's "email" to the username, simply as many tests rely on this.
- config.fxaccount.user.email = config.username;
- fxa.internal.currentAccountState.signedInUser = {
- version: DATA_FORMAT_VERSION,
- accountData: config.fxaccount.user
- };
- fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
- this.cert = {
- validUntil: fxa.internal.now() + CERT_LIFETIME,
- cert: "certificate",
- };
- return Promise.resolve(this.cert.cert);
- };
-
- let mockTSC = { // TokenServerClient
- getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
- config.fxaccount.token.uid = config.username;
- cb(null, config.fxaccount.token);
- },
- };
- authService._fxaService = fxa;
- authService._tokenServerClient = mockTSC;
- // Set the "account" of the browserId manager to be the "email" of the
- // logged in user of the mockFXA service.
- authService._signedInUser = fxa.internal.currentAccountState.signedInUser.accountData;
- authService._account = config.fxaccount.user.email;
-}
-
this.configureIdentity = function(identityOverrides) {
let config = makeIdentityConfig(identityOverrides);
let ns = {};
Cu.import("resource://services-sync/service.js", ns);
- if (ns.Service.identity instanceof BrowserIDManager) {
- // do the FxAccounts thang...
- configureFxAccountIdentity(ns.Service.identity, config);
- return ns.Service.identity.initializeWithCurrentIdentity().then(() => {
- // need to wait until this identity manager is readyToAuthenticate.
- return ns.Service.identity.whenReadyToAuthenticate.promise;
- });
- }
- // old style identity provider.
setBasicCredentials(config.username, config.sync.password, config.sync.syncKey);
let deferred = Promise.defer();
deferred.resolve();
@@ -184,7 +114,6 @@ this.SyncTestingInfrastructure = function (server, username, password, syncKey)
ensureLegacyIdentityManager();
let config = makeIdentityConfig();
- // XXX - hacks for the sync identity provider.
if (username)
config.username = username;
if (password)
@@ -223,10 +152,10 @@ this.encryptPayload = function encryptPayload(cleartext) {
};
}
-// This helper can be used instead of 'add_test' or 'add_task' to run the
+// This helper was used instead of 'add_test' or 'add_task' to run the
// specified test function twice - once with the old-style sync identity
// manager and once with the new-style BrowserID identity manager, to ensure
-// it works in both cases.
+// it worked in both cases. Currently it's equal to just one. XXX: cleanup?
//
// * The test itself should be passed as 'test' - ie, test code will generally
// pass |this|.
@@ -248,12 +177,4 @@ this.add_identity_test = function(test, testFunction) {
yield testFunction();
Status.__authManager = ns.Service.identity = oldIdentity;
});
- // another task for the FxAccounts identity manager.
- test.add_task(function() {
- note("FxAccounts");
- let oldIdentity = Status._authManager;
- Status.__authManager = ns.Service.identity = new BrowserIDManager();
- yield testFunction();
- Status.__authManager = ns.Service.identity = oldIdentity;
- });
}
diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js
index 12496d23a..7fd5a7971 100644
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -19,13 +19,6 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
-// FxAccountsCommon.js doesn't use a "namespace", so create one here.
-XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
- let FxAccountsCommon = {};
- Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
- return FxAccountsCommon;
-});
-
/*
* Utility functions
*/
@@ -599,9 +592,6 @@ this.Utils = {
*/
getSyncCredentialsHosts: function() {
let result = new Set(this.getSyncCredentialsHostsLegacy());
- for (let host of this.getSyncCredentialsHostsFxA()) {
- result.add(host);
- }
return result;
},
@@ -613,36 +603,6 @@ this.Utils = {
return new Set([PWDMGR_HOST]);
},
- /*
- * Get the FxA identity hosts.
- */
- getSyncCredentialsHostsFxA: function() {
- // This is somewhat expensive and the result static, so we cache the result.
- if (this._syncCredentialsHostsFxA) {
- return this._syncCredentialsHostsFxA;
- }
- let result = new Set();
- // the FxA host
- result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
- //
- // The FxA hosts - these almost certainly all have the same hostname, but
- // better safe than sorry...
- for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
- "identity.fxaccounts.remote.signup.uri",
- "identity.fxaccounts.remote.signin.uri",
- "identity.fxaccounts.settings.uri"]) {
- let prefVal;
- try {
- prefVal = Services.prefs.getCharPref(prefName);
- } catch (_) {
- continue;
- }
- let uri = Services.io.newURI(prefVal, null, null);
- result.add(uri.prePath);
- }
- return this._syncCredentialsHostsFxA = result;
- },
-
getDefaultDeviceName() {
// Generate a client name if we don't have a useful one yet
let env = Cc["@mozilla.org/process/environment;1"]
diff --git a/services/sync/moz.build b/services/sync/moz.build
index ceb4eb502..56421a03e 100644
--- a/services/sync/moz.build
+++ b/services/sync/moz.build
@@ -54,7 +54,6 @@ EXTRA_JS_MODULES['services-sync'].stages += [
TESTING_JS_MODULES.services.sync += [
'modules-testing/fakeservices.js',
- 'modules-testing/fxa_utils.js',
'modules-testing/rotaryengine.js',
'modules-testing/utils.js',
]
diff --git a/services/sync/tests/unit/test_browserid_identity.js b/services/sync/tests/unit/test_browserid_identity.js
deleted file mode 100644
index f3cde9f8f..000000000
--- a/services/sync/tests/unit/test_browserid_identity.js
+++ /dev/null
@@ -1,682 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://services-sync/browserid_identity.js");
-Cu.import("resource://services-sync/rest.js");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://services-common/utils.js");
-Cu.import("resource://services-crypto/utils.js");
-Cu.import("resource://testing-common/services/sync/utils.js");
-Cu.import("resource://testing-common/services/sync/fxa_utils.js");
-Cu.import("resource://services-common/hawkclient.js");
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://gre/modules/FxAccountsCommon.js");
-Cu.import("resource://services-sync/service.js");
-Cu.import("resource://services-sync/status.js");
-Cu.import("resource://services-sync/constants.js");
-
-const SECOND_MS = 1000;
-const MINUTE_MS = SECOND_MS * 60;
-const HOUR_MS = MINUTE_MS * 60;
-
-let identityConfig = makeIdentityConfig();
-let browseridManager = new BrowserIDManager();
-configureFxAccountIdentity(browseridManager, identityConfig);
-
-/**
- * Mock client clock and skew vs server in FxAccounts signed-in user module and
- * API client. browserid_identity.js queries these values to construct HAWK
- * headers. We will use this to test clock skew compensation in these headers
- * below.
- */
-let MockFxAccountsClient = function() {
- FxAccountsClient.apply(this);
-};
-MockFxAccountsClient.prototype = {
- __proto__: FxAccountsClient.prototype
-};
-
-function MockFxAccounts() {
- let fxa = new FxAccounts({
- _now_is: Date.now(),
-
- now: function () {
- return this._now_is;
- },
-
- fxAccountsClient: new MockFxAccountsClient()
- });
- fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
- this.cert = {
- validUntil: fxa.internal.now() + CERT_LIFETIME,
- cert: "certificate",
- };
- return Promise.resolve(this.cert.cert);
- };
- return fxa;
-}
-
-function run_test() {
- initTestLogging("Trace");
- Log.repository.getLogger("Sync.Identity").level = Log.Level.Trace;
- Log.repository.getLogger("Sync.BrowserIDManager").level = Log.Level.Trace;
- run_next_test();
-};
-
-add_test(function test_initial_state() {
- _("Verify initial state");
- do_check_false(!!browseridManager._token);
- do_check_false(browseridManager.hasValidToken());
- run_next_test();
- }
-);
-
-add_task(function test_initialializeWithCurrentIdentity() {
- _("Verify start after initializeWithCurrentIdentity");
- browseridManager.initializeWithCurrentIdentity();
- yield browseridManager.whenReadyToAuthenticate.promise;
- do_check_true(!!browseridManager._token);
- do_check_true(browseridManager.hasValidToken());
- do_check_eq(browseridManager.account, identityConfig.fxaccount.user.email);
- }
-);
-
-add_task(function test_initialializeWithNoKeys() {
- _("Verify start after initializeWithCurrentIdentity without kA, kB or keyFetchToken");
- let identityConfig = makeIdentityConfig();
- delete identityConfig.fxaccount.user.kA;
- delete identityConfig.fxaccount.user.kB;
- // there's no keyFetchToken by default, so the initialize should fail.
- configureFxAccountIdentity(browseridManager, identityConfig);
-
- yield browseridManager.initializeWithCurrentIdentity();
- yield browseridManager.whenReadyToAuthenticate.promise;
- do_check_eq(Status.login, LOGIN_SUCCEEDED, "login succeeded even without keys");
- do_check_false(browseridManager._canFetchKeys(), "_canFetchKeys reflects lack of keys");
- do_check_eq(browseridManager._token, null, "we don't have a token");
-});
-
-add_test(function test_getResourceAuthenticator() {
- _("BrowserIDManager supplies a Resource Authenticator callback which returns a Hawk header.");
- configureFxAccountIdentity(browseridManager);
- let authenticator = browseridManager.getResourceAuthenticator();
- do_check_true(!!authenticator);
- let req = {uri: CommonUtils.makeURI(
- "https://example.net/somewhere/over/the/rainbow"),
- method: 'GET'};
- let output = authenticator(req, 'GET');
- do_check_true('headers' in output);
- do_check_true('authorization' in output.headers);
- do_check_true(output.headers.authorization.startsWith('Hawk'));
- _("Expected internal state after successful call.");
- do_check_eq(browseridManager._token.uid, identityConfig.fxaccount.token.uid);
- run_next_test();
- }
-);
-
-add_test(function test_getRESTRequestAuthenticator() {
- _("BrowserIDManager supplies a REST Request Authenticator callback which sets a Hawk header on a request object.");
- let request = new SyncStorageRequest(
- "https://example.net/somewhere/over/the/rainbow");
- let authenticator = browseridManager.getRESTRequestAuthenticator();
- do_check_true(!!authenticator);
- let output = authenticator(request, 'GET');
- do_check_eq(request.uri, output.uri);
- do_check_true(output._headers.authorization.startsWith('Hawk'));
- do_check_true(output._headers.authorization.includes('nonce'));
- do_check_true(browseridManager.hasValidToken());
- run_next_test();
- }
-);
-
-add_test(function test_resourceAuthenticatorSkew() {
- _("BrowserIDManager Resource Authenticator compensates for clock skew in Hawk header.");
-
- // Clock is skewed 12 hours into the future
- // We pick a date in the past so we don't risk concealing bugs in code that
- // uses new Date() instead of our given date.
- let now = new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS;
- let browseridManager = new BrowserIDManager();
- let hawkClient = new HawkClient("https://example.net/v1", "/foo");
-
- // mock fxa hawk client skew
- hawkClient.now = function() {
- dump("mocked client now: " + now + '\n');
- return now;
- }
- // Imagine there's already been one fxa request and the hawk client has
- // already detected skew vs the fxa auth server.
- let localtimeOffsetMsec = -1 * 12 * HOUR_MS;
- hawkClient._localtimeOffsetMsec = localtimeOffsetMsec;
-
- let fxaClient = new MockFxAccountsClient();
- fxaClient.hawk = hawkClient;
-
- // Sanity check
- do_check_eq(hawkClient.now(), now);
- do_check_eq(hawkClient.localtimeOffsetMsec, localtimeOffsetMsec);
-
- // Properly picked up by the client
- do_check_eq(fxaClient.now(), now);
- do_check_eq(fxaClient.localtimeOffsetMsec, localtimeOffsetMsec);
-
- let fxa = new MockFxAccounts();
- fxa.internal._now_is = now;
- fxa.internal.fxAccountsClient = fxaClient;
-
- // Picked up by the signed-in user module
- do_check_eq(fxa.internal.now(), now);
- do_check_eq(fxa.internal.localtimeOffsetMsec, localtimeOffsetMsec);
-
- do_check_eq(fxa.now(), now);
- do_check_eq(fxa.localtimeOffsetMsec, localtimeOffsetMsec);
-
- // Mocks within mocks...
- configureFxAccountIdentity(browseridManager, identityConfig);
-
- // Ensure the new FxAccounts mock has a signed-in user.
- fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
-
- browseridManager._fxaService = fxa;
-
- do_check_eq(browseridManager._fxaService.internal.now(), now);
- do_check_eq(browseridManager._fxaService.internal.localtimeOffsetMsec,
- localtimeOffsetMsec);
-
- do_check_eq(browseridManager._fxaService.now(), now);
- do_check_eq(browseridManager._fxaService.localtimeOffsetMsec,
- localtimeOffsetMsec);
-
- let request = new SyncStorageRequest("https://example.net/i/like/pie/");
- let authenticator = browseridManager.getResourceAuthenticator();
- let output = authenticator(request, 'GET');
- dump("output" + JSON.stringify(output));
- let authHeader = output.headers.authorization;
- do_check_true(authHeader.startsWith('Hawk'));
-
- // Skew correction is applied in the header and we're within the two-minute
- // window.
- do_check_eq(getTimestamp(authHeader), now - 12 * HOUR_MS);
- do_check_true(
- (getTimestampDelta(authHeader, now) - 12 * HOUR_MS) < 2 * MINUTE_MS);
-
- run_next_test();
-});
-
-add_test(function test_RESTResourceAuthenticatorSkew() {
- _("BrowserIDManager REST Resource Authenticator compensates for clock skew in Hawk header.");
-
- // Clock is skewed 12 hours into the future from our arbitary date
- let now = new Date("Fri Apr 09 2004 00:00:00 GMT-0700").valueOf() + 12 * HOUR_MS;
- let browseridManager = new BrowserIDManager();
- let hawkClient = new HawkClient("https://example.net/v1", "/foo");
-
- // mock fxa hawk client skew
- hawkClient.now = function() {
- return now;
- }
- // Imagine there's already been one fxa request and the hawk client has
- // already detected skew vs the fxa auth server.
- hawkClient._localtimeOffsetMsec = -1 * 12 * HOUR_MS;
-
- let fxaClient = new MockFxAccountsClient();
- fxaClient.hawk = hawkClient;
- let fxa = new MockFxAccounts();
- fxa.internal._now_is = now;
- fxa.internal.fxAccountsClient = fxaClient;
-
- configureFxAccountIdentity(browseridManager, identityConfig);
-
- // Ensure the new FxAccounts mock has a signed-in user.
- fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
-
- browseridManager._fxaService = fxa;
-
- do_check_eq(browseridManager._fxaService.internal.now(), now);
-
- let request = new SyncStorageRequest("https://example.net/i/like/pie/");
- let authenticator = browseridManager.getResourceAuthenticator();
- let output = authenticator(request, 'GET');
- dump("output" + JSON.stringify(output));
- let authHeader = output.headers.authorization;
- do_check_true(authHeader.startsWith('Hawk'));
-
- // Skew correction is applied in the header and we're within the two-minute
- // window.
- do_check_eq(getTimestamp(authHeader), now - 12 * HOUR_MS);
- do_check_true(
- (getTimestampDelta(authHeader, now) - 12 * HOUR_MS) < 2 * MINUTE_MS);
-
- run_next_test();
-});
-
-add_task(function test_ensureLoggedIn() {
- configureFxAccountIdentity(browseridManager);
- yield browseridManager.initializeWithCurrentIdentity();
- yield browseridManager.whenReadyToAuthenticate.promise;
- Assert.equal(Status.login, LOGIN_SUCCEEDED, "original initialize worked");
- yield browseridManager.ensureLoggedIn();
- Assert.equal(Status.login, LOGIN_SUCCEEDED, "original ensureLoggedIn worked");
- Assert.ok(browseridManager._shouldHaveSyncKeyBundle,
- "_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
-
- // arrange for no logged in user.
- let fxa = browseridManager._fxaService
- let signedInUser = fxa.internal.currentAccountState.signedInUser;
- fxa.internal.currentAccountState.signedInUser = null;
- browseridManager.initializeWithCurrentIdentity();
- Assert.ok(!browseridManager._shouldHaveSyncKeyBundle,
- "_shouldHaveSyncKeyBundle should be false so we know we are testing what we think we are.");
- Status.login = LOGIN_FAILED_NO_USERNAME;
- yield Assert.rejects(browseridManager.ensureLoggedIn(), "expecting rejection due to no user");
- Assert.ok(browseridManager._shouldHaveSyncKeyBundle,
- "_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
- fxa.internal.currentAccountState.signedInUser = signedInUser;
- Status.login = LOGIN_FAILED_LOGIN_REJECTED;
- yield Assert.rejects(browseridManager.ensureLoggedIn(),
- "LOGIN_FAILED_LOGIN_REJECTED should have caused immediate rejection");
- Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED,
- "status should remain LOGIN_FAILED_LOGIN_REJECTED");
- Status.login = LOGIN_FAILED_NETWORK_ERROR;
- yield browseridManager.ensureLoggedIn();
- Assert.equal(Status.login, LOGIN_SUCCEEDED, "final ensureLoggedIn worked");
-});
-
-add_test(function test_tokenExpiration() {
- _("BrowserIDManager notices token expiration:");
- let bimExp = new BrowserIDManager();
- configureFxAccountIdentity(bimExp, identityConfig);
-
- let authenticator = bimExp.getResourceAuthenticator();
- do_check_true(!!authenticator);
- let req = {uri: CommonUtils.makeURI(
- "https://example.net/somewhere/over/the/rainbow"),
- method: 'GET'};
- authenticator(req, 'GET');
-
- // Mock the clock.
- _("Forcing the token to expire ...");
- Object.defineProperty(bimExp, "_now", {
- value: function customNow() {
- return (Date.now() + 3000001);
- },
- writable: true,
- });
- do_check_true(bimExp._token.expiration < bimExp._now());
- _("... means BrowserIDManager knows to re-fetch it on the next call.");
- do_check_false(bimExp.hasValidToken());
- run_next_test();
- }
-);
-
-add_test(function test_sha256() {
- // Test vectors from http://www.bichlmeier.info/sha256test.html
- let vectors = [
- ["",
- "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"],
- ["abc",
- "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"],
- ["message digest",
- "f7846f55cf23e14eebeab5b4e1550cad5b509e3348fbc4efa3a1413d393cb650"],
- ["secure hash algorithm",
- "f30ceb2bb2829e79e4ca9753d35a8ecc00262d164cc077080295381cbd643f0d"],
- ["SHA256 is considered to be safe",
- "6819d915c73f4d1e77e4e1b52d1fa0f9cf9beaead3939f15874bd988e2a23630"],
- ["abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
- "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"],
- ["For this sample, this 63-byte string will be used as input data",
- "f08a78cbbaee082b052ae0708f32fa1e50c5c421aa772ba5dbb406a2ea6be342"],
- ["This is exactly 64 bytes long, not counting the terminating byte",
- "ab64eff7e88e2e46165e29f2bce41826bd4c7b3552f6b382a9e7d3af47c245f8"]
- ];
- let bidUser = new BrowserIDManager();
- for (let [input,output] of vectors) {
- do_check_eq(CommonUtils.bytesAsHex(bidUser._sha256(input)), output);
- }
- run_next_test();
-});
-
-add_test(function test_computeXClientStateHeader() {
- let kBhex = "fd5c747806c07ce0b9d69dcfea144663e630b65ec4963596a22f24910d7dd15d";
- let kB = CommonUtils.hexToBytes(kBhex);
-
- let bidUser = new BrowserIDManager();
- let header = bidUser._computeXClientState(kB);
-
- do_check_eq(header, "6ae94683571c7a7c54dab4700aa3995f");
- run_next_test();
-});
-
-add_task(function test_getTokenErrors() {
- _("BrowserIDManager correctly handles various failures to get a token.");
-
- _("Arrange for a 401 - Sync should reflect an auth error.");
- initializeIdentityWithTokenServerResponse({
- status: 401,
- headers: {"content-type": "application/json"},
- body: JSON.stringify({}),
- });
- let browseridManager = Service.identity;
-
- yield browseridManager.initializeWithCurrentIdentity();
- yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
- "should reject due to 401");
- Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
-
- // XXX - other interesting responses to return?
-
- // And for good measure, some totally "unexpected" errors - we generally
- // assume these problems are going to magically go away at some point.
- _("Arrange for an empty body with a 200 response - should reflect a network error.");
- initializeIdentityWithTokenServerResponse({
- status: 200,
- headers: [],
- body: "",
- });
- browseridManager = Service.identity;
- yield browseridManager.initializeWithCurrentIdentity();
- yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
- "should reject due to non-JSON response");
- Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
-});
-
-add_task(function test_getTokenErrorWithRetry() {
- _("tokenserver sends an observer notification on various backoff headers.");
-
- // Set Sync's backoffInterval to zero - after we simulated the backoff header
- // it should reflect the value we sent.
- Status.backoffInterval = 0;
- _("Arrange for a 503 with a Retry-After header.");
- initializeIdentityWithTokenServerResponse({
- status: 503,
- headers: {"content-type": "application/json",
- "retry-after": "100"},
- body: JSON.stringify({}),
- });
- let browseridManager = Service.identity;
-
- yield browseridManager.initializeWithCurrentIdentity();
- yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
- "should reject due to 503");
-
- // The observer should have fired - check it got the value in the response.
- Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
- // Sync will have the value in ms with some slop - so check it is at least that.
- Assert.ok(Status.backoffInterval >= 100000);
-
- _("Arrange for a 200 with an X-Backoff header.");
- Status.backoffInterval = 0;
- initializeIdentityWithTokenServerResponse({
- status: 503,
- headers: {"content-type": "application/json",
- "x-backoff": "200"},
- body: JSON.stringify({}),
- });
- browseridManager = Service.identity;
-
- yield browseridManager.initializeWithCurrentIdentity();
- yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
- "should reject due to no token in response");
-
- // The observer should have fired - check it got the value in the response.
- Assert.ok(Status.backoffInterval >= 200000);
-});
-
-add_task(function test_getKeysErrorWithBackoff() {
- _("Auth server (via hawk) sends an observer notification on backoff headers.");
-
- // Set Sync's backoffInterval to zero - after we simulated the backoff header
- // it should reflect the value we sent.
- Status.backoffInterval = 0;
- _("Arrange for a 503 with a X-Backoff header.");
-
- let config = makeIdentityConfig();
- // We want no kA or kB so we attempt to fetch them.
- delete config.fxaccount.user.kA;
- delete config.fxaccount.user.kB;
- config.fxaccount.user.keyFetchToken = "keyfetchtoken";
- yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
- Assert.equal(method, "get");
- Assert.equal(uri, "http://mockedserver:9999/account/keys")
- return {
- status: 503,
- headers: {"content-type": "application/json",
- "x-backoff": "100"},
- body: "{}",
- }
- });
-
- let browseridManager = Service.identity;
- yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
- "should reject due to 503");
-
- // The observer should have fired - check it got the value in the response.
- Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
- // Sync will have the value in ms with some slop - so check it is at least that.
- Assert.ok(Status.backoffInterval >= 100000);
-});
-
-add_task(function test_getKeysErrorWithRetry() {
- _("Auth server (via hawk) sends an observer notification on retry headers.");
-
- // Set Sync's backoffInterval to zero - after we simulated the backoff header
- // it should reflect the value we sent.
- Status.backoffInterval = 0;
- _("Arrange for a 503 with a Retry-After header.");
-
- let config = makeIdentityConfig();
- // We want no kA or kB so we attempt to fetch them.
- delete config.fxaccount.user.kA;
- delete config.fxaccount.user.kB;
- config.fxaccount.user.keyFetchToken = "keyfetchtoken";
- yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
- Assert.equal(method, "get");
- Assert.equal(uri, "http://mockedserver:9999/account/keys")
- return {
- status: 503,
- headers: {"content-type": "application/json",
- "retry-after": "100"},
- body: "{}",
- }
- });
-
- let browseridManager = Service.identity;
- yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
- "should reject due to 503");
-
- // The observer should have fired - check it got the value in the response.
- Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login was rejected");
- // Sync will have the value in ms with some slop - so check it is at least that.
- Assert.ok(Status.backoffInterval >= 100000);
-});
-
-add_task(function test_getHAWKErrors() {
- _("BrowserIDManager correctly handles various HAWK failures.");
-
- _("Arrange for a 401 - Sync should reflect an auth error.");
- let config = makeIdentityConfig();
- yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
- Assert.equal(method, "post");
- Assert.equal(uri, "http://mockedserver:9999/certificate/sign")
- return {
- status: 401,
- headers: {"content-type": "application/json"},
- body: JSON.stringify({}),
- }
- });
- Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
-
- // XXX - other interesting responses to return?
-
- // And for good measure, some totally "unexpected" errors - we generally
- // assume these problems are going to magically go away at some point.
- _("Arrange for an empty body with a 200 response - should reflect a network error.");
- yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
- Assert.equal(method, "post");
- Assert.equal(uri, "http://mockedserver:9999/certificate/sign")
- return {
- status: 200,
- headers: [],
- body: "",
- }
- });
- Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "login state is LOGIN_FAILED_NETWORK_ERROR");
-});
-
-add_task(function test_getGetKeysFailing401() {
- _("BrowserIDManager correctly handles 401 responses fetching keys.");
-
- _("Arrange for a 401 - Sync should reflect an auth error.");
- let config = makeIdentityConfig();
- // We want no kA or kB so we attempt to fetch them.
- delete config.fxaccount.user.kA;
- delete config.fxaccount.user.kB;
- config.fxaccount.user.keyFetchToken = "keyfetchtoken";
- yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
- Assert.equal(method, "get");
- Assert.equal(uri, "http://mockedserver:9999/account/keys")
- return {
- status: 401,
- headers: {"content-type": "application/json"},
- body: "{}",
- }
- });
- Assert.equal(Status.login, LOGIN_FAILED_LOGIN_REJECTED, "login was rejected");
-});
-
-add_task(function test_getGetKeysFailing503() {
- _("BrowserIDManager correctly handles 5XX responses fetching keys.");
-
- _("Arrange for a 503 - Sync should reflect a network error.");
- let config = makeIdentityConfig();
- // We want no kA or kB so we attempt to fetch them.
- delete config.fxaccount.user.kA;
- delete config.fxaccount.user.kB;
- config.fxaccount.user.keyFetchToken = "keyfetchtoken";
- yield initializeIdentityWithHAWKResponseFactory(config, function(method, data, uri) {
- Assert.equal(method, "get");
- Assert.equal(uri, "http://mockedserver:9999/account/keys")
- return {
- status: 503,
- headers: {"content-type": "application/json"},
- body: "{}",
- }
- });
- Assert.equal(Status.login, LOGIN_FAILED_NETWORK_ERROR, "state reflects network error");
-});
-
-add_task(function test_getKeysMissing() {
- _("BrowserIDManager correctly handles getKeys succeeding but not returning keys.");
-
- let browseridManager = new BrowserIDManager();
- let identityConfig = makeIdentityConfig();
- // our mock identity config already has kA and kB - remove them or we never
- // try and fetch them.
- delete identityConfig.fxaccount.user.kA;
- delete identityConfig.fxaccount.user.kB;
- identityConfig.fxaccount.user.keyFetchToken = 'keyFetchToken';
-
- configureFxAccountIdentity(browseridManager, identityConfig);
-
- // Mock a fxAccounts object that returns no keys
- let fxa = new FxAccounts({
- fetchAndUnwrapKeys: function () {
- return Promise.resolve({});
- },
- fxAccountsClient: new MockFxAccountsClient()
- });
-
- // Add a mock to the currentAccountState object.
- fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) {
- this.cert = {
- validUntil: fxa.internal.now() + CERT_LIFETIME,
- cert: "certificate",
- };
- return Promise.resolve(this.cert.cert);
- };
-
- // Ensure the new FxAccounts mock has a signed-in user.
- fxa.internal.currentAccountState.signedInUser = browseridManager._fxaService.internal.currentAccountState.signedInUser;
-
- browseridManager._fxaService = fxa;
-
- yield browseridManager.initializeWithCurrentIdentity();
-
- let ex;
- try {
- yield browseridManager.whenReadyToAuthenticate.promise;
- } catch (e) {
- ex = e;
- }
-
- Assert.ok(ex.message.indexOf("missing kA or kB") >= 0);
-});
-
-// End of tests
-// Utility functions follow
-
-// Create a new browserid_identity object and initialize it with a
-// hawk mock that simulates HTTP responses.
-// The callback function will be called each time the mocked hawk server wants
-// to make a request. The result of the callback should be the mock response
-// object that will be returned to hawk.
-// A token server mock will be used that doesn't hit a server, so we move
-// directly to a hawk request.
-function* initializeIdentityWithHAWKResponseFactory(config, cbGetResponse) {
- // A mock request object.
- function MockRESTRequest(uri, credentials, extra) {
- this._uri = uri;
- this._credentials = credentials;
- this._extra = extra;
- };
- MockRESTRequest.prototype = {
- setHeader: function() {},
- post: function(data, callback) {
- this.response = cbGetResponse("post", data, this._uri, this._credentials, this._extra);
- callback.call(this);
- },
- get: function(callback) {
- this.response = cbGetResponse("get", null, this._uri, this._credentials, this._extra);
- callback.call(this);
- }
- }
-
- // The hawk client.
- function MockedHawkClient() {}
- MockedHawkClient.prototype = new HawkClient("http://mockedserver:9999");
- MockedHawkClient.prototype.constructor = MockedHawkClient;
- MockedHawkClient.prototype.newHAWKAuthenticatedRESTRequest = function(uri, credentials, extra) {
- return new MockRESTRequest(uri, credentials, extra);
- }
- // Arrange for the same observerPrefix as FxAccountsClient uses
- MockedHawkClient.prototype.observerPrefix = "FxA:hawk";
-
- // tie it all together - configureFxAccountIdentity isn't useful here :(
- let fxaClient = new MockFxAccountsClient();
- fxaClient.hawk = new MockedHawkClient();
- let internal = {
- fxAccountsClient: fxaClient,
- }
- let fxa = new FxAccounts(internal);
- fxa.internal.currentAccountState.signedInUser = {
- accountData: config.fxaccount.user,
- };
-
- browseridManager._fxaService = fxa;
- browseridManager._signedInUser = null;
- yield browseridManager.initializeWithCurrentIdentity();
- yield Assert.rejects(browseridManager.whenReadyToAuthenticate.promise,
- "expecting rejection due to hawk error");
-}
-
-
-function getTimestamp(hawkAuthHeader) {
- return parseInt(/ts="(\d+)"/.exec(hawkAuthHeader)[1], 10) * SECOND_MS;
-}
-
-function getTimestampDelta(hawkAuthHeader, now=Date.now()) {
- return Math.abs(getTimestamp(hawkAuthHeader) - now);
-}
-
diff --git a/services/sync/tests/unit/test_errorhandler.js b/services/sync/tests/unit/test_errorhandler.js
index c087acc9f..25d79002c 100644
--- a/services/sync/tests/unit/test_errorhandler.js
+++ b/services/sync/tests/unit/test_errorhandler.js
@@ -486,8 +486,6 @@ add_identity_test(this, function test_shouldReportLoginFailureWithNoCluster() {
do_check_false(errorHandler.shouldReportError());
});
-// XXX - how to arrange for 'Service.identity.basicPassword = null;' in
-// an fxaccounts environment?
add_task(function test_login_syncAndReportErrors_non_network_error() {
// Test non-network errors are reported
// when calling syncAndReportErrors
@@ -536,8 +534,6 @@ add_identity_test(this, function test_sync_syncAndReportErrors_non_network_error
yield deferred.promise;
});
-// XXX - how to arrange for 'Service.identity.basicPassword = null;' in
-// an fxaccounts environment?
add_task(function test_login_syncAndReportErrors_prolonged_non_network_error() {
// Test prolonged, non-network errors are
// reported when calling syncAndReportErrors.
diff --git a/services/sync/tests/unit/test_fxa_node_reassignment.js b/services/sync/tests/unit/test_fxa_node_reassignment.js
deleted file mode 100644
index 2f61afd6f..000000000
--- a/services/sync/tests/unit/test_fxa_node_reassignment.js
+++ /dev/null
@@ -1,321 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-_("Test that node reassignment happens correctly using the FxA identity mgr.");
-// The node-reassignment logic is quite different for FxA than for the legacy
-// provider. In particular, there's no special request necessary for
-// reassignment - it comes from the token server - so we need to ensure the
-// Fxa cluster manager grabs a new token.
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://services-common/rest.js");
-Cu.import("resource://services-sync/constants.js");
-Cu.import("resource://services-sync/service.js");
-Cu.import("resource://services-sync/status.js");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://testing-common/services/sync/rotaryengine.js");
-Cu.import("resource://services-sync/browserid_identity.js");
-Cu.import("resource://testing-common/services/sync/utils.js");
-
-Service.engineManager.clear();
-
-function run_test() {
- Log.repository.getLogger("Sync.AsyncResource").level = Log.Level.Trace;
- Log.repository.getLogger("Sync.ErrorHandler").level = Log.Level.Trace;
- Log.repository.getLogger("Sync.Resource").level = Log.Level.Trace;
- Log.repository.getLogger("Sync.RESTRequest").level = Log.Level.Trace;
- Log.repository.getLogger("Sync.Service").level = Log.Level.Trace;
- Log.repository.getLogger("Sync.SyncScheduler").level = Log.Level.Trace;
- initTestLogging();
-
- Service.engineManager.register(RotaryEngine);
-
- // Setup the FxA identity manager and cluster manager.
- Status.__authManager = Service.identity = new BrowserIDManager();
- Service._clusterManager = Service.identity.createClusterManager(Service);
-
- // None of the failures in this file should result in a UI error.
- function onUIError() {
- do_throw("Errors should not be presented in the UI.");
- }
- Svc.Obs.add("weave:ui:login:error", onUIError);
- Svc.Obs.add("weave:ui:sync:error", onUIError);
-
- run_next_test();
-}
-
-
-// API-compatible with SyncServer handler. Bind `handler` to something to use
-// as a ServerCollection handler.
-function handleReassign(handler, req, resp) {
- resp.setStatusLine(req.httpVersion, 401, "Node reassignment");
- resp.setHeader("Content-Type", "application/json");
- let reassignBody = JSON.stringify({error: "401inator in place"});
- resp.bodyOutputStream.write(reassignBody, reassignBody.length);
-}
-
-let numTokenRequests = 0;
-
-function prepareServer(cbAfterTokenFetch) {
- let config = makeIdentityConfig({username: "johndoe"});
- let server = new SyncServer();
- server.registerUser("johndoe");
- server.start();
-
- // Set the token endpoint for the initial token request that's done implicitly
- // via configureIdentity.
- config.fxaccount.token.endpoint = server.baseURI + "1.1/johndoe";
- // And future token fetches will do magic around numReassigns.
- let numReassigns = 0;
- return configureIdentity(config).then(() => {
- Service.identity._tokenServerClient = {
- getTokenFromBrowserIDAssertion: function(uri, assertion, cb) {
- // Build a new URL with trailing zeros for the SYNC_VERSION part - this
- // will still be seen as equivalent by the test server, but different
- // by sync itself.
- numReassigns += 1;
- let trailingZeros = new Array(numReassigns + 1).join('0');
- let token = config.fxaccount.token;
- token.endpoint = server.baseURI + "1.1" + trailingZeros + "/johndoe";
- token.uid = config.username;
- numTokenRequests += 1;
- cb(null, token);
- if (cbAfterTokenFetch) {
- cbAfterTokenFetch();
- }
- },
- };
- Service.clusterURL = config.fxaccount.token.endpoint;
- return server;
- });
-}
-
-function getReassigned() {
- try {
- return Services.prefs.getBoolPref("services.sync.lastSyncReassigned");
- } catch (ex if (ex.result == Cr.NS_ERROR_UNEXPECTED)) {
- return false;
- } catch (ex) {
- do_throw("Got exception retrieving lastSyncReassigned: " +
- Utils.exceptionStr(ex));
- }
-}
-
-/**
- * Make a test request to `url`, then watch the result of two syncs
- * to ensure that a node request was made.
- * Runs `between` between the two. This can be used to undo deliberate failure
- * setup, detach observers, etc.
- */
-function syncAndExpectNodeReassignment(server, firstNotification, between,
- secondNotification, url) {
- _("Starting syncAndExpectNodeReassignment\n");
- let deferred = Promise.defer();
- function onwards() {
- let numTokenRequestsBefore;
- function onFirstSync() {
- _("First sync completed.");
- Svc.Obs.remove(firstNotification, onFirstSync);
- Svc.Obs.add(secondNotification, onSecondSync);
-
- do_check_eq(Service.clusterURL, "");
-
- // Track whether we fetched a new token.
- numTokenRequestsBefore = numTokenRequests;
-
- // Allow for tests to clean up error conditions.
- between();
- }
- function onSecondSync() {
- _("Second sync completed.");
- Svc.Obs.remove(secondNotification, onSecondSync);
- Service.scheduler.clearSyncTriggers();
-
- // Make absolutely sure that any event listeners are done with their work
- // before we proceed.
- waitForZeroTimer(function () {
- _("Second sync nextTick.");
- do_check_eq(numTokenRequests, numTokenRequestsBefore + 1, "fetched a new token");
- Service.startOver();
- server.stop(deferred.resolve);
- });
- }
-
- Svc.Obs.add(firstNotification, onFirstSync);
- Service.sync();
- }
-
- // Make sure that it works!
- _("Making request to " + url + " which should 401");
- let request = new RESTRequest(url);
- request.get(function () {
- do_check_eq(request.response.status, 401);
- Utils.nextTick(onwards);
- });
- yield deferred.promise;
-}
-
-add_task(function test_momentary_401_engine() {
- _("Test a failure for engine URLs that's resolved by reassignment.");
- let server = yield prepareServer();
- let john = server.user("johndoe");
-
- _("Enabling the Rotary engine.");
- let engine = Service.engineManager.get("rotary");
- engine.enabled = true;
-
- // We need the server to be correctly set up prior to experimenting. Do this
- // through a sync.
- let global = {syncID: Service.syncID,
- storageVersion: STORAGE_VERSION,
- rotary: {version: engine.version,
- syncID: engine.syncID}}
- john.createCollection("meta").insert("global", global);
-
- _("First sync to prepare server contents.");
- Service.sync();
-
- _("Setting up Rotary collection to 401.");
- let rotary = john.createCollection("rotary");
- let oldHandler = rotary.collectionHandler;
- rotary.collectionHandler = handleReassign.bind(this, undefined);
-
- // We want to verify that the clusterURL pref has been cleared after a 401
- // inside a sync. Flag the Rotary engine to need syncing.
- john.collection("rotary").timestamp += 1000;
-
- function between() {
- _("Undoing test changes.");
- rotary.collectionHandler = oldHandler;
-
- function onLoginStart() {
- // lastSyncReassigned shouldn't be cleared until a sync has succeeded.
- _("Ensuring that lastSyncReassigned is still set at next sync start.");
- Svc.Obs.remove("weave:service:login:start", onLoginStart);
- do_check_true(getReassigned());
- }
-
- _("Adding observer that lastSyncReassigned is still set on login.");
- Svc.Obs.add("weave:service:login:start", onLoginStart);
- }
-
- yield syncAndExpectNodeReassignment(server,
- "weave:service:sync:finish",
- between,
- "weave:service:sync:finish",
- Service.storageURL + "rotary");
-});
-
-// This test ends up being a failing info fetch *after we're already logged in*.
-add_task(function test_momentary_401_info_collections_loggedin() {
- _("Test a failure for info/collections after login that's resolved by reassignment.");
- let server = yield prepareServer();
-
- _("First sync to prepare server contents.");
- Service.sync();
-
- _("Arrange for info/collections to return a 401.");
- let oldHandler = server.toplevelHandlers.info;
- server.toplevelHandlers.info = handleReassign;
-
- function undo() {
- _("Undoing test changes.");
- server.toplevelHandlers.info = oldHandler;
- }
-
- do_check_true(Service.isLoggedIn, "already logged in");
-
- yield syncAndExpectNodeReassignment(server,
- "weave:service:sync:error",
- undo,
- "weave:service:sync:finish",
- Service.infoURL);
-});
-
-// This test ends up being a failing info fetch *before we're logged in*.
-// In this case we expect to recover during the login phase - so the first
-// sync succeeds.
-add_task(function test_momentary_401_info_collections_loggedout() {
- _("Test a failure for info/collections before login that's resolved by reassignment.");
-
- let oldHandler;
- let sawTokenFetch = false;
-
- function afterTokenFetch() {
- // After a single token fetch, we undo our evil handleReassign hack, so
- // the next /info request returns the collection instead of a 401
- server.toplevelHandlers.info = oldHandler;
- sawTokenFetch = true;
- }
-
- let server = yield prepareServer(afterTokenFetch);
-
- // Return a 401 for the next /info request - it will be reset immediately
- // after a new token is fetched.
- oldHandler = server.toplevelHandlers.info
- server.toplevelHandlers.info = handleReassign;
-
- do_check_false(Service.isLoggedIn, "not already logged in");
-
- Service.sync();
- do_check_eq(Status.sync, SYNC_SUCCEEDED, "sync succeeded");
- // sync was successful - check we grabbed a new token.
- do_check_true(sawTokenFetch, "a new token was fetched by this test.")
- // and we are done.
- Service.startOver();
- let deferred = Promise.defer();
- server.stop(deferred.resolve);
- yield deferred.promise;
-});
-
-// This test ends up being a failing meta/global fetch *after we're already logged in*.
-add_task(function test_momentary_401_storage_loggedin() {
- _("Test a failure for any storage URL after login that's resolved by" +
- "reassignment.");
- let server = yield prepareServer();
-
- _("First sync to prepare server contents.");
- Service.sync();
-
- _("Arrange for meta/global to return a 401.");
- let oldHandler = server.toplevelHandlers.storage;
- server.toplevelHandlers.storage = handleReassign;
-
- function undo() {
- _("Undoing test changes.");
- server.toplevelHandlers.storage = oldHandler;
- }
-
- do_check_true(Service.isLoggedIn, "already logged in");
-
- yield syncAndExpectNodeReassignment(server,
- "weave:service:sync:error",
- undo,
- "weave:service:sync:finish",
- Service.storageURL + "meta/global");
-});
-
-// This test ends up being a failing meta/global fetch *before we've logged in*.
-add_task(function test_momentary_401_storage_loggedout() {
- _("Test a failure for any storage URL before login, not just engine parts. " +
- "Resolved by reassignment.");
- let server = yield prepareServer();
-
- // Return a 401 for all storage requests.
- let oldHandler = server.toplevelHandlers.storage;
- server.toplevelHandlers.storage = handleReassign;
-
- function undo() {
- _("Undoing test changes.");
- server.toplevelHandlers.storage = oldHandler;
- }
-
- do_check_false(Service.isLoggedIn, "already logged in");
-
- yield syncAndExpectNodeReassignment(server,
- "weave:service:login:error",
- undo,
- "weave:service:sync:finish",
- Service.storageURL + "meta/global");
-});
-
diff --git a/services/sync/tests/unit/test_fxa_service_cluster.js b/services/sync/tests/unit/test_fxa_service_cluster.js
deleted file mode 100644
index f6f97184a..000000000
--- a/services/sync/tests/unit/test_fxa_service_cluster.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-Cu.import("resource://services-sync/service.js");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://testing-common/services/sync/fxa_utils.js");
-Cu.import("resource://testing-common/services/sync/utils.js");
-
-add_task(function test_findCluster() {
- _("Test FxA _findCluster()");
-
- _("_findCluster() throws on 500 errors.");
- initializeIdentityWithTokenServerResponse({
- status: 500,
- headers: [],
- body: "",
- });
-
- yield Service.identity.initializeWithCurrentIdentity();
- yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
- "should reject due to 500");
-
- Assert.throws(function() {
- Service._clusterManager._findCluster();
- });
-
- _("_findCluster() returns null on authentication errors.");
- initializeIdentityWithTokenServerResponse({
- status: 401,
- headers: {"content-type": "application/json"},
- body: "{}",
- });
-
- yield Service.identity.initializeWithCurrentIdentity();
- yield Assert.rejects(Service.identity.whenReadyToAuthenticate.promise,
- "should reject due to 401");
-
- cluster = Service._clusterManager._findCluster();
- Assert.strictEqual(cluster, null);
-
- _("_findCluster() works with correct tokenserver response.");
- let endpoint = "http://example.com/something";
- initializeIdentityWithTokenServerResponse({
- status: 200,
- headers: {"content-type": "application/json"},
- body:
- JSON.stringify({
- api_endpoint: endpoint,
- duration: 300,
- id: "id",
- key: "key",
- uid: "uid",
- })
- });
-
- yield Service.identity.initializeWithCurrentIdentity();
- yield Service.identity.whenReadyToAuthenticate.promise;
- cluster = Service._clusterManager._findCluster();
- // The cluster manager ensures a trailing "/"
- Assert.strictEqual(cluster, endpoint + "/");
-
- Svc.Prefs.resetBranch("");
-});
-
-function run_test() {
- initTestLogging();
- run_next_test();
-}
diff --git a/services/sync/tests/unit/test_fxa_startOver.js b/services/sync/tests/unit/test_fxa_startOver.js
deleted file mode 100644
index e27d86ea0..000000000
--- a/services/sync/tests/unit/test_fxa_startOver.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-Cu.import("resource://testing-common/services/sync/utils.js");
-Cu.import("resource://services-sync/identity.js");
-Cu.import("resource://services-sync/browserid_identity.js");
-Cu.import("resource://services-sync/service.js");
-
-function run_test() {
- initTestLogging("Trace");
- run_next_test();
-}
-
-add_task(function* test_startover() {
- let oldValue = Services.prefs.getBoolPref("services.sync-testing.startOverKeepIdentity", true);
- Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", false);
-
- ensureLegacyIdentityManager();
- yield configureIdentity({username: "johndoe"});
-
- // The boolean flag on the xpcom service should reflect a legacy provider.
- let xps = Cc["@mozilla.org/weave/service;1"]
- .getService(Components.interfaces.nsISupports)
- .wrappedJSObject;
- do_check_false(xps.fxAccountsEnabled);
-
- // we expect the "legacy" provider (but can't instanceof that, as BrowserIDManager
- // extends it)
- do_check_false(Service.identity instanceof BrowserIDManager);
-
- Service.serverURL = "https://localhost/";
- Service.clusterURL = Service.serverURL;
-
- Service.login();
- // We should have a cluster URL
- do_check_true(Service.clusterURL.length > 0);
-
- // remember some stuff so we can reset it after.
- let oldIdentity = Service.identity;
- let oldClusterManager = Service._clusterManager;
- let deferred = Promise.defer();
- Services.obs.addObserver(function observeStartOverFinished() {
- Services.obs.removeObserver(observeStartOverFinished, "weave:service:start-over:finish");
- deferred.resolve();
- }, "weave:service:start-over:finish", false);
-
- Service.startOver();
- yield deferred.promise; // wait for the observer to fire.
-
- // the xpcom service should indicate FxA is enabled.
- do_check_true(xps.fxAccountsEnabled);
- // should have swapped identities.
- do_check_true(Service.identity instanceof BrowserIDManager);
- // should have clobbered the cluster URL
- do_check_eq(Service.clusterURL, "");
-
- // we should have thrown away the old identity provider and cluster manager.
- do_check_neq(oldIdentity, Service.identity);
- do_check_neq(oldClusterManager, Service._clusterManager);
-
- // reset the world.
- Services.prefs.setBoolPref("services.sync-testing.startOverKeepIdentity", oldValue);
-});
diff --git a/services/sync/tests/unit/test_load_modules.js b/services/sync/tests/unit/test_load_modules.js
index 4f561bae6..8e3fcf1f3 100644
--- a/services/sync/tests/unit/test_load_modules.js
+++ b/services/sync/tests/unit/test_load_modules.js
@@ -37,7 +37,6 @@ const testingModules = [
"fakeservices.js",
"rotaryengine.js",
"utils.js",
- "fxa_utils.js",
];
function run_test() {
diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini
index 7e97835ac..2f9884751 100644
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -50,7 +50,6 @@ skip-if = os == "win" || os == "android"
[test_syncstoragerequest.js]
# Generic Sync types.
-[test_browserid_identity.js]
[test_collection_inc_get.js]
[test_collections_recovery.js]
[test_identity_manager.js]
@@ -122,11 +121,6 @@ skip-if = os == "android"
[test_syncscheduler.js]
[test_upgrade_old_sync_key.js]
-# Firefox Accounts specific tests
-[test_fxa_startOver.js]
-[test_fxa_service_cluster.js]
-[test_fxa_node_reassignment.js]
-
# Finally, we test each engine.
[test_addons_engine.js]
run-sequentially = Hardcoded port in static files.
diff --git a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
deleted file mode 100644
index f5daa14be..000000000
--- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm
+++ /dev/null
@@ -1,96 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * 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/. */
-
-"use strict";
-
-this.EXPORTED_SYMBOLS = [
- "Authentication",
-];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/FxAccounts.jsm");
-Cu.import("resource://gre/modules/FxAccountsClient.jsm");
-Cu.import("resource://services-common/async.js");
-Cu.import("resource://services-sync/main.js");
-Cu.import("resource://tps/logger.jsm");
-
-
-/**
- * Helper object for Firefox Accounts authentication
- */
-var Authentication = {
-
- /**
- * Check if an user has been logged in
- */
- get isLoggedIn() {
- return !!this.getSignedInUser();
- },
-
- /**
- * Wrapper to retrieve the currently signed in user
- *
- * @returns Information about the currently signed in user
- */
- getSignedInUser: function getSignedInUser() {
- let cb = Async.makeSpinningCallback();
-
- fxAccounts.getSignedInUser().then(user => {
- cb(null, user);
- }, error => {
- cb(error);
- })
-
- try {
- return cb.wait();
- } catch (error) {
- Logger.logError("getSignedInUser() failed with: " + JSON.stringify(error));
- throw error;
- }
- },
-
- /**
- * Wrapper to synchronize the login of a user
- *
- * @param account
- * Account information of the user to login
- * @param account.username
- * The username for the account (utf8)
- * @param account.password
- * The user's password
- */
- signIn: function signIn(account) {
- let cb = Async.makeSpinningCallback();
-
- Logger.AssertTrue(account["username"], "Username has been found");
- Logger.AssertTrue(account["password"], "Password has been found");
-
- Logger.logInfo("Login user: " + account["username"] + '\n');
-
- let client = new FxAccountsClient();
- client.signIn(account["username"], account["password"], true).then(credentials => {
- return fxAccounts.setSignedInUser(credentials);
- }).then(() => {
- cb(null, true);
- }, error => {
- cb(error, false);
- });
-
- try {
- cb.wait();
-
- if (Weave.Status.login !== Weave.LOGIN_SUCCEEDED) {
- Logger.logInfo("Logging into Weave.");
- Weave.Service.login();
- Logger.AssertEqual(Weave.Status.login, Weave.LOGIN_SUCCEEDED,
- "Weave logged in");
- }
-
- return true;
- } catch (error) {
- throw new Error("signIn() failed with: " + error.message);
- }
- }
-};
diff --git a/services/sync/tps/extensions/tps/resource/tps.jsm b/services/sync/tps/extensions/tps/resource/tps.jsm
index ca3e4d578..c94112a6f 100644
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -74,9 +74,7 @@ const ACTIONS = [
ACTION_VERIFY_NOT,
];
-const OBSERVER_TOPICS = ["fxaccounts:onlogin",
- "fxaccounts:onlogout",
- "private-browsing",
+const OBSERVER_TOPICS = ["private-browsing",
"quit-application-requested",
"sessionstore-windows-restored",
"weave:engine:start-tracking",