diff options
Diffstat (limited to 'services/sync/tests/unit/test_service_sync_remoteSetup.js')
-rw-r--r-- | services/sync/tests/unit/test_service_sync_remoteSetup.js | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/services/sync/tests/unit/test_service_sync_remoteSetup.js b/services/sync/tests/unit/test_service_sync_remoteSetup.js new file mode 100644 index 000000000..83dbf3cd7 --- /dev/null +++ b/services/sync/tests/unit/test_service_sync_remoteSetup.js @@ -0,0 +1,237 @@ +/* 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); + } +} |