author | Gregory Szorc <gps@mozilla.com> |
Thu, 22 Mar 2012 15:49:50 -0700 | |
changeset 90703 | 82c4a54e0437dd4f03e887bad846e188dd665791 |
parent 90702 | e8b6b6b810ee2914fcdb694798ad1c81955dad72 |
child 90704 | d6b9981245d2f2e4d042f48fa0333fcfd56ef648 |
push id | 22374 |
push user | gszorc@mozilla.com |
push date | Fri, 30 Mar 2012 18:51:50 +0000 |
treeherder | mozilla-central@cf60d15e8804 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | rnewman |
bugs | 730989 |
milestone | 14.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/base/content/sync/addDevice.js +++ b/browser/base/content/sync/addDevice.js @@ -78,17 +78,17 @@ let gSyncAddDevice = { this.pin1.focus(); break; case SYNC_KEY_PAGE: this.wizard.canAdvance = false; this.wizard.canRewind = true; this.wizard.getButton("back").hidden = false; this.wizard.getButton("next").hidden = true; document.getElementById("weavePassphrase").value = - Weave.Utils.hyphenatePassphrase(Weave.Service.passphrase); + Weave.Utils.hyphenatePassphrase(Weave.Identity.syncKey); break; case DEVICE_CONNECTED_PAGE: this.wizard.canAdvance = true; this.wizard.canRewind = false; this.wizard.getButton("cancel").hidden = true; break; } }, @@ -108,19 +108,19 @@ let gSyncAddDevice = { startTransfer: function startTransfer() { this.errorRow.hidden = true; // When onAbort is called, Weave may already be gone. const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT; let self = this; let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({ onPaired: function onPaired() { - let credentials = {account: Weave.Service.account, - password: Weave.Service.password, - synckey: Weave.Service.passphrase, + let credentials = {account: Weave.Identity.account, + password: Weave.Identity.basicPassword, + synckey: Weave.Identity.syncKey, serverURL: Weave.Service.serverURL}; jpakeclient.sendAndComplete(credentials); }, onComplete: function onComplete() { delete self._jpakeclient; self.wizard.pageIndex = DEVICE_CONNECTED_PAGE; // Schedule a Sync for soonish to fetch the data uploaded by the
--- a/browser/base/content/sync/genericChange.js +++ b/browser/base/content/sync/genericChange.js @@ -101,17 +101,17 @@ let Change = { introText.textContent = this._str("new.recoverykey.introText"); this._dialog.getButton("finish").label = this._str("new.recoverykey.acceptButton"); } else { document.getElementById("generatePassphraseButton").hidden = false; document.getElementById("passphraseBackupButtons").hidden = false; this._passphraseBox.setAttribute("readonly", "true"); - let pp = Weave.Service.passphrase; + let pp = Weave.Identity.syncKey; if (Weave.Utils.isPassphrase(pp)) pp = Weave.Utils.hyphenatePassphrase(pp); this._passphraseBox.value = pp; this._passphraseBox.focus(); document.title = this._str("change.recoverykey.title"); introText.textContent = this._str("change.synckey.introText2"); warningText.textContent = this._str("change.recoverykey.warningText"); this._dialog.getButton("finish").label @@ -188,17 +188,17 @@ let Change = { let passphrase = Weave.Utils.generatePassphrase(); this._passphraseBox.value = Weave.Utils.hyphenatePassphrase(passphrase); this._dialog.getButton("finish").disabled = false; }, doChangePassphrase: function Change_doChangePassphrase() { let pp = Weave.Utils.normalizePassphrase(this._passphraseBox.value); if (this._updatingPassphrase) { - Weave.Service.passphrase = pp; + Weave.Identity.syncKey = pp; if (Weave.Service.login()) { this._updateStatus("change.recoverykey.success", "success"); Weave.Service.persistLogin(); Weave.SyncScheduler.delayedAutoConnect(0); } else { this._updateStatus("new.passphrase.status.incorrect", "error"); } @@ -212,17 +212,17 @@ let Change = { this._updateStatus("change.recoverykey.error", "error"); } return false; }, doChangePassword: function Change_doChangePassword() { if (this._currentPasswordInvalid) { - Weave.Service.password = this._firstBox.value; + Weave.Identity.basicPassword = this._firstBox.value; if (Weave.Service.login()) { this._updateStatus("change.password.status.success", "success"); Weave.Service.persistLogin(); } else { this._updateStatus("new.password.status.incorrect", "error"); } }
--- a/browser/base/content/sync/setup.js +++ b/browser/base/content/sync/setup.js @@ -174,23 +174,23 @@ var gSyncSetup = { } else { this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE; } }, resetPassphrase: function resetPassphrase() { // Apply the existing form fields so that // Weave.Service.changePassphrase() has the necessary credentials. - Weave.Service.account = document.getElementById("existingAccountName").value; - Weave.Service.password = document.getElementById("existingPassword").value; + Weave.Identity.account = document.getElementById("existingAccountName").value; + Weave.Identity.basicPassword = document.getElementById("existingPassword").value; // Generate a new passphrase so that Weave.Service.login() will // actually do something. let passphrase = Weave.Utils.generatePassphrase(); - Weave.Service.passphrase = passphrase; + Weave.Identity.syncKey = passphrase; // Only open the dialog if username + password are actually correct. Weave.Service.login(); if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE, Weave.LOGIN_FAILED_NO_PASSPHRASE, Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) { return; } @@ -204,37 +204,37 @@ var gSyncSetup = { // changePassphrase() will sync, make sure we set the "firstSync" pref // according to the user's pref. Weave.Svc.Prefs.reset("firstSync"); this.setupInitialSync(); gSyncUtils.resetPassphrase(true); }, onResetPassphrase: function () { - document.getElementById("existingPassphrase").value = - Weave.Utils.hyphenatePassphrase(Weave.Service.passphrase); + document.getElementById("existingPassphrase").value = + Weave.Utils.hyphenatePassphrase(Weave.Identity.syncKey); this.checkFields(); this.wizard.advance(); }, onLoginStart: function () { this.toggleLoginFeedback(false); }, onLoginEnd: function () { this.toggleLoginFeedback(true); }, sendCredentialsAfterSync: function () { let send = function() { Services.obs.removeObserver("weave:service:sync:finish", send); Services.obs.removeObserver("weave:service:sync:error", send); - let credentials = {account: Weave.Service.account, - password: Weave.Service.password, - synckey: Weave.Service.passphrase, + let credentials = {account: Weave.Identity.account, + password: Weave.Identity.basicPassword, + synckey: Weave.Identity.syncKey, serverURL: Weave.Service.serverURL}; this._jpakeclient.sendAndComplete(credentials); }.bind(this); Services.obs.addObserver("weave:service:sync:finish", send, false); Services.obs.addObserver("weave:service:sync:error", send, false); }, toggleLoginFeedback: function (stop) { @@ -363,17 +363,17 @@ var gSyncSetup = { else str = availCheck; } } this._setFeedbackMessage(feedback, valid, str); this.status.email = valid; if (valid) - Weave.Service.account = value; + Weave.Identity.account = value; this.checkFields(); }, onPasswordChange: function () { let password = document.getElementById("weavePassword"); let pwconfirm = document.getElementById("weavePasswordConfirm"); let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm); @@ -499,34 +499,35 @@ var gSyncSetup = { document.getElementById("weaveEmail").value); let challenge = getField("challenge"); let response = getField("response"); let error = Weave.Service.createAccount(email, password, challenge, response); if (error == null) { - Weave.Service.account = email; - Weave.Service.password = password; - Weave.Service.passphrase = Weave.Utils.generatePassphrase(); + Weave.Identity.account = email; + Weave.Identity.basicPassword = password; + Weave.Identity.syncKey = Weave.Utils.generatePassphrase(); this._handleNoScript(false); Weave.Svc.Prefs.set("firstSync", "newAccount"); this.wizardFinish(); return false; } image.setAttribute("status", "error"); label.value = Weave.Utils.getErrorString(error); return false; case EXISTING_ACCOUNT_LOGIN_PAGE: - Weave.Service.account = Weave.Utils.normalizeAccount( + Weave.Identity.account = Weave.Utils.normalizeAccount( document.getElementById("existingAccountName").value); - Weave.Service.password = document.getElementById("existingPassword").value; + Weave.Identity.basicPssword = + document.getElementById("existingPassword").value; let pp = document.getElementById("existingPassphrase").value; - Weave.Service.passphrase = Weave.Utils.normalizePassphrase(pp); + Weave.Identity.syncKey = Weave.Utils.normalizePassphrase(pp); if (Weave.Service.login()) { this.wizardFinish(); } return false; case OPTIONS_PAGE: let desc = document.getElementById("mergeChoiceRadio").selectedIndex; // No confirmation needed on new account setup or merge option // with existing account. @@ -695,19 +696,19 @@ var gSyncSetup = { document.getElementById("easySetupPIN1").value = pin.slice(0, 4); document.getElementById("easySetupPIN2").value = pin.slice(4, 8); document.getElementById("easySetupPIN3").value = pin.slice(8); }, onPairingStart: function onPairingStart() {}, onComplete: function onComplete(credentials) { - Weave.Service.account = credentials.account; - Weave.Service.password = credentials.password; - Weave.Service.passphrase = credentials.synckey; + Weave.Identity.account = credentials.account; + Weave.Identity.basicPassword = credentials.password; + Weave.Identity.syncKey = credentials.synckey; Weave.Service.serverURL = credentials.serverURL; gSyncSetup.wizardFinish(); }, onAbort: function onAbort(error) { delete self._jpakeclient; // Ignore if wizard is aborted.
--- a/browser/base/content/sync/utils.js +++ b/browser/base/content/sync/utils.js @@ -222,23 +222,23 @@ let gSyncUtils = { validatePassword: function (el1, el2) { let valid = false; let val1 = el1.value; let val2 = el2 ? el2.value : ""; let error = ""; if (!el2) valid = val1.length >= Weave.MIN_PASS_LENGTH; - else if (val1 && val1 == Weave.Service.username) + else if (val1 && val1 == Weave.Identity.username) error = "change.password.pwSameAsUsername"; - else if (val1 && val1 == Weave.Service.account) + else if (val1 && val1 == Weave.Identity.account) error = "change.password.pwSameAsEmail"; - else if (val1 && val1 == Weave.Service.password) + else if (val1 && val1 == Weave.Identity.basicPassword) error = "change.password.pwSameAsPassword"; - else if (val1 && val1 == Weave.Service.passphrase) + else if (val1 && val1 == Weave.Identity.syncKey) error = "change.password.pwSameAsRecoveryKey"; else if (val1 && val2) { if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH) valid = true; else if (val1.length < Weave.MIN_PASS_LENGTH) error = "change.password.tooShort"; else if (val1 != val2) error = "change.password.mismatch";
--- a/services/sync/modules/engines.js +++ b/services/sync/modules/engines.js @@ -568,17 +568,17 @@ SyncEngine.prototype = { // URI length limitations. guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE, mobileGUIDFetchBatchSize: DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE, // How many records to process in a single batch. applyIncomingBatchSize: DEFAULT_STORE_BATCH_SIZE, get storageURL() Svc.Prefs.get("clusterURL") + SYNC_API_VERSION + - "/" + ID.get("WeaveID").username + "/storage/", + "/" + Identity.username + "/storage/", get engineURL() this.storageURL + this.name, get cryptoKeysURL() this.storageURL + "crypto/keys", get metaURL() this.storageURL + "meta/global", get syncID() {
--- a/services/sync/modules/identity.js +++ b/services/sync/modules/identity.js @@ -1,143 +1,491 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is Bookmarks Sync. - * - * The Initial Developer of the Original Code is Mozilla. - * Portions created by the Initial Developer are Copyright (C) 2007 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Dan Mills <thunder@mozilla.com> - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ +/* 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 EXPORTED_SYMBOLS = ['Identity', 'ID']; +"use strict"; -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cr = Components.results; -const Cu = Components.utils; +const EXPORTED_SYMBOLS = ["Identity", "IdentityManager"]; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/util.js"); -// Avoid circular import. -__defineGetter__("Service", function() { - delete this.Service; - Cu.import("resource://services-sync/service.js", this); - return this.Service; -}); - -XPCOMUtils.defineLazyGetter(this, "ID", function () { - return new IDManager(); +XPCOMUtils.defineLazyGetter(this, "Identity", function() { + return new IdentityManager(); }); -// For storing identities we'll use throughout Weave -function IDManager() { - this._ids = {}; -} -IDManager.prototype = { - get: function IDMgr_get(name) { - if (name in this._ids) - return this._ids[name]; - return null; - }, - set: function IDMgr_set(name, id) { - this._ids[name] = id; - return id; - }, - del: function IDMgr_del(name) { - delete this._ids[name]; - } -}; +/** + * Manages identity and authentication for Sync. + * + * The following entities are managed: + * + * account - The main Sync/services account. This is typically an email + * address. + * username - A normalized version of your account. This is what's + * transmitted to the server. + * basic password - UTF-8 password used for authenticating when using HTTP + * basic authentication. + * sync key - The main encryption key used by Sync. + * sync key bundle - A representation of your sync key. + * + * An instance of this type is lazily instantiated under Weave.Identity. It is + * and should be treated as a global variable. The reason is that saved changes + * are stored in preferences and the password manager. So, if you created + * multiple instances, they would just step on each other's state. + * + * When changes are made to entities that are stored in the password manager + * (basic password, sync key), those changes are merely staged. To commit them + * to the password manager, you'll need to call persistCredentials(). + * + * This type also manages authenticating Sync's network requests. Sync's + * network code calls into getRESTRequestAuthenticator and + * getResourceAuthenticator (depending on the network layer being used). Each + * returns a function which can be used to add authentication information to an + * outgoing request. + * + * In theory, this type supports arbitrary identity and authentication + * mechanisms. You can add support for them by monkeypatching the global + * instance of this type. Specifically, you'll need to redefine the + * aforementioned network code functions to do whatever your authentication + * mechanism needs them to do. In addition, you may wish to install custom + * functions to support your API. Although, that is certainly not required. + * If you do monkeypatch, please be advised that Sync expects the core + * attributes to have values. You will need to carry at least account and + * username forward. If you do not wish to support one of the built-in + * authentication mechanisms, you'll probably want to redefine currentAuthState + * and any other function that involves the built-in functionality. + */ +function IdentityManager() { + this._log = Log4Moz.repository.getLogger("Sync.Identity"); + this._log.Level = Log4Moz.Level[Svc.Prefs.get("log.logger.identity")]; -/* - * Identity - * These objects hold a realm, username, and password - * They can hold a password in memory, but will try to fetch it from - * the password manager if it's not set. - * FIXME: need to rethink this stuff as part of a bigger identity mgmt framework - */ + this._basicPassword = null; + this._basicPasswordAllowLookup = true; + this._basicPasswordUpdated = false; + this._syncKey = null; + this._syncKeyAllowLookup = true; + this._syncKeySet = false; + this._syncKeyBundle = null; +} +IdentityManager.prototype = { + _log: null, + + _basicPassword: null, + _basicPasswordAllowLookup: true, + _basicPasswordUpdated: false, + + _syncKey: null, + _syncKeyAllowLookup: true, + _syncKeySet: false, + + _syncKeyBundle: null, + + get account() { + return Svc.Prefs.get("account", this.username); + }, -function Identity(realm, username, password) { - this.realm = realm; - this.username = username; - this._password = password; - if (password) - this._passwordUTF8 = Utils.encodeUTF8(password); -} -Identity.prototype = { - get password() { - // Look up the password then cache it - if (this._password == null) - for each (let login in this._logins) - if (login.username.toLowerCase() == this.username) { - this._password = login.password; - this._passwordUTF8 = Utils.encodeUTF8(login.password); - } - return this._password; + /** + * Sets the active account name. + * + * This should almost always be called in favor of setting username, as + * username is derived from account. + * + * Changing the account name has the side-effect of wiping out stored + * credentials. Keep in mind that persistCredentials() will need to be called + * to flush the changes to disk. + * + * Set this value to null to clear out identity information. + */ + set account(value) { + if (value) { + value = value.toLowerCase(); + Svc.Prefs.set("account", value); + } else { + Svc.Prefs.reset("account"); + } + + this.username = this.usernameFromAccount(value); + }, + + get username() { + return Svc.Prefs.get("username", null); }, - set password(value) { - this._password = value; - this._passwordUTF8 = Utils.encodeUTF8(value); + /** + * Set the username value. + * + * Changing the username has the side-effect of wiping credentials. + */ + set username(value) { + if (value) { + value = value.toLowerCase(); + + if (value == this.username) { + return; + } + + Svc.Prefs.set("username", value); + } else { + Svc.Prefs.reset("username"); + } + + // If we change the username, we interpret this as a major change event + // and wipe out the credentials. + this._log.info("Username changed. Removing stored credentials."); + this.basicPassword = null; + this.syncKey = null; + // syncKeyBundle cleared as a result of setting syncKey. }, - get passwordUTF8() { - if (!this._passwordUTF8) - this.password; // invoke password getter - return this._passwordUTF8; + /** + * Obtains the HTTP Basic auth password. + * + * Returns a string if set or null if it is not set. + */ + get basicPassword() { + if (this._basicPasswordAllowLookup) { + // We need a username to find the credentials. + let username = this.username; + if (!username) { + return null; + } + + for each (let login in this._getLogins(PWDMGR_PASSWORD_REALM)) { + if (login.username.toLowerCase() == username) { + // It should already be UTF-8 encoded, but we don't take any chances. + this._basicPassword = Utils.encodeUTF8(login.password); + } + } + + this._basicPasswordAllowLookup = false; + } + + return this._basicPassword; }, - persist: function persist() { - // Clean up any existing passwords unless it's what we're persisting - let exists = false; - for each (let login in this._logins) { - if (login.username == this.username && login.password == this._password) - exists = true; - else - Services.logins.removeLogin(login); + /** + * Set the HTTP basic password to use. + * + * Changes will not persist unless persistSyncCredentials() is called. + */ + set basicPassword(value) { + // Wiping out value. + if (!value) { + this._log.info("Basic password has no value. Removing."); + this._basicPassword = null; + this._basicPasswordUpdated = true; + this._basicPasswordAllowLookup = false; + return; + } + + let username = this.username; + if (!username) { + throw new Error("basicPassword cannot be set before username."); } - // No need to create the login after clearing out the other ones - let log = Log4Moz.repository.getLogger("Sync.Identity"); - if (exists) { - log.trace("Skipping persist: " + this.realm + " for " + this.username); + this._log.info("Basic password being updated."); + this._basicPassword = Utils.encodeUTF8(value); + this._basicPasswordUpdated = true; + }, + + /** + * Obtain the Sync Key. + * + * This returns a 26 character "friendly" Base32 encoded string on success or + * null if no Sync Key could be found. + * + * If the Sync Key hasn't been set in this session, this will look in the + * password manager for the sync key. + */ + get syncKey() { + if (this._syncKeyAllowLookup) { + let username = this.username; + if (!username) { + return null; + } + + for each (let login in this._getLogins(PWDMGR_PASSPHRASE_REALM)) { + if (login.username.toLowerCase() == username) { + this._syncKey = login.password; + } + } + + this._syncKeyAllowLookup = false; + } + + return this._syncKey; + }, + + /** + * Set the active Sync Key. + * + * If being set to null, the Sync Key and its derived SyncKeyBundle are + * removed. However, the Sync Key won't be deleted from the password manager + * until persistSyncCredentials() is called. + * + * If a value is provided, it should be a 26 or 32 character "friendly" + * Base32 string for which Utils.isPassphrase() returns true. + * + * A side-effect of setting the Sync Key is that a SyncKeyBundle is + * generated. For historical reasons, this will silently error out if the + * value is not a proper Sync Key (!Utils.isPassphrase()). This should be + * fixed in the future (once service.js is more sane) to throw if the passed + * value is not valid. + */ + set syncKey(value) { + if (!value) { + this._log.info("Sync Key has no value. Deleting."); + this._syncKey = null; + this._syncKeyBundle = null; + this._syncKeyUpdated = true; return; } - // Add the new username/password - log.trace("Persisting " + this.realm + " for " + this.username); - let nsLoginInfo = new Components.Constructor( - "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init"); - let newLogin = new nsLoginInfo(PWDMGR_HOST, null, this.realm, - this.username, this.password, "", ""); - Services.logins.addLogin(newLogin); + if (!this.username) { + throw new Error("syncKey cannot be set before username."); + } + + this._log.info("Sync Key being updated."); + this._syncKey = value; + + // Calling the getter has the side-effect of populating the object, which + // we desire. + let bundle = this.syncKeyBundle; + + this._syncKeyUpdated = true; + }, + + /** + * Obtain the active SyncKeyBundle. + * + * This returns a SyncKeyBundle representing a key pair derived from the + * Sync Key on success. If no Sync Key is present or if the Sync Key is not + * valid, this returns null. + * + * The SyncKeyBundle should be treated as immutable. + */ + get syncKeyBundle() { + // We can't obtain a bundle without a username set. + if (!this.username) { + this._log.warn("Attempted to obtain Sync Key Bundle with no username set!"); + return null; + } + + if (!this.syncKey) { + this._log.warn("Attempted to obtain Sync Key Bundle with no Sync Key " + + "set!"); + return null; + } + + if (!this._syncKeyBundle) { + try { + this._syncKeyBundle = new SyncKeyBundle(this.username, this.syncKey); + } catch (ex) { + this._log.warn(Utils.exceptionStr(ex)); + return null; + } + } + + return this._syncKeyBundle; + }, + + /** + * The current state of the auth credentials. + * + * This essentially validates that enough credentials are available to use + * Sync. + */ + get currentAuthState() { + if (!this.username) { + return LOGIN_FAILED_NO_USERNAME; + } + + if (Utils.mpLocked()) { + return STATUS_OK; + } + + if (!this.basicPassword) { + return LOGIN_FAILED_NO_PASSWORD; + } + + if (!this.syncKey) { + return LOGIN_FAILED_NO_PASSPHRASE; + } + + return STATUS_OK; + }, + + /** + * Persist credentials to password store. + * + * When credentials are updated, they are changed in memory only. This will + * need to be called to save them to the underlying password store. + * + * If the password store is locked (e.g. if the master password hasn't been + * entered), this could throw an exception. + */ + persistCredentials: function persistCredentials() { + if (this._basicPasswordUpdated) { + if (this._basicPassword) { + this._setLogin(PWDMGR_PASSWORD_REALM, this.username, + this._basicPassword); + } else { + for each (let login in this._getLogins(PWDMGR_PASSWORD_REALM)) { + Services.logins.removeLogin(login); + } + } + + this._basicPasswordUpdated = false; + } + + if (this._syncKeyUpdated) { + if (this._syncKey) { + this._setLogin(PWDMGR_PASSPHRASE_REALM, this.username, this._syncKey); + } else { + for each (let login in this._getLogins(PWDMGR_PASSPHRASE_REALM)) { + Services.logins.removeLogin(login); + } + } + + this._syncKeyUpdated = false; + } + + }, + + /** + * Deletes the Sync Key from the system. + */ + deleteSyncKey: function deleteSyncKey() { + this.syncKey = null; + this.persistCredentials(); }, - get _logins() Services.logins.findLogins({}, PWDMGR_HOST, null, this.realm) + hasBasicCredentials: function hasBasicCredentials() { + // Because JavaScript. + return this.username && this.basicPassword && true; + }, + + /** + * Obtains the array of basic logins from nsiPasswordManager. + */ + _getLogins: function _getLogins(realm) { + return Services.logins.findLogins({}, PWDMGR_HOST, null, realm); + }, + + /** + * Set a login in the password manager. + * + * This has the side-effect of deleting any other logins for the specified + * realm. + */ + _setLogin: function _setLogin(realm, username, password) { + let exists = false; + for each (let login in this._getLogins(realm)) { + if (login.username == username && login.password == password) { + exists = true; + } else { + this._log.debug("Pruning old login for " + username + " from " + realm); + Services.logins.removeLogin(login); + } + } + + if (exists) { + return; + } + + this._log.debug("Updating saved password for " + username + " in " + + realm); + + let loginInfo = new Components.Constructor( + "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init"); + let login = new loginInfo(PWDMGR_HOST, null, realm, username, + password, "", ""); + Services.logins.addLogin(login); + }, + + /** + * Deletes Sync credentials from the password manager. + */ + deleteSyncCredentials: function deleteSyncCredentials() { + let logins = Services.logins.findLogins({}, PWDMGR_HOST, "", ""); + for each (let login in logins) { + Services.logins.removeLogin(login); + } + + // Wait until after store is updated in case it fails. + this._basicPassword = null; + this._basicPasswordAllowLookup = true; + this._basicPasswordUpdated = false; + + this._syncKey = null; + // this._syncKeyBundle is nullified as part of _syncKey setter. + this._syncKeyAllowLookup = true; + this._syncKeyUpdated = false; + }, + + usernameFromAccount: function usernameFromAccount(value) { + // If we encounter characters not allowed by the API (as found for + // instance in an email address), hash the value. + if (value && value.match(/[^A-Z0-9._-]/i)) { + return Utils.sha1Base32(value.toLowerCase()).toLowerCase(); + } + + return value ? value.toLowerCase() : value; + }, + + /** + * Obtain a function to be used for adding auth to Resource HTTP requests. + */ + getResourceAuthenticator: function getResourceAuthenticator() { + if (this.hasBasicCredentials()) { + return this._onResourceRequestBasic.bind(this); + } + + return null; + }, + + /** + * Helper method to return an authenticator for basic Resource requests. + */ + getBasicResourceAuthenticator: + function getBasicResourceAuthenticator(username, password) { + + return function basicAuthenticator(resource) { + let value = "Basic " + btoa(username + ":" + password); + return {headers: {authorization: value}}; + }; + }, + + _onResourceRequestBasic: function _onResourceRequestBasic(resource) { + let value = "Basic " + btoa(this.username + ":" + this.basicPassword); + return {headers: {authorization: value}}; + }, + + _onResourceRequestMAC: function _onResourceRequestMAC(resource, method) { + // TODO Get identifier and key from somewhere. + let identifier; + let key; + let result = Utils.computeHTTPMACSHA1(identifier, key, method, resource.uri); + + return {headers: {authorization: result.header}}; + }, + + /** + * Obtain a function to be used for adding auth to RESTRequest instances. + */ + getRESTRequestAuthenticator: function getRESTRequestAuthenticator() { + if (this.hasBasicCredentials()) { + return this.onRESTRequestBasic.bind(this); + } + + return null; + }, + + onRESTRequestBasic: function onRESTRequestBasic(request) { + let up = this.username + ":" + this.basicPassword; + request.setHeader("authorization", "Basic " + btoa(up)); + } };
new file mode 100644 --- /dev/null +++ b/services/sync/modules/keys.js @@ -0,0 +1,214 @@ +/* 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"; + +const EXPORTED_SYMBOLS = [ + "BulkKeyBundle", + "SyncKeyBundle" +]; + +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; + +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/log4moz.js"); +Cu.import("resource://services-sync/util.js"); + +/** + * Represents a pair of keys. + * + * Each key stored in a key bundle is 256 bits. One key is used for symmetric + * encryption. The other is used for HMAC. + * + * A KeyBundle by itself is just an anonymous pair of keys. Other types + * deriving from this one add semantics, such as associated collections or + * generating a key bundle via HKDF from another key. + */ +function KeyBundle() { + this._encrypt = null; + this._encryptB64 = null; + this._hmac = null; + this._hmacB64 = null; + this._hmacObj = null; + this._sha256HMACHasher = null; +} +KeyBundle.prototype = { + _encrypt: null, + _encryptB64: null, + _hmac: null, + _hmacB64: null, + _hmacObj: null, + _sha256HMACHasher: null, + + equals: function equals(bundle) { + return bundle && + (bundle.hmacKey == this.hmacKey) && + (bundle.encryptionKey == this.encryptionKey); + }, + + /* + * Accessors for the two keys. + */ + get encryptionKey() { + return this._encrypt; + }, + + set encryptionKey(value) { + if (!value || typeof value != "string") { + throw new Error("Encryption key can only be set to string values."); + } + + if (value.length < 16) { + throw new Error("Encryption key must be at least 128 bits long."); + } + + this._encrypt = value; + this._encryptB64 = btoa(value); + }, + + get encryptionKeyB64() { + return this._encryptB64; + }, + + get hmacKey() { + return this._hmac; + }, + + set hmacKey(value) { + if (!value || typeof value != "string") { + throw new Error("HMAC key can only be set to string values."); + } + + if (value.length < 16) { + throw new Error("HMAC key must be at least 128 bits long."); + } + + this._hmac = value; + this._hmacB64 = btoa(value); + this._hmacObj = value ? Utils.makeHMACKey(value) : null; + this._sha256HMACHasher = value ? Utils.makeHMACHasher( + Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null; + }, + + get hmacKeyB64() { + return this._hmacB64; + }, + + get hmacKeyObject() { + return this._hmacObj; + }, + + get sha256HMACHasher() { + return this._sha256HMACHasher; + }, + + /** + * Populate this key pair with 2 new, randomly generated keys. + */ + generateRandom: function generateRandom() { + let generatedHMAC = Svc.Crypto.generateRandomKey(); + let generatedEncr = Svc.Crypto.generateRandomKey(); + this.keyPairB64 = [generatedEncr, generatedHMAC]; + }, + +}; + +/** + * Represents a KeyBundle associated with a collection. + * + * This is just a KeyBundle with a collection attached. + */ +function BulkKeyBundle(collection) { + let log = Log4Moz.repository.getLogger("Sync.BulkKeyBundle"); + log.info("BulkKeyBundle being created for " + collection); + KeyBundle.call(this); + + this._collection = collection; +} + +BulkKeyBundle.prototype = { + __proto__: KeyBundle.prototype, + + get collection() { + return this._collection; + }, + + /** + * Obtain the key pair in this key bundle. + * + * The returned keys are represented as raw byte strings. + */ + get keyPair() { + return [this.encryptionKey, this.hmacKey]; + }, + + set keyPair(value) { + if (!Array.isArray(value) || value.length != 2) { + throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys."); + } + + this.encryptionKey = value[0]; + this.hmacKey = value[1]; + }, + + get keyPairB64() { + return [this.encryptionKeyB64, this.hmacKeyB64]; + }, + + set keyPairB64(value) { + if (!Array.isArray(value) || value.length != 2) { + throw new Error("BulkKeyBundle.keyPairB64 value must be an array of 2 " + + "keys."); + } + + this.encryptionKey = Utils.safeAtoB(value[0]); + this.hmacKey = Utils.safeAtoB(value[1]); + }, +}; + +/** + * Represents a key pair derived from a Sync Key via HKDF. + * + * Instances of this type should be considered immutable. You create an + * instance by specifying the username and 26 character "friendly" Base32 + * encoded Sync Key. The Sync Key is derived at instance creation time. + * + * If the username or Sync Key is invalid, an Error will be thrown. + */ +function SyncKeyBundle(username, syncKey) { + let log = Log4Moz.repository.getLogger("Sync.SyncKeyBundle"); + log.info("SyncKeyBundle being created."); + KeyBundle.call(this); + + this.generateFromKey(username, syncKey); +} +SyncKeyBundle.prototype = { + __proto__: KeyBundle.prototype, + + /* + * If we've got a string, hash it into keys and store them. + */ + generateFromKey: function generateFromKey(username, syncKey) { + if (!username || (typeof username != "string")) { + throw new Error("Sync Key cannot be generated from non-string username."); + } + + if (!syncKey || (typeof syncKey != "string")) { + throw new Error("Sync Key cannot be generated from non-string key."); + } + + if (!Utils.isPassphrase(syncKey)) { + throw new Error("Provided key is not a passphrase, cannot derive Sync " + + "Key Bundle."); + } + + // Expand the base32 Sync Key to an AES 256 and 256 bit HMAC key. + let prk = Utils.decodeKeyBase32(syncKey); + let info = HMAC_INPUT + username; + let okm = Utils.hkdfExpand(prk, info, 32 * 2); + this.encryptionKey = okm.slice(0, 32); + this.hmacKey = okm.slice(32, 64); + }, +}; +
--- a/services/sync/modules/main.js +++ b/services/sync/modules/main.js @@ -35,34 +35,34 @@ * * ***** END LICENSE BLOCK ***** */ const EXPORTED_SYMBOLS = ['Weave']; let Weave = {}; Components.utils.import("resource://services-sync/constants.js", Weave); let lazies = { - "record.js": ["CollectionKeys", "BulkKeyBundle", "SyncKeyBundle"], + "record.js": ["CollectionKeys"], "engines.js": ['Engines', 'Engine', 'SyncEngine', 'Store'], "engines/addons.js": ["AddonsEngine"], "engines/bookmarks.js": ['BookmarksEngine', 'BookmarksSharingManager'], "engines/clients.js": ["Clients"], "engines/forms.js": ["FormEngine"], "engines/history.js": ["HistoryEngine"], "engines/prefs.js": ["PrefsEngine"], "engines/passwords.js": ["PasswordEngine"], "engines/tabs.js": ["TabEngine"], "engines/apps.js": ["AppsEngine"], - "identity.js": ["Identity", "ID"], + "identity.js": ["Identity"], "jpakeclient.js": ["JPAKEClient"], + "keys.js": ["BulkKeyBundle", "SyncKeyBundle"], "notifications.js": ["Notifications", "Notification", "NotificationButton"], "policies.js": ["SyncScheduler", "ErrorHandler", "SendCredentialsController"], - "resource.js": ["Resource", "AsyncResource", "Auth", - "BasicAuthenticator", "NoOpAuthenticator"], + "resource.js": ["Resource", "AsyncResource"], "service.js": ["Service"], "status.js": ["Status"], "util.js": ['Utils', 'Svc', 'Str'] }; function lazyImport(module, dest, props) { function getter(prop) function() { let ns = {};
--- a/services/sync/modules/policies.js +++ b/services/sync/modules/policies.js @@ -415,17 +415,19 @@ let SyncScheduler = { }, /** * Incorporates the backoff/retry logic used in error handling and elective * non-syncing. */ scheduleAtInterval: function scheduleAtInterval(minimumInterval) { - let interval = Utils.calculateBackoff(this._syncErrors, MINIMUM_BACKOFF_INTERVAL); + let interval = Utils.calculateBackoff(this._syncErrors, + MINIMUM_BACKOFF_INTERVAL, + Status.backoffInterval); if (minimumInterval) { interval = Math.max(minimumInterval, interval); } this._log.debug("Starting client-initiated backoff. Next sync in " + interval + " ms."); this.scheduleNextSync(interval); }, @@ -902,19 +904,19 @@ SendCredentialsController.prototype = { // This will call onAbort which will call unload(). this.jpakeclient.abort(); break; } }, sendCredentials: function sendCredentials() { this._log.trace("Sending credentials."); - let credentials = {account: Weave.Service.account, - password: Weave.Service.password, - synckey: Weave.Service.passphrase, + let credentials = {account: Weave.Identity.account, + password: Weave.Identity.basicPassword, + synckey: Weave.Identity.syncKey, serverURL: Weave.Service.serverURL}; this.jpakeclient.sendAndComplete(credentials); }, // JPAKEClient controller API onComplete: function onComplete() { this._log.debug("Exchange was completed successfully!");
--- a/services/sync/modules/record.js +++ b/services/sync/modules/record.js @@ -33,29 +33,29 @@ * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ const EXPORTED_SYMBOLS = ["WBORecord", "RecordManager", "Records", - "CryptoWrapper", "CollectionKeys", "BulkKeyBundle", - "SyncKeyBundle", "Collection"]; + "CryptoWrapper", "CollectionKeys", "Collection"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; const CRYPTO_COLLECTION = "crypto"; const KEYS_WBO = "keys"; Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/util.js"); function WBORecord(collection, id) { this.data = {}; this.payload = {}; this.collection = collection; // Optional. @@ -193,63 +193,66 @@ function CryptoWrapper(collection, id) { this.id = id; } CryptoWrapper.prototype = { __proto__: WBORecord.prototype, _logName: "Sync.Record.CryptoWrapper", ciphertextHMAC: function ciphertextHMAC(keyBundle) { let hasher = keyBundle.sha256HMACHasher; - if (!hasher) + if (!hasher) { throw "Cannot compute HMAC without an HMAC key."; + } return Utils.bytesAsHex(Utils.digestUTF8(this.ciphertext, hasher)); }, /* * Don't directly use the sync key. Instead, grab a key for this * collection, which is decrypted with the sync key. * * Cache those keys; invalidate the cache if the time on the keys collection * changes, or other auth events occur. * * Optional key bundle overrides the collection key lookup. */ encrypt: function encrypt(keyBundle) { keyBundle = keyBundle || CollectionKeys.keyForCollection(this.collection); - if (!keyBundle) + if (!keyBundle) { throw new Error("Key bundle is null for " + this.uri.spec); + } this.IV = Svc.Crypto.generateRandomIV(); this.ciphertext = Svc.Crypto.encrypt(JSON.stringify(this.cleartext), - keyBundle.encryptionKey, this.IV); + keyBundle.encryptionKeyB64, this.IV); this.hmac = this.ciphertextHMAC(keyBundle); this.cleartext = null; }, // Optional key bundle. decrypt: function decrypt(keyBundle) { if (!this.ciphertext) { throw "No ciphertext: nothing to decrypt?"; } keyBundle = keyBundle || CollectionKeys.keyForCollection(this.collection); - if (!keyBundle) + if (!keyBundle) { throw new Error("Key bundle is null for " + this.collection + "/" + this.id); + } // Authenticate the encrypted blob with the expected HMAC let computedHMAC = this.ciphertextHMAC(keyBundle); if (computedHMAC != this.hmac) { Utils.throwHMACMismatch(this.hmac, computedHMAC); } // Handle invalid data here. Elsewhere we assume that cleartext is an object. let cleartext = Svc.Crypto.decrypt(this.ciphertext, - keyBundle.encryptionKey, this.IV); + keyBundle.encryptionKeyB64, this.IV); let json_result = JSON.parse(cleartext); if (json_result && (json_result instanceof Object)) { this.cleartext = json_result; this.ciphertext = null; } else { throw "Decryption failed: result is <" + json_result + ">, not an object."; } @@ -288,18 +291,17 @@ Utils.deferGetSet(CryptoWrapper, "payloa Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted"); XPCOMUtils.defineLazyGetter(this, "CollectionKeys", function () { return new CollectionKeyManager(); }); /** - * Keeps track of mappings between collection names ('tabs') and - * keyStrs, which you can feed into KeyBundle to get encryption tokens. + * Keeps track of mappings between collection names ('tabs') and KeyBundles. * * You can update this thing simply by giving it /info/collections. It'll * use the last modified time to bring itself up to date. */ function CollectionKeyManager() { this.lastModified = 0; this._collections = {}; this._default = null; @@ -360,20 +362,20 @@ CollectionKeyManager.prototype = { * If `collections` (an array of strings) is provided, iterate * over it and generate random keys for each collection. * Create a WBO for the given data. */ _makeWBO: function(collections, defaultBundle) { let wbo = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO); let c = {}; for (let k in collections) { - c[k] = collections[k].keyPair; + c[k] = collections[k].keyPairB64; } wbo.cleartext = { - "default": defaultBundle ? defaultBundle.keyPair : null, + "default": defaultBundle ? defaultBundle.keyPairB64 : null, "collections": c, "collection": CRYPTO_COLLECTION, "id": KEYS_WBO }; return wbo; }, /** @@ -381,23 +383,23 @@ CollectionKeyManager.prototype = { */ asWBO: function(collection, id) this._makeWBO(this._collections, this._default), /** * Compute a new default key, and new keys for any specified collections. */ newKeys: function(collections) { - let newDefaultKey = new BulkKeyBundle(null, DEFAULT_KEYBUNDLE_NAME); + let newDefaultKey = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME); newDefaultKey.generateRandom(); let newColls = {}; if (collections) { collections.forEach(function (c) { - let b = new BulkKeyBundle(null, c); + let b = new BulkKeyBundle(c); b.generateRandom(); newColls[c] = b; }); } return [newDefaultKey, newColls]; }, /** @@ -454,30 +456,30 @@ CollectionKeyManager.prototype = { if (!payload.default) { this._log.warn("No downloaded default key: this should not occur."); this._log.warn("Not clearing local keys."); throw "No default key in CollectionKeys.setContents(). Cannot proceed."; } // Process the incoming default key. - let b = new BulkKeyBundle(null, DEFAULT_KEYBUNDLE_NAME); - b.keyPair = payload.default; + let b = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME); + b.keyPairB64 = payload.default; let newDefault = b; // Process the incoming collections. let newCollections = {}; if ("collections" in payload) { this._log.info("Processing downloaded per-collection keys."); let colls = payload.collections; for (let k in colls) { let v = colls[k]; if (v) { - let keyObj = new BulkKeyBundle(null, k); - keyObj.keyPair = v; + let keyObj = new BulkKeyBundle(k); + keyObj.keyPairB64 = v; if (keyObj) { newCollections[k] = keyObj; } } } } // Check to see if these are already our keys. @@ -523,212 +525,16 @@ CollectionKeyManager.prototype = { } let r = this.setContents(payload, storage_keys.modified); log.info("Collection keys updated."); return r; } } -/** - * Abuse Identity: store the collection name (or default) in the - * username field, and the keyStr in the password field. - * - * We very rarely want to override the realm, so pass null and - * it'll default to PWDMGR_KEYBUNDLE_REALM. - * - * KeyBundle is the base class for two similar classes: - * - * SyncKeyBundle: - * - * A key string is provided, and it must be hashed to derive two different - * keys (one HMAC, one AES). - * - * BulkKeyBundle: - * - * Two independent keys are provided, or randomly generated on request. - * - */ -function KeyBundle(realm, collectionName, keyStr) { - let realm = realm || PWDMGR_KEYBUNDLE_REALM; - - if (keyStr && !keyStr.charAt) - // Ensure it's valid. - throw "KeyBundle given non-string key."; - - Identity.call(this, realm, collectionName, keyStr); -} -KeyBundle.prototype = { - __proto__: Identity.prototype, - - _encrypt: null, - _hmac: null, - _hmacObj: null, - _sha256HMACHasher: null, - - equals: function equals(bundle) { - return bundle && - (bundle.hmacKey == this.hmacKey) && - (bundle.encryptionKey == this.encryptionKey); - }, - - /* - * Accessors for the two keys. - */ - get encryptionKey() { - return this._encrypt; - }, - - set encryptionKey(value) { - this._encrypt = value; - }, - - get hmacKey() { - return this._hmac; - }, - - set hmacKey(value) { - this._hmac = value; - this._hmacObj = value ? Utils.makeHMACKey(value) : null; - this._sha256HMACHasher = value ? Utils.makeHMACHasher( - Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null; - }, - - get hmacKeyObject() { - return this._hmacObj; - }, - - get sha256HMACHasher() { - return this._sha256HMACHasher; - } -}; - -function BulkKeyBundle(realm, collectionName) { - let log = Log4Moz.repository.getLogger("Sync.BulkKeyBundle"); - log.info("BulkKeyBundle being created for " + collectionName); - KeyBundle.call(this, realm, collectionName); -} - -BulkKeyBundle.prototype = { - __proto__: KeyBundle.prototype, - - generateRandom: function generateRandom() { - let generatedHMAC = Svc.Crypto.generateRandomKey(); - let generatedEncr = Svc.Crypto.generateRandomKey(); - this.keyPair = [generatedEncr, generatedHMAC]; - }, - - get keyPair() { - return [this._encrypt, btoa(this._hmac)]; - }, - - /* - * Use keyPair = [enc, hmac], or generateRandom(), when - * you want to manage the two individual keys. - */ - set keyPair(value) { - if (value.length && (value.length == 2)) { - let json = JSON.stringify(value); - let en = value[0]; - let hm = value[1]; - - this.password = json; - this.hmacKey = Utils.safeAtoB(hm); - this._encrypt = en; // Store in base64. - } - else { - throw "Invalid keypair"; - } - } -}; - -function SyncKeyBundle(realm, collectionName, syncKey) { - let log = Log4Moz.repository.getLogger("Sync.SyncKeyBundle"); - log.info("SyncKeyBundle being created for " + collectionName); - KeyBundle.call(this, realm, collectionName, syncKey); - if (syncKey) - this.keyStr = syncKey; // Accessor sets up keys. -} - -SyncKeyBundle.prototype = { - __proto__: KeyBundle.prototype, - - /* - * Use keyStr when you want to work with a key string that's - * hashed into individual keys. - */ - get keyStr() { - return this.password; - }, - - set keyStr(value) { - this.password = value; - this._hmac = null; - this._hmacObj = null; - this._encrypt = null; - this._sha256HMACHasher = null; - }, - - /* - * Can't rely on password being set through any of our setters: - * Identity does work under the hood. - * - * Consequently, make sure we derive keys if that work hasn't already been - * done. - */ - get encryptionKey() { - if (!this._encrypt) - this.generateEntry(); - return this._encrypt; - }, - - get hmacKey() { - if (!this._hmac) - this.generateEntry(); - return this._hmac; - }, - - get hmacKeyObject() { - if (!this._hmacObj) - this.generateEntry(); - return this._hmacObj; - }, - - get sha256HMACHasher() { - if (!this._sha256HMACHasher) - this.generateEntry(); - return this._sha256HMACHasher; - }, - - /* - * If we've got a string, hash it into keys and store them. - */ - generateEntry: function generateEntry() { - let syncKey = this.keyStr; - if (!syncKey) - return; - - // Expand the base32 Sync Key to an AES 256 and 256 bit HMAC key. - let prk = Utils.decodeKeyBase32(syncKey); - let info = HMAC_INPUT + this.username; - let okm = Utils.hkdfExpand(prk, info, 32 * 2); - let enc = okm.slice(0, 32); - let hmac = okm.slice(32, 64); - - // Save them. - this._encrypt = btoa(enc); - // Individual sets: cheaper than calling parent setter. - this._hmac = hmac; - this._hmacObj = Utils.makeHMACKey(hmac); - this._sha256HMACHasher = Utils.makeHMACHasher( - Ci.nsICryptoHMAC.SHA256, this._hmacObj); - } -}; - - function Collection(uri, recordObj) { Resource.call(this, uri); this._recordObj = recordObj; this._full = false; this._ids = null; this._limit = 0; this._older = 0;
--- a/services/sync/modules/resource.js +++ b/services/sync/modules/resource.js @@ -32,91 +32,34 @@ * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ -const EXPORTED_SYMBOLS = ["Resource", "AsyncResource", - "Auth", "BrokenBasicAuthenticator", - "BasicAuthenticator", "NoOpAuthenticator"]; +const EXPORTED_SYMBOLS = [ + "AsyncResource", + "Resource" +]; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; Cu.import("resource://services-sync/async.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/ext/Observers.js"); Cu.import("resource://services-sync/ext/Preferences.js"); +Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/util.js"); -XPCOMUtils.defineLazyGetter(this, "Auth", function () { - return new AuthMgr(); -}); - -// XXX: the authenticator api will probably need to be changed to support -// other methods (digest, oauth, etc) - -function NoOpAuthenticator() {} -NoOpAuthenticator.prototype = { - onRequest: function NoOpAuth_onRequest(headers) { - return headers; - } -}; - -// Warning: This will drop the high unicode bytes from passwords. -// Use BasicAuthenticator to send non-ASCII passwords UTF8-encoded. -function BrokenBasicAuthenticator(identity) { - this._id = identity; -} -BrokenBasicAuthenticator.prototype = { - onRequest: function BasicAuth_onRequest(headers) { - headers['authorization'] = 'Basic ' + - btoa(this._id.username + ':' + this._id.password); - return headers; - } -}; - -function BasicAuthenticator(identity) { - this._id = identity; -} -BasicAuthenticator.prototype = { - onRequest: function onRequest(headers) { - headers['authorization'] = 'Basic ' + - btoa(this._id.username + ':' + this._id.passwordUTF8); - return headers; - } -}; - -function AuthMgr() { - this._authenticators = {}; - this.defaultAuthenticator = new NoOpAuthenticator(); -} -AuthMgr.prototype = { - defaultAuthenticator: null, - - registerAuthenticator: function AuthMgr_register(match, authenticator) { - this._authenticators[match] = authenticator; - }, - - lookupAuthenticator: function AuthMgr_lookup(uri) { - for (let match in this._authenticators) { - if (uri.match(match)) - return this._authenticators[match]; - } - return this.defaultAuthenticator; - } -}; - - /* * AsyncResource represents a remote network resource, identified by a URI. * Create an instance like so: * * let resource = new AsyncResource("http://foobar.com/path/to/resource"); * * The 'resource' object has the following methods to issue HTTP requests * of the corresponding HTTP methods: @@ -145,16 +88,24 @@ function AsyncResource(uri) { AsyncResource.prototype = { _logName: "Sync.AsyncResource", // ** {{{ AsyncResource.serverTime }}} ** // // Caches the latest server timestamp (X-Weave-Timestamp header). serverTime: null, + /** + * Callback to be invoked at request time to add authentication details. + * + * By default, a global authenticator is provided. If this is set, it will + * be used instead of the global one. + */ + authenticator: null, + // The string to use as the base User-Agent in Sync requests. // These strings will look something like // // Firefox/4.0 FxSync/1.8.0.20100101.mobile // // or // // Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop @@ -162,39 +113,23 @@ AsyncResource.prototype = { _userAgent: Services.appinfo.name + "/" + Services.appinfo.version + // Product. " FxSync/" + WEAVE_VERSION + "." + // Sync. Services.appinfo.appBuildID + ".", // Build. // Wait 5 minutes before killing a request. ABORT_TIMEOUT: 300000, - // ** {{{ AsyncResource.authenticator }}} ** - // - // Getter and setter for the authenticator module - // responsible for this particular resource. The authenticator - // module may modify the headers to perform authentication - // while performing a request for the resource, for example. - get authenticator() { - if (this._authenticator) - return this._authenticator; - else - return Auth.lookupAuthenticator(this.spec); - }, - set authenticator(value) { - this._authenticator = value; - }, - // ** {{{ AsyncResource.headers }}} ** // // Headers to be included when making a request for the resource. // Note: Header names should be all lower case, there's no explicit // check for duplicates due to case! get headers() { - return this.authenticator.onRequest(this._headers); + return this._headers; }, set headers(value) { this._headers = value; }, setHeader: function Res_setHeader(header, value) { this._headers[header.toLowerCase()] = value; }, @@ -230,17 +165,17 @@ AsyncResource.prototype = { }, // ** {{{ AsyncResource._createRequest }}} ** // // This method returns a new IO Channel for requests to be made // through. It is never called directly, only {{{_doRequest}}} uses it // to obtain a request channel. // - _createRequest: function Res__createRequest() { + _createRequest: function Res__createRequest(method) { let channel = Services.io.newChannel(this.spec, null, null) .QueryInterface(Ci.nsIRequest) .QueryInterface(Ci.nsIHttpChannel); // Always validate the cache: channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; @@ -248,34 +183,49 @@ AsyncResource.prototype = { channel.notificationCallbacks = new ChannelNotificationListener(); // Compose a UA string fragment from the various available identifiers. if (Svc.Prefs.get("sendVersionInfo", true)) { let ua = this._userAgent + Svc.Prefs.get("client.type", "desktop"); channel.setRequestHeader("user-agent", ua, false); } - // Avoid calling the authorizer more than once. let headers = this.headers; - for (let key in headers) { + + let authenticator = this.authenticator; + if (!authenticator) { + authenticator = Identity.getResourceAuthenticator(); + } + if (authenticator) { + let result = authenticator(this, method); + if (result && result.headers) { + for (let [k, v] in Iterator(result.headers)) { + headers[k.toLowerCase()] = v; + } + } + } else { + this._log.debug("No authenticator found."); + } + + for (let [key, value] in Iterator(headers)) { if (key == 'authorization') this._log.trace("HTTP Header " + key + ": ***** (suppressed)"); else this._log.trace("HTTP Header " + key + ": " + headers[key]); channel.setRequestHeader(key, headers[key], false); } return channel; }, _onProgress: function Res__onProgress(channel) {}, _doRequest: function _doRequest(action, data, callback) { this._log.trace("In _doRequest."); this._callback = callback; - let channel = this._createRequest(); + let channel = this._createRequest(action); if ("undefined" != typeof(data)) this._data = data; // PUT and POST are treated differently because they have payload data. if ("PUT" == action || "POST" == action) { // Convert non-string bodies into JSON if (this._data.constructor.toString() != String)
--- a/services/sync/modules/rest.js +++ b/services/sync/modules/rest.js @@ -649,23 +649,21 @@ SyncStorageRequest.prototype = { dispatch: function dispatch(method, data, onComplete, onProgress) { // Compose a UA string fragment from the various available identifiers. if (Svc.Prefs.get("sendVersionInfo", true)) { let ua = this.userAgent + Svc.Prefs.get("client.type", "desktop"); this.setHeader("user-agent", ua); } - // Set the BasicAuth header. - let id = ID.get("WeaveID"); - if (id) { - let auth_header = "Basic " + btoa(id.username + ':' + id.passwordUTF8); - this.setHeader("authorization", auth_header); + let authenticator = Identity.getRESTRequestAuthenticator(); + if (authenticator) { + authenticator(this); } else { - this._log.debug("Couldn't set Authentication header: WeaveID not found."); + this._log.debug("No authenticator found."); } return RESTRequest.prototype.dispatch.apply(this, arguments); }, onStartRequest: function onStartRequest(channel) { RESTRequest.prototype.onStartRequest.call(this, channel); if (this.status == this.ABORTED) {
--- a/services/sync/modules/service.js +++ b/services/sync/modules/service.js @@ -86,63 +86,23 @@ const STORAGE_INFO_TYPES = [INFO_COLLECT function WeaveSvc() { this._notify = Utils.notify("weave:service:"); } WeaveSvc.prototype = { _lock: Utils.lock, _locked: false, _loggedIn: false, - - get account() Svc.Prefs.get("account", this.username), - set account(value) { - if (value) { - value = value.toLowerCase(); - Svc.Prefs.set("account", value); - } else { - Svc.Prefs.reset("account"); - } - this.username = this._usernameFromAccount(value); - }, - - _usernameFromAccount: function _usernameFromAccount(value) { - // If we encounter characters not allowed by the API (as found for - // instance in an email address), hash the value. - if (value && value.match(/[^A-Z0-9._-]/i)) - return Utils.sha1Base32(value.toLowerCase()).toLowerCase(); - return value; - }, + _identity: Weave.Identity, - get username() { - return Svc.Prefs.get("username", "").toLowerCase(); - }, - set username(value) { - if (value) { - // Make sure all uses of this new username is lowercase - value = value.toLowerCase(); - Svc.Prefs.set("username", value); - } - else - Svc.Prefs.reset("username"); - - // fixme - need to loop over all Identity objects - needs some rethinking... - ID.get('WeaveID').username = value; - ID.get('WeaveCryptoID').username = value; - - // FIXME: need to also call this whenever the username pref changes - this._updateCachedURLs(); - }, - - get password() ID.get("WeaveID").password, - set password(value) ID.get("WeaveID").password = value, - - get passphrase() ID.get("WeaveCryptoID").keyStr, - set passphrase(value) ID.get("WeaveCryptoID").keyStr = value, - - get syncKeyBundle() ID.get("WeaveCryptoID"), + userBaseURL: null, + infoURL: null, + storageURL: null, + metaURL: null, + cryptoKeyURL: null, get serverURL() Svc.Prefs.get("serverURL"), set serverURL(value) { // Only do work if it's actually changing if (value == this.serverURL) return; // A new server most likely uses a different cluster, so clear that @@ -213,21 +173,21 @@ WeaveSvc.prototype = { } } return Utils.catch.call(this, func, lockExceptions); }, _updateCachedURLs: function _updateCachedURLs() { // Nothing to cache yet if we don't have the building blocks - if (this.clusterURL == "" || this.username == "") + if (this.clusterURL == "" || this._identity.username == "") return; let storageAPI = this.clusterURL + SYNC_API_VERSION + "/"; - this.userBaseURL = storageAPI + this.username + "/"; + this.userBaseURL = storageAPI + this._identity.username + "/"; this._log.debug("Caching URLs under storage user base: " + this.userBaseURL); // Generate and cache various URLs under the storage API for this user this.infoURL = this.userBaseURL + "info/collections"; this.storageURL = this.userBaseURL + "storage/"; this.metaURL = this.storageURL + "meta/global"; this.cryptoKeysURL = this.storageURL + CRYPTO_COLLECTION + "/" + KEYS_WBO; }, @@ -296,17 +256,17 @@ WeaveSvc.prototype = { // CollectionKeys, this will prevent us from uploading junk. let cipherText = cryptoKeys.ciphertext; if (!cryptoResp.success) { this._log.warn("Failed to download keys."); return false; } - let keysChanged = this.handleFetchedKeys(this.syncKeyBundle, + let keysChanged = this.handleFetchedKeys(this._identity.syncKeyBundle, cryptoKeys, true); if (keysChanged) { // Did they change? If so, carry on. this._log.info("Suggesting retry."); return true; // Try again. } // If not, reupload them and continue the current sync. @@ -385,47 +345,40 @@ WeaveSvc.prototype = { "Weave, since it will not work correctly."); } Svc.Obs.add("weave:service:setup-complete", this); Svc.Prefs.observe("engine.", this); SyncScheduler.init(); - if (!this.enabled) - this._log.info("Weave Sync disabled"); - - // Create Weave identities (for logging in, and for encryption) - let id = ID.get("WeaveID"); - if (!id) - id = ID.set("WeaveID", new Identity(PWDMGR_PASSWORD_REALM, this.username)); - Auth.defaultAuthenticator = new BasicAuthenticator(id); - - if (!ID.get("WeaveCryptoID")) - ID.set("WeaveCryptoID", - new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, this.username)); + if (!this.enabled) { + this._log.info("Firefox Sync disabled."); + } this._updateCachedURLs(); let status = this._checkSetup(); - if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) + if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) { Svc.Obs.notify("weave:engine:start-tracking"); + } // Send an event now that Weave service is ready. We don't do this // synchronously so that observers can import this module before // registering an observer. Utils.nextTick(function() { Status.ready = true; Svc.Obs.notify("weave:service:ready"); }); }, - _checkSetup: function WeaveSvc__checkSetup() { - if (!this.enabled) + _checkSetup: function _checkSetup() { + if (!this.enabled) { return Status.service = STATUS_DISABLED; + } return Status.checkSetup(); }, _migratePrefs: function _migratePrefs() { // Migrate old debugLog prefs. let logLevel = Svc.Prefs.get("log.appender.debugLog"); if (logLevel) { Svc.Prefs.set("log.appender.file.level", logLevel); @@ -502,20 +455,20 @@ WeaveSvc.prototype = { } else { // Remember that the engine status changed locally until the next sync. Svc.Prefs.set("engineStatusChanged." + engine, true); } }, // gets cluster from central LDAP server and returns it, or null on error _findCluster: function _findCluster() { - this._log.debug("Finding cluster for user " + this.username); + this._log.debug("Finding cluster for user " + this._identity.username); let fail; - let res = new Resource(this.userAPI + this.username + "/node/weave"); + let res = new Resource(this.userAPI + this._identity.username + "/node/weave"); try { let node = res.get(); switch (node.status) { case 400: Status.login = LOGIN_FAILED_LOGIN_REJECTED; fail = "Find cluster denied: " + ErrorHandler.errorStr(node); break; case 404: @@ -596,26 +549,26 @@ WeaveSvc.prototype = { verifyAndFetchSymmetricKeys: function verifyAndFetchSymmetricKeys(infoResponse) { this._log.debug("Fetching and verifying -- or generating -- symmetric keys."); // Don't allow empty/missing passphrase. // Furthermore, we assume that our sync key is already upgraded, // and fail if that assumption is invalidated. - let syncKey = this.syncKeyBundle; - if (!syncKey) { + let syncKeyBundle = this._identity.syncKeyBundle; + if (!syncKeyBundle) { this._log.error("No sync key: cannot fetch symmetric keys."); Status.login = LOGIN_FAILED_NO_PASSPHRASE; Status.sync = CREDENTIALS_CHANGED; // For want of a better option. return false; } // Not sure this validation is necessary now. - if (!Utils.isPassphrase(syncKey.keyStr)) { + if (!Utils.isPassphrase(this._identity.syncKey)) { this._log.warn("Sync key input is invalid: cannot fetch symmetric keys."); Status.login = LOGIN_FAILED_INVALID_PASSPHRASE; Status.sync = CREDENTIALS_CHANGED; return false; } try { if (!infoResponse) @@ -642,17 +595,17 @@ WeaveSvc.prototype = { let cryptoKeys; if (infoCollections && (CRYPTO_COLLECTION in infoCollections)) { try { cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO); let cryptoResp = cryptoKeys.fetch(this.cryptoKeysURL).response; if (cryptoResp.success) { - let keysChanged = this.handleFetchedKeys(syncKey, cryptoKeys); + let keysChanged = this.handleFetchedKeys(syncKeyBundle, cryptoKeys); return true; } else if (cryptoResp.status == 404) { // On failure, ask CollectionKeys to generate new keys and upload them. // Fall through to the behavior below. this._log.warn("Got 404 for crypto/keys, but 'crypto' in info/collections. Regenerating."); cryptoKeys = null; } @@ -710,29 +663,29 @@ WeaveSvc.prototype = { + Utils.exceptionStr(ex)); ErrorHandler.checkServerError(ex); return false; } }, verifyLogin: function verifyLogin() this._notify("verify-login", "", function() { - if (!this.username) { + if (!this._identity.username) { this._log.warn("No username in verifyLogin."); Status.login = LOGIN_FAILED_NO_USERNAME; return false; } // Unlock master password, or return. // Attaching auth credentials to a request requires access to // passwords, which means that Resource.get can throw MP-related // exceptions! // Try to fetch the passphrase first, while we still have control. try { - this.passphrase; + this._identity.syncKey; } catch (ex) { this._log.debug("Fetching passphrase threw " + ex + "; assuming master password locked."); Status.login = MASTER_PASSWORD_LOCKED; return false; } try { @@ -750,17 +703,17 @@ WeaveSvc.prototype = { switch (test.status) { case 200: // The user is authenticated. // We have no way of verifying the passphrase right now, // so wait until remoteSetup to do so. // Just make the most trivial checks. - if (!this.passphrase) { + if (!this._identity.syncKey) { this._log.warn("No passphrase in verifyLogin."); Status.login = LOGIN_FAILED_NO_PASSPHRASE; return false; } // Go ahead and do remote setup, so that we can determine // conclusively that our passphrase is correct. if (this._remoteSetup()) { @@ -770,36 +723,18 @@ WeaveSvc.prototype = { } this._log.warn("Remote setup failed."); // Remote setup must have failed. return false; case 401: this._log.warn("401: login failed."); - // Login failed. If the password contains non-ASCII characters, - // perhaps the server password is an old low-byte only one? - let id = ID.get('WeaveID'); - if (id.password != id.passwordUTF8) { - let res = new Resource(this.infoURL); - let auth = new BrokenBasicAuthenticator(id); - res.authenticator = auth; - test = res.get(); - if (test.status == 200) { - this._log.debug("Non-ASCII password detected. " - + "Changing to UTF-8 version."); - // Let's change the password on the server to the UTF8 version. - let url = this.userAPI + this.username + "/password"; - res = new Resource(url); - res.authenticator = auth; - res.post(id.passwordUTF8); - return this.verifyLogin(); - } - } - // Yes, we want to fall through to the 404 case. + // Fall through to the 404 case. + case 404: // Check that we're verifying with the correct cluster if (this._setCluster()) return this.verifyLogin(); // We must have the right cluster, but the server doesn't expect us Status.login = LOGIN_FAILED_LOGIN_REJECTED; return false; @@ -820,17 +755,17 @@ WeaveSvc.prototype = { } })(), generateNewSymmetricKeys: function WeaveSvc_generateNewSymmetricKeys() { this._log.info("Generating new keys WBO..."); let wbo = CollectionKeys.generateNewKeysWBO(); this._log.info("Encrypting new key bundle."); - wbo.encrypt(this.syncKeyBundle); + wbo.encrypt(this._identity.syncKeyBundle); this._log.info("Uploading..."); let uploadRes = wbo.upload(this.cryptoKeysURL); if (uploadRes.status != 200) { this._log.warn("Got status " + uploadRes.status + " uploading new keys. What to do? Throw!"); ErrorHandler.checkServerError(uploadRes); throw new Error("Unable to upload symmetric keys."); } @@ -866,74 +801,74 @@ WeaveSvc.prototype = { // Download and install them. let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO); let cryptoResp = cryptoKeys.fetch(this.cryptoKeysURL).response; if (cryptoResp.status != 200) { this._log.warn("Failed to download keys."); throw new Error("Symmetric key download failed."); } - let keysChanged = this.handleFetchedKeys(this.syncKeyBundle, + let keysChanged = this.handleFetchedKeys(this._identity.syncKeyBundle, cryptoKeys, true); if (keysChanged) { this._log.info("Downloaded keys differed, as expected."); } }, changePassword: function WeaveSvc_changePassword(newpass) this._notify("changepwd", "", function() { - let url = this.userAPI + this.username + "/password"; + let url = this.userAPI + this._identity.username + "/password"; try { let resp = new Resource(url).post(Utils.encodeUTF8(newpass)); if (resp.status != 200) { this._log.debug("Password change failed: " + resp); return false; } } catch(ex) { // Must have failed on some network issue this._log.debug("changePassword failed: " + Utils.exceptionStr(ex)); return false; } // Save the new password for requests and login manager. - this.password = newpass; + this._identity.basicPassword = newpass; this.persistLogin(); return true; })(), changePassphrase: function WeaveSvc_changePassphrase(newphrase) this._catch(this._notify("changepph", "", function() { /* Wipe. */ this.wipeServer(); this.logout(); /* Set this so UI is updated on next run. */ - this.passphrase = newphrase; + this._identity.syncKey = newphrase; this.persistLogin(); /* We need to re-encrypt everything, so reset. */ this.resetClient(); CollectionKeys.clear(); /* Login and sync. This also generates new keys. */ this.sync(); return true; }))(), - startOver: function() { + startOver: function startOver() { this._log.trace("Invoking Service.startOver."); Svc.Obs.notify("weave:engine:stop-tracking"); Status.resetSync(); // We want let UI consumers of the following notification know as soon as // possible, so let's fake for the CLIENT_NOT_CONFIGURED status for now // by emptying the passphrase (we still need the password). - Service.passphrase = ""; + this._identity.syncKey = null; Status.login = LOGIN_FAILED_NO_PASSPHRASE; this.logout(); Svc.Obs.notify("weave:service:start-over"); // Deletion doesn't make sense if we aren't set up yet! if (this.clusterURL != "") { // Clear client-specific data from the server, including disabled engines. for each (let engine in [Clients].concat(Engines.getAll())) { @@ -954,86 +889,87 @@ WeaveSvc.prototype = { Status.resetBackoff(); // Reset Weave prefs. this._ignorePrefObserver = true; Svc.Prefs.resetBranch(""); this._ignorePrefObserver = false; Svc.Prefs.set("lastversion", WEAVE_VERSION); - // Find weave logins and remove them. - this.password = ""; - Services.logins.findLogins({}, PWDMGR_HOST, "", "").map(function(login) { - Services.logins.removeLogin(login); - }); + + this._identity.deleteSyncCredentials(); }, persistLogin: function persistLogin() { - // Canceled master password prompt can prevent these from succeeding. try { - ID.get("WeaveID").persist(); - ID.get("WeaveCryptoID").persist(); + this._identity.persistCredentials(); + } catch (ex) { + this._log.info("Unable to persist credentials: " + ex); } - catch(ex) {} }, - login: function WeaveSvc_login(username, password, passphrase) + login: function login(username, password, passphrase) this._catch(this._lock("service.js: login", this._notify("login", "", function() { this._loggedIn = false; if (Services.io.offline) { Status.login = LOGIN_FAILED_NETWORK_ERROR; throw "Application is offline, login should not be called"; } let initialStatus = this._checkSetup(); - if (username) - this.username = username; - if (password) - this.password = password; - if (passphrase) - this.passphrase = passphrase; + if (username) { + this._identity.username = username; + } + if (password) { + this._identity.basicPassword = password; + } + if (passphrase) { + this._identity.syncKey = passphrase; + } - if (this._checkSetup() == CLIENT_NOT_CONFIGURED) - throw "aborting login, client not configured"; + if (this._checkSetup() == CLIENT_NOT_CONFIGURED) { + throw "Aborting login, client not configured."; + } // Calling login() with parameters when the client was // previously not configured means setup was completed. if (initialStatus == CLIENT_NOT_CONFIGURED - && (username || password || passphrase)) + && (username || password || passphrase)) { Svc.Obs.notify("weave:service:setup-complete"); + } - this._log.info("Logging in user " + this.username); + this._log.info("Logging in user " + this._identity.username); + this._updateCachedURLs(); if (!this.verifyLogin()) { // verifyLogin sets the failure states here. throw "Login failed: " + Status.login; } this._loggedIn = true; return true; })))(), - logout: function WeaveSvc_logout() { + logout: function logout() { // No need to do anything if we're already logged out. if (!this._loggedIn) return; this._log.info("Logging out"); this._loggedIn = false; Svc.Obs.notify("weave:service:logout:finish"); }, checkAccount: function checkAccount(account) { - let username = this._usernameFromAccount(account); + let username = this._identity.usernameFromAccount(account); let url = this.userAPI + username; let res = new Resource(url); - res.authenticator = new NoOpAuthenticator(); let data = ""; try { data = res.get(); if (data.status == 200) { if (data == "0") return "available"; else if (data == "1") @@ -1044,27 +980,26 @@ WeaveSvc.prototype = { catch(ex) {} // Convert to the error string, or default to generic on exception. return ErrorHandler.errorStr(data); }, createAccount: function createAccount(email, password, captchaChallenge, captchaResponse) { - let username = this._usernameFromAccount(email); + let username = this._identity.usernameFromAccount(email); let payload = JSON.stringify({ "password": Utils.encodeUTF8(password), "email": email, "captcha-challenge": captchaChallenge, "captcha-response": captchaResponse }); let url = this.userAPI + username; let res = new Resource(url); - res.authenticator = new NoOpAuthenticator(); // Hint to server to allow scripted user creation or otherwise // ignore captcha. if (Svc.Prefs.isSet("admin-secret")) res.setHeader("X-Weave-Secret", Svc.Prefs.get("admin-secret", "")); let error = "generic-server-error"; try { @@ -1101,17 +1036,17 @@ WeaveSvc.prototype = { // Delete the cached meta record... this._log.debug("Clearing cached meta record. metaModified is " + JSON.stringify(this.metaModified) + ", setting to " + JSON.stringify(infoResponse.obj.meta)); Records.del(this.metaURL); // ... fetch the current record from the server, and COPY THE FLAGS. - let newMeta = Records.get(this.metaURL); + let newMeta = Records.get(this.metaURL); if (!Records.response.success || !newMeta) { this._log.debug("No meta/global record on the server. Creating one."); newMeta = new WBORecord("meta", "global"); newMeta.payload.syncID = this.syncID; newMeta.payload.storageVersion = STORAGE_VERSION; newMeta.isNew = true; @@ -1504,60 +1439,51 @@ WeaveSvc.prototype = { // appropriate value. return false; } } return true; }, /** - * Silently fixes case issues. - */ - syncKeyNeedsUpgrade: function syncKeyNeedsUpgrade() { - let p = this.passphrase; - - // Check whether it's already a key that we generated. - if (Utils.isPassphrase(p)) { - this._log.info("Sync key is up-to-date: no need to upgrade."); - return false; - } - - return true; - }, - - /** * If we have a passphrase, rather than a 25-alphadigit sync key, * use the provided sync ID to bootstrap it using PBKDF2. * * Store the new 'passphrase' back into the identity manager. * * We can check this as often as we want, because once it's done the * check will no longer succeed. It only matters that it happens after * we decide to bump the server storage version. */ upgradeSyncKey: function upgradeSyncKey(syncID) { - let p = this.passphrase; + let p = this._identity.syncKey; + + if (!p) { + return false; + } // Check whether it's already a key that we generated. - if (!this.syncKeyNeedsUpgrade(p)) + if (Utils.isPassphrase(p)) { + this._log.info("Sync key is up-to-date: no need to upgrade."); return true; + } // Otherwise, let's upgrade it. // N.B., we persist the sync key without testing it first... let s = btoa(syncID); // It's what WeaveCrypto expects. *sigh* let k = Utils.derivePresentableKeyFromPassphrase(p, s, PBKDF2_KEY_BYTES); // Base 32. if (!k) { this._log.error("No key resulted from derivePresentableKeyFromPassphrase. Failing upgrade."); return false; } this._log.info("Upgrading sync key..."); - this.passphrase = k; + this._identity.syncKey = k; this._log.info("Saving upgraded sync key..."); this.persistLogin(); this._log.info("Done saving."); return true; }, _freshStart: function WeaveSvc__freshStart() { this._log.info("Fresh start. Resetting client and considering key upgrade.");
--- a/services/sync/modules/status.js +++ b/services/sync/modules/status.js @@ -37,20 +37,22 @@ const EXPORTED_SYMBOLS = ["Status"]; const Cc = Components.classes; const Ci = Components.interfaces; const Cr = Components.results; const Cu = Components.utils; Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/log4moz.js"); +Cu.import("resource://services-sync/identity.js"); Cu.import("resource://gre/modules/Services.jsm"); let Status = { _log: Log4Moz.repository.getLogger("Sync.Status"), + _authManager: Identity, ready: false, get service() { return this._service; }, set service(code) { this._log.debug("Status.service: " + this._service + " => " + code); @@ -60,20 +62,20 @@ let Status = { get login() { return this._login; }, set login(code) { this._log.debug("Status.login: " + this._login + " => " + code); this._login = code; - if (code == LOGIN_FAILED_NO_USERNAME || - code == LOGIN_FAILED_NO_PASSWORD || + if (code == LOGIN_FAILED_NO_USERNAME || + code == LOGIN_FAILED_NO_PASSWORD || code == LOGIN_FAILED_NO_PASSPHRASE) { - this.service = CLIENT_NOT_CONFIGURED; + this.service = CLIENT_NOT_CONFIGURED; } else if (code != LOGIN_SUCCEEDED) { this.service = LOGIN_FAILED; } else { this.service = STATUS_OK; } }, get sync() { @@ -104,56 +106,24 @@ let Status = { toString: function toString() { return "<Status" + ": login: " + Status.login + ", service: " + Status.service + ", sync: " + Status.sync + ">"; }, checkSetup: function checkSetup() { - // Check whether we have a username without importing The World(tm). - let prefs = Cc["@mozilla.org/preferences-service;1"] - .getService(Ci.nsIPrefService) - .getBranch(PREFS_BRANCH); - let username; - try { - username = prefs.getCharPref("username"); - } catch(ex) {} - - if (!username) { - Status.login = LOGIN_FAILED_NO_USERNAME; - return Status.service; + let result = this._authManager.currentAuthState; + if (result == STATUS_OK) { + Status.service = result; + return result; } - Cu.import("resource://services-sync/util.js"); - Cu.import("resource://services-sync/identity.js"); - Cu.import("resource://services-sync/record.js"); - if (!Utils.mpLocked()) { - let id = ID.get("WeaveID"); - if (!id) { - id = ID.set("WeaveID", new Identity(PWDMGR_PASSWORD_REALM, username)); - } - - if (!id.password) { - Status.login = LOGIN_FAILED_NO_PASSWORD; - return Status.service; - } - - id = ID.get("WeaveCryptoID"); - if (!id) { - id = ID.set("WeaveCryptoID", - new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, username)); - } - - if (!id.keyStr) { - Status.login = LOGIN_FAILED_NO_PASSPHRASE; - return Status.service; - } - } - return Status.service = STATUS_OK; + Status.login = result; + return Status.service; }, resetBackoff: function resetBackoff() { this.enforceBackoff = false; this.backoffInterval = 0; this.minimumNextSync = 0; },
--- a/services/sync/modules/util.js +++ b/services/sync/modules/util.js @@ -44,17 +44,16 @@ const Cr = Components.results; const Cu = Components.utils; Cu.import("resource://services-sync/async.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/ext/Observers.js"); Cu.import("resource://services-sync/ext/Preferences.js"); Cu.import("resource://services-sync/ext/StringBundle.js"); Cu.import("resource://services-sync/log4moz.js"); -Cu.import("resource://services-sync/status.js"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/PlacesUtils.jsm"); Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/FileUtils.jsm"); /* * Utility functions @@ -1228,22 +1227,24 @@ let Utils = { return false; }, /** * Return a value for a backoff interval. Maximum is eight hours, unless * Status.backoffInterval is higher. * */ - calculateBackoff: function calculateBackoff(attempts, base_interval) { + calculateBackoff: function calculateBackoff(attempts, baseInterval, + statusInterval) { let backoffInterval = attempts * - (Math.floor(Math.random() * base_interval) + - base_interval); - return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL), Status.backoffInterval); - } + (Math.floor(Math.random() * baseInterval) + + baseInterval); + return Math.max(Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL), + statusInterval); + }, }; XPCOMUtils.defineLazyGetter(Utils, "_utf8Converter", function() { let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] .createInstance(Ci.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; return converter; });
--- a/services/sync/tests/unit/head_helpers.js +++ b/services/sync/tests/unit/head_helpers.js @@ -1,12 +1,13 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ Cu.import("resource://services-sync/async.js"); +Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/engines.js"); let btoa; let atob; let provider = { getFile: function(prop, persistent) { @@ -255,24 +256,32 @@ FakeCryptoService.prototype = { return "some derived key string composed of bytes"; }, generateRandomBytes: function(aByteCount) { return "not-so-random-now-are-we-HA-HA-HA! >:)".slice(aByteCount); } }; - -function SyncTestingInfrastructure() { - Cu.import("resource://services-sync/identity.js"); +function setBasicCredentials(username, password, syncKey) { + let auth = Identity; + auth.username = username; + auth.basicPassword = password; + auth.syncKey = syncKey; +} - ID.set('WeaveID', - new Identity('Mozilla Services Encryption Passphrase', 'foo')); - ID.set('WeaveCryptoID', - new Identity('Mozilla Services Encryption Passphrase', 'foo')); +function SyncTestingInfrastructure(username, password, syncKey) { + Cu.import("resource://services-sync/service.js"); + + Identity.account = username || "foo"; + Identity.basicPassword = password || "password"; + Identity.syncKey = syncKey || "foo"; + + Service.serverURL = TEST_SERVER_URL; + Service.clusterURL = TEST_CLUSTER_URL; this.logStats = initTestLogging(); this.fakeFilesystem = new FakeFilesystemService({}); this.fakeGUIDService = new FakeGUIDService(); this.fakeCryptoService = new FakeCryptoService(); } /*
--- a/services/sync/tests/unit/head_http_server.js +++ b/services/sync/tests/unit/head_http_server.js @@ -59,18 +59,22 @@ function httpd_handler(statusCode, statu }; } function basic_auth_header(user, password) { return "Basic " + btoa(user + ":" + Utils.encodeUTF8(password)); } function basic_auth_matches(req, user, password) { - return req.hasHeader("Authorization") && - (req.getHeader("Authorization") == basic_auth_header(user, password)); + if (!req.hasHeader("Authorization")) { + return false; + } + + let expected = basic_auth_header(user, Utils.encodeUTF8(password)); + return req.getHeader("Authorization") == expected; } function httpd_basic_auth_handler(body, metadata, response) { if (basic_auth_matches(metadata, "guest", "guest")) { response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); } else { body = "This path exists and is protected - failed";
--- a/services/sync/tests/unit/test_addons_engine.js +++ b/services/sync/tests/unit/test_addons_engine.js @@ -149,21 +149,17 @@ add_test(function test_disabled_install_ Svc.Prefs.set("addons.ignoreRepositoryChecking", true); const USER = "foo"; const PASSWORD = "password"; const PASSPHRASE = "abcdeabcdeabcdeabcdeabcdea"; const ADDON_ID = "addon1@tests.mozilla.org"; - Service.username = USER; - Service.password = PASSWORD; - Service.passphrase = PASSPHRASE; - Service.serverURL = TEST_SERVER_URL; - Service.clusterURL = TEST_CLUSTER_URL; + new SyncTestingInfrastructure(USER, PASSWORD, PASSPHRASE); generateNewKeys(); let contents = { meta: {global: {engines: {addons: {version: engine.version, syncID: engine.syncID}}}}, crypto: {}, addons: {}
deleted file mode 100644 --- a/services/sync/tests/unit/test_auth_manager.js +++ /dev/null @@ -1,65 +0,0 @@ -Cu.import("resource://services-sync/identity.js"); -Cu.import("resource://services-sync/log4moz.js"); -Cu.import("resource://services-sync/resource.js"); -Cu.import("resource://services-sync/util.js"); - -let logger; - -function server_handler(metadata, response) { - let body, statusCode, status; - let guestHeader = basic_auth_header("guest", "guest"); - let johnHeader = basic_auth_header("johndoe", "moneyislike$£¥"); - - _("Guest header: " + guestHeader); - _("John header: " + johnHeader); - - switch (metadata.getHeader("Authorization")) { - case guestHeader: - body = "This path exists and is protected"; - statusCode = 200; - status = "OK"; - break; - case johnHeader: - body = "This path exists and is protected by a UTF8 password"; - statusCode = 200; - status = "OK"; - break; - default: - body = "This path exists and is protected - failed"; - statusCode = 401; - status = "Unauthorized"; - } - - response.setStatusLine(metadata.httpVersion, statusCode, status); - response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); - response.bodyOutputStream.write(body, body.length); -} - -function run_test() { - initTestLogging("Trace"); - - do_test_pending(); - let server = new nsHttpServer(); - server.registerPathHandler("/foo", server_handler); - server.registerPathHandler("/bar", server_handler); - server.start(8080); - - let guestIdentity = new Identity("secret", "guest", "guest"); - let johnIdentity = new Identity("secret2", "johndoe", "moneyislike$£¥") - let guestAuth = new BasicAuthenticator(guestIdentity); - let johnAuth = new BasicAuthenticator(johnIdentity); - Auth.defaultAuthenticator = guestAuth; - Auth.registerAuthenticator("bar$", johnAuth); - - try { - let content = new Resource("http://localhost:8080/foo").get(); - do_check_eq(content, "This path exists and is protected"); - do_check_eq(content.status, 200); - - content = new Resource("http://localhost:8080/bar").get(); - do_check_eq(content, "This path exists and is protected by a UTF8 password"); - do_check_eq(content.status, 200); - } finally { - server.stop(do_test_finished); - } -}
--- a/services/sync/tests/unit/test_bookmark_engine.js +++ b/services/sync/tests/unit/test_bookmark_engine.js @@ -87,20 +87,17 @@ function serverForFoo(engine) { meta: {global: {engines: {bookmarks: {version: engine.version, syncID: engine.syncID}}}}, bookmarks: {} }); } add_test(function test_processIncoming_error_orderChildren() { _("Ensure that _orderChildren() is called even when _processIncoming() throws an error."); - let syncTesting = new SyncTestingInfrastructure(); - Svc.Prefs.set("serverURL", TEST_SERVER_URL); - Svc.Prefs.set("clusterURL", TEST_CLUSTER_URL); - Svc.Prefs.set("username", "foo"); + new SyncTestingInfrastructure(); let engine = new BookmarksEngine(); let store = engine._store; let server = serverForFoo(engine); let collection = server.user("foo").collection("bookmarks"); try { @@ -160,20 +157,17 @@ add_test(function test_processIncoming_e Svc.Prefs.resetBranch(""); Records.clearCache(); server.stop(run_next_test); } }); add_test(function test_restorePromptsReupload() { _("Ensure that restoring from a backup will reupload all records."); - let syncTesting = new SyncTestingInfrastructure(); - Svc.Prefs.set("username", "foo"); - Service.serverURL = TEST_SERVER_URL; - Service.clusterURL = TEST_CLUSTER_URL; + new SyncTestingInfrastructure(); let engine = new BookmarksEngine(); let store = engine._store; let server = serverForFoo(engine); let collection = server.user("foo").collection("bookmarks"); Svc.Obs.notify("weave:engine:start-tracking"); // We skip usual startup... @@ -328,20 +322,17 @@ add_test(function test_mismatched_types( "description":null, "children": ["HCRq40Rnxhrd", "YeyWCV1RVsYw", "GCceVZMhvMbP", "sYi2hevdArlF", "vjbZlPlSyGY8", "UtjUhVyrpeG6", "rVq8WMG2wfZI", "Lx0tcy43ZKhZ", "oT74WwV8_j4P", "IztsItWVSo3-"], "parentid": "toolbar" }; - let syncTesting = new SyncTestingInfrastructure(); - Svc.Prefs.set("username", "foo"); - Service.serverURL = TEST_SERVER_URL; - Service.clusterURL = TEST_CLUSTER_URL; + new SyncTestingInfrastructure(); let engine = new BookmarksEngine(); let store = engine._store; let server = serverForFoo(engine); _("GUID: " + store.GUIDForId(6, true)); try { @@ -374,20 +365,18 @@ add_test(function test_mismatched_types( Records.clearCache(); server.stop(run_next_test); } }); add_test(function test_bookmark_guidMap_fail() { _("Ensure that failures building the GUID map cause early death."); - let syncTesting = new SyncTestingInfrastructure(); - Svc.Prefs.set("serverURL", TEST_SERVER_URL); - Svc.Prefs.set("clusterURL", TEST_CLUSTER_URL); - Svc.Prefs.set("username", "foo"); + new SyncTestingInfrastructure(); + let engine = new BookmarksEngine(); let store = engine._store; let store = engine._store; let server = serverForFoo(engine); let coll = server.user("foo").collection("bookmarks"); // Add one item to the server.
--- a/services/sync/tests/unit/test_bookmark_record.js +++ b/services/sync/tests/unit/test_bookmark_record.js @@ -1,26 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/engines/bookmarks.js"); -Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/util.js"); - + function prepareBookmarkItem(collection, id) { let b = new Bookmark(collection, id); b.cleartext.stuff = "my payload here"; return b; } function run_test() { - let keyBundle = ID.set("WeaveCryptoID", new SyncKeyBundle(null, "john@example.com")); - keyBundle.keyStr = "abcdeabcdeabcdeabcdeabcdea"; - + Identity.username = "john@example.com"; + Identity.syncKey = "abcdeabcdeabcdeabcdeabcdea"; generateNewKeys(); - + let keyBundle = Identity.syncKeyBundle; + let log = Log4Moz.repository.getLogger("Test"); Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender()); log.info("Creating a record"); let u = "http://localhost:8080/storage/bookmarks/foo"; let placesItem = new PlacesItem("bookmarks", "foo", "bookmark"); let bookmarkItem = prepareBookmarkItem("bookmarks", "foo");
--- a/services/sync/tests/unit/test_bookmark_smart_bookmarks.js +++ b/services/sync/tests/unit/test_bookmark_smart_bookmarks.js @@ -53,16 +53,18 @@ function serverForFoo(engine) { syncID: engine.syncID}}}}, bookmarks: {} }); } // Verify that Places smart bookmarks have their annotation uploaded and // handled locally. add_test(function test_annotation_uploaded() { + new SyncTestingInfrastructure(); + let startCount = smartBookmarkCount(); _("Start count is " + startCount); if (startCount > 0) { // This can happen in XULRunner. clearBookmarks(); _("Start count is now " + startCount); @@ -101,20 +103,16 @@ add_test(function test_annotation_upload _("Make sure the new record carries with it the annotation."); do_check_eq("MostVisited", record.queryId); _("Our count has increased since we started."); do_check_eq(smartBookmarkCount(), startCount + 1); _("Sync record to the server."); - Svc.Prefs.set("username", "foo"); - Service.serverURL = TEST_SERVER_URL; - Service.clusterURL = TEST_CLUSTER_URL; - let server = serverForFoo(engine); let collection = server.user("foo").collection("bookmarks"); try { engine.sync(); let wbos = collection.keys(function (id) { return ["menu", "toolbar", "mobile"].indexOf(id) == -1; }); @@ -173,34 +171,32 @@ add_test(function test_annotation_upload store.wipe(); Svc.Prefs.resetBranch(""); Records.clearCache(); server.stop(run_next_test); } }); add_test(function test_smart_bookmarks_duped() { + new SyncTestingInfrastructure(); + let parent = PlacesUtils.toolbarFolderId; let uri = Utils.makeURI("place:redirectsMode=" + Ci.nsINavHistoryQueryOptions.REDIRECTS_MODE_TARGET + "&sort=" + Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING + "&maxResults=10"); let title = "Most Visited"; let mostVisitedID = newSmartBookmark(parent, uri, -1, title, "MostVisited"); let mostVisitedGUID = store.GUIDForId(mostVisitedID); let record = store.createRecord(mostVisitedGUID); _("Prepare sync."); - Svc.Prefs.set("username", "foo"); - Service.serverURL = TEST_SERVER_URL; - Service.clusterURL = TEST_CLUSTER_URL; - let server = serverForFoo(engine); let collection = server.user("foo").collection("bookmarks"); try { engine._syncStartup(); _("Verify that mapDupe uses the anno, discovering a dupe regardless of URI."); do_check_eq(mostVisitedGUID, engine._mapDupe(record));
--- a/services/sync/tests/unit/test_clients_engine.js +++ b/services/sync/tests/unit/test_clients_engine.js @@ -1,8 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/engines/clients.js"); Cu.import("resource://services-sync/service.js"); @@ -43,17 +46,17 @@ add_test(function test_bad_hmac() { let coll = user.collection("clients"); let wbo = coll.wbo(id); return !wbo || !wbo.payload; } function uploadNewKeys() { generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Weave.Service.syncKeyBundle); + serverKeys.encrypt(Weave.Identity.syncKeyBundle); do_check_true(serverKeys.upload(Weave.Service.cryptoKeysURL).success); } try { let passphrase = "abcdeabcdeabcdeabcdeabcdea"; Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; Service.login("foo", "ilovejane", passphrase); @@ -72,17 +75,17 @@ add_test(function test_bad_hmac() { deletedItems = []; _("Change our keys and our client ID, reupload keys."); let oldLocalID = Clients.localID; // Preserve to test for deletion! Clients.localID = Utils.makeGUID(); Clients.resetClient(); generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Weave.Service.syncKeyBundle); + serverKeys.encrypt(Weave.Identity.syncKeyBundle); do_check_true(serverKeys.upload(Weave.Service.cryptoKeysURL).success); _("Sync."); Clients._sync(); _("Old record " + oldLocalID + " was deleted, new one uploaded."); check_clients_count(1); check_client_deleted(oldLocalID); @@ -159,20 +162,18 @@ add_test(function test_properties() { } finally { Svc.Prefs.resetBranch(""); run_next_test(); } }); add_test(function test_sync() { _("Ensure that Clients engine uploads a new client record once a week."); - - Svc.Prefs.set("serverURL", TEST_SERVER_URL); - Svc.Prefs.set("clusterURL", TEST_CLUSTER_URL); - Svc.Prefs.set("username", "foo"); + + new SyncTestingInfrastructure(); generateNewKeys(); let contents = { meta: {global: {engines: {clients: {version: Clients.version, syncID: Clients.syncID}}}}, clients: {}, crypto: {} }; @@ -400,19 +401,17 @@ add_test(function test_process_incoming_ // logout command causes processIncomingCommands to return explicit false. do_check_false(Clients.processIncomingCommands()); }); add_test(function test_command_sync() { _("Ensure that commands are synced across clients."); - Svc.Prefs.set("serverURL", TEST_SERVER_URL); - Svc.Prefs.set("clusterURL", TEST_CLUSTER_URL); - Svc.Prefs.set("username", "foo"); + new SyncTestingInfrastructure(); Clients._store.wipe(); generateNewKeys(); let contents = { meta: {global: {engines: {clients: {version: Clients.version, syncID: Clients.syncID}}}}, clients: {},
--- a/services/sync/tests/unit/test_clients_escape.js +++ b/services/sync/tests/unit/test_clients_escape.js @@ -1,24 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/engines/clients.js"); -Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); -Cu.import("resource://services-sync/identity.js"); function run_test() { _("Set up test fixtures."); - ID.set('WeaveID', new Identity('Some Identity', 'foo')); + + Identity.username = "john@example.com"; Svc.Prefs.set("clusterURL", "http://fakebase/"); let baseUri = "http://fakebase/1.1/foo/storage/"; let pubUri = baseUri + "keys/pubkey"; let privUri = baseUri + "keys/privkey"; - let keyBundle = ID.set("WeaveCryptoID", - new SyncKeyBundle(null, "john@example.com", "abcdeabcdeabcdeabcdeabcdea")); + Identity.syncKey = "abcdeabcdeabcdeabcdeabcdea"; + let keyBundle = Identity.syncKeyBundle; try { _("Test that serializing client records results in uploadable ascii"); Clients.localID = "ascii"; Clients.localName = "wéävê"; _("Make sure we have the expected record"); let record = Clients._createRecord("ascii");
--- a/services/sync/tests/unit/test_collections_recovery.js +++ b/services/sync/tests/unit/test_collections_recovery.js @@ -14,21 +14,19 @@ add_test(function test_missing_crypto_co response.setStatusLine(request.httpVersion, 200, "OK"); response.bodyOutputStream.write(body, body.length); } else { handler(request, response); } }; } + setBasicCredentials("johndoe", "ilovejane", "a-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "a-aaaaa-aaaaa-aaaaa-aaaaa-aaaaa"; let handlers = { "/1.1/johndoe/info/collections": maybe_empty(johnHelper.handler), "/1.1/johndoe/storage/crypto/keys": johnU("crypto", new ServerWBO("keys").handler()), "/1.1/johndoe/storage/meta/global": johnU("meta", new ServerWBO("global").handler()) }; let collections = ["clients", "bookmarks", "forms", "history", "passwords", "prefs", "tabs"];
--- a/services/sync/tests/unit/test_corrupt_keys.js +++ b/services/sync/tests/unit/test_corrupt_keys.js @@ -45,24 +45,21 @@ add_test(function test_locally_changed_k }, extData: { weaveLastUsed: 1 }}]}]}; delete Svc.Session; Svc.Session = { getBrowserState: function () JSON.stringify(myTabs) }; - - Weave.Service.username = "johndoe"; - Weave.Service.password = "ilovejane"; - Weave.Service.passphrase = passphrase; - - Weave.Service.serverURL = TEST_SERVER_URL; - Weave.Service.clusterURL = TEST_CLUSTER_URL; - + + setBasicCredentials("johndoe", "password", passphrase); + Service.serverURL = TEST_SERVER_URL; + Service.clusterURL = TEST_CLUSTER_URL; + Engines.register(HistoryEngine); Weave.Service._registerEngines(); function corrupt_local_keys() { CollectionKeys._default.keyPair = [Svc.Crypto.generateRandomKey(), Svc.Crypto.generateRandomKey()]; } @@ -74,17 +71,17 @@ add_test(function test_locally_changed_k "storageVersion": STORAGE_VERSION}; m.upload(Weave.Service.metaURL); _("New meta/global: " + JSON.stringify(johndoe.collection("meta").wbo("global"))); // Upload keys. generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Weave.Service.syncKeyBundle); + serverKeys.encrypt(Weave.Identity.syncKeyBundle); do_check_true(serverKeys.upload(Weave.Service.cryptoKeysURL).success); // Check that login works. do_check_true(Weave.Service.login("johndoe", "ilovejane", passphrase)); do_check_true(Weave.Service.isLoggedIn); // Sync should upload records. Weave.Service.sync();
--- a/services/sync/tests/unit/test_engine_abort.js +++ b/services/sync/tests/unit/test_engine_abort.js @@ -1,17 +1,14 @@ Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/util.js"); add_test(function test_processIncoming_abort() { _("An abort exception, raised in applyIncoming, will abort _processIncoming."); - let syncTesting = new SyncTestingInfrastructure(); - Svc.Prefs.set("serverURL", TEST_SERVER_URL); - Svc.Prefs.set("clusterURL", TEST_CLUSTER_URL); - Svc.Prefs.set("username", "foo"); + new SyncTestingInfrastructure(); generateNewKeys(); let engine = new RotaryEngine(); _("Create some server data."); let meta_global = Records.set(engine.metaURL, new WBORecord(engine.metaURL)); meta_global.payload.engines = {rotary: {version: engine.version, syncID: engine.syncID}};
--- a/services/sync/tests/unit/test_errorhandler.js +++ b/services/sync/tests/unit/test_errorhandler.js @@ -1,13 +1,14 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ Cu.import("resource://services-sync/engines/clients.js"); Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/policies.js"); Cu.import("resource://services-sync/status.js"); Svc.DefaultPrefs.set("registerEngines", ""); Cu.import("resource://services-sync/service.js"); const TEST_MAINTENANCE_URL = "http://localhost:8080/maintenance/"; const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true); @@ -47,18 +48,17 @@ function run_test() { Log4Moz.repository.getLogger("Sync.ErrorHandler").level = Log4Moz.Level.Trace; run_next_test(); } function generateCredentialsChangedFailure() { // Make sync fail due to changed credentials. We simply re-encrypt // the keys with a different Sync Key, without changing the local one. - let newSyncKeyBundle = new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, Service.username); - newSyncKeyBundle.keyStr = "23456234562345623456234562"; + let newSyncKeyBundle = new SyncKeyBundle("johndoe", "23456234562345623456234562"); let keys = CollectionKeys.asWBO(); keys.encrypt(newSyncKeyBundle); keys.upload(Service.cryptoKeysURL); } function service_unavailable(request, response) { let body = "Service Unavailable"; response.setStatusLine(request.httpVersion, 503, "Service Unavailable"); @@ -113,29 +113,27 @@ function sync_httpd_setup() { upd("crypto", (new ServerWBO("keys")).handler()), "/maintenance/1.1/broken.wipe/storage": service_unavailable, "/maintenance/1.1/broken.wipe/storage/clients": upd("clients", clientsColl.handler()), "/maintenance/1.1/broken.wipe/storage/catapult": service_unavailable }); } function setUp() { - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; return generateAndUploadKeys(); } function generateAndUploadKeys() { generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Service.syncKeyBundle); + serverKeys.encrypt(Identity.syncKeyBundle); return serverKeys.upload(Service.cryptoKeysURL).success; } function clean() { Service.startOver(); Status.resetSync(); Status.resetBackoff(); } @@ -167,17 +165,18 @@ add_test(function test_401_logout() { Service.startOver(); server.stop(run_next_test); }); } Svc.Obs.add("weave:service:login:error", onLoginError); } // Make sync fail due to login rejected. - Service.username = "janedoe"; + setBasicCredentials("janedoe", "irrelevant", "irrelevant"); + Service._updateCachedURLs(); _("Starting first sync."); Service.sync(); _("First sync done."); }); add_test(function test_credentials_changed_logout() { let server = sync_httpd_setup(); @@ -420,17 +419,17 @@ add_test(function test_shouldReportError server.stop(run_next_test); }); add_test(function test_login_syncAndReportErrors_non_network_error() { // Test non-network errors are reported // when calling syncAndReportErrors let server = sync_httpd_setup(); setUp(); - Service.password = ""; + Identity.basicPassword = null; Svc.Obs.add("weave:ui:login:error", function onSyncError() { Svc.Obs.remove("weave:ui:login:error", onSyncError); do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); clean(); server.stop(run_next_test); }); @@ -464,17 +463,17 @@ add_test(function test_sync_syncAndRepor ErrorHandler.syncAndReportErrors(); }); add_test(function test_login_syncAndReportErrors_prolonged_non_network_error() { // Test prolonged, non-network errors are // reported when calling syncAndReportErrors. let server = sync_httpd_setup(); setUp(); - Service.password = ""; + Identity.basicPassword = null; Svc.Obs.add("weave:ui:login:error", function onSyncError() { Svc.Obs.remove("weave:ui:login:error", onSyncError); do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); clean(); server.stop(run_next_test); }); @@ -505,19 +504,17 @@ add_test(function test_sync_syncAndRepor }); setLastSync(PROLONGED_ERROR_DURATION); ErrorHandler.syncAndReportErrors(); }); add_test(function test_login_syncAndReportErrors_network_error() { // Test network errors are reported when calling syncAndReportErrors. - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.wipe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; Svc.Obs.add("weave:ui:login:error", function onSyncError() { Svc.Obs.remove("weave:ui:login:error", onSyncError); do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); clean(); @@ -544,19 +541,17 @@ add_test(function test_sync_syncAndRepor setLastSync(NON_PROLONGED_ERROR_DURATION); ErrorHandler.syncAndReportErrors(); }); add_test(function test_login_syncAndReportErrors_prolonged_network_error() { // Test prolonged, network errors are reported // when calling syncAndReportErrors. - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; Svc.Obs.add("weave:ui:login:error", function onSyncError() { Svc.Obs.remove("weave:ui:login:error", onSyncError); do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); clean(); @@ -584,17 +579,17 @@ add_test(function test_sync_syncAndRepor setLastSync(PROLONGED_ERROR_DURATION); ErrorHandler.syncAndReportErrors(); }); add_test(function test_login_prolonged_non_network_error() { // Test prolonged, non-network errors are reported let server = sync_httpd_setup(); setUp(); - Service.password = ""; + Identity.basicPassword = null; Svc.Obs.add("weave:ui:login:error", function onSyncError() { Svc.Obs.remove("weave:ui:login:error", onSyncError); do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); clean(); server.stop(run_next_test); }); @@ -624,19 +619,17 @@ add_test(function test_sync_prolonged_no }); setLastSync(PROLONGED_ERROR_DURATION); Service.sync(); }); add_test(function test_login_prolonged_network_error() { // Test prolonged, network errors are reported - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; Svc.Obs.add("weave:ui:login:error", function onSyncError() { Svc.Obs.remove("weave:ui:login:error", onSyncError); do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE); clean(); @@ -663,17 +656,17 @@ add_test(function test_sync_prolonged_ne setLastSync(PROLONGED_ERROR_DURATION); Service.sync(); }); add_test(function test_login_non_network_error() { // Test non-network errors are reported let server = sync_httpd_setup(); setUp(); - Service.password = ""; + Identity.basicPassword = null; Svc.Obs.add("weave:ui:login:error", function onSyncError() { Svc.Obs.remove("weave:ui:login:error", onSyncError); do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); clean(); server.stop(run_next_test); }); @@ -702,19 +695,17 @@ add_test(function test_sync_non_network_ server.stop(run_next_test); }); setLastSync(NON_PROLONGED_ERROR_DURATION); Service.sync(); }); add_test(function test_login_network_error() { - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; // Test network errors are not reported. Svc.Obs.add("weave:ui:clear-error", function onClearError() { Svc.Obs.remove("weave:ui:clear-error", onClearError); do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); @@ -779,27 +770,28 @@ add_test(function test_sync_server_maint }); add_test(function test_info_collections_login_server_maintenance_error() { // Test info/collections server maintenance errors are not reported. let server = sync_httpd_setup(); setUp(); Service.username = "broken.info"; + setBasicCredentials("broken.info", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); function onUIUpdate() { - do_throw("Shouldn't get here!"); + do_throw("Shouldn't experience UI update!"); } Svc.Obs.add("weave:ui:login:error", onUIUpdate); do_check_false(Status.enforceBackoff); do_check_eq(Status.service, STATUS_OK); Svc.Obs.add("weave:ui:clear-error", function onLoginFinish() { Svc.Obs.remove("weave:ui:clear-error", onLoginFinish); @@ -818,17 +810,17 @@ add_test(function test_info_collections_ Service.sync(); }); add_test(function test_meta_global_login_server_maintenance_error() { // Test meta/global server maintenance errors are not reported. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.meta"; + setBasicCredentials("broken.meta", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -858,19 +850,20 @@ add_test(function test_meta_global_login Service.sync(); }); add_test(function test_crypto_keys_login_server_maintenance_error() { // Test crypto/keys server maintenance errors are not reported. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.keys"; + setBasicCredentials("broken.keys", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; + // Force re-download of keys CollectionKeys.clear(); let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -926,17 +919,17 @@ add_test(function test_sync_prolonged_se Service.sync(); }); add_test(function test_info_collections_login_prolonged_server_maintenance_error(){ // Test info/collections prolonged server maintenance errors are reported. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.info"; + setBasicCredentials("broken.info", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -959,17 +952,17 @@ add_test(function test_info_collections_ Service.sync(); }); add_test(function test_meta_global_login_prolonged_server_maintenance_error(){ // Test meta/global prolonged server maintenance errors are reported. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.meta"; + setBasicCredentials("broken.meta", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -992,17 +985,17 @@ add_test(function test_meta_global_login Service.sync(); }); add_test(function test_download_crypto_keys_login_prolonged_server_maintenance_error(){ // Test crypto/keys prolonged server maintenance errors are reported. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.keys"; + setBasicCredentials("broken.keys", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; // Force re-download of keys CollectionKeys.clear(); let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); @@ -1027,19 +1020,17 @@ add_test(function test_download_crypto_k Service.sync(); }); add_test(function test_upload_crypto_keys_login_prolonged_server_maintenance_error(){ // Test crypto/keys prolonged server maintenance errors are reported. let server = sync_httpd_setup(); // Start off with an empty account, do not upload a key. - Service.username = "broken.keys"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.keys", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1063,19 +1054,17 @@ add_test(function test_upload_crypto_key }); add_test(function test_wipeServer_login_prolonged_server_maintenance_error(){ // Test that we report prolonged server maintenance errors that occur whilst // wiping the server. let server = sync_httpd_setup(); // Start off with an empty account, do not upload a key. - Service.username = "broken.wipe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.wipe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1098,19 +1087,18 @@ add_test(function test_wipeServer_login_ Service.sync(); }); add_test(function test_wipeRemote_prolonged_server_maintenance_error(){ // Test that we report prolonged server maintenance errors that occur whilst // wiping all remote devices. let server = sync_httpd_setup(); - Service.username = "broken.wipe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + server.registerPathHandler("/1.1/broken.wipe/storage/catapult", service_unavailable); + setBasicCredentials("broken.wipe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; generateAndUploadKeys(); let engine = Engines.get("catapult"); engine.exception = null; engine.enabled = true; @@ -1168,17 +1156,17 @@ add_test(function test_sync_syncAndRepor }); add_test(function test_info_collections_login_syncAndReportErrors_server_maintenance_error() { // Test info/collections server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.info"; + setBasicCredentials("broken.info", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1202,17 +1190,17 @@ add_test(function test_info_collections_ }); add_test(function test_meta_global_login_syncAndReportErrors_server_maintenance_error() { // Test meta/global server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.meta"; + setBasicCredentials("broken.meta", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1236,17 +1224,17 @@ add_test(function test_meta_global_login }); add_test(function test_download_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { // Test crypto/keys server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.keys"; + setBasicCredentials("broken.keys", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; // Force re-download of keys CollectionKeys.clear(); let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); @@ -1272,19 +1260,17 @@ add_test(function test_download_crypto_k }); add_test(function test_upload_crypto_keys_login_syncAndReportErrors_server_maintenance_error() { // Test crypto/keys server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); // Start off with an empty account, do not upload a key. - Service.username = "broken.keys"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.keys", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1308,19 +1294,17 @@ add_test(function test_upload_crypto_key }); add_test(function test_wipeServer_login_syncAndReportErrors_server_maintenance_error() { // Test crypto/keys server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); // Start off with an empty account, do not upload a key. - Service.username = "broken.wipe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.wipe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1343,19 +1327,17 @@ add_test(function test_wipeServer_login_ ErrorHandler.syncAndReportErrors(); }); add_test(function test_wipeRemote_syncAndReportErrors_server_maintenance_error(){ // Test that we report prolonged server maintenance errors that occur whilst // wiping all remote devices. let server = sync_httpd_setup(); - Service.username = "broken.wipe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.wipe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; generateAndUploadKeys(); let engine = Engines.get("catapult"); engine.exception = null; engine.enabled = true; @@ -1413,17 +1395,17 @@ add_test(function test_sync_syncAndRepor }); add_test(function test_info_collections_login_syncAndReportErrors_prolonged_server_maintenance_error() { // Test info/collections server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.info"; + setBasicCredentials("broken.info", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1447,17 +1429,17 @@ add_test(function test_info_collections_ }); add_test(function test_meta_global_login_syncAndReportErrors_prolonged_server_maintenance_error() { // Test meta/global server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.meta"; + setBasicCredentials("broken.meta", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1481,17 +1463,17 @@ add_test(function test_meta_global_login }); add_test(function test_download_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() { // Test crypto/keys server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); setUp(); - Service.username = "broken.keys"; + setBasicCredentials("broken.keys", "irrelevant", "irrelevant"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; // Force re-download of keys CollectionKeys.clear(); let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); @@ -1517,19 +1499,17 @@ add_test(function test_download_crypto_k }); add_test(function test_upload_crypto_keys_login_syncAndReportErrors_prolonged_server_maintenance_error() { // Test crypto/keys server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); // Start off with an empty account, do not upload a key. - Service.username = "broken.keys"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.keys", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); @@ -1553,19 +1533,17 @@ add_test(function test_upload_crypto_key }); add_test(function test_wipeServer_login_syncAndReportErrors_prolonged_server_maintenance_error() { // Test crypto/keys server maintenance errors are reported // when calling syncAndReportErrors. let server = sync_httpd_setup(); // Start off with an empty account, do not upload a key. - Service.username = "broken.wipe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("broken.wipe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_MAINTENANCE_URL; Service.clusterURL = TEST_MAINTENANCE_URL; let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; });
--- a/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js +++ b/services/sync/tests/unit/test_errorhandler_sync_checkServerError.js @@ -43,28 +43,26 @@ function sync_httpd_setup() { "/1.1/johndoe/storage/meta/global": upd("meta", globalWBO.handler()), "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()) }; return httpd_setup(handlers); } function setUp() { - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "aabcdeabcdeabcdeabcdeabcde"; + setBasicCredentials("johndoe", "ilovejane", "aabcdeabcdeabcdeabcdeabcde"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; new FakeCryptoService(); } function generateAndUploadKeys() { generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Weave.Service.syncKeyBundle); + serverKeys.encrypt(Weave.Identity.syncKeyBundle); return serverKeys.upload("http://localhost:8080/1.1/johndoe/storage/crypto/keys").success; } add_test(function test_backoff500() { _("Test: HTTP 500 sets backoff status."); setUp(); let server = sync_httpd_setup();
--- a/services/sync/tests/unit/test_history_engine.js +++ b/services/sync/tests/unit/test_history_engine.js @@ -1,25 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/engines/history.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); -var syncTesting = new SyncTestingInfrastructure(); - add_test(function test_processIncoming_mobile_history_batched() { _("SyncEngine._processIncoming works on history engine."); let FAKE_DOWNLOAD_LIMIT = 100; - - Svc.Prefs.set("serverURL", TEST_SERVER_URL); - Svc.Prefs.set("clusterURL", TEST_CLUSTER_URL); - Svc.Prefs.set("username", "foo"); + + new SyncTestingInfrastructure(); + Svc.Prefs.set("client.type", "mobile"); PlacesUtils.history.removeAllPages(); Engines.register(HistoryEngine); // A collection that logs each GET let collection = new ServerCollection(); collection.get_log = []; collection._get = collection.get;
--- a/services/sync/tests/unit/test_hmac_error.js +++ b/services/sync/tests/unit/test_hmac_error.js @@ -13,21 +13,19 @@ let hmacErrorCount = 0; return hHE.call(Service); }; })(); function shared_setup() { hmacErrorCount = 0; // Do not instantiate SyncTestingInfrastructure; we need real crypto. + setBasicCredentials("foo", "foo", "aabcdeabcdeabcdeabcdeabcde"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; - Service.username = "foo"; - Service.password = "foo"; - Service.passphrase = "aabcdeabcdeabcdeabcdeabcde"; // Make sure RotaryEngine is the only one we sync. Engines._engines = {}; Engines.register(RotaryEngine); let engine = Engines.get("rotary"); engine.enabled = true; engine.lastSync = 123; // Needs to be non-zero so that tracker is queried. engine._store.items = {flying: "LNER Class A3 4472",
new file mode 100644 --- /dev/null +++ b/services/sync/tests/unit/test_identity_manager.js @@ -0,0 +1,226 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/identity.js"); + +function run_test() { + initTestLogging("Trace"); + Log4Moz.repository.getLogger("Sync.Identity").level = + Log4Moz.Level.Trace; + + run_next_test(); +} + +add_test(function test_username_from_account() { + _("Ensure usernameFromAccount works properly."); + + do_check_eq(Identity.usernameFromAccount(null), null); + do_check_eq(Identity.usernameFromAccount("user"), "user"); + do_check_eq(Identity.usernameFromAccount("User"), "user"); + do_check_eq(Identity.usernameFromAccount("john@doe.com"), + "7wohs32cngzuqt466q3ge7indszva4of"); + + run_next_test(); +}); + +add_test(function test_account_username() { + _("Ensure the account and username attributes work properly."); + + _("Verify initial state"); + do_check_eq(Svc.Prefs.get("account"), undefined); + do_check_eq(Svc.Prefs.get("username"), undefined); + do_check_eq(Identity.account, null); + do_check_eq(Identity.username, null); + + _("The 'username' attribute is normalized to lower case, updates preferences and identities."); + Identity.username = "TarZan"; + do_check_eq(Identity.username, "tarzan"); + do_check_eq(Svc.Prefs.get("username"), "tarzan"); + do_check_eq(Identity.username, "tarzan"); + + _("If not set, the 'account attribute' falls back to the username for backwards compatibility."); + do_check_eq(Identity.account, "tarzan"); + + _("Setting 'username' to a non-truthy value resets the pref."); + Identity.username = null; + do_check_eq(Identity.username, null); + do_check_eq(Identity.account, null); + const default_marker = {}; + do_check_eq(Svc.Prefs.get("username", default_marker), default_marker); + do_check_eq(Identity.username, null); + + _("The 'account' attribute will set the 'username' if it doesn't contain characters that aren't allowed in the username."); + Identity.account = "johndoe"; + do_check_eq(Identity.account, "johndoe"); + do_check_eq(Identity.username, "johndoe"); + do_check_eq(Svc.Prefs.get("username"), "johndoe"); + do_check_eq(Identity.username, "johndoe"); + + _("If 'account' contains disallowed characters such as @, 'username' will the base32 encoded SHA1 hash of 'account'"); + Identity.account = "John@Doe.com"; + do_check_eq(Identity.account, "john@doe.com"); + do_check_eq(Identity.username, "7wohs32cngzuqt466q3ge7indszva4of"); + + _("Setting 'account' to a non-truthy value resets the pref."); + Identity.account = null; + do_check_eq(Identity.account, null); + do_check_eq(Svc.Prefs.get("account", default_marker), default_marker); + do_check_eq(Identity.username, null); + do_check_eq(Svc.Prefs.get("username", default_marker), default_marker); + + Svc.Prefs.resetBranch(""); + run_next_test(); +}); + +add_test(function test_basic_password() { + _("Ensure basic password setting works as expected."); + + Identity.account = null; + do_check_eq(Identity.currentAuthState, LOGIN_FAILED_NO_USERNAME); + let thrown = false; + try { + Identity.basicPassword = "foobar"; + } catch (ex) { + thrown = true; + } + + do_check_true(thrown); + thrown = false; + + Identity.account = "johndoe"; + do_check_eq(Identity.currentAuthState, LOGIN_FAILED_NO_PASSWORD); + Identity.basicPassword = "password"; + do_check_eq(Identity.basicPassword, "password"); + do_check_eq(Identity.currentAuthState, LOGIN_FAILED_NO_PASSPHRASE); + do_check_true(Identity.hasBasicCredentials()); + + Identity.account = null; + + run_next_test(); +}); + +add_test(function test_basic_password_persistence() { + _("Ensure credentials are saved and restored to the login manager properly."); + + // Just in case. + Identity.account = null; + Identity.deleteSyncCredentials(); + + Identity.account = "janesmith"; + Identity.basicPassword = "ilovejohn"; + Identity.persistCredentials(); + + let im1 = new IdentityManager(); + do_check_eq(im1._basicPassword, null); + do_check_eq(im1.username, "janesmith"); + do_check_eq(im1.basicPassword, "ilovejohn"); + + let im2 = new IdentityManager(); + do_check_eq(im2._basicPassword, null); + + _("Now remove the password and ensure it is deleted from storage."); + Identity.basicPassword = null; + Identity.persistCredentials(); // This should nuke from storage. + do_check_eq(im2.basicPassword, null); + + _("Ensure that retrieving an unset but unpersisted removal returns null."); + Identity.account = "janesmith"; + Identity.basicPassword = "myotherpassword"; + Identity.persistCredentials(); + + Identity.basicPassword = null; + do_check_eq(Identity.basicPassword, null); + + // Reset for next test. + Identity.account = null; + Identity.persistCredentials(); + + run_next_test(); +}); + +add_test(function test_sync_key() { + _("Ensure Sync Key works as advertised."); + + _("Ensure setting a Sync Key before an account throws."); + let thrown = false; + try { + Identity.syncKey = "blahblah"; + } catch (ex) { + thrown = true; + } + do_check_true(thrown); + thrown = false; + + Identity.account = "johnsmith"; + Identity.basicPassword = "johnsmithpw"; + + do_check_eq(Identity.syncKey, null); + do_check_eq(Identity.syncKeyBundle, null); + + _("An invalid Sync Key is silently accepted for historical reasons."); + Identity.syncKey = "synckey"; + do_check_eq(Identity.syncKey, "synckey"); + + _("But the SyncKeyBundle should not be created from bad keys."); + do_check_eq(Identity.syncKeyBundle, null); + + let syncKey = Utils.generatePassphrase(); + Identity.syncKey = syncKey; + do_check_eq(Identity.syncKey, syncKey); + do_check_neq(Identity.syncKeyBundle, null); + + let im = new IdentityManager(); + im.account = "pseudojohn"; + do_check_eq(im.syncKey, null); + do_check_eq(im.syncKeyBundle, null); + + Identity.account = null; + + run_next_test(); +}); + +add_test(function test_sync_key_persistence() { + _("Ensure Sync Key persistence works as expected."); + + Identity.account = "pseudojohn"; + Identity.password = "supersecret"; + + let syncKey = Utils.generatePassphrase(); + Identity.syncKey = syncKey; + + Identity.persistCredentials(); + + let im = new IdentityManager(); + im.account = "pseudojohn"; + do_check_eq(im.syncKey, syncKey); + do_check_neq(im.syncKeyBundle, null); + + let kb1 = Identity.syncKeyBundle; + let kb2 = im.syncKeyBundle; + + do_check_eq(kb1.encryptionKeyB64, kb2.encryptionKeyB64); + do_check_eq(kb1.hmacKeyB64, kb2.hmacKeyB64); + + Identity.account = null; + Identity.persistCredentials(); + + let im2 = new IdentityManager(); + im2.account = "pseudojohn"; + do_check_eq(im2.syncKey, null); + + im2.account = null; + + _("Ensure deleted but not persisted value is retrieved."); + Identity.account = "someoneelse"; + Identity.syncKey = Utils.generatePassphrase(); + Identity.persistCredentials(); + Identity.syncKey = null; + do_check_eq(Identity.syncKey, null); + + // Clean up. + Identity.account = null; + Identity.persistCredentials(); + + run_next_test(); +});
--- a/services/sync/tests/unit/test_interval_triggers.js +++ b/services/sync/tests/unit/test_interval_triggers.js @@ -27,25 +27,23 @@ function sync_httpd_setup() { "/1.1/johndoe/info/collections": collectionsHelper.handler, "/1.1/johndoe/storage/crypto/keys": upd("crypto", (new ServerWBO("keys")).handler()), "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()) }); } function setUp() { - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Service.syncKeyBundle); + serverKeys.encrypt(Identity.syncKeyBundle); return serverKeys.upload(Service.cryptoKeysURL); } function run_test() { initTestLogging("Trace"); Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace; Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace;
--- a/services/sync/tests/unit/test_jpakeclient.js +++ b/services/sync/tests/unit/test_jpakeclient.js @@ -178,19 +178,17 @@ function run_test() { Svc.Prefs.resetBranch(""); }); // Ensure PSM is initialized. Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports); // Simulate Sync setup with credentials in place. We want to make // sure the J-PAKE requests don't include those data. - let id = new Identity(PWDMGR_PASSWORD_REALM, "johndoe"); - id.password = "ilovejane"; - ID.set("WeaveID", id); + setBasicCredentials("johndoe", "ilovejane"); server = httpd_setup({"/new_channel": server_new_channel, "/report": server_report}); initTestLogging("Trace"); Log4Moz.repository.getLogger("Sync.JPAKEClient").level = Log4Moz.Level.Trace; Log4Moz.repository.getLogger("Sync.RESTRequest").level = Log4Moz.Level.Trace; run_next_test();
--- a/services/sync/tests/unit/test_keys.js +++ b/services/sync/tests/unit/test_keys.js @@ -1,260 +1,327 @@ -var btoa; +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ -Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/constants.js"); -btoa = Cu.import("resource://services-sync/util.js").btoa; +Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/keys.js"); function sha256HMAC(message, key) { let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key); return Utils.digestBytes(message, h); } -function test_time_keyFromString(iterations) { - let k; - let o; - let b = new BulkKeyBundle(); - let d = Utils.decodeKeyBase32("ababcdefabcdefabcdefabcdef"); - b.generateRandom(); - - _("Running " + iterations + " iterations of hmacKeyObject + sha256HMAC."); - for (let i = 0; i < iterations; ++i) { - let k = b.hmacKeyObject; - o = sha256HMAC(d, k); - } - do_check_true(!!o); - _("Done."); -} - -function test_repeated_hmac() { - let testKey = "ababcdefabcdefabcdefabcdef"; - let k = Utils.makeHMACKey("foo"); - let one = sha256HMAC(Utils.decodeKeyBase32(testKey), k); - let two = sha256HMAC(Utils.decodeKeyBase32(testKey), k); - do_check_eq(one, two); -} - function do_check_array_eq(a1, a2) { do_check_eq(a1.length, a2.length); for (let i = 0; i < a1.length; ++i) { do_check_eq(a1[i], a2[i]); } } -function test_keymanager() { - let testKey = "ababcdefabcdefabcdefabcdef"; - - let username = "john@example.com"; - - // Decode the key here to mirror what generateEntry will do, - // but pass it encoded into the KeyBundle call below. - - let sha256inputE = "" + HMAC_INPUT + username + "\x01"; - let key = Utils.makeHMACKey(Utils.decodeKeyBase32(testKey)); - let encryptKey = sha256HMAC(sha256inputE, key); - - let sha256inputH = encryptKey + HMAC_INPUT + username + "\x02"; - let hmacKey = sha256HMAC(sha256inputH, key); - - // Encryption key is stored in base64 for WeaveCrypto convenience. - do_check_eq(btoa(encryptKey), new SyncKeyBundle(null, username, testKey).encryptionKey); - do_check_eq(hmacKey, new SyncKeyBundle(null, username, testKey).hmacKey); - - // Test with the same KeyBundle for both. - let obj = new SyncKeyBundle(null, username, testKey); - do_check_eq(hmacKey, obj.hmacKey); - do_check_eq(btoa(encryptKey), obj.encryptionKey); -} - function do_check_keypair_eq(a, b) { do_check_eq(2, a.length); do_check_eq(2, b.length); do_check_eq(a[0], b[0]); do_check_eq(a[1], b[1]); } -function test_collections_manager() { +function test_time_keyFromString(iterations) { + let k; + let o; + let b = new BulkKeyBundle("dummy"); + let d = Utils.decodeKeyBase32("ababcdefabcdefabcdefabcdef"); + b.generateRandom(); + + _("Running " + iterations + " iterations of hmacKeyObject + sha256HMAC."); + for (let i = 0; i < iterations; ++i) { + let k = b.hmacKeyObject; + o = sha256HMAC(d, k); + } + do_check_true(!!o); + _("Done."); +} + +add_test(function test_set_invalid_values() { + _("Ensure that setting invalid encryption and HMAC key values is caught."); + + let bundle = new BulkKeyBundle("foo"); + + let thrown = false; + try { + bundle.encryptionKey = null; + } catch (ex) { + thrown = true; + do_check_eq(ex.message.indexOf("Encryption key can only be set to"), 0); + } finally { + do_check_true(thrown); + thrown = false; + } + + try { + bundle.encryptionKey = ["trollololol"]; + } catch (ex) { + thrown = true; + do_check_eq(ex.message.indexOf("Encryption key can only be set to"), 0); + } finally { + do_check_true(thrown); + thrown = false; + } + + try { + bundle.hmacKey = Utils.generateRandomBytes(15); + } catch (ex) { + thrown = true; + do_check_eq(ex.message.indexOf("HMAC key must be at least 128"), 0); + } finally { + do_check_true(thrown); + thrown = false; + } + + try { + bundle.hmacKey = null; + } catch (ex) { + thrown = true; + do_check_eq(ex.message.indexOf("HMAC key can only be set to string"), 0); + } finally { + do_check_true(thrown); + thrown = false; + } + + try { + bundle.hmacKey = ["trollolol"]; + } catch (ex) { + thrown = true; + do_check_eq(ex.message.indexOf("HMAC key can only be set to"), 0); + } finally { + do_check_true(thrown); + thrown = false; + } + + try { + bundle.hmacKey = Utils.generateRandomBytes(15); + } catch (ex) { + thrown = true; + do_check_eq(ex.message.indexOf("HMAC key must be at least 128"), 0); + } finally { + do_check_true(thrown); + thrown = false; + } + + run_next_test(); +}); + +add_test(function test_repeated_hmac() { + let testKey = "ababcdefabcdefabcdefabcdef"; + let k = Utils.makeHMACKey("foo"); + let one = sha256HMAC(Utils.decodeKeyBase32(testKey), k); + let two = sha256HMAC(Utils.decodeKeyBase32(testKey), k); + do_check_eq(one, two); + + run_next_test(); +}); + +add_test(function test_sync_key_bundle_derivation() { + _("Ensure derivation from known values works."); + + // The known values in this test were originally verified against Firefox + // Home. + let bundle = new SyncKeyBundle("st3fan", "q7ynpwq7vsc9m34hankbyi3s3i"); + + // These should be compared to the results from Home, as they once were. + let e = "14b8c09fa84e92729ee695160af6e0385f8f6215a25d14906e1747bdaa2de426"; + let h = "370e3566245d79fe602a3adb5137e42439cd2a571235197e0469d7d541b07875"; + + let realE = Utils.bytesAsHex(bundle.encryptionKey); + let realH = Utils.bytesAsHex(bundle.hmacKey); + + _("Real E: " + realE); + _("Real H: " + realH); + do_check_eq(realH, h); + do_check_eq(realE, e); + + run_next_test(); +}); + +add_test(function test_keymanager() { + let testKey = "ababcdefabcdefabcdefabcdef"; + let username = "john@example.com"; + + // Decode the key here to mirror what generateEntry will do, + // but pass it encoded into the KeyBundle call below. + + let sha256inputE = "" + HMAC_INPUT + username + "\x01"; + let key = Utils.makeHMACKey(Utils.decodeKeyBase32(testKey)); + let encryptKey = sha256HMAC(sha256inputE, key); + + let sha256inputH = encryptKey + HMAC_INPUT + username + "\x02"; + let hmacKey = sha256HMAC(sha256inputH, key); + + // Encryption key is stored in base64 for WeaveCrypto convenience. + do_check_eq(encryptKey, new SyncKeyBundle(username, testKey).encryptionKey); + do_check_eq(hmacKey, new SyncKeyBundle(username, testKey).hmacKey); + + // Test with the same KeyBundle for both. + let obj = new SyncKeyBundle(username, testKey); + do_check_eq(hmacKey, obj.hmacKey); + do_check_eq(encryptKey, obj.encryptionKey); + + run_next_test(); +}); + +add_test(function test_collections_manager() { let log = Log4Moz.repository.getLogger("Test"); Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender()); - - let keyBundle = ID.set("WeaveCryptoID", - new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, "john@example.com", "a-bbbbb-ccccc-ddddd-eeeee-fffff")); - + + Identity.account = "john@example.com"; + Identity.syncKey = "a-bbbbb-ccccc-ddddd-eeeee-fffff"; + + let keyBundle = Identity.syncKeyBundle; + /* * Build a test version of storage/crypto/keys. * Encrypt it with the sync key. * Pass it into the CollectionKeyManager. */ - + log.info("Building storage keys..."); let storage_keys = new CryptoWrapper("crypto", "keys"); let default_key64 = Svc.Crypto.generateRandomKey(); let default_hmac64 = Svc.Crypto.generateRandomKey(); let bookmarks_key64 = Svc.Crypto.generateRandomKey(); let bookmarks_hmac64 = Svc.Crypto.generateRandomKey(); - + storage_keys.cleartext = { "default": [default_key64, default_hmac64], "collections": {"bookmarks": [bookmarks_key64, bookmarks_hmac64]}, }; storage_keys.modified = Date.now()/1000; storage_keys.id = "keys"; - + log.info("Encrypting storage keys..."); - + // Use passphrase (sync key) itself to encrypt the key bundle. storage_keys.encrypt(keyBundle); - + // Sanity checking. do_check_true(null == storage_keys.cleartext); do_check_true(null != storage_keys.ciphertext); - + log.info("Updating CollectionKeys."); - + // updateContents decrypts the object, releasing the payload for us to use. // Returns true, because the default key has changed. do_check_true(CollectionKeys.updateContents(keyBundle, storage_keys)); let payload = storage_keys.cleartext; - + _("CK: " + JSON.stringify(CollectionKeys._collections)); - + // Test that the CollectionKeyManager returns a similar WBO. let wbo = CollectionKeys.asWBO("crypto", "keys"); - + _("WBO: " + JSON.stringify(wbo)); _("WBO cleartext: " + JSON.stringify(wbo.cleartext)); - + // Check the individual contents. do_check_eq(wbo.collection, "crypto"); do_check_eq(wbo.id, "keys"); do_check_eq(undefined, wbo.modified); do_check_eq(CollectionKeys.lastModified, storage_keys.modified); do_check_true(!!wbo.cleartext.default); do_check_keypair_eq(payload.default, wbo.cleartext.default); do_check_keypair_eq(payload.collections.bookmarks, wbo.cleartext.collections.bookmarks); - + do_check_true('bookmarks' in CollectionKeys._collections); do_check_false('tabs' in CollectionKeys._collections); - + _("Updating contents twice with the same data doesn't proceed."); storage_keys.encrypt(keyBundle); do_check_false(CollectionKeys.updateContents(keyBundle, storage_keys)); - + /* * Test that we get the right keys out when we ask for * a collection's tokens. */ - let b1 = new BulkKeyBundle(null, "bookmarks"); - b1.keyPair = [bookmarks_key64, bookmarks_hmac64]; + let b1 = new BulkKeyBundle("bookmarks"); + b1.keyPairB64 = [bookmarks_key64, bookmarks_hmac64]; let b2 = CollectionKeys.keyForCollection("bookmarks"); do_check_keypair_eq(b1.keyPair, b2.keyPair); - + // Check key equality. do_check_true(b1.equals(b2)); do_check_true(b2.equals(b1)); - - b1 = new BulkKeyBundle(null, "[default]"); - b1.keyPair = [default_key64, default_hmac64]; - + + b1 = new BulkKeyBundle("[default]"); + b1.keyPairB64 = [default_key64, default_hmac64]; + do_check_false(b1.equals(b2)); do_check_false(b2.equals(b1)); - + b2 = CollectionKeys.keyForCollection(null); do_check_keypair_eq(b1.keyPair, b2.keyPair); - + /* * Checking for update times. */ let info_collections = {}; do_check_true(CollectionKeys.updateNeeded(info_collections)); info_collections["crypto"] = 5000; do_check_false(CollectionKeys.updateNeeded(info_collections)); info_collections["crypto"] = 1 + (Date.now()/1000); // Add one in case computers are fast! do_check_true(CollectionKeys.updateNeeded(info_collections)); - + CollectionKeys.lastModified = null; do_check_true(CollectionKeys.updateNeeded({})); - + /* * Check _compareKeyBundleCollections. */ function newBundle(name) { - let r = new BulkKeyBundle(null, name); + let r = new BulkKeyBundle(name); r.generateRandom(); return r; } let k1 = newBundle("k1"); let k2 = newBundle("k2"); let k3 = newBundle("k3"); let k4 = newBundle("k4"); let k5 = newBundle("k5"); let coll1 = {"foo": k1, "bar": k2}; let coll2 = {"foo": k1, "bar": k2}; let coll3 = {"foo": k1, "bar": k3}; let coll4 = {"foo": k4}; let coll5 = {"baz": k5, "bar": k2}; let coll6 = {}; - + let d1 = CollectionKeys._compareKeyBundleCollections(coll1, coll2); // [] let d2 = CollectionKeys._compareKeyBundleCollections(coll1, coll3); // ["bar"] let d3 = CollectionKeys._compareKeyBundleCollections(coll3, coll2); // ["bar"] let d4 = CollectionKeys._compareKeyBundleCollections(coll1, coll4); // ["bar", "foo"] let d5 = CollectionKeys._compareKeyBundleCollections(coll5, coll2); // ["baz", "foo"] let d6 = CollectionKeys._compareKeyBundleCollections(coll6, coll1); // ["bar", "foo"] let d7 = CollectionKeys._compareKeyBundleCollections(coll5, coll5); // [] let d8 = CollectionKeys._compareKeyBundleCollections(coll6, coll6); // [] - + do_check_true(d1.same); do_check_false(d2.same); do_check_false(d3.same); do_check_false(d4.same); do_check_false(d5.same); do_check_false(d6.same); do_check_true(d7.same); do_check_true(d8.same); - + do_check_array_eq(d1.changed, []); do_check_array_eq(d2.changed, ["bar"]); do_check_array_eq(d3.changed, ["bar"]); do_check_array_eq(d4.changed, ["bar", "foo"]); do_check_array_eq(d5.changed, ["baz", "foo"]); do_check_array_eq(d6.changed, ["bar", "foo"]); -} -// Make sure that KeyBundles work when persisted through Identity. -function test_key_persistence() { - _("Testing key persistence."); - - // Create our sync key bundle and persist it. - let k = new SyncKeyBundle(null, null, "abcdeabcdeabcdeabcdeabcdea"); - k.username = "john@example.com"; - ID.set("WeaveCryptoID", k); - let id = ID.get("WeaveCryptoID"); - do_check_eq(k, id); - id.persist(); - - // Now erase any memory of it. - ID.del("WeaveCryptoID"); - k = id = null; - - // Now recreate via the persisted value. - id = new SyncKeyBundle(); - id.username = "john@example.com"; - - // The password should have been fetched from storage... - do_check_eq(id.password, "abcdeabcdeabcdeabcdeabcdea"); - - // ... and we should be able to grab these by derivation. - do_check_true(!!id.hmacKeyObject); - do_check_true(!!id.hmacKey); - do_check_true(!!id.encryptionKey); -} + run_next_test(); +}); function run_test() { - test_keymanager(); - test_collections_manager(); - test_key_persistence(); - test_repeated_hmac(); - // Only do 1,000 to avoid a 5-second pause in test runs. test_time_keyFromString(1000); + + run_next_test(); }
--- a/services/sync/tests/unit/test_load_modules.js +++ b/services/sync/tests/unit/test_load_modules.js @@ -10,16 +10,17 @@ const modules = [ "engines/passwords.js", "engines/prefs.js", "engines/tabs.js", "engines.js", "ext/Observers.js", "ext/Preferences.js", "identity.js", "jpakeclient.js", + "keys.js", "log4moz.js", "main.js", "notifications.js", "policies.js", "record.js", "resource.js", "rest.js", "service.js",
--- a/services/sync/tests/unit/test_node_reassignment.js +++ b/services/sync/tests/unit/test_node_reassignment.js @@ -68,19 +68,17 @@ function installNodeHandler(server, next Utils.nextTick(next); } let nodePath = "/user/1.0/johndoe/node/weave"; server.server.registerPathHandler(nodePath, handleNodeRequest); _("Registered node handler at " + nodePath); } function prepareServer() { - Service.username = "johndoe"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; - Service.password = "ilovejane"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; do_check_eq(Service.userAPI, "http://localhost:8080/user/1.0/"); let server = new SyncServer(); server.registerUser("johndoe"); server.start(); return server;
--- a/services/sync/tests/unit/test_records_crypto.js +++ b/services/sync/tests/unit/test_records_crypto.js @@ -1,13 +1,17 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/identity.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/record.js"); Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/log4moz.js"); -Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); let cryptoWrap; function crypted_resource_handler(metadata, response) { let obj = {id: "resource", modified: cryptoWrap.modified, payload: JSON.stringify(cryptoWrap.payload)}; @@ -21,35 +25,33 @@ function prepareCryptoWrap(collection, i w.id = id; return w; } function run_test() { let server; do_test_pending(); - let keyBundle = ID.set("WeaveCryptoID", new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, "john@example.com")); - keyBundle.keyStr = "a-abcde-abcde-abcde-abcde-abcde"; + Identity.username = "john@example.com"; + Identity.syncKey = "a-abcde-abcde-abcde-abcde-abcde"; + let keyBundle = Identity.syncKeyBundle; try { let log = Log4Moz.repository.getLogger("Test"); Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender()); log.info("Setting up server and authenticator"); server = httpd_setup({"/steam/resource": crypted_resource_handler}); - let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest")); - Auth.defaultAuthenticator = auth; - log.info("Creating a record"); let cryptoUri = "http://localhost:8080/crypto/steam"; cryptoWrap = prepareCryptoWrap("steam", "resource"); - + log.info("cryptoWrap: " + cryptoWrap.toString()); log.info("Encrypting a record"); cryptoWrap.encrypt(keyBundle); log.info("Ciphertext is " + cryptoWrap.ciphertext); do_check_true(cryptoWrap.ciphertext != null); @@ -102,52 +104,48 @@ function run_test() { cryptoWrap.decrypt(keyBundle); } catch(ex) { error = ex; } do_check_eq(error.substr(0, 42), "Record SHA256 HMAC mismatch: should be foo"); // Checking per-collection keys and default key handling. - + generateNewKeys(); let bu = "http://localhost:8080/storage/bookmarks/foo"; let bookmarkItem = prepareCryptoWrap("bookmarks", "foo"); bookmarkItem.encrypt(); log.info("Ciphertext is " + bookmarkItem.ciphertext); do_check_true(bookmarkItem.ciphertext != null); log.info("Decrypting the record explicitly with the default key."); do_check_eq(bookmarkItem.decrypt(CollectionKeys._default).stuff, "my payload here"); - + // Per-collection keys. // Generate a key for "bookmarks". generateNewKeys(["bookmarks"]); bookmarkItem = prepareCryptoWrap("bookmarks", "foo"); do_check_eq(bookmarkItem.collection, "bookmarks"); - + // Encrypt. This'll use the "bookmarks" encryption key, because we have a // special key for it. The same key will need to be used for decryption. bookmarkItem.encrypt(); do_check_true(bookmarkItem.ciphertext != null); - - _("Default key is " + CollectionKeys._default.keyStr); - _("Bookmarks key is " + CollectionKeys.keyForCollection("bookmarks").keyStr); - _("Bookmarks key is " + CollectionKeys._collections["bookmarks"].keyStr); - + // Attempt to use the default key, because this is a collision that could // conceivably occur in the real world. Decryption will error, because // it's not the bookmarks key. let err; try { bookmarkItem.decrypt(CollectionKeys._default); } catch (ex) { err = ex; } do_check_eq("Record SHA256 HMAC mismatch", err.substr(0, 27)); - + // Explicitly check that it's using the bookmarks key. // This should succeed. do_check_eq(bookmarkItem.decrypt(CollectionKeys.keyForCollection("bookmarks")).stuff, "my payload here"); log.info("Done!"); } finally {
deleted file mode 100644 --- a/services/sync/tests/unit/test_records_crypto_generateEntry.js +++ /dev/null @@ -1,26 +0,0 @@ -let atob = Cu.import("resource://services-sync/util.js").atob; -Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/record.js"); - -/** - * Testing the SHA256-HMAC key derivation process against test vectors - * verified with the Firefox Home implementation. - */ -function run_test() { - - // Test the production of keys from a sync key. - let bundle = new SyncKeyBundle(PWDMGR_PASSPHRASE_REALM, "st3fan", "q7ynpwq7vsc9m34hankbyi3s3i"); - - // These should be compared to the results from Home, as they once were. - let e = "14b8c09fa84e92729ee695160af6e0385f8f6215a25d14906e1747bdaa2de426"; - let h = "370e3566245d79fe602a3adb5137e42439cd2a571235197e0469d7d541b07875"; - - // The encryption key is stored as base64 for handing off to WeaveCrypto. - let realE = Utils.bytesAsHex(atob(bundle.encryptionKey)); - let realH = Utils.bytesAsHex(bundle.hmacKey); - - _("Real E: " + realE); - _("Real H: " + realH); - do_check_eq(realH, h); - do_check_eq(realE, e); -}
--- a/services/sync/tests/unit/test_resource.js +++ b/services/sync/tests/unit/test_resource.js @@ -1,8 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/ext/Observers.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/util.js"); let logger; @@ -141,16 +144,18 @@ function server_headers(metadata, respon headers[header] = metadata.getHeader(header); } let body = JSON.stringify(headers); response.setStatusLine(metadata.httpVersion, 200, "OK"); response.bodyOutputStream.write(body, body.length); } function run_test() { + initTestLogging("Trace"); + do_test_pending(); logger = Log4Moz.repository.getLogger('Test'); Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender()); let server = httpd_setup({ "/open": server_open, "/protected": server_protected, @@ -222,40 +227,28 @@ function run_test() { do_check_eq(debugMessages.length, 1); do_check_eq(debugMessages[0], "Parse fail: Response body starts: \"\"This path exists\"\"."); logger.debug = dbg; _("Test that the BasicAuthenticator doesn't screw up header case."); let res1 = new Resource("http://localhost:8080/foo"); res1.setHeader("Authorization", "Basic foobar"); - res1.authenticator = new NoOpAuthenticator(); - do_check_eq(res1._headers["authorization"], "Basic foobar"); do_check_eq(res1.headers["authorization"], "Basic foobar"); - let id = new Identity("secret", "guest", "guest"); - res1.authenticator = new BasicAuthenticator(id); - - // In other words... it correctly overwrites our downcased version - // when accessed through .headers. - do_check_eq(res1._headers["authorization"], "Basic foobar"); - do_check_eq(res1.headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); - do_check_eq(res1._headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); - do_check_true(!res1._headers["Authorization"]); - do_check_true(!res1.headers["Authorization"]); _("GET a password protected resource (test that it'll fail w/o pass, no throw)"); let res2 = new Resource("http://localhost:8080/protected"); content = res2.get(); do_check_eq(content, "This path exists and is protected - failed"); do_check_eq(content.status, 401); do_check_false(content.success); _("GET a password protected resource"); - let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest")); let res3 = new Resource("http://localhost:8080/protected"); + let auth = Identity.getBasicResourceAuthenticator("guest", "guest"); res3.authenticator = auth; do_check_eq(res3.authenticator, auth); content = res3.get(); do_check_eq(content, "This path exists and is protected"); do_check_eq(content.status, 200); do_check_true(content.success); _("GET a non-existent resource (test that it'll fail, but not throw)");
--- a/services/sync/tests/unit/test_resource_async.js +++ b/services/sync/tests/unit/test_resource_async.js @@ -1,8 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/ext/Observers.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/log4moz.js"); Cu.import("resource://services-sync/resource.js"); Cu.import("resource://services-sync/util.js"); const RES_UPLOAD_URL = "http://localhost:8080/upload"; const RES_HEADERS_URL = "http://localhost:8080/headers"; @@ -256,29 +259,18 @@ add_test(function test_get() { run_next_test(); }); }); add_test(function test_basicauth() { _("Test that the BasicAuthenticator doesn't screw up header case."); let res1 = new AsyncResource("http://localhost:8080/foo"); res1.setHeader("Authorization", "Basic foobar"); - res1.authenticator = new NoOpAuthenticator(); do_check_eq(res1._headers["authorization"], "Basic foobar"); do_check_eq(res1.headers["authorization"], "Basic foobar"); - let id = new Identity("secret", "guest", "guest"); - res1.authenticator = new BasicAuthenticator(id); - - // In other words... it correctly overwrites our downcased version - // when accessed through .headers. - do_check_eq(res1._headers["authorization"], "Basic foobar"); - do_check_eq(res1.headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); - do_check_eq(res1._headers["authorization"], "Basic Z3Vlc3Q6Z3Vlc3Q="); - do_check_true(!res1._headers["Authorization"]); - do_check_true(!res1.headers["Authorization"]); run_next_test(); }); add_test(function test_get_protected_fail() { _("GET a password protected resource (test that it'll fail w/o pass, no throw)"); let res2 = new AsyncResource("http://localhost:8080/protected"); res2.get(function (error, content) { @@ -287,17 +279,17 @@ add_test(function test_get_protected_fai do_check_eq(content.status, 401); do_check_false(content.success); run_next_test(); }); }); add_test(function test_get_protected_success() { _("GET a password protected resource"); - let auth = new BasicAuthenticator(new Identity("secret", "guest", "guest")); + let auth = Identity.getBasicResourceAuthenticator("guest", "guest"); let res3 = new AsyncResource("http://localhost:8080/protected"); res3.authenticator = auth; do_check_eq(res3.authenticator, auth); res3.get(function (error, content) { do_check_eq(error, null); do_check_eq(content, "This path exists and is protected"); do_check_eq(content.status, 200); do_check_true(content.success);
--- a/services/sync/tests/unit/test_resource_ua.js +++ b/services/sync/tests/unit/test_resource_ua.js @@ -20,20 +20,19 @@ function test_resource_user_agent() { } do_test_pending(); let server = httpd_setup({ "/1.1/johndoe/info/collections": uaHandler(collectionsHelper.handler), "/1.1/johndoe/storage/meta/global": uaHandler(meta_global.handler()), }); + setBasicCredentials("johndoe", "ilovejane"); Weave.Service.serverURL = TEST_SERVER_URL; Weave.Service.clusterURL = TEST_CLUSTER_URL; - Weave.Service.username = "johndoe"; - Weave.Service.password = "ilovejane"; let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version + " FxSync/" + WEAVE_VERSION + "." + Services.appinfo.appBuildID; function test_fetchInfo(next) { _("Testing _fetchInfo."); Weave.Service._fetchInfo();
--- a/services/sync/tests/unit/test_score_triggers.js +++ b/services/sync/tests/unit/test_score_triggers.js @@ -38,22 +38,17 @@ function sync_httpd_setup() { let cl = new ServerCollection(); handlers["/1.1/johndoe/storage/clients"] = upd("clients", cl.handler()); return httpd_setup(handlers); } function setUp() { - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "sekrit"; - Service.serverURL = TEST_SERVER_URL; - Service.clusterURL = TEST_CLUSTER_URL; - new FakeCryptoService(); + new SyncTestingInfrastructure("johndoe", "ilovejane", "sekrit"); } function run_test() { initTestLogging("Trace"); Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace; run_next_test();
--- a/services/sync/tests/unit/test_sendcredentials_controller.js +++ b/services/sync/tests/unit/test_sendcredentials_controller.js @@ -2,19 +2,17 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ Cu.import("resource://services-sync/policies.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); function run_test() { - Service.account = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = Utils.generatePassphrase(); + setBasicCredentials("johndoe", "ilovejane", Utils.generatePassphrase()); Service.serverURL = "http://weave.server/"; initTestLogging("Trace"); Log4Moz.repository.getLogger("Sync.SendCredentialsController").level = Log4Moz.Level.Trace; Log4Moz.repository.getLogger("Sync.SyncScheduler").level = Log4Moz.Level.Trace; run_next_test(); } @@ -26,19 +24,19 @@ function make_sendCredentials_test(topic let jpakeclient = { sendAndComplete: function sendAndComplete(data) { // Verify that the controller unregisters itself as an observer // when the exchange is complete by faking another notification. do_check_false(sendAndCompleteCalled); sendAndCompleteCalled = true; // Verify it sends the correct data. - do_check_eq(data.account, Service.account); - do_check_eq(data.password, Service.password); - do_check_eq(data.synckey, Service.passphrase); + do_check_eq(data.account, Identity.account); + do_check_eq(data.password, Identity.basicPassword); + do_check_eq(data.synckey, Identity.syncKey); do_check_eq(data.serverURL, Service.serverURL); this.controller.onComplete(); // Verify it schedules a sync for the expected interval. let expectedInterval = SyncScheduler.activeInterval; do_check_true(SyncScheduler.nextSync - Date.now() <= expectedInterval); // Signal the end of another sync. We shouldn't be registered anymore,
--- a/services/sync/tests/unit/test_service_attributes.js +++ b/services/sync/tests/unit/test_service_attributes.js @@ -1,80 +1,29 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/util.js"); -function test_identities() { - _("Account related Service properties correspond to preference settings and update other object properties upon being set."); - - try { - _("Verify initial state"); - do_check_eq(Svc.Prefs.get("account"), undefined); - do_check_eq(Svc.Prefs.get("username"), undefined); - do_check_eq(ID.get("WeaveID").username, ""); - do_check_eq(ID.get("WeaveCryptoID").username, ""); - - _("The 'username' attribute is normalized to lower case, updates preferences and identities."); - Service.username = "TarZan"; - do_check_eq(Service.username, "tarzan"); - do_check_eq(Svc.Prefs.get("username"), "tarzan"); - do_check_eq(ID.get("WeaveID").username, "tarzan"); - do_check_eq(ID.get("WeaveCryptoID").username, "tarzan"); - - _("If not set, the 'account attribute' falls back to the username for backwards compatibility."); - do_check_eq(Service.account, "tarzan"); - - _("Setting 'username' to a non-truthy value resets the pref."); - Service.username = null; - do_check_eq(Service.username, ""); - do_check_eq(Service.account, ""); - const default_marker = {}; - do_check_eq(Svc.Prefs.get("username", default_marker), default_marker); - do_check_eq(ID.get("WeaveID").username, null); - do_check_eq(ID.get("WeaveCryptoID").username, null); - - _("The 'account' attribute will set the 'username' if it doesn't contain characters that aren't allowed in the username."); - Service.account = "johndoe"; - do_check_eq(Service.account, "johndoe"); - do_check_eq(Service.username, "johndoe"); - do_check_eq(Svc.Prefs.get("username"), "johndoe"); - do_check_eq(ID.get("WeaveID").username, "johndoe"); - do_check_eq(ID.get("WeaveCryptoID").username, "johndoe"); - - _("If 'account' contains disallowed characters such as @, 'username' will the base32 encoded SHA1 hash of 'account'"); - Service.account = "John@Doe.com"; - do_check_eq(Service.account, "john@doe.com"); - do_check_eq(Service.username, "7wohs32cngzuqt466q3ge7indszva4of"); - - _("Setting 'account' to a non-truthy value resets the pref."); - Service.account = null; - do_check_eq(Service.account, ""); - do_check_eq(Svc.Prefs.get("account", default_marker), default_marker); - do_check_eq(Service.username, ""); - do_check_eq(Svc.Prefs.get("username", default_marker), default_marker); - - } finally { - Svc.Prefs.resetBranch(""); - } -} - function test_urls() { _("URL related Service properties corresopnd to preference settings."); try { do_check_true(!!Service.serverURL); // actual value may change do_check_eq(Service.clusterURL, ""); do_check_eq(Service.userBaseURL, undefined); do_check_eq(Service.infoURL, undefined); do_check_eq(Service.storageURL, undefined); do_check_eq(Service.metaURL, undefined); _("The 'clusterURL' attribute updates preferences and cached URLs."); - Service.username = "johndoe"; + Identity.username = "johndoe"; // Since we don't have a cluster URL yet, these will still not be defined. do_check_eq(Service.infoURL, undefined); do_check_eq(Service.userBaseURL, undefined); do_check_eq(Service.storageURL, undefined); do_check_eq(Service.metaURL, undefined); Service.serverURL = "http://weave.server/"; @@ -101,20 +50,19 @@ function test_urls() { Svc.Prefs.set("userURL", "http://weave.user.services/"); do_check_eq(Service.miscAPI, "http://weave.misc.services/1.0/"); do_check_eq(Service.userAPI, "http://weave.user.services/1.0/"); do_check_eq(Service.pwResetURL, "http://weave.server/weave-password-reset"); _("Empty/false value for 'username' resets preference."); - Service.username = ""; + Identity.username = ""; do_check_eq(Svc.Prefs.get("username"), undefined); - do_check_eq(ID.get("WeaveID").username, ""); - do_check_eq(ID.get("WeaveCryptoID").username, ""); + do_check_eq(Identity.username, null); _("The 'serverURL' attributes updates/resets preferences."); // Identical value doesn't do anything Service.serverURL = Service.serverURL; do_check_eq(Svc.Prefs.get("clusterURL"), "http://weave.cluster/"); Service.serverURL = "http://different.auth.node/"; do_check_eq(Svc.Prefs.get("serverURL"), "http://different.auth.node/"); @@ -159,13 +107,12 @@ function test_locked() { // Locking again will return false do_check_eq(Service.lock(), false); Service.unlock(); do_check_eq(Service.locked, false); } function run_test() { - test_identities(); test_urls(); test_syncID(); test_locked(); }
--- a/services/sync/tests/unit/test_service_changePassword.js +++ b/services/sync/tests/unit/test_service_changePassword.js @@ -1,8 +1,12 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/main.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/log4moz.js"); function run_test() { initTestLogging("Trace"); @@ -23,53 +27,52 @@ add_test(function test_change_password() response.setStatusLine(request.httpVersion, statusCode, status); response.bodyOutputStream.write(body, body.length); }; } try { Weave.Service.serverURL = TEST_SERVER_URL; Weave.Service.clusterURL = TEST_CLUSTER_URL; - Weave.Service.username = "johndoe"; - Weave.Service.password = "ilovejane"; + setBasicCredentials("johndoe", "ilovejane"); _("changePassword() returns false for a network error, the password won't change."); let res = Weave.Service.changePassword("ILoveJane83"); do_check_false(res); - do_check_eq(Weave.Service.password, "ilovejane"); + do_check_eq(Identity.basicPassword, "ilovejane"); _("Let's fire up the server and actually change the password."); server = httpd_setup({ "/user/1.0/johndoe/password": send(200, "OK", ""), "/user/1.0/janedoe/password": send(401, "Unauthorized", "Forbidden!") }); res = Weave.Service.changePassword("ILoveJane83"); do_check_true(res); - do_check_eq(Weave.Service.password, "ILoveJane83"); + do_check_eq(Identity.basicPassword, "ILoveJane83"); do_check_eq(requestBody, "ILoveJane83"); _("Make sure the password has been persisted in the login manager."); let logins = Services.logins.findLogins({}, PWDMGR_HOST, null, PWDMGR_PASSWORD_REALM); + do_check_eq(logins.length, 1); do_check_eq(logins[0].password, "ILoveJane83"); _("A non-ASCII password is UTF-8 encoded."); const moneyPassword = "moneyislike$£¥"; res = Weave.Service.changePassword(moneyPassword); do_check_true(res); - do_check_eq(Weave.Service.password, moneyPassword); + do_check_eq(Identity.basicPassword, Utils.encodeUTF8(moneyPassword)); do_check_eq(requestBody, Utils.encodeUTF8(moneyPassword)); _("changePassword() returns false for a server error, the password won't change."); Services.logins.removeAllLogins(); - Weave.Service.username = "janedoe"; - Weave.Service.password = "ilovejohn"; + setBasicCredentials("janedoe", "ilovejohn"); res = Weave.Service.changePassword("ILoveJohn86"); do_check_false(res); - do_check_eq(Weave.Service.password, "ilovejohn"); + do_check_eq(Identity.basicPassword, "ilovejohn"); } finally { Weave.Svc.Prefs.resetBranch(""); Services.logins.removeAllLogins(); server.stop(run_next_test); } });
--- a/services/sync/tests/unit/test_service_cluster.js +++ b/services/sync/tests/unit/test_service_cluster.js @@ -11,17 +11,17 @@ function do_check_throws(func) { do_check_true(raised); } function test_findCluster() { _("Test Service._findCluster()"); let server; try { Service.serverURL = TEST_SERVER_URL; - Service.username = "johndoe"; + Identity.account = "johndoe"; _("_findCluster() throws on network errors (e.g. connection refused)."); do_check_throws(function() { Service._findCluster(); }); server = httpd_setup({ "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "http://weave.user.node/"), @@ -31,33 +31,33 @@ function test_findCluster() { "/user/1.0/joedoe/node/weave": httpd_handler(500, "Server Error", "Server Error") }); _("_findCluster() returns the user's cluster node"); let cluster = Service._findCluster(); do_check_eq(cluster, "http://weave.user.node/"); _("A 'null' response is converted to null."); - Service.username = "jimdoe"; + Identity.account = "jimdoe"; cluster = Service._findCluster(); do_check_eq(cluster, null); _("If a 404 is encountered, the server URL is taken as the cluster URL"); - Service.username = "janedoe"; + Identity.account = "janedoe"; cluster = Service._findCluster(); do_check_eq(cluster, Service.serverURL); _("A 400 response will throw an error."); - Service.username = "juliadoe"; + Identity.account = "juliadoe"; do_check_throws(function() { Service._findCluster(); }); _("Any other server response (e.g. 500) will throw an error."); - Service.username = "joedoe"; + Identity.account = "joedoe"; do_check_throws(function() { Service._findCluster(); }); } finally { Svc.Prefs.resetBranch(""); if (server) { server.stop(runNextTest); @@ -69,49 +69,49 @@ function test_findCluster() { function test_setCluster() { _("Test Service._setCluster()"); let server = httpd_setup({ "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "http://weave.user.node/"), "/user/1.0/jimdoe/node/weave": httpd_handler(200, "OK", "null") }); try { Service.serverURL = TEST_SERVER_URL; - Service.username = "johndoe"; + Identity.account = "johndoe"; _("Check initial state."); do_check_eq(Service.clusterURL, ""); _("Set the cluster URL."); do_check_true(Service._setCluster()); do_check_eq(Service.clusterURL, "http://weave.user.node/"); _("Setting it again won't make a difference if it's the same one."); do_check_false(Service._setCluster()); do_check_eq(Service.clusterURL, "http://weave.user.node/"); _("A 'null' response won't make a difference either."); - Service.username = "jimdoe"; + Identity.account = "jimdoe"; do_check_false(Service._setCluster()); - do_check_eq(Service.clusterURL, "http://weave.user.node/"); + do_check_eq(Service.clusterURL, "http://weave.user.node/"); } finally { Svc.Prefs.resetBranch(""); server.stop(runNextTest); } } function test_updateCluster() { _("Test Service._updateCluster()"); let server = httpd_setup({ "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "http://weave.user.node/"), "/user/1.0/janedoe/node/weave": httpd_handler(200, "OK", "http://weave.cluster.url/") }); try { Service.serverURL = TEST_SERVER_URL; - Service.username = "johndoe"; + Identity.account = "johndoe"; _("Check initial state."); do_check_eq(Service.clusterURL, ""); do_check_eq(Svc.Prefs.get("lastClusterUpdate"), null); _("Set the cluster URL."); let before = Date.now(); do_check_true(Service._updateCluster()); @@ -120,17 +120,17 @@ function test_updateCluster() { do_check_true(lastUpdate >= before); _("Trying to update the cluster URL within the backoff timeout won't do anything."); do_check_false(Service._updateCluster()); do_check_eq(Service.clusterURL, "http://weave.user.node/"); do_check_eq(parseFloat(Svc.Prefs.get("lastClusterUpdate")), lastUpdate); _("Time travel 30 mins into the past and the update will work."); - Service.username = "janedoe"; + Identity.account = "janedoe"; Svc.Prefs.set("lastClusterUpdate", (lastUpdate - 30*60*1000).toString()); before = Date.now(); do_check_true(Service._updateCluster()); do_check_eq(Service.clusterURL, "http://weave.cluster.url/"); lastUpdate = parseFloat(Svc.Prefs.get("lastClusterUpdate")); do_check_true(lastUpdate >= before);
--- a/services/sync/tests/unit/test_service_detect_upgrade.js +++ b/services/sync/tests/unit/test_service_detect_upgrade.js @@ -1,15 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/main.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/record.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/engines/tabs.js"); Cu.import("resource://services-sync/log4moz.js"); Engines.register(TabEngine); add_test(function v4_upgrade() { let passphrase = "abcdeabcdeabcdeabcdeabcdea"; @@ -109,52 +113,52 @@ add_test(function v4_upgrade() { do_check_true(Weave.Service.isLoggedIn); Weave.Service.sync(); do_check_true(Weave.Service.isLoggedIn); let serverDecrypted; let serverKeys; let serverResp; - + function retrieve_server_default() { serverKeys = serverResp = serverDecrypted = null; - + serverKeys = new CryptoWrapper("crypto", "keys"); serverResp = serverKeys.fetch(Weave.Service.cryptoKeysURL).response; do_check_true(serverResp.success); - - serverDecrypted = serverKeys.decrypt(Weave.Service.syncKeyBundle); + + serverDecrypted = serverKeys.decrypt(Weave.Identity.syncKeyBundle); _("Retrieved WBO: " + JSON.stringify(serverDecrypted)); _("serverKeys: " + JSON.stringify(serverKeys)); - + return serverDecrypted.default; } - + function retrieve_and_compare_default(should_succeed) { let serverDefault = retrieve_server_default(); - let localDefault = CollectionKeys.keyForCollection().keyPair; + let localDefault = CollectionKeys.keyForCollection().keyPairB64; _("Retrieved keyBundle: " + JSON.stringify(serverDefault)); _("Local keyBundle: " + JSON.stringify(localDefault)); if (should_succeed) do_check_eq(JSON.stringify(serverDefault), JSON.stringify(localDefault)); else do_check_neq(JSON.stringify(serverDefault), JSON.stringify(localDefault)); } - + // Uses the objects set above. function set_server_keys(pair) { serverDecrypted.default = pair; serverKeys.cleartext = serverDecrypted; - serverKeys.encrypt(Weave.Service.syncKeyBundle); + serverKeys.encrypt(Weave.Identity.syncKeyBundle); serverKeys.upload(Weave.Service.cryptoKeysURL); } - + _("Checking we have the latest keys."); retrieve_and_compare_default(true); _("Update keys on server."); set_server_keys(["KaaaaaaaaaaaHAtfmuRY0XEJ7LXfFuqvF7opFdBD/MY=", "aaaaaaaaaaaapxMO6TEWtLIOv9dj6kBAJdzhWDkkkis="]); _("Checking that we no longer have the latest keys."); @@ -227,74 +231,67 @@ add_test(function v5_upgrade() { }, extData: { weaveLastUsed: 1 }}]}]}; delete Svc.Session; Svc.Session = { getBrowserState: function () JSON.stringify(myTabs) }; - + Status.resetSync(); - - Weave.Service.username = "johndoe"; - Weave.Service.password = "ilovejane"; - Weave.Service.passphrase = passphrase; - + + setBasicCredentials("johndoe", "ilovejane", passphrase); Weave.Service.serverURL = TEST_SERVER_URL; Weave.Service.clusterURL = TEST_CLUSTER_URL; - - // + // Test an upgrade where the contents of the server would cause us to error // -- keys decrypted with a different sync key, for example. - // - _("Testing v4 -> v5 (or similar) upgrade."); function update_server_keys(syncKeyBundle, wboName, collWBO) { generateNewKeys(); serverKeys = CollectionKeys.asWBO("crypto", wboName); serverKeys.encrypt(syncKeyBundle); do_check_true(serverKeys.upload(Weave.Service.storageURL + collWBO).success); } - + _("Bumping version."); // Bump version on the server. let m = new WBORecord("meta", "global"); m.payload = {"syncID": "foooooooooooooooooooooooooo", "storageVersion": STORAGE_VERSION + 1}; m.upload(Weave.Service.metaURL); - + _("New meta/global: " + JSON.stringify(meta_global)); - + // Fill the keys with bad data. - let badKeys = new SyncKeyBundle(null, null, "aaaaaaaaaaaaaaaaaaaaaaaaaa"); - badKeys.generateEntry(); + let badKeys = new SyncKeyBundle("foobar", "aaaaaaaaaaaaaaaaaaaaaaaaaa"); update_server_keys(badKeys, "keys", "crypto/keys"); // v4 update_server_keys(badKeys, "bulk", "crypto/bulk"); // v5 - - // ... and get new ones. + + _("Generating new keys."); generateNewKeys(); - + // Now sync and see what happens. It should be a version fail, not a crypto // fail. - + _("Logging in."); try { Weave.Service.login("johndoe", "ilovejane", passphrase); } catch (e) { _("Exception: " + e); } _("Status: " + Status); do_check_false(Weave.Service.isLoggedIn); do_check_eq(VERSION_OUT_OF_DATE, Status.sync); // Clean up. Weave.Service.startOver(); - + } finally { Weave.Svc.Prefs.resetBranch(""); server.stop(run_next_test); } }); function run_test() { let logger = Log4Moz.repository.rootLogger;
--- a/services/sync/tests/unit/test_service_getStorageInfo.js +++ b/services/sync/tests/unit/test_service_getStorageInfo.js @@ -6,18 +6,17 @@ Cu.import("resource://services-sync/rest Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/util.js"); let collections = {steam: 65.11328, petrol: 82.488281, diesel: 2.25488281}; function run_test() { - Service.username = "johndoe"; - Service.password = "ilovejane"; + setBasicCredentials("johndoe", "ilovejane"); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; Log4Moz.repository.getLogger("Sync.Service").level = Log4Moz.Level.Trace; Log4Moz.repository.getLogger("Sync.StorageRequest").level = Log4Moz.Level.Trace; initTestLogging(); run_next_test(); @@ -27,18 +26,19 @@ add_test(function test_success() { let handler = httpd_handler(200, "OK", JSON.stringify(collections)); let server = httpd_setup({"/1.1/johndoe/info/collections": handler}); let request = Service.getStorageInfo("collections", function (error, info) { do_check_eq(error, null); do_check_true(Utils.deepEquals(info, collections)); // Ensure that the request is sent off with the right bits. - do_check_true(basic_auth_matches(handler.request, Service.username, - Service.password)); + do_check_true(basic_auth_matches(handler.request, + Identity.username, + Identity.basicPassword)); let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version + " FxSync/" + WEAVE_VERSION + "." + Services.appinfo.appBuildID + ".desktop"; do_check_eq(handler.request.getHeader("User-Agent"), expectedUA); server.stop(run_next_test); }); do_check_true(request instanceof RESTRequest);
--- a/services/sync/tests/unit/test_service_login.js +++ b/services/sync/tests/unit/test_service_login.js @@ -74,53 +74,49 @@ add_test(function test_login_logout() { _("Try logging in. It won't work because we're not configured yet."); Service.login(); do_check_eq(Status.service, CLIENT_NOT_CONFIGURED); do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME); do_check_false(Service.isLoggedIn); _("Try again with username and password set."); - Service.username = "johndoe"; - Service.password = "ilovejane"; + Identity.account = "johndoe"; + Identity.basicPassword = "ilovejane"; Service.login(); do_check_eq(Status.service, CLIENT_NOT_CONFIGURED); do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE); do_check_false(Service.isLoggedIn); _("Success if passphrase is set."); - Service.passphrase = "foo"; + Identity.syncKey = "foo"; Service.login(); do_check_eq(Status.service, STATUS_OK); do_check_eq(Status.login, LOGIN_SUCCEEDED); do_check_true(Service.isLoggedIn); _("We can also pass username, password and passphrase to login()."); Service.login("janedoe", "incorrectpassword", "bar"); - do_check_eq(Service.username, "janedoe"); - do_check_eq(Service.password, "incorrectpassword"); - do_check_eq(Service.passphrase, "bar"); + setBasicCredentials("janedoe", "incorrectpassword", "bar"); do_check_eq(Status.service, LOGIN_FAILED); do_check_eq(Status.login, LOGIN_FAILED_LOGIN_REJECTED); do_check_false(Service.isLoggedIn); _("Try again with correct password."); Service.login("janedoe", "ilovejohn"); do_check_eq(Status.service, STATUS_OK); do_check_eq(Status.login, LOGIN_SUCCEEDED); do_check_true(Service.isLoggedIn); _("Calling login() with parameters when the client is unconfigured sends notification."); let notified = false; Svc.Obs.add("weave:service:setup-complete", function() { notified = true; }); - Service.username = ""; - Service.password = ""; - Service.passphrase = ""; + setBasicCredentials(null, null, null); Service.login("janedoe", "ilovejohn", "bar"); do_check_true(notified); do_check_eq(Status.service, STATUS_OK); do_check_eq(Status.login, LOGIN_SUCCEEDED); do_check_true(Service.isLoggedIn); _("Logout."); Service.logout(); @@ -133,19 +129,17 @@ add_test(function test_login_logout() { } finally { Svc.Prefs.resetBranch(""); server.stop(run_next_test); } }); add_test(function test_login_on_sync() { let server = setup(); - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "bar"; + setBasicCredentials("johndoe", "ilovejane", "bar"); try { _("Sync calls login."); let oldLogin = Service.login; let loginCalled = false; Service.login = function() { loginCalled = true; Status.login = LOGIN_SUCCEEDED; @@ -197,20 +191,21 @@ add_test(function test_login_on_sync() { do_check_true(scheduleCalled); SyncScheduler.scheduleNextSync = scheduleNextSyncF; // TODO: need better tests around master password prompting. See Bug 620583. mpLocked = true; // Testing exception handling if master password dialog is canceled. - // Do this by stubbing out Service.passphrase. - let oldPP = Service.__lookupGetter__("passphrase"); - _("Old passphrase function is " + oldPP); - Service.__defineGetter__("passphrase", + // Do this by monkeypatching. + let oldGetter = Identity.__lookupGetter__("syncKey"); + let oldSetter = Identity.__lookupSetter__("syncKey"); + _("Old passphrase function is " + oldGetter); + Identity.__defineGetter__("syncKey", function() { throw "User canceled Master Password entry"; }); let oldClearSyncTriggers = SyncScheduler.clearSyncTriggers; let oldLockedSync = Service._lockedSync; let cSTCalled = false; @@ -228,16 +223,19 @@ add_test(function test_login_on_sync() { do_check_eq(Service._checkSync(), kSyncMasterPasswordLocked); _("Sync doesn't proceed and clears triggers if MP is still locked."); Service.sync(); do_check_true(cSTCalled); do_check_false(lockedSyncCalled); + Identity.__defineGetter__("syncKey", oldGetter); + Identity.__defineSetter__("syncKey", oldSetter); + // N.B., a bunch of methods are stubbed at this point. Be careful putting // new tests after this point! } finally { Svc.Prefs.resetBranch(""); server.stop(run_next_test); } });
--- a/services/sync/tests/unit/test_service_passwordUTF8.js +++ b/services/sync/tests/unit/test_service_passwordUTF8.js @@ -57,34 +57,33 @@ function run_test() { do_test_pending(); let server = httpd_setup({ "/1.1/johndoe/info/collections": login_handling(collectionsHelper.handler), "/1.1/johndoe/storage/meta/global": upd("meta", new ServerWBO("global").handler()), "/1.1/johndoe/storage/crypto/keys": upd("crypto", new ServerWBO("keys").handler()), "/user/1.0/johndoe/password": change_password }); - Service.username = "johndoe"; - Service.password = JAPANESE; - Service.passphrase = "cantentsveryrelevantabbbb"; + setBasicCredentials("johndoe", JAPANESE, "irrelevant"); Service.serverURL = TEST_SERVER_URL; try { _("Try to log in with the password."); server_password = "foobar"; do_check_false(Service.verifyLogin()); do_check_eq(server_password, "foobar"); - _("Make the server password the low byte version of our password. Login should work and have transparently changed the password to the UTF8 version."); + _("Make the server password the low byte version of our password."); server_password = LOWBYTES; - do_check_true(Service.verifyLogin()); - do_check_eq(server_password, Utils.encodeUTF8(JAPANESE)); + do_check_false(Service.verifyLogin()); + do_check_eq(server_password, LOWBYTES); _("Can't use a password that has the same low bytes as ours."); - Service.password = APPLES; + server_password = Utils.encodeUTF8(JAPANESE); + Identity.basicPassword = APPLES; do_check_false(Service.verifyLogin()); do_check_eq(server_password, Utils.encodeUTF8(JAPANESE)); } finally { server.stop(do_test_finished); Svc.Prefs.resetBranch(""); } }
--- a/services/sync/tests/unit/test_service_persistLogin.js +++ b/services/sync/tests/unit/test_service_persistLogin.js @@ -1,20 +1,18 @@ Cu.import("resource://services-sync/main.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/constants.js"); function run_test() { try { // Ensure we have a blank slate to start. Services.logins.removeAllLogins(); - - Weave.Service.username = "johndoe"; - Weave.Service.password = "ilovejane"; - Weave.Service.passphrase = "abbbbbcccccdddddeeeeefffff"; + + setBasicCredentials("johndoe", "ilovejane", "abbbbbcccccdddddeeeeefffff"); _("Confirm initial environment is empty."); let logins = Services.logins.findLogins({}, PWDMGR_HOST, null, PWDMGR_PASSWORD_REALM); do_check_eq(logins.length, 0); logins = Services.logins.findLogins({}, PWDMGR_HOST, null, PWDMGR_PASSPHRASE_REALM); do_check_eq(logins.length, 0);
--- a/services/sync/tests/unit/test_service_startOver.js +++ b/services/sync/tests/unit/test_service_startOver.js @@ -1,8 +1,11 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/engines.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/policies.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/util.js"); function BlaEngine() { @@ -22,19 +25,18 @@ Engines.register(BlaEngine); function run_test() { initTestLogging("Trace"); run_next_test(); } add_test(function test_resetLocalData() { // Set up. - Service.username = "foobar"; - Service.password = "blablabla"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("foobar", "blablabla", // Law Blog + "abcdeabcdeabcdeabcdeabcdea"); Status.enforceBackoff = true; Status.backoffInterval = 42; Status.minimumNextSync = 23; Service.persistLogin(); // Verify set up. do_check_eq(Status.checkSetup(), STATUS_OK); @@ -47,18 +49,18 @@ add_test(function test_resetLocalData() do_check_eq(Status.service, CLIENT_NOT_CONFIGURED); }); Service.startOver(); do_check_true(observerCalled); // Verify the site was nuked from orbit. do_check_eq(Svc.Prefs.get("username"), undefined); - do_check_eq(Service.password, ""); - do_check_eq(Service.passphrase, ""); + do_check_eq(Identity.basicPassword, null); + do_check_eq(Identity.syncKey, null); do_check_eq(Status.service, CLIENT_NOT_CONFIGURED); do_check_false(Status.enforceBackoff); do_check_eq(Status.backoffInterval, 0); do_check_eq(Status.minimumNextSync, 0); run_next_test(); });
--- a/services/sync/tests/unit/test_service_startup.js +++ b/services/sync/tests/unit/test_service_startup.js @@ -1,35 +1,36 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/ext/Observers.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/engines.js"); function run_test() { _("When imported, Service.onStartup is called"); + Svc.Prefs.set("registerEngines", "Tab,Bookmarks,Form,History"); + new SyncTestingInfrastructure(); + // Test fixtures - Svc.Prefs.set("registerEngines", "Tab,Bookmarks,Form,History"); - Svc.Prefs.set("username", "johndoe"); + Identity.username = "johndoe"; Cu.import("resource://services-sync/service.js"); _("Service is enabled."); do_check_eq(Service.enabled, true); _("Engines are registered."); let engines = Engines.getAll(); do_check_true(Utils.deepEquals([engine.name for each (engine in engines)], ['tabs', 'bookmarks', 'forms', 'history'])); - _("Identities are registered."); - do_check_eq(ID.get('WeaveID').username, "johndoe"); - do_check_eq(ID.get('WeaveCryptoID').username, "johndoe"); - _("Observers are notified of startup"); do_test_pending(); do_check_false(Status.ready); Observers.add("weave:service:ready", function (subject, data) { do_check_true(Status.ready); // Clean up. Svc.Prefs.resetBranch("");
--- a/services/sync/tests/unit/test_service_sync_401.js +++ b/services/sync/tests/unit/test_service_sync_401.js @@ -27,37 +27,33 @@ function run_test() { "/1.1/johndoe/storage/meta/global": upd("meta", new ServerWBO("global").handler()), "/1.1/johndoe/info/collections": login_handling(collectionsHelper.handler) }); const GLOBAL_SCORE = 42; try { _("Set up test fixtures."); - Weave.Service.serverURL = TEST_SERVER_URL; - Weave.Service.clusterURL = TEST_CLUSTER_URL; - Weave.Service.username = "johndoe"; - Weave.Service.password = "ilovejane"; - Weave.Service.passphrase = "foo"; + new SyncTestingInfrastructure("johndoe", "ilovejane", "foo"); SyncScheduler.globalScore = GLOBAL_SCORE; // Avoid daily ping Weave.Svc.Prefs.set("lastPing", Math.floor(Date.now() / 1000)); let threw = false; Weave.Svc.Obs.add("weave:service:sync:error", function (subject, data) { threw = true; }); _("Initial state: We're successfully logged in."); Weave.Service.login(); do_check_true(Weave.Service.isLoggedIn); do_check_eq(Weave.Status.login, Weave.LOGIN_SUCCEEDED); _("Simulate having changed the password somewhere else."); - Weave.Service.password = "ilovejosephine"; + Identity.basicPassword = "ilovejosephine"; _("Let's try to sync."); Weave.Service.sync(); _("Verify that sync() threw an exception."); do_check_true(threw); _("We're no longer logged in.");
--- a/services/sync/tests/unit/test_service_sync_remoteSetup.js +++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js @@ -1,27 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/main.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/constants.js"); -Cu.import("resource://services-sync/record.js"); +Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/log4moz.js"); function run_test() { let logger = Log4Moz.repository.rootLogger; Log4Moz.repository.rootLogger.addAppender(new Log4Moz.DumpAppender()); let guidSvc = new FakeGUIDService(); let clients = new ServerCollection(); let meta_global = new ServerWBO('global'); let collectionsHelper = track_collections_helper(); let upd = collectionsHelper.with_updated_collection; let collections = collectionsHelper.collections; - + function wasCalledHandler(wbo) { let handler = wbo.handler(); return function() { wbo.wasCalled = true; handler.apply(this, arguments); }; } @@ -58,39 +61,39 @@ function run_test() { "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)), "/1.1/johndoe/info/collections": collectionsHelper.handler }); try { _("Log in."); Weave.Service.serverURL = TEST_SERVER_URL; Weave.Service.clusterURL = TEST_CLUSTER_URL; - + _("Checking Status.sync with no credentials."); Weave.Service.verifyAndFetchSymmetricKeys(); do_check_eq(Status.sync, CREDENTIALS_CHANGED); - do_check_eq(Status.login, LOGIN_FAILED_INVALID_PASSPHRASE); + do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE); _("Log in with an old secret phrase, is upgraded to Sync Key."); Weave.Service.login("johndoe", "ilovejane", "my old secret phrase!!1!"); + _("End of login"); do_check_true(Weave.Service.isLoggedIn); - do_check_true(Utils.isPassphrase(Weave.Service.passphrase)); - do_check_true(Utils.isPassphrase(Weave.Service.syncKeyBundle.keyStr)); - let syncKey = Weave.Service.passphrase; + do_check_true(Utils.isPassphrase(Identity.syncKey)); + let syncKey = Identity.syncKey; Weave.Service.startOver(); Weave.Service.serverURL = TEST_SERVER_URL; Weave.Service.clusterURL = TEST_CLUSTER_URL; Weave.Service.login("johndoe", "ilovejane", syncKey); do_check_true(Weave.Service.isLoggedIn); _("Checking that remoteSetup returns true when credentials have changed."); Records.get(Weave.Service.metaURL).payload.syncID = "foobar"; do_check_true(Weave.Service._remoteSetup()); - + _("Do an initial sync."); let beforeSync = Date.now()/1000; Weave.Service.sync(); _("Checking that remoteSetup returns true."); do_check_true(Weave.Service._remoteSetup()); _("Verify that the meta record was uploaded."); @@ -115,51 +118,51 @@ function run_test() { let metaModified = meta_global.modified; Weave.Service.sync(); do_check_true(meta_global.wasCalled); do_check_eq(metaModified, meta_global.modified); _("Checking bad passphrases."); - let pp = Weave.Service.passphrase; - Weave.Service.passphrase = "notvalid"; + let pp = Identity.syncKey; + Identity.syncKey = "notvalid"; do_check_false(Weave.Service.verifyAndFetchSymmetricKeys()); do_check_eq(Status.sync, CREDENTIALS_CHANGED); do_check_eq(Status.login, LOGIN_FAILED_INVALID_PASSPHRASE); - Weave.Service.passphrase = pp; + Identity.syncKey = pp; do_check_true(Weave.Service.verifyAndFetchSymmetricKeys()); - + // changePassphrase wipes our keys, and they're regenerated on next sync. _("Checking changed passphrase."); let existingDefault = CollectionKeys.keyForCollection(); let existingKeysPayload = keysWBO.payload; let newPassphrase = "bbbbbabcdeabcdeabcdeabcdea"; Weave.Service.changePassphrase(newPassphrase); - + _("Local key cache is full, but different."); do_check_true(!!CollectionKeys._default); do_check_false(CollectionKeys._default.equals(existingDefault)); - + _("Server has new keys."); do_check_true(!!keysWBO.payload); do_check_true(!!keysWBO.modified); do_check_neq(keysWBO.payload, existingKeysPayload); // Try to screw up HMAC calculation. // Re-encrypt keys with a new random keybundle, and upload them to the // server, just as might happen with a second client. _("Attempting to screw up HMAC by re-encrypting keys."); let keys = CollectionKeys.asWBO(); - let b = new BulkKeyBundle("hmacerror", "hmacerror"); + let b = new BulkKeyBundle("hmacerror"); b.generateRandom(); collections.crypto = keys.modified = 100 + (Date.now()/1000); // Future modification time. keys.encrypt(b); keys.upload(Weave.Service.cryptoKeysURL); - + do_check_false(Weave.Service.verifyAndFetchSymmetricKeys()); do_check_eq(Status.login, LOGIN_FAILED_INVALID_PASSPHRASE); } finally { Weave.Svc.Prefs.resetBranch(""); server.stop(do_test_finished); } }
--- a/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js +++ b/services/sync/tests/unit/test_service_sync_updateEnabledEngines.js @@ -61,29 +61,23 @@ function sync_httpd_setup(handlers) { let cl = new ServerCollection(); handlers["/1.1/johndoe/storage/clients"] = upd("clients", cl.handler()); return httpd_setup(handlers); } function setUp() { - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; - Service.serverURL = TEST_SERVER_URL; - Service.clusterURL = TEST_CLUSTER_URL; - // So that we can poke at meta/global. - new FakeCryptoService(); - + new SyncTestingInfrastructure("johndoe", "ilovejane", + "abcdeabcdeabcdeabcdeabcdea"); // Ensure that the server has valid keys so that logging in will work and not // result in a server wipe, rendering many of these tests useless. generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Service.syncKeyBundle); + serverKeys.encrypt(Identity.syncKeyBundle); return serverKeys.upload(Service.cryptoKeysURL).success; } const PAYLOAD = 42; function run_test() { initTestLogging("Trace"); @@ -255,17 +249,17 @@ add_test(function test_enabledRemotely() }); setUp(); // We need to be very careful how we do this, so that we don't trigger a // fresh start! try { _("Upload some keys to avoid a fresh start."); let wbo = CollectionKeys.generateNewKeysWBO(); - wbo.encrypt(Service.syncKeyBundle); + wbo.encrypt(Identity.syncKeyBundle); do_check_eq(200, wbo.upload(Service.cryptoKeysURL).status); _("Engine is disabled."); do_check_false(engine.enabled); _("Sync."); Weave.Service.sync();
--- a/services/sync/tests/unit/test_service_verifyLogin.js +++ b/services/sync/tests/unit/test_service_verifyLogin.js @@ -53,35 +53,35 @@ function run_test() { _("Credentials won't check out because we're not configured yet."); Status.resetSync(); do_check_false(Service.verifyLogin()); do_check_eq(Status.service, CLIENT_NOT_CONFIGURED); do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME); _("Try again with username and password set."); Status.resetSync(); - Service.username = "johndoe"; - Service.password = "ilovejane"; + setBasicCredentials("johndoe", "ilovejane", null); do_check_false(Service.verifyLogin()); do_check_eq(Status.service, CLIENT_NOT_CONFIGURED); do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE); _("verifyLogin() has found out the user's cluster URL, though."); do_check_eq(Service.clusterURL, "http://localhost:8080/api/"); _("Success if passphrase is set."); Status.resetSync(); - Service.passphrase = "foo"; + Identity.syncKey = "foo"; do_check_true(Service.verifyLogin()); do_check_eq(Status.service, STATUS_OK); do_check_eq(Status.login, LOGIN_SUCCEEDED); _("If verifyLogin() encounters a server error, it flips on the backoff flag and notifies observers on a 503 with Retry-After."); Status.resetSync(); - Service.username = "janedoe"; + Identity.account = "janedoe"; + Service._updateCachedURLs(); do_check_false(Status.enforceBackoff); let backoffInterval; Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) { Svc.Obs.remove("weave:service:backoff:interval", observe); backoffInterval = subject; }); do_check_false(Service.verifyLogin()); do_check_true(Status.enforceBackoff);
--- a/services/sync/tests/unit/test_service_wipeServer.js +++ b/services/sync/tests/unit/test_service_wipeServer.js @@ -26,18 +26,18 @@ FakeCollection.prototype = { } }; function setUpTestFixtures() { let cryptoService = new FakeCryptoService(); Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; - Service.username = "johndoe"; - Service.passphrase = "aabcdeabcdeabcdeabcdeabcde"; + + setBasicCredentials("johndoe", null, "aabcdeabcdeabcdeabcdeabcde"); } function run_test() { initTestLogging("Trace"); run_next_test(); } @@ -50,16 +50,17 @@ add_test(function test_wipeServer_list_s let server = httpd_setup({ "/1.1/johndoe/storage/steam": steam_coll.handler(), "/1.1/johndoe/storage/diesel": diesel_coll.handler(), "/1.1/johndoe/storage/petrol": httpd_handler(404, "Not Found") }); try { setUpTestFixtures(); + new SyncTestingInfrastructure("johndoe", "irrelevant", "irrelevant"); _("Confirm initial environment."); do_check_false(steam_coll.deleted); do_check_false(diesel_coll.deleted); _("wipeServer() will happily ignore the non-existent collection and use the timestamp of the last DELETE that was successful."); let timestamp = Service.wipeServer(["steam", "diesel", "petrol"]); do_check_eq(timestamp, diesel_coll.timestamp); @@ -83,16 +84,17 @@ add_test(function test_wipeServer_list_5 let server = httpd_setup({ "/1.1/johndoe/storage/steam": steam_coll.handler(), "/1.1/johndoe/storage/petrol": httpd_handler(503, "Service Unavailable"), "/1.1/johndoe/storage/diesel": diesel_coll.handler() }); try { setUpTestFixtures(); + new SyncTestingInfrastructure("johndoe", "irrelevant", "irrelevant"); _("Confirm initial environment."); do_check_false(steam_coll.deleted); do_check_false(diesel_coll.deleted); _("wipeServer() will happily ignore the non-existent collection, delete the 'steam' collection and abort after an receiving an error on the 'petrol' collection."); let error; try { @@ -130,16 +132,17 @@ add_test(function test_wipeServer_all_su } let server = httpd_setup({ "/1.1/johndoe/storage": storageHandler }); setUpTestFixtures(); _("Try deletion."); + new SyncTestingInfrastructure("johndoe", "irrelevant", "irrelevant"); let returnedTimestamp = Service.wipeServer(); do_check_true(deleted); do_check_eq(returnedTimestamp, serverTimestamp); server.stop(run_next_test); Svc.Prefs.resetBranch(""); }); @@ -161,16 +164,17 @@ add_test(function test_wipeServer_all_40 } let server = httpd_setup({ "/1.1/johndoe/storage": storageHandler }); setUpTestFixtures(); _("Try deletion."); + new SyncTestingInfrastructure("johndoe", "irrelevant", "irrelevant"); let returnedTimestamp = Service.wipeServer(); do_check_true(deleted); do_check_eq(returnedTimestamp, serverTimestamp); server.stop(run_next_test); Svc.Prefs.resetBranch(""); }); @@ -189,16 +193,17 @@ add_test(function test_wipeServer_all_50 let server = httpd_setup({ "/1.1/johndoe/storage": storageHandler }); setUpTestFixtures(); _("Try deletion."); let error; try { + new SyncTestingInfrastructure("johndoe", "irrelevant", "irrelevant"); Service.wipeServer(); do_throw("Should have thrown!"); } catch (ex) { error = ex; } do_check_eq(error.status, 503); server.stop(run_next_test);
--- a/services/sync/tests/unit/test_status_checkSetup.js +++ b/services/sync/tests/unit/test_status_checkSetup.js @@ -1,45 +1,44 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/status.js"); Cu.import("resource://services-sync/util.js"); function run_test() { + initTestLogging("Trace"); + try { - _("Verify initial setup."); - do_check_eq(ID.get("WeaveID"), null); - do_check_eq(ID.get("WeaveCryptoID"), null); + _("Ensure fresh config."); + Identity.deleteSyncCredentials(); _("Fresh setup, we're not configured."); do_check_eq(Status.checkSetup(), CLIENT_NOT_CONFIGURED); do_check_eq(Status.login, LOGIN_FAILED_NO_USERNAME); Status.resetSync(); _("Let's provide a username."); - Svc.Prefs.set("username", "johndoe"); + Identity.username = "johndoe"; do_check_eq(Status.checkSetup(), CLIENT_NOT_CONFIGURED); do_check_eq(Status.login, LOGIN_FAILED_NO_PASSWORD); Status.resetSync(); - _("checkSetup() created a WeaveID identity."); - let id = ID.get("WeaveID"); - do_check_true(!!id); + do_check_neq(Identity.username, null); _("Let's provide a password."); - id.password = "carotsalad"; + Identity.basicPassword = "carotsalad"; do_check_eq(Status.checkSetup(), CLIENT_NOT_CONFIGURED); do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE); Status.resetSync(); - _("checkSetup() created a WeaveCryptoID identity"); - id = ID.get("WeaveCryptoID"); - do_check_true(!!id); - _("Let's provide a passphrase"); - id.keyStr = "a-bcdef-abcde-acbde-acbde-acbde"; + Identity.syncKey = "a-bcdef-abcde-acbde-acbde-acbde"; + _("checkSetup()"); do_check_eq(Status.checkSetup(), STATUS_OK); Status.resetSync(); } finally { Svc.Prefs.resetBranch(""); } }
--- a/services/sync/tests/unit/test_syncengine.js +++ b/services/sync/tests/unit/test_syncengine.js @@ -90,17 +90,17 @@ function test_toFetch() { // Read file from disk toFetch = [Utils.makeGUID(), Utils.makeGUID()]; syncTesting.fakeFilesystem.fakeContents[filename] = JSON.stringify(toFetch); engine.loadToFetch(); do_check_eq(engine.toFetch.length, 2); do_check_eq(engine.toFetch[0], toFetch[0]); do_check_eq(engine.toFetch[1], toFetch[1]); } finally { - syncTesting = new SyncTestingInfrastructure(makeSteamEngine); + Svc.Prefs.resetBranch(""); } } function test_previousFailed() { _("SyncEngine.previousFailed corresponds to file on disk"); let syncTesting = new SyncTestingInfrastructure(); const filename = "weave/failed/steam.json"; let engine = makeSteamEngine(); @@ -120,17 +120,17 @@ function test_previousFailed() { // Read file from disk previousFailed = [Utils.makeGUID(), Utils.makeGUID()]; syncTesting.fakeFilesystem.fakeContents[filename] = JSON.stringify(previousFailed); engine.loadPreviousFailed(); do_check_eq(engine.previousFailed.length, 2); do_check_eq(engine.previousFailed[0], previousFailed[0]); do_check_eq(engine.previousFailed[1], previousFailed[1]); } finally { - syncTesting = new SyncTestingInfrastructure(makeSteamEngine); + Svc.Prefs.resetBranch(""); } } function test_resetClient() { _("SyncEngine.resetClient resets lastSync and toFetch"); let syncTesting = new SyncTestingInfrastructure(); let engine = makeSteamEngine(); try { @@ -145,17 +145,16 @@ function test_resetClient() { engine.previousFailed = [Utils.makeGUID(), Utils.makeGUID(), Utils.makeGUID()]; engine.resetClient(); do_check_eq(engine.lastSync, 0); do_check_eq(engine.lastSyncLocal, 0); do_check_eq(engine.toFetch.length, 0); do_check_eq(engine.previousFailed.length, 0); } finally { - syncTesting = new SyncTestingInfrastructure(makeSteamEngine); Svc.Prefs.resetBranch(""); } } function test_wipeServer() { _("SyncEngine.wipeServer deletes server data and resets the client."); let syncTesting = new SyncTestingInfrastructure(); Svc.Prefs.set("serverURL", TEST_SERVER_URL); @@ -177,17 +176,16 @@ function test_wipeServer() { _("Wipe server data and reset client."); engine.wipeServer(); do_check_eq(steamCollection.payload, undefined); do_check_eq(engine.lastSync, 0); do_check_eq(engine.toFetch.length, 0); } finally { server.stop(do_test_finished); - syncTesting = new SyncTestingInfrastructure(makeSteamEngine); Svc.Prefs.resetBranch(""); } } function run_test() { test_url_attributes(); test_syncID(); test_lastSync();
--- a/services/sync/tests/unit/test_syncengine_sync.js +++ b/services/sync/tests/unit/test_syncengine_sync.js @@ -13,16 +13,23 @@ function makeRotaryEngine() { function cleanAndGo(server) { Svc.Prefs.resetBranch(""); Svc.Prefs.set("log.logger.engine.rotary", "Trace"); Records.clearCache(); server.stop(run_next_test); } +function configureService(username, password) { + Service.clusterURL = TEST_CLUSTER_URL; + + Identity.account = username || "foo"; + Identity.basicPassword = password || "password"; +} + function createServerAndConfigureClient() { let engine = new RotaryEngine(); let contents = { meta: {global: {engines: {rotary: {version: engine.version, syncID: engine.syncID}}}}, crypto: {}, rotary: {}
--- a/services/sync/tests/unit/test_syncscheduler.js +++ b/services/sync/tests/unit/test_syncscheduler.js @@ -44,24 +44,22 @@ function sync_httpd_setup() { "/1.1/johndoe/storage/crypto/keys": upd("crypto", (new ServerWBO("keys")).handler()), "/1.1/johndoe/storage/clients": upd("clients", clientsColl.handler()), "/user/1.0/johndoe/node/weave": httpd_handler(200, "OK", "null") }); } function setUp() { - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); Service.clusterURL = TEST_CLUSTER_URL; generateNewKeys(); let serverKeys = CollectionKeys.asWBO("crypto", "keys"); - serverKeys.encrypt(Service.syncKeyBundle); + serverKeys.encrypt(Identity.syncKeyBundle); return serverKeys.upload(Service.cryptoKeysURL).success; } function cleanUpAndGo(server) { Utils.nextTick(function () { Service.startOver(); if (server) { server.stop(run_next_test); @@ -208,24 +206,26 @@ add_test(function test_masterpassword_lo }); add_test(function test_calculateBackoff() { do_check_eq(Status.backoffInterval, 0); // Test no interval larger than the maximum backoff is used if // Status.backoffInterval is smaller. Status.backoffInterval = 5; - let backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL); + let backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL, + Status.backoffInterval); do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL); // Test Status.backoffInterval is used if it is // larger than MAXIMUM_BACKOFF_INTERVAL. Status.backoffInterval = MAXIMUM_BACKOFF_INTERVAL + 10; - backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL); + backoffInterval = Utils.calculateBackoff(50, MAXIMUM_BACKOFF_INTERVAL, + Status.backoffInterval); do_check_eq(backoffInterval, MAXIMUM_BACKOFF_INTERVAL + 10); cleanUpAndGo(); }); add_test(function test_scheduleNextSync_nowOrPast() { Svc.Obs.add("weave:service:sync:finish", function onSyncFinish() { @@ -461,47 +461,47 @@ add_test(function test_autoconnect_nextS waitForZeroTimer(function () { do_check_eq(SyncScheduler.nextSync, expectedSync); do_check_true(SyncScheduler.syncTimer.delay >= expectedInterval); Svc.Obs.remove("weave:service:login:start", onLoginStart); cleanUpAndGo(); }); - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); SyncScheduler.delayedAutoConnect(0); }); add_test(function test_autoconnect_mp_locked() { let server = sync_httpd_setup(); setUp(); // Pretend user did not unlock master password. let origLocked = Utils.mpLocked; Utils.mpLocked = function() true; - let origPP = Service.__lookupGetter__("passphrase"); - delete Service.passphrase; - Service.__defineGetter__("passphrase", function() { + let origGetter = Identity.__lookupGetter__("syncKey"); + let origSetter = Identity.__lookupSetter__("syncKey"); + delete Identity.syncKey; + Identity.__defineGetter__("syncKey", function() { _("Faking Master Password entry cancelation."); throw "User canceled Master Password entry"; }); // A locked master password will still trigger a sync, but then we'll hit // MASTER_PASSWORD_LOCKED and hence MASTER_PASSWORD_LOCKED_RETRY_INTERVAL. Svc.Obs.add("weave:service:login:error", function onLoginError() { Svc.Obs.remove("weave:service:login:error", onLoginError); Utils.nextTick(function aLittleBitAfterLoginError() { do_check_eq(Status.login, MASTER_PASSWORD_LOCKED); Utils.mpLocked = origLocked; - delete Service.passphrase; - Service.__defineGetter__("passphrase", origPP); + delete Identity.syncKey; + Identity.__defineGetter__("syncKey", origGetter); + Identity.__defineSetter__("syncKey", origSetter); cleanUpAndGo(server); }); }); SyncScheduler.delayedAutoConnect(0); }); @@ -844,20 +844,18 @@ add_test(function test_sync_503_Retry_Af do_check_true(SyncScheduler.nextSync >= Date.now() + minimumExpectedDelay); do_check_true(SyncScheduler.syncTimer.delay >= minimumExpectedDelay); cleanUpAndGo(server); }); add_test(function test_loginError_recoverable_reschedules() { _("Verify that a recoverable login error schedules a new sync."); - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; - Service.serverURL = TEST_SERVER_URL; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); + Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; Service.persistLogin(); Status.resetSync(); // reset Status.login Svc.Obs.add("weave:service:login:error", function onLoginError() { Svc.Obs.remove("weave:service:login:error", onLoginError); Utils.nextTick(function aLittleBitAfterLoginError() { do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR); @@ -888,20 +886,18 @@ add_test(function test_loginError_recove do_check_eq(Status.checkSetup(), STATUS_OK); do_check_eq(Status.login, LOGIN_SUCCEEDED); SyncScheduler.scheduleNextSync(0); }); add_test(function test_loginError_fatal_clearsTriggers() { _("Verify that a fatal login error clears sync triggers."); - Service.username = "johndoe"; - Service.password = "ilovejane"; - Service.passphrase = "abcdeabcdeabcdeabcdeabcdea"; - Service.serverURL = TEST_SERVER_URL; + setBasicCredentials("johndoe", "ilovejane", "abcdeabcdeabcdeabcdeabcdea"); + Service.serverURL = TEST_SERVER_URL; Service.clusterURL = TEST_CLUSTER_URL; Service.persistLogin(); Status.resetSync(); // reset Status.login let server = httpd_setup({ "/1.1/johndoe/info/collections": httpd_handler(401, "Unauthorized") });
--- a/services/sync/tests/unit/test_syncstoragerequest.js +++ b/services/sync/tests/unit/test_syncstoragerequest.js @@ -52,27 +52,26 @@ add_test(function test_user_agent_mobile server.stop(run_next_test); }); }); add_test(function test_auth() { let handler = httpd_handler(200, "OK"); let server = httpd_setup({"/resource": handler}); - let id = new Identity(PWDMGR_PASSWORD_REALM, "johndoe"); - id.password = "ilovejane"; - ID.set("WeaveID", id); + setBasicCredentials("johndoe", "ilovejane", "XXXXXXXXX"); let request = new SyncStorageRequest(STORAGE_REQUEST_RESOURCE_URL); request.get(function (error) { do_check_eq(error, null); do_check_eq(this.response.status, 200); do_check_true(basic_auth_matches(handler.request, "johndoe", "ilovejane")); - ID.del("WeaveID"); + Svc.Prefs.reset(""); + server.stop(run_next_test); }); }); /** * The X-Weave-Timestamp header updates SyncStorageRequest.serverTime. */ add_test(function test_weave_timestamp() {
--- a/services/sync/tests/unit/test_upgrade_old_sync_key.js +++ b/services/sync/tests/unit/test_upgrade_old_sync_key.js @@ -1,9 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + Cu.import("resource://services-sync/constants.js"); +Cu.import("resource://services-sync/identity.js"); Cu.import("resource://services-sync/main.js"); var btoa = Cu.import("resource://services-sync/util.js").btoa; // Test upgrade of a dashed old-style sync key. function run_test() { const PBKDF2_KEY_BYTES = 16; initTestLogging("Trace"); @@ -16,21 +20,22 @@ function run_test() { // Still not a modern passphrase... do_check_false(Utils.isPassphrase(normalized)); // ... but different. do_check_neq(normalized, passphrase); do_check_eq(normalized, "abcdeabcdeabcdeabcde"); // Now run through the upgrade. + Identity.account = "johndoe"; Weave.Service.syncID = "1234567890"; - Weave.Service.passphrase = normalized; // UI normalizes. - do_check_false(Utils.isPassphrase(Weave.Service.passphrase)); + Identity.syncKey = normalized; // UI normalizes. + do_check_false(Utils.isPassphrase(Identity.syncKey)); Weave.Service.upgradeSyncKey(Weave.Service.syncID); - let upgraded = Weave.Service.passphrase; + let upgraded = Identity.syncKey; _("Upgraded: " + upgraded); do_check_true(Utils.isPassphrase(upgraded)); // Now let's verify that it's been derived correctly, from the normalized // version, and the encoded sync ID. _("Sync ID: " + Weave.Service.syncID); let derivedKeyStr = Utils.derivePresentableKeyFromPassphrase(normalized,
--- a/services/sync/tests/unit/xpcshell.ini +++ b/services/sync/tests/unit/xpcshell.ini @@ -1,78 +1,85 @@ [DEFAULT] head = head_appinfo.js head_helpers.js head_http_server.js tail = [test_load_modules.js] +# The manifest is roughly ordered from low-level to high-level. When making +# systemic sweeping changes, this makes it easier to identify errors closer to +# the source. + +# Ensure we can import everything. +[test_load_modules.js] + +# util contains a bunch of functionality used throughout. +[test_utils_atob.js] +[test_utils_catch.js] +[test_utils_deepCopy.js] +[test_utils_deepEquals.js] +[test_utils_deferGetSet.js] +[test_utils_deriveKey.js] +[test_utils_encodeBase32.js] +[test_utils_ensureOneOpen.js] +[test_utils_getErrorString.js] +[test_utils_getIcon.js] +[test_utils_hkdfExpand.js] +[test_utils_httpmac.js] +[test_utils_json.js] +[test_utils_lazyStrings.js] +[test_utils_lock.js] +[test_utils_makeGUID.js] +[test_utils_makeURI.js] +[test_utils_namedTimer.js] +[test_utils_notify.js] +[test_utils_passphrase.js] +[test_utils_pbkdf2.js] +[test_utils_sha1.js] +[test_utils_stackTrace.js] +[test_utils_utf8.js] + +# We have a number of other libraries that are pretty much standalone. [test_Observers.js] [test_Preferences.js] -[test_addons_engine.js] -[test_addons_reconciler.js] -[test_addons_store.js] -[test_addons_tracker.js] [test_async_chain.js] [test_async_querySpinningly.js] -[test_auth_manager.js] -[test_bookmark_batch_fail.js] -[test_bookmark_engine.js] -[test_bookmark_legacy_microsummaries_support.js] -[test_bookmark_livemarks.js] -[test_bookmark_order.js] -[test_bookmark_places_query_rewriting.js] -[test_bookmark_record.js] -[test_bookmark_smart_bookmarks.js] -[test_bookmark_store.js] -[test_bookmark_tracker.js] -[test_clients_engine.js] -[test_clients_escape.js] -[test_collection_inc_get.js] -[test_collections_recovery.js] -[test_corrupt_keys.js] -[test_engine.js] -[test_engine_abort.js] -[test_enginemanager.js] -[test_errorhandler.js] -[test_errorhandler_filelog.js] -# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) -skip-if = os == "android" -[test_errorhandler_sync_checkServerError.js] -# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) -skip-if = os == "android" -[test_forms_store.js] -[test_forms_tracker.js] -[test_history_engine.js] -[test_history_store.js] -[test_history_tracker.js] -[test_hmac_error.js] [test_httpd_sync_server.js] -[test_interval_triggers.js] +[test_log4moz.js] +[test_restrequest.js] [test_jpakeclient.js] # Bug 618233: this test produces random failures on Windows 7. # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) skip-if = os == "win" || os == "android" -[test_keys.js] -[test_log4moz.js] -[test_node_reassignment.js] -[test_notifications.js] -[test_password_store.js] -[test_password_tracker.js] -[test_places_guid_downgrade.js] -[test_prefs_store.js] -[test_prefs_tracker.js] -[test_records_crypto.js] -[test_records_crypto_generateEntry.js] -[test_records_wbo.js] + +# HTTP layers. [test_resource.js] [test_resource_async.js] [test_resource_ua.js] -[test_restrequest.js] -[test_score_triggers.js] -[test_sendcredentials_controller.js] +[test_syncstoragerequest.js] + +# Generic Sync types. +[test_collection_inc_get.js] +[test_collections_recovery.js] +[test_identity_manager.js] +[test_keys.js] +[test_records_crypto.js] +[test_records_wbo.js] + +# Engine APIs. +[test_engine.js] +[test_engine_abort.js] +[test_enginemanager.js] +[test_syncengine.js] +[test_syncengine_sync.js] +# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) +skip-if = os == "android" +[test_tracker_addChanged.js] + +# Service semantics. [test_service_attributes.js] [test_service_changePassword.js] [test_service_checkAccount.js] [test_service_cluster.js] [test_service_createAccount.js] [test_service_detect_upgrade.js] [test_service_getStorageInfo.js] [test_service_login.js] @@ -87,45 +94,58 @@ skip-if = os == "win" || os == "android" # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) skip-if = os == "android" [test_service_sync_updateEnabledEngines.js] # Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) skip-if = os == "android" [test_service_verifyLogin.js] [test_service_wipeClient.js] [test_service_wipeServer.js] + +[test_corrupt_keys.js] +[test_errorhandler.js] +[test_errorhandler_filelog.js] +# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) +skip-if = os == "android" +[test_errorhandler_sync_checkServerError.js] +# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) +skip-if = os == "android" +[test_hmac_error.js] +[test_interval_triggers.js] +[test_node_reassignment.js] +[test_notifications.js] +[test_score_triggers.js] +[test_sendcredentials_controller.js] [test_status.js] [test_status_checkSetup.js] -[test_syncengine.js] -[test_syncengine_sync.js] -# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini) -skip-if = os == "android" [test_syncscheduler.js] -[test_syncstoragerequest.js] +[test_upgrade_old_sync_key.js] + +# Finally, we test each engine. +[test_addons_engine.js] +[test_addons_reconciler.js] +[test_addons_store.js] +[test_addons_tracker.js] +[test_bookmark_batch_fail.js] +[test_bookmark_engine.js] +[test_bookmark_legacy_microsummaries_support.js] +[test_bookmark_livemarks.js] +[test_bookmark_order.js] +[test_bookmark_places_query_rewriting.js] +[test_bookmark_record.js] +[test_bookmark_smart_bookmarks.js] +[test_bookmark_store.js] +[test_bookmark_tracker.js] +[test_clients_engine.js] +[test_clients_escape.js] +[test_forms_store.js] +[test_forms_tracker.js] +[test_history_engine.js] +[test_history_store.js] +[test_history_tracker.js] +[test_places_guid_downgrade.js] +[test_password_store.js] +[test_password_tracker.js] +[test_prefs_store.js] +[test_prefs_tracker.js] [test_tab_engine.js] [test_tab_store.js] [test_tab_tracker.js] -[test_tracker_addChanged.js] -[test_upgrade_old_sync_key.js] -[test_utils_atob.js] -[test_utils_catch.js] -[test_utils_deepCopy.js] -[test_utils_deepEquals.js] -[test_utils_deferGetSet.js] -[test_utils_deriveKey.js] -[test_utils_encodeBase32.js] -[test_utils_ensureOneOpen.js] -[test_utils_getErrorString.js] -[test_utils_getIcon.js] -[test_utils_hkdfExpand.js] -[test_utils_httpmac.js] -[test_utils_json.js] -[test_utils_lazyStrings.js] -[test_utils_lock.js] -[test_utils_makeGUID.js] -[test_utils_makeURI.js] -[test_utils_namedTimer.js] -[test_utils_notify.js] -[test_utils_passphrase.js] -[test_utils_pbkdf2.js] -[test_utils_sha1.js] -[test_utils_stackTrace.js] -[test_utils_utf8.js]
--- a/services/sync/tps/extensions/tps/modules/sync.jsm +++ b/services/sync/tps/extensions/tps/modules/sync.jsm @@ -97,19 +97,19 @@ var TPS = { SetupSyncAccount: function TPS__SetupSyncAccount() { try { let serverURL = prefs.getCharPref('tps.account.serverURL'); if (serverURL) { Weave.Service.serverURL = serverURL; } } catch(e) {} - Weave.Service.account = prefs.getCharPref('tps.account.username'); - Weave.Service.password = prefs.getCharPref('tps.account.password'); - Weave.Service.passphrase = prefs.getCharPref('tps.account.passphrase'); + Weave.Identity.account = prefs.getCharPref('tps.account.username'); + Weave.Identity.basicPassword = prefs.getCharPref('tps.account.password'); + Weave.Identity.syncKey = prefs.getCharPref('tps.account.passphrase'); Weave.Svc.Obs.notify("weave:service:setup-complete"); }, Sync: function TPS__Sync(options) { Logger.logInfo('Mozmill starting sync operation: ' + options); switch(options) { case SYNC_WIPE_REMOTE: Weave.Svc.Prefs.set("firstSync", "wipeRemote");
--- a/services/sync/tps/extensions/tps/modules/tps.jsm +++ b/services/sync/tps/extensions/tps/modules/tps.jsm @@ -779,27 +779,27 @@ let TPS = } Logger.logInfo("Setting client credentials."); if (account["admin-secret"]) { // if admin-secret is specified, we'll dynamically create // a new sync account Weave.Svc.Prefs.set("admin-secret", account["admin-secret"]); let suffix = account["account-suffix"]; - Service.account = "tps" + suffix + "@mozilla.com"; - Service.password = "tps" + suffix + "tps" + suffix; - Service.passphrase = Weave.Utils.generatePassphrase(); - Service.createAccount(Service.account, - Service.password, + Weave.Identity.account = "tps" + suffix + "@mozilla.com"; + Weave.Identity.basicPassword = "tps" + suffix + "tps" + suffix; + Weave.Identity.syncKey = Weave.Utils.generatePassphrase(); + Service.createAccount(Weave.Identity.account, + Weave.Identity.basicPassword, "dummy1", "dummy2"); } else if (account["username"] && account["password"] && account["passphrase"]) { - Service.account = account["username"]; - Service.password = account["password"]; - Service.passphrase = account["passphrase"]; + Weave.Identity.account = account["username"]; + Weave.Identity.basicPassword = account["password"]; + Weave.Identity.syncKey = account["passphrase"]; } else { this.DumpError("Must specify admin-secret, or " + "username/password/passphrase in the config file"); return; } Service.login(); Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status not OK");