gecko-dev/services/sync/tests/unit/test_hmac_error.js

244 строки
8.2 KiB
JavaScript

/* 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/util.js");
Cu.import("resource://testing-common/services/sync/rotaryengine.js");
Cu.import("resource://testing-common/services/sync/utils.js");
// Track HMAC error counts.
var hmacErrorCount = 0;
(function() {
let hHE = Service.handleHMACEvent;
Service.handleHMACEvent = async function() {
hmacErrorCount++;
return hHE.call(Service);
};
})();
async function shared_setup() {
enableValidationPrefs();
hmacErrorCount = 0;
// Make sure RotaryEngine is the only one we sync.
let { engine, tracker } = await registerRotaryEngine();
engine.lastSync = 123; // Needs to be non-zero so that tracker is queried.
engine._store.items = {flying: "LNER Class A3 4472",
scotsman: "Flying Scotsman"};
tracker.addChangedID("scotsman", 0);
do_check_eq(1, Service.engineManager.getEnabled().length);
let engines = {rotary: {version: engine.version,
syncID: engine.syncID},
clients: {version: Service.clientsEngine.version,
syncID: Service.clientsEngine.syncID}};
// Common server objects.
let global = new ServerWBO("global", {engines});
let keysWBO = new ServerWBO("keys");
let rotaryColl = new ServerCollection({}, true);
let clientsColl = new ServerCollection({}, true);
return [engine, rotaryColl, clientsColl, keysWBO, global, tracker];
}
add_task(async function hmac_error_during_404() {
_("Attempt to replicate the HMAC error setup.");
let [engine, rotaryColl, clientsColl, keysWBO, global, tracker] = await shared_setup();
// Hand out 404s for crypto/keys.
let keysHandler = keysWBO.handler();
let key404Counter = 0;
let keys404Handler = function(request, response) {
if (key404Counter > 0) {
let body = "Not Found";
response.setStatusLine(request.httpVersion, 404, body);
response.bodyOutputStream.write(body, body.length);
key404Counter--;
return;
}
keysHandler(request, response);
};
let collectionsHelper = track_collections_helper();
let upd = collectionsHelper.with_updated_collection;
let handlers = {
"/1.1/foo/info/collections": collectionsHelper.handler,
"/1.1/foo/storage/meta/global": upd("meta", global.handler()),
"/1.1/foo/storage/crypto/keys": upd("crypto", keys404Handler),
"/1.1/foo/storage/clients": upd("clients", clientsColl.handler()),
"/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler())
};
let server = sync_httpd_setup(handlers);
// Do not instantiate SyncTestingInfrastructure; we need real crypto.
await configureIdentity({ username: "foo" }, server);
await Service.login();
try {
_("Syncing.");
await sync_and_validate_telem();
_("Partially resetting client, as if after a restart, and forcing redownload.");
Service.collectionKeys.clear();
engine.lastSync = 0; // So that we redownload records.
key404Counter = 1;
_("---------------------------");
await sync_and_validate_telem();
_("---------------------------");
// Two rotary items, one client record... no errors.
do_check_eq(hmacErrorCount, 0)
} finally {
tracker.clearChangedIDs();
Service.engineManager.unregister(engine);
Svc.Prefs.resetBranch("");
Service.recordManager.clearCache();
await promiseStopServer(server);
}
});
add_task(async function hmac_error_during_node_reassignment() {
_("Attempt to replicate an HMAC error during node reassignment.");
let [engine, rotaryColl, clientsColl, keysWBO, global, tracker] = await shared_setup();
let collectionsHelper = track_collections_helper();
let upd = collectionsHelper.with_updated_collection;
// We'll provide a 401 mid-way through the sync. This function
// simulates shifting to a node which has no data.
function on401() {
_("Deleting server data...");
global.delete();
rotaryColl.delete();
keysWBO.delete();
clientsColl.delete();
delete collectionsHelper.collections.rotary;
delete collectionsHelper.collections.crypto;
delete collectionsHelper.collections.clients;
_("Deleted server data.");
}
let should401 = false;
function upd401(coll, handler) {
return function(request, response) {
if (should401 && (request.method != "DELETE")) {
on401();
should401 = false;
let body = "\"reassigned!\"";
response.setStatusLine(request.httpVersion, 401, "Node reassignment.");
response.bodyOutputStream.write(body, body.length);
return;
}
handler(request, response);
};
}
let handlers = {
"/1.1/foo/info/collections": collectionsHelper.handler,
"/1.1/foo/storage/meta/global": upd("meta", global.handler()),
"/1.1/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()),
"/1.1/foo/storage/clients": upd401("clients", clientsColl.handler()),
"/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler())
};
let server = sync_httpd_setup(handlers);
// Do not instantiate SyncTestingInfrastructure; we need real crypto.
await configureIdentity({ username: "foo" }, server);
_("Syncing.");
// First hit of clients will 401. This will happen after meta/global and
// keys -- i.e., in the middle of the sync, but before RotaryEngine.
should401 = true;
// Use observers to perform actions when our sync finishes.
// This allows us to observe the automatic next-tick sync that occurs after
// an abort.
function onSyncError() {
do_throw("Should not get a sync error!");
}
let onSyncFinished = function() {}
let obs = {
observe: function observe(subject, topic, data) {
switch (topic) {
case "weave:service:sync:error":
onSyncError();
break;
case "weave:service:sync:finish":
onSyncFinished();
break;
}
}
};
Svc.Obs.add("weave:service:sync:finish", obs);
Svc.Obs.add("weave:service:sync:error", obs);
// This kicks off the actual test. Split into a function here to allow this
// source file to broadly follow actual execution order.
function onwards() {
_("== Invoking first sync.");
Async.promiseSpinningly(Service.sync());
_("We should not simultaneously have data but no keys on the server.");
let hasData = rotaryColl.wbo("flying") ||
rotaryColl.wbo("scotsman");
let hasKeys = keysWBO.modified;
_("We correctly handle 401s by aborting the sync and starting again.");
do_check_true(!hasData == !hasKeys);
_("Be prepared for the second (automatic) sync...");
}
_("Make sure that syncing again causes recovery.");
await new Promise(resolve => {
onSyncFinished = function() {
_("== First sync done.");
_("---------------------------");
onSyncFinished = function() {
_("== Second (automatic) sync done.");
let hasData = rotaryColl.wbo("flying") ||
rotaryColl.wbo("scotsman");
let hasKeys = keysWBO.modified;
do_check_true(!hasData == !hasKeys);
// Kick off another sync. Can't just call it, because we're inside the
// lock...
Utils.nextTick(function() {
_("Now a fresh sync will get no HMAC errors.");
_("Partially resetting client, as if after a restart, and forcing redownload.");
Service.collectionKeys.clear();
engine.lastSync = 0;
hmacErrorCount = 0;
onSyncFinished = function() {
// Two rotary items, one client record... no errors.
do_check_eq(hmacErrorCount, 0)
Svc.Obs.remove("weave:service:sync:finish", obs);
Svc.Obs.remove("weave:service:sync:error", obs);
tracker.clearChangedIDs();
Service.engineManager.unregister(engine);
Svc.Prefs.resetBranch("");
Service.recordManager.clearCache();
server.stop(resolve);
};
Async.promiseSpinningly(Service.sync());
},
this);
};
};
onwards();
});
});
function run_test() {
initTestLogging("Trace");
run_next_test();
}