michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "btoa", // It comes from a module import. michael@0: "encryptPayload", michael@0: "ensureLegacyIdentityManager", michael@0: "setBasicCredentials", michael@0: "makeIdentityConfig", michael@0: "configureFxAccountIdentity", michael@0: "configureIdentity", michael@0: "SyncTestingInfrastructure", michael@0: "waitForZeroTimer", michael@0: "Promise", // from a module import michael@0: "add_identity_test", michael@0: ]; michael@0: michael@0: const {utils: Cu} = Components; michael@0: michael@0: Cu.import("resource://services-sync/status.js"); michael@0: Cu.import("resource://services-sync/identity.js"); michael@0: Cu.import("resource://services-common/utils.js"); michael@0: Cu.import("resource://services-crypto/utils.js"); michael@0: Cu.import("resource://services-sync/util.js"); michael@0: Cu.import("resource://services-sync/browserid_identity.js"); michael@0: Cu.import("resource://testing-common/services-common/logging.js"); michael@0: Cu.import("resource://testing-common/services/sync/fakeservices.js"); michael@0: Cu.import("resource://gre/modules/FxAccounts.jsm"); michael@0: Cu.import("resource://gre/modules/FxAccountsCommon.js"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: michael@0: /** michael@0: * First wait >100ms (nsITimers can take up to that much time to fire, so michael@0: * we can account for the timer in delayedAutoconnect) and then two event michael@0: * loop ticks (to account for the Utils.nextTick() in autoConnect). michael@0: */ michael@0: this.waitForZeroTimer = function waitForZeroTimer(callback) { michael@0: let ticks = 2; michael@0: function wait() { michael@0: if (ticks) { michael@0: ticks -= 1; michael@0: CommonUtils.nextTick(wait); michael@0: return; michael@0: } michael@0: callback(); michael@0: } michael@0: CommonUtils.namedTimer(wait, 150, {}, "timer"); michael@0: } michael@0: michael@0: /** michael@0: * Ensure Sync is configured with the "legacy" identity provider. michael@0: */ michael@0: this.ensureLegacyIdentityManager = function() { michael@0: let ns = {}; michael@0: Cu.import("resource://services-sync/service.js", ns); michael@0: michael@0: Status.__authManager = ns.Service.identity = new IdentityManager(); michael@0: ns.Service._clusterManager = ns.Service.identity.createClusterManager(ns.Service); michael@0: } michael@0: michael@0: this.setBasicCredentials = michael@0: function setBasicCredentials(username, password, syncKey) { michael@0: let ns = {}; michael@0: Cu.import("resource://services-sync/service.js", ns); michael@0: michael@0: let auth = ns.Service.identity; michael@0: auth.username = username; michael@0: auth.basicPassword = password; michael@0: auth.syncKey = syncKey; michael@0: } michael@0: michael@0: // Return an identity configuration suitable for testing with our identity michael@0: // providers. |overrides| can specify overrides for any default values. michael@0: this.makeIdentityConfig = function(overrides) { michael@0: // first setup the defaults. michael@0: let result = { michael@0: // Username used in both fxaccount and sync identity configs. michael@0: username: "foo", michael@0: // fxaccount specific credentials. michael@0: fxaccount: { michael@0: user: { michael@0: assertion: 'assertion', michael@0: email: 'email', michael@0: kA: 'kA', michael@0: kB: 'kB', michael@0: sessionToken: 'sessionToken', michael@0: uid: 'user_uid', michael@0: verified: true, michael@0: }, michael@0: token: { michael@0: endpoint: Svc.Prefs.get("tokenServerURI"), michael@0: duration: 300, michael@0: id: "id", michael@0: key: "key", michael@0: // uid will be set to the username. michael@0: } michael@0: }, michael@0: sync: { michael@0: // username will come from the top-level username michael@0: password: "whatever", michael@0: syncKey: "abcdeabcdeabcdeabcdeabcdea", michael@0: } michael@0: }; michael@0: michael@0: // Now handle any specified overrides. michael@0: if (overrides) { michael@0: if (overrides.username) { michael@0: result.username = overrides.username; michael@0: } michael@0: if (overrides.sync) { michael@0: // TODO: allow just some attributes to be specified michael@0: result.sync = overrides.sync; michael@0: } michael@0: if (overrides.fxaccount) { michael@0: // TODO: allow just some attributes to be specified michael@0: result.fxaccount = overrides.fxaccount; michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: // Configure an instance of an FxAccount identity provider with the specified michael@0: // config (or the default config if not specified). michael@0: this.configureFxAccountIdentity = function(authService, michael@0: config = makeIdentityConfig()) { michael@0: let MockInternal = {}; michael@0: let fxa = new FxAccounts(MockInternal); michael@0: michael@0: // until we get better test infrastructure for bid_identity, we set the michael@0: // signedin user's "email" to the username, simply as many tests rely on this. michael@0: config.fxaccount.user.email = config.username; michael@0: fxa.internal.currentAccountState.signedInUser = { michael@0: version: DATA_FORMAT_VERSION, michael@0: accountData: config.fxaccount.user michael@0: }; michael@0: fxa.internal.currentAccountState.getCertificate = function(data, keyPair, mustBeValidUntil) { michael@0: this.cert = { michael@0: validUntil: fxa.internal.now() + CERT_LIFETIME, michael@0: cert: "certificate", michael@0: }; michael@0: return Promise.resolve(this.cert.cert); michael@0: }; michael@0: michael@0: let mockTSC = { // TokenServerClient michael@0: getTokenFromBrowserIDAssertion: function(uri, assertion, cb) { michael@0: config.fxaccount.token.uid = config.username; michael@0: cb(null, config.fxaccount.token); michael@0: }, michael@0: }; michael@0: authService._fxaService = fxa; michael@0: authService._tokenServerClient = mockTSC; michael@0: // Set the "account" of the browserId manager to be the "email" of the michael@0: // logged in user of the mockFXA service. michael@0: authService._signedInUser = fxa.internal.currentAccountState.signedInUser.accountData; michael@0: authService._account = config.fxaccount.user.email; michael@0: } michael@0: michael@0: this.configureIdentity = function(identityOverrides) { michael@0: let config = makeIdentityConfig(identityOverrides); michael@0: let ns = {}; michael@0: Cu.import("resource://services-sync/service.js", ns); michael@0: michael@0: if (ns.Service.identity instanceof BrowserIDManager) { michael@0: // do the FxAccounts thang... michael@0: configureFxAccountIdentity(ns.Service.identity, config); michael@0: return ns.Service.identity.initializeWithCurrentIdentity().then(() => { michael@0: // need to wait until this identity manager is readyToAuthenticate. michael@0: return ns.Service.identity.whenReadyToAuthenticate.promise; michael@0: }); michael@0: } michael@0: // old style identity provider. michael@0: setBasicCredentials(config.username, config.sync.password, config.sync.syncKey); michael@0: let deferred = Promise.defer(); michael@0: deferred.resolve(); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: this.SyncTestingInfrastructure = function (server, username, password, syncKey) { michael@0: let ns = {}; michael@0: Cu.import("resource://services-sync/service.js", ns); michael@0: michael@0: ensureLegacyIdentityManager(); michael@0: let config = makeIdentityConfig(); michael@0: // XXX - hacks for the sync identity provider. michael@0: if (username) michael@0: config.username = username; michael@0: if (password) michael@0: config.sync.password = password; michael@0: if (syncKey) michael@0: config.sync.syncKey = syncKey; michael@0: let cb = Async.makeSpinningCallback(); michael@0: configureIdentity(config).then(cb, cb); michael@0: cb.wait(); michael@0: michael@0: let i = server.identity; michael@0: let uri = i.primaryScheme + "://" + i.primaryHost + ":" + michael@0: i.primaryPort + "/"; michael@0: michael@0: ns.Service.serverURL = uri; michael@0: ns.Service.clusterURL = uri; michael@0: michael@0: this.logStats = initTestLogging(); michael@0: this.fakeFilesystem = new FakeFilesystemService({}); michael@0: this.fakeGUIDService = new FakeGUIDService(); michael@0: this.fakeCryptoService = new FakeCryptoService(); michael@0: } michael@0: michael@0: /** michael@0: * Turn WBO cleartext into fake "encrypted" payload as it goes over the wire. michael@0: */ michael@0: this.encryptPayload = function encryptPayload(cleartext) { michael@0: if (typeof cleartext == "object") { michael@0: cleartext = JSON.stringify(cleartext); michael@0: } michael@0: michael@0: return { michael@0: ciphertext: cleartext, // ciphertext == cleartext with fake crypto michael@0: IV: "irrelevant", michael@0: hmac: fakeSHA256HMAC(cleartext, CryptoUtils.makeHMACKey("")), michael@0: }; michael@0: } michael@0: michael@0: // This helper can be used instead of 'add_test' or 'add_task' to run the michael@0: // specified test function twice - once with the old-style sync identity michael@0: // manager and once with the new-style BrowserID identity manager, to ensure michael@0: // it works in both cases. michael@0: // michael@0: // * The test itself should be passed as 'test' - ie, test code will generally michael@0: // pass |this|. michael@0: // * The test function is a regular test function - although note that it must michael@0: // be a generator - async operations should yield them, and run_next_test michael@0: // mustn't be called. michael@0: this.add_identity_test = function(test, testFunction) { michael@0: function note(what) { michael@0: let msg = "running test " + testFunction.name + " with " + what + " identity manager"; michael@0: test._log("test_info", michael@0: {_message: "TEST-INFO | | " + msg + "\n"}); michael@0: } michael@0: let ns = {}; michael@0: Cu.import("resource://services-sync/service.js", ns); michael@0: // one task for the "old" identity manager. michael@0: test.add_task(function() { michael@0: note("sync"); michael@0: let oldIdentity = Status._authManager; michael@0: ensureLegacyIdentityManager(); michael@0: yield testFunction(); michael@0: Status.__authManager = ns.Service.identity = oldIdentity; michael@0: }); michael@0: // another task for the FxAccounts identity manager. michael@0: test.add_task(function() { michael@0: note("FxAccounts"); michael@0: let oldIdentity = Status._authManager; michael@0: Status.__authManager = ns.Service.identity = new BrowserIDManager(); michael@0: yield testFunction(); michael@0: Status.__authManager = ns.Service.identity = oldIdentity; michael@0: }); michael@0: }