test_service_sync_remoteSetup.js (8406B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 const { Service } = ChromeUtils.importESModule( 5 "resource://services-sync/service.sys.mjs" 6 ); 7 8 // This sucks, but this test fails if this engine is enabled, due to dumb 9 // things that aren't related to this engine. In short: 10 // * Because the addon manager isn't initialized, the addons engine fails to 11 // initialize. So we end up writing a meta/global with `extension-storage` 12 // but not addons. 13 // * After we sync, we discover 'addons' is locally enabled, but because it's 14 // not in m/g, we decide it's been remotely declined (and it decides this 15 // without even considering `declined`). So we disable 'addons'. 16 // * Disabling 'addons' means 'extension-storage' is disabled - but because 17 // that *is* in meta/global we re-update meta/global to remove it. 18 // * This test fails due to the extra, unexpected update of m/g. 19 // 20 // Another option would be to ensure the addons manager is initialized, but 21 // that's a larger patch and still isn't strictly relevant to what's being 22 // tested here, so... 23 Services.prefs.setBoolPref( 24 "services.sync.engine.extension-storage.force", 25 false 26 ); 27 28 add_task(async function run_test() { 29 enableValidationPrefs(); 30 31 validate_all_future_pings(); 32 Log.repository.rootLogger.addAppender(new Log.DumpAppender()); 33 34 let clients = new ServerCollection(); 35 let meta_global = new ServerWBO("global"); 36 37 let collectionsHelper = track_collections_helper(); 38 let upd = collectionsHelper.with_updated_collection; 39 let collections = collectionsHelper.collections; 40 41 function wasCalledHandler(wbo) { 42 let handler = wbo.handler(); 43 return function () { 44 wbo.wasCalled = true; 45 handler.apply(this, arguments); 46 }; 47 } 48 49 let keysWBO = new ServerWBO("keys"); 50 let cryptoColl = new ServerCollection({ keys: keysWBO }); 51 let metaColl = new ServerCollection({ global: meta_global }); 52 do_test_pending(); 53 54 /** 55 * Handle the bulk DELETE request sent by wipeServer. 56 */ 57 function storageHandler(request, response) { 58 Assert.equal("DELETE", request.method); 59 Assert.ok(request.hasHeader("X-Confirm-Delete")); 60 61 _("Wiping out all collections."); 62 cryptoColl.delete({}); 63 clients.delete({}); 64 metaColl.delete({}); 65 66 let ts = new_timestamp(); 67 collectionsHelper.update_collection("crypto", ts); 68 collectionsHelper.update_collection("clients", ts); 69 collectionsHelper.update_collection("meta", ts); 70 return_timestamp(request, response, ts); 71 } 72 73 const GLOBAL_PATH = "/1.1/johndoe/storage/meta/global"; 74 75 let handlers = { 76 "/1.1/johndoe/storage": storageHandler, 77 "/1.1/johndoe/storage/crypto/keys": upd("crypto", keysWBO.handler()), 78 "/1.1/johndoe/storage/crypto": upd("crypto", cryptoColl.handler()), 79 "/1.1/johndoe/storage/clients": upd("clients", clients.handler()), 80 "/1.1/johndoe/storage/meta": upd("meta", wasCalledHandler(metaColl)), 81 "/1.1/johndoe/storage/meta/global": upd( 82 "meta", 83 wasCalledHandler(meta_global) 84 ), 85 "/1.1/johndoe/info/collections": collectionsHelper.handler, 86 }; 87 88 function mockHandler(path, mock) { 89 server.registerPathHandler(path, mock(handlers[path])); 90 return { 91 restore() { 92 server.registerPathHandler(path, handlers[path]); 93 }, 94 }; 95 } 96 97 let server = httpd_setup(handlers); 98 99 try { 100 _("Checking Status.sync with no credentials."); 101 await Service.verifyAndFetchSymmetricKeys(); 102 Assert.equal(Service.status.sync, CREDENTIALS_CHANGED); 103 Assert.equal(Service.status.login, LOGIN_FAILED_NO_PASSPHRASE); 104 105 await configureIdentity({ username: "johndoe" }, server); 106 107 await Service.login(); 108 _("Checking that remoteSetup returns true when credentials have changed."); 109 (await Service.recordManager.get(Service.metaURL)).payload.syncID = 110 "foobar"; 111 Assert.ok(await Service._remoteSetup()); 112 113 let returnStatusCode = (method, code) => oldMethod => (req, res) => { 114 if (req.method === method) { 115 res.setStatusLine(req.httpVersion, code, ""); 116 } else { 117 oldMethod(req, res); 118 } 119 }; 120 121 let mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 401)); 122 Service.recordManager.del(Service.metaURL); 123 _( 124 "Checking that remoteSetup returns false on 401 on first get /meta/global." 125 ); 126 Assert.equal(false, await Service._remoteSetup()); 127 mock.restore(); 128 129 await Service.login(); 130 mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503)); 131 Service.recordManager.del(Service.metaURL); 132 _( 133 "Checking that remoteSetup returns false on 503 on first get /meta/global." 134 ); 135 Assert.equal(false, await Service._remoteSetup()); 136 Assert.equal(Service.status.sync, METARECORD_DOWNLOAD_FAIL); 137 mock.restore(); 138 139 await Service.login(); 140 mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404)); 141 Service.recordManager.del(Service.metaURL); 142 _("Checking that remoteSetup recovers on 404 on first get /meta/global."); 143 Assert.ok(await Service._remoteSetup()); 144 mock.restore(); 145 146 let makeOutdatedMeta = async () => { 147 Service.metaModified = 0; 148 let infoResponse = await Service._fetchInfo(); 149 return { 150 status: infoResponse.status, 151 obj: { 152 crypto: infoResponse.obj.crypto, 153 clients: infoResponse.obj.clients, 154 meta: 1, 155 }, 156 }; 157 }; 158 159 _( 160 "Checking that remoteSetup recovers on 404 on get /meta/global after clear cached one." 161 ); 162 mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 404)); 163 Service.recordManager.set(Service.metaURL, { isNew: false }); 164 Assert.ok(await Service._remoteSetup(await makeOutdatedMeta())); 165 mock.restore(); 166 167 _( 168 "Checking that remoteSetup returns false on 503 on get /meta/global after clear cached one." 169 ); 170 mock = mockHandler(GLOBAL_PATH, returnStatusCode("GET", 503)); 171 Service.status.sync = ""; 172 Service.recordManager.set(Service.metaURL, { isNew: false }); 173 Assert.equal(false, await Service._remoteSetup(await makeOutdatedMeta())); 174 Assert.equal(Service.status.sync, ""); 175 mock.restore(); 176 177 metaColl.delete({}); 178 179 _("Do an initial sync."); 180 await Service.sync(); 181 182 _("Checking that remoteSetup returns true."); 183 Assert.ok(await Service._remoteSetup()); 184 185 _("Verify that the meta record was uploaded."); 186 Assert.equal(meta_global.data.syncID, Service.syncID); 187 Assert.equal(meta_global.data.storageVersion, STORAGE_VERSION); 188 Assert.equal( 189 meta_global.data.engines.clients.version, 190 Service.clientsEngine.version 191 ); 192 Assert.equal( 193 meta_global.data.engines.clients.syncID, 194 await Service.clientsEngine.getSyncID() 195 ); 196 197 _( 198 "Set the collection info hash so that sync() will remember the modified times for future runs." 199 ); 200 let lastSync = await Service.clientsEngine.getLastSync(); 201 collections.meta = lastSync; 202 collections.clients = lastSync; 203 await Service.sync(); 204 205 _("Sync again and verify that meta/global wasn't downloaded again"); 206 meta_global.wasCalled = false; 207 await Service.sync(); 208 Assert.ok(!meta_global.wasCalled); 209 210 _( 211 "Fake modified records. This will cause a redownload, but not reupload since it hasn't changed." 212 ); 213 collections.meta += 42; 214 meta_global.wasCalled = false; 215 216 let metaModified = meta_global.modified; 217 218 await Service.sync(); 219 Assert.ok(meta_global.wasCalled); 220 Assert.equal(metaModified, meta_global.modified); 221 222 // Try to screw up HMAC calculation. 223 // Re-encrypt keys with a new random keybundle, and upload them to the 224 // server, just as might happen with a second client. 225 _("Attempting to screw up HMAC by re-encrypting keys."); 226 let keys = Service.collectionKeys.asWBO(); 227 let b = new BulkKeyBundle("hmacerror"); 228 await b.generateRandom(); 229 collections.crypto = keys.modified = 100 + Date.now() / 1000; // Future modification time. 230 await keys.encrypt(b); 231 await keys.upload(Service.resource(Service.cryptoKeysURL)); 232 233 Assert.equal(false, await Service.verifyAndFetchSymmetricKeys()); 234 Assert.equal(Service.status.login, LOGIN_FAILED_INVALID_PASSPHRASE); 235 } finally { 236 for (const pref of Svc.PrefBranch.getChildList("")) { 237 Svc.PrefBranch.clearUserPref(pref); 238 } 239 server.stop(do_test_finished); 240 } 241 });