/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/keys.js"); Cu.import("resource://services-sync/service.js"); Cu.import("resource://services-sync/util.js"); Cu.import("resource://testing-common/services/sync/fakeservices.js"); Cu.import("resource://testing-common/services/sync/utils.js"); function run_test() { validate_all_future_pings(); let logger = Log.repository.rootLogger; Log.repository.rootLogger.addAppender(new Log.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); }; } let keysWBO = new ServerWBO("keys"); let cryptoColl = new ServerCollection({keys: keysWBO}); let metaColl = new ServerCollection({global: meta_global}); do_test_pending(); /** * Handle the bulk DELETE request sent by wipeServer. */ function storageHandler(request, response) { do_check_eq("DELETE", request.method); do_check_true(request.hasHeader("X-Confirm-Delete")); _("Wiping out all collections."); cryptoColl.delete({}); clients.delete({}); metaColl.delete({}); let ts = new_timestamp(); collectionsHelper.update_collection("crypto", ts); collectionsHelper.update_collection("clients", ts); collectionsHelper.update_collection("meta", ts); return_timestamp(request, response, ts); } const GLOBAL_PATH = "/1.1/johndoe/storage/meta/global"; const INFO_PATH = "/1.1/johndoe/info/collections"; let handlers = { "/1.1/johndoe/storage": storageHandler, "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), "/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()), "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)), "/1.1/johndoe/storage/meta/global": upd("meta", wasCalledHandler(meta_global)), "/1.1/johndoe/info/collections": collectionsHelper.handler }; function mockHandler(path, mock) { server.registerPathHandler(path, mock(handlers[path])); return { restore() { server.registerPathHandler(path, handlers[path]); } } } let server = httpd_setup(handlers); try { _("Log in."); ensureLegacyIdentityManager(); Service.serverURL = server.baseURI; _("Checking Status.sync with no credentials."); Service.verifyAndFetchSymmetricKeys(); do_check_eq(Service.status.sync, CREDENTIALS_CHANGED); do_check_eq(Service.status.login, LOGIN_FAILED_NO_PASSPHRASE); _("Log in with an old secret phrase, is upgraded to Sync Key."); Service.login("johndoe", "ilovejane", "my old secret phrase!!1!"); _("End of login"); do_check_true(Service.isLoggedIn); do_check_true(Utils.isPassphrase(Service.identity.syncKey)); let syncKey = Service.identity.syncKey; Service.startOver(); Service.serverURL = server.baseURI; Service.login("johndoe", "ilovejane", syncKey); do_check_true(Service.isLoggedIn); _("Checking that remoteSetup returns true when credentials have changed."); Service.recordManager.get(Service.metaURL).payload.syncID = "foobar"; do_check_true(Service._remoteSetup()); let returnStatusCode = (method, code) => (oldMethod) => (req, res) => { if (req.method === method) { res.setStatusLine(req.httpVersion, code, ""); } else { oldMethod(req, res); } }; let mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 401)); Service.recordManager.del(Service.metaURL); _("Checking that remoteSetup returns false on 401 on first get /meta/global."); do_check_false(Service._remoteSetup()); mock.restore(); Service.login("johndoe", "ilovejane", syncKey); mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503)); Service.recordManager.del(Service.metaURL); _("Checking that remoteSetup returns false on 503 on first get /meta/global."); do_check_false(Service._remoteSetup()); do_check_eq(Service.status.sync, METARECORD_DOWNLOAD_FAIL); mock.restore(); mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404)); Service.recordManager.del(Service.metaURL); _("Checking that remoteSetup recovers on 404 on first get /meta/global."); do_check_true(Service._remoteSetup()); mock.restore(); let makeOutdatedMeta = () => { Service.metaModified = 0; let infoResponse = Service._fetchInfo(); return { status: infoResponse.status, obj: { crypto: infoResponse.obj.crypto, clients: infoResponse.obj.clients, meta: 1 } }; } _("Checking that remoteSetup recovers on 404 on get /meta/global after clear cached one."); mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404)); Service.recordManager.set(Service.metaURL, { isNew: false }); do_check_true(Service._remoteSetup(makeOutdatedMeta())); mock.restore(); _("Checking that remoteSetup returns false on 503 on get /meta/global after clear cached one."); mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503)); Service.status.sync = ""; Service.recordManager.set(Service.metaURL, { isNew: false }); do_check_false(Service._remoteSetup(makeOutdatedMeta())); do_check_eq(Service.status.sync, ""); mock.restore(); metaColl.delete({}); _("Do an initial sync."); let beforeSync = Date.now()/1000; Service.sync(); _("Checking that remoteSetup returns true."); do_check_true(Service._remoteSetup()); _("Verify that the meta record was uploaded."); do_check_eq(meta_global.data.syncID, Service.syncID); do_check_eq(meta_global.data.storageVersion, STORAGE_VERSION); do_check_eq(meta_global.data.engines.clients.version, Service.clientsEngine.version); do_check_eq(meta_global.data.engines.clients.syncID, Service.clientsEngine.syncID); _("Set the collection info hash so that sync() will remember the modified times for future runs."); collections.meta = Service.clientsEngine.lastSync; collections.clients = Service.clientsEngine.lastSync; Service.sync(); _("Sync again and verify that meta/global wasn't downloaded again"); meta_global.wasCalled = false; Service.sync(); do_check_false(meta_global.wasCalled); _("Fake modified records. This will cause a redownload, but not reupload since it hasn't changed."); collections.meta += 42; meta_global.wasCalled = false; let metaModified = meta_global.modified; Service.sync(); do_check_true(meta_global.wasCalled); do_check_eq(metaModified, meta_global.modified); _("Checking bad passphrases."); let pp = Service.identity.syncKey; Service.identity.syncKey = "notvalid"; do_check_false(Service.verifyAndFetchSymmetricKeys()); do_check_eq(Service.status.sync, CREDENTIALS_CHANGED); do_check_eq(Service.status.login, LOGIN_FAILED_INVALID_PASSPHRASE); Service.identity.syncKey = pp; do_check_true(Service.verifyAndFetchSymmetricKeys()); // changePassphrase wipes our keys, and they're regenerated on next sync. _("Checking changed passphrase."); let existingDefault = Service.collectionKeys.keyForCollection(); let existingKeysPayload = keysWBO.payload; let newPassphrase = "bbbbbabcdeabcdeabcdeabcdea"; Service.changePassphrase(newPassphrase); _("Local key cache is full, but different."); do_check_true(!!Service.collectionKeys._default); do_check_false(Service.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 = Service.collectionKeys.asWBO(); let b = new BulkKeyBundle("hmacerror"); b.generateRandom(); collections.crypto = keys.modified = 100 + (Date.now()/1000); // Future modification time. keys.encrypt(b); keys.upload(Service.resource(Service.cryptoKeysURL)); do_check_false(Service.verifyAndFetchSymmetricKeys()); do_check_eq(Service.status.login, LOGIN_FAILED_INVALID_PASSPHRASE); } finally { Svc.Prefs.resetBranch(""); server.stop(do_test_finished); } }