1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/tests/unit/test_hmac_error.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,247 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +Cu.import("resource://services-sync/engines.js"); 1.8 +Cu.import("resource://services-sync/service.js"); 1.9 +Cu.import("resource://services-sync/util.js"); 1.10 +Cu.import("resource://testing-common/services/sync/rotaryengine.js"); 1.11 +Cu.import("resource://testing-common/services/sync/utils.js"); 1.12 + 1.13 +// Track HMAC error counts. 1.14 +let hmacErrorCount = 0; 1.15 +(function () { 1.16 + let hHE = Service.handleHMACEvent; 1.17 + Service.handleHMACEvent = function () { 1.18 + hmacErrorCount++; 1.19 + return hHE.call(Service); 1.20 + }; 1.21 +})(); 1.22 + 1.23 +function shared_setup() { 1.24 + hmacErrorCount = 0; 1.25 + 1.26 + // Do not instantiate SyncTestingInfrastructure; we need real crypto. 1.27 + ensureLegacyIdentityManager(); 1.28 + setBasicCredentials("foo", "foo", "aabcdeabcdeabcdeabcdeabcde"); 1.29 + 1.30 + // Make sure RotaryEngine is the only one we sync. 1.31 + Service.engineManager._engines = {}; 1.32 + Service.engineManager.register(RotaryEngine); 1.33 + let engine = Service.engineManager.get("rotary"); 1.34 + engine.enabled = true; 1.35 + engine.lastSync = 123; // Needs to be non-zero so that tracker is queried. 1.36 + engine._store.items = {flying: "LNER Class A3 4472", 1.37 + scotsman: "Flying Scotsman"}; 1.38 + engine._tracker.addChangedID('scotsman', 0); 1.39 + do_check_eq(1, Service.engineManager.getEnabled().length); 1.40 + 1.41 + let engines = {rotary: {version: engine.version, 1.42 + syncID: engine.syncID}, 1.43 + clients: {version: Service.clientsEngine.version, 1.44 + syncID: Service.clientsEngine.syncID}}; 1.45 + 1.46 + // Common server objects. 1.47 + let global = new ServerWBO("global", {engines: engines}); 1.48 + let keysWBO = new ServerWBO("keys"); 1.49 + let rotaryColl = new ServerCollection({}, true); 1.50 + let clientsColl = new ServerCollection({}, true); 1.51 + 1.52 + return [engine, rotaryColl, clientsColl, keysWBO, global]; 1.53 +} 1.54 + 1.55 +add_test(function hmac_error_during_404() { 1.56 + _("Attempt to replicate the HMAC error setup."); 1.57 + let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup(); 1.58 + 1.59 + // Hand out 404s for crypto/keys. 1.60 + let keysHandler = keysWBO.handler(); 1.61 + let key404Counter = 0; 1.62 + let keys404Handler = function (request, response) { 1.63 + if (key404Counter > 0) { 1.64 + let body = "Not Found"; 1.65 + response.setStatusLine(request.httpVersion, 404, body); 1.66 + response.bodyOutputStream.write(body, body.length); 1.67 + key404Counter--; 1.68 + return; 1.69 + } 1.70 + keysHandler(request, response); 1.71 + }; 1.72 + 1.73 + let collectionsHelper = track_collections_helper(); 1.74 + let upd = collectionsHelper.with_updated_collection; 1.75 + let collections = collectionsHelper.collections; 1.76 + let handlers = { 1.77 + "/1.1/foo/info/collections": collectionsHelper.handler, 1.78 + "/1.1/foo/storage/meta/global": upd("meta", global.handler()), 1.79 + "/1.1/foo/storage/crypto/keys": upd("crypto", keys404Handler), 1.80 + "/1.1/foo/storage/clients": upd("clients", clientsColl.handler()), 1.81 + "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()) 1.82 + }; 1.83 + 1.84 + let server = sync_httpd_setup(handlers); 1.85 + Service.serverURL = server.baseURI; 1.86 + 1.87 + try { 1.88 + _("Syncing."); 1.89 + Service.sync(); 1.90 + _("Partially resetting client, as if after a restart, and forcing redownload."); 1.91 + Service.collectionKeys.clear(); 1.92 + engine.lastSync = 0; // So that we redownload records. 1.93 + key404Counter = 1; 1.94 + _("---------------------------"); 1.95 + Service.sync(); 1.96 + _("---------------------------"); 1.97 + 1.98 + // Two rotary items, one client record... no errors. 1.99 + do_check_eq(hmacErrorCount, 0) 1.100 + } finally { 1.101 + Svc.Prefs.resetBranch(""); 1.102 + Service.recordManager.clearCache(); 1.103 + server.stop(run_next_test); 1.104 + } 1.105 +}); 1.106 + 1.107 +add_test(function hmac_error_during_node_reassignment() { 1.108 + _("Attempt to replicate an HMAC error during node reassignment."); 1.109 + let [engine, rotaryColl, clientsColl, keysWBO, global] = shared_setup(); 1.110 + 1.111 + let collectionsHelper = track_collections_helper(); 1.112 + let upd = collectionsHelper.with_updated_collection; 1.113 + 1.114 + // We'll provide a 401 mid-way through the sync. This function 1.115 + // simulates shifting to a node which has no data. 1.116 + function on401() { 1.117 + _("Deleting server data..."); 1.118 + global.delete(); 1.119 + rotaryColl.delete(); 1.120 + keysWBO.delete(); 1.121 + clientsColl.delete(); 1.122 + delete collectionsHelper.collections.rotary; 1.123 + delete collectionsHelper.collections.crypto; 1.124 + delete collectionsHelper.collections.clients; 1.125 + _("Deleted server data."); 1.126 + } 1.127 + 1.128 + let should401 = false; 1.129 + function upd401(coll, handler) { 1.130 + return function (request, response) { 1.131 + if (should401 && (request.method != "DELETE")) { 1.132 + on401(); 1.133 + should401 = false; 1.134 + let body = "\"reassigned!\""; 1.135 + response.setStatusLine(request.httpVersion, 401, "Node reassignment."); 1.136 + response.bodyOutputStream.write(body, body.length); 1.137 + return; 1.138 + } 1.139 + handler(request, response); 1.140 + }; 1.141 + } 1.142 + 1.143 + function sameNodeHandler(request, response) { 1.144 + // Set this so that _setCluster will think we've really changed. 1.145 + let url = Service.serverURL.replace("localhost", "LOCALHOST"); 1.146 + _("Client requesting reassignment; pointing them to " + url); 1.147 + response.setStatusLine(request.httpVersion, 200, "OK"); 1.148 + response.bodyOutputStream.write(url, url.length); 1.149 + } 1.150 + 1.151 + let handlers = { 1.152 + "/user/1.0/foo/node/weave": sameNodeHandler, 1.153 + "/1.1/foo/info/collections": collectionsHelper.handler, 1.154 + "/1.1/foo/storage/meta/global": upd("meta", global.handler()), 1.155 + "/1.1/foo/storage/crypto/keys": upd("crypto", keysWBO.handler()), 1.156 + "/1.1/foo/storage/clients": upd401("clients", clientsColl.handler()), 1.157 + "/1.1/foo/storage/rotary": upd("rotary", rotaryColl.handler()) 1.158 + }; 1.159 + 1.160 + let server = sync_httpd_setup(handlers); 1.161 + Service.serverURL = server.baseURI; 1.162 + _("Syncing."); 1.163 + // First hit of clients will 401. This will happen after meta/global and 1.164 + // keys -- i.e., in the middle of the sync, but before RotaryEngine. 1.165 + should401 = true; 1.166 + 1.167 + // Use observers to perform actions when our sync finishes. 1.168 + // This allows us to observe the automatic next-tick sync that occurs after 1.169 + // an abort. 1.170 + function onSyncError() { 1.171 + do_throw("Should not get a sync error!"); 1.172 + } 1.173 + function onSyncFinished() {} 1.174 + let obs = { 1.175 + observe: function observe(subject, topic, data) { 1.176 + switch (topic) { 1.177 + case "weave:service:sync:error": 1.178 + onSyncError(); 1.179 + break; 1.180 + case "weave:service:sync:finish": 1.181 + onSyncFinished(); 1.182 + break; 1.183 + } 1.184 + } 1.185 + }; 1.186 + 1.187 + Svc.Obs.add("weave:service:sync:finish", obs); 1.188 + Svc.Obs.add("weave:service:sync:error", obs); 1.189 + 1.190 + // This kicks off the actual test. Split into a function here to allow this 1.191 + // source file to broadly follow actual execution order. 1.192 + function onwards() { 1.193 + _("== Invoking first sync."); 1.194 + Service.sync(); 1.195 + _("We should not simultaneously have data but no keys on the server."); 1.196 + let hasData = rotaryColl.wbo("flying") || 1.197 + rotaryColl.wbo("scotsman"); 1.198 + let hasKeys = keysWBO.modified; 1.199 + 1.200 + _("We correctly handle 401s by aborting the sync and starting again."); 1.201 + do_check_true(!hasData == !hasKeys); 1.202 + 1.203 + _("Be prepared for the second (automatic) sync..."); 1.204 + } 1.205 + 1.206 + _("Make sure that syncing again causes recovery."); 1.207 + onSyncFinished = function() { 1.208 + _("== First sync done."); 1.209 + _("---------------------------"); 1.210 + onSyncFinished = function() { 1.211 + _("== Second (automatic) sync done."); 1.212 + hasData = rotaryColl.wbo("flying") || 1.213 + rotaryColl.wbo("scotsman"); 1.214 + hasKeys = keysWBO.modified; 1.215 + do_check_true(!hasData == !hasKeys); 1.216 + 1.217 + // Kick off another sync. Can't just call it, because we're inside the 1.218 + // lock... 1.219 + Utils.nextTick(function() { 1.220 + _("Now a fresh sync will get no HMAC errors."); 1.221 + _("Partially resetting client, as if after a restart, and forcing redownload."); 1.222 + Service.collectionKeys.clear(); 1.223 + engine.lastSync = 0; 1.224 + hmacErrorCount = 0; 1.225 + 1.226 + onSyncFinished = function() { 1.227 + // Two rotary items, one client record... no errors. 1.228 + do_check_eq(hmacErrorCount, 0) 1.229 + 1.230 + Svc.Obs.remove("weave:service:sync:finish", obs); 1.231 + Svc.Obs.remove("weave:service:sync:error", obs); 1.232 + 1.233 + Svc.Prefs.resetBranch(""); 1.234 + Service.recordManager.clearCache(); 1.235 + server.stop(run_next_test); 1.236 + }; 1.237 + 1.238 + Service.sync(); 1.239 + }, 1.240 + this); 1.241 + }; 1.242 + }; 1.243 + 1.244 + onwards(); 1.245 +}); 1.246 + 1.247 +function run_test() { 1.248 + initTestLogging("Trace"); 1.249 + run_next_test(); 1.250 +}