Bug 1458920 - Filter RemoteSettings sync event data r=Gijs,mgoodwin

MozReview-Commit-ID: Hw9CA5W2J26

--HG--
extra : rebase_source : 689aae16d007c19f1d9c73c3be95bd5618b0fe36
This commit is contained in:
Mathieu Leplatre 2018-05-08 16:30:40 +02:00
Родитель 28f6b114ec
Коммит 2136d2066e
4 изменённых файлов: 233 добавлений и 29 удалений

Просмотреть файл

@ -68,6 +68,9 @@ The ``sync`` event allows to be notified when the remote settings are changed on
}
});
.. important::
If one of the event handler fails, the others handlers for the same remote settings collection won't be executed.
.. note::
Currently, the update of remote settings is triggered by the `nsBlocklistService <https://dxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/nsBlocklistService.js>`_ (~ every 24H).

Просмотреть файл

@ -347,22 +347,34 @@ class RemoteSettingsClient {
throw e;
}
}
// Read local collection of records.
const { data: current } = await collection.list();
// Handle the obtained records (ie. apply locally).
try {
// Execute callbacks in order and sequentially.
// Handle the obtained records (ie. apply locally through events).
// Build the event data list. It should be filtered (ie. by application target)
const { created: allCreated, updated: allUpdated, deleted: allDeleted } = syncResult;
const [created, deleted, updatedFiltered] = await Promise.all(
[allCreated, allDeleted, allUpdated.map(e => e.new)].map(this._filterEntries.bind(this))
);
// For updates, keep entries whose updated form is matches the target.
const updatedFilteredIds = new Set(updatedFiltered.map(e => e.id));
const updated = allUpdated.filter(({ new: { id } }) => updatedFilteredIds.has(id));
// If every changed entry is filtered, we don't even fire the event.
if (created.length || updated.length || deleted.length) {
// Read local collection of records (also filtered).
const { data: allData } = await collection.list();
const current = await this._filterEntries(allData);
// Fire the event: execute callbacks in order and sequentially.
// If one fails everything fails.
const { created, updated, deleted } = syncResult;
const event = { data: { current, created, updated, deleted } };
const callbacks = this._callbacks.get("sync");
for (const cb of callbacks) {
await cb(event);
try {
for (const cb of callbacks) {
await cb(event);
}
} catch (e) {
reportStatus = UptakeTelemetry.STATUS.APPLY_ERROR;
throw e;
}
} catch (e) {
reportStatus = UptakeTelemetry.STATUS.APPLY_ERROR;
throw e;
}
// Track last update.

Просмотреть файл

@ -175,6 +175,45 @@ add_task(async function test_sends_reload_message_when_blocklist_has_changes() {
});
add_task(clear_state);
add_task(async function test_sync_event_data_is_filtered_for_target() {
// Here we will synchronize 4 times, the first two to initialize the local DB and
// the last two about event filtered data.
const timestamp1 = 2000;
const timestamp2 = 3000;
const timestamp3 = 4000;
const timestamp4 = 5000;
// Fake a date value obtained from server (used to store a pref, useless here).
const fakeServerTime = Date.now();
for (let {client} of gBlocklistClients) {
// Initialize the collection with some data (local is empty, thus no ?_since)
await client.maybeSync(timestamp1, fakeServerTime - 30, {loadDump: false});
// This will pick the data with ?_since=3000.
await client.maybeSync(timestamp2 + 1, fakeServerTime - 20);
// In ?_since=4000 entries, no target matches. The sync event is not called.
let called = false;
client.on("sync", e => called = true);
await client.maybeSync(timestamp3 + 1, fakeServerTime - 10);
equal(called, false, `no sync event for ${client.collectionName}`);
// In ?_since=5000 entries, only one entry matches.
let syncEventData;
client.on("sync", e => syncEventData = e.data);
await client.maybeSync(timestamp4 + 1, fakeServerTime);
const { current, created, updated, deleted } = syncEventData;
equal(created.length + updated.length + deleted.length, 1, `event filtered data for ${client.collectionName}`);
// Since we had entries whose target does not match, the internal storage list
// and the event current data should differ.
const collection = await client.openCollection();
const { data: internalData } = await collection.list();
ok(internalData.length > current.length, `event current data for ${client.collectionName}`);
}
});
add_task(clear_state);
// get a response for a given request from sample data
function getSampleResponse(req, port) {
const responses = {
@ -322,7 +361,7 @@ function getSampleResponse(req, port) {
"versionRange": [{
"targetApplication": [],
"minVersion": "11.2.202.509",
"maxVersion": "11.2.202.539",
"maxVersion": "*",
"severity": "0",
"vulnerabilityStatus": "1"
}],
@ -336,7 +375,6 @@ function getSampleResponse(req, port) {
"versionRange": [{
"targetApplication": [{
"minVersion": "3.0",
"guid": "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
"maxVersion": "*"
}]
}]
@ -370,6 +408,163 @@ function getSampleResponse(req, port) {
"os": "Darwin 11",
"featureStatus": "BLOCKED_DEVICE"
}]})
},
"GET:/v1/buckets/blocklists/collections/addons/records?_sort=-last_modified&_since=4000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"5000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data": [{
"last_modified": 4001,
"versionRange": [{
"targetApplication": [{
"guid": "some-guid"
}],
}],
"id": "8f03b264-57b7-4263-9b15-ad91b033a034"
}]})
},
"GET:/v1/buckets/blocklists/collections/plugins/records?_sort=-last_modified&_since=4000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"5000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data": [{
"last_modified": 4001,
"versionRange": [{
"targetApplication": [{
"guid": "xpcshell@tests.mozilla.org",
"minVersion": "0",
"maxVersion": "57.*"
}]
}],
"id": "cd3ea0b2-1ba8-4fb6-b242-976a87626116"
}]})
},
"GET:/v1/buckets/blocklists/collections/gfx/records?_sort=-last_modified&_since=4000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"5000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data": [{
"last_modified": 4001,
"versionRange": [{
"targetApplication": [{
"guid": "xpcshell@tests.mozilla.org",
"minVersion": "99999"
}],
}],
"id": "86771771-e803-4006-95e9-c9275d58b3d1"
}]})
},
"GET:/v1/buckets/blocklists/collections/addons/records?_sort=-last_modified&_since=5000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"6000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data": [{
// delete an entry with non matching target (see above)
"last_modified": 5001,
"deleted": true,
"id": "8f03b264-57b7-4263-9b15-ad91b033a034"
}, {
// delete entry with matching target (see above)
"last_modified": 5002,
"deleted": true,
"id": "9ccfac91-e463-c30c-f0bd-14143794a8dd"
}, {
// create an extra non matching
"last_modified": 5003,
"id": "75b36589-435a-48d4-8ee4-bacee3fb6119",
"versionRange": [{
"targetApplication": [{
"guid": "xpcshell@tests.mozilla.org",
"minVersion": "0",
"maxVersion": "57.*"
}]
}],
}]})
},
"GET:/v1/buckets/blocklists/collections/plugins/records?_sort=-last_modified&_since=5000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"6000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data": [{
// entry with non matching target (see above)
"newAttribute": 42,
"versionRange": [{
"targetApplication": [{
"guid": "xpcshell@tests.mozilla.org",
"minVersion": "0",
"maxVersion": "57.*"
}]
}],
"id": "cd3ea0b2-1ba8-4fb6-b242-976a87626116"
}, {
// entry with matching target (see above)
"newAttribute": 42,
"matchFilename": "npViewpoint.dll",
"blockID": "p32",
"id": "1f48af42-c508-b8ef-b8d5-609d48e4f6c9",
"last_modified": 3500,
"versionRange": [{
"targetApplication": [{
"guid": "xpcshell@tests.mozilla.org",
"minVersion": "3.0",
"maxVersion": "*"
}]
}]
}]})
},
"GET:/v1/buckets/blocklists/collections/gfx/records?_sort=-last_modified&_since=5000": {
"sampleHeaders": [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
"Content-Type: application/json; charset=UTF-8",
"Server: waitress",
"Etag: \"6000\""
],
"status": {status: 200, statusText: "OK"},
"responseBody": JSON.stringify({"data": [{
"versionRange": [{
"targetApplication": [{
"guid": "xpcshell@tests.mozilla.org",
"minVersion": "0",
"maxVersion": "*"
}]
}],
"id": "43031a81-5f36-4eef-9b35-52f0bbeba363"
}, {
"versionRange": [{
"targetApplication": [{
"guid": "xpcshell@tests.mozilla.org",
"minVersion": "0",
"maxVersion": "3"
}]
}],
"id": "75a06bd3-f906-427d-a448-02092ee589fc"
}]})
}
};
return responses[`${req.method}:${req.path}?${req.queryString}`] ||

Просмотреть файл

@ -449,19 +449,16 @@ add_task(async function test_check_signatures() {
startHistogram = getUptakeTelemetrySnapshot(TELEMETRY_HISTOGRAM_KEY);
let retrySyncData;
OneCRLBlocklistClient.on("sync", ({ data }) => { retrySyncData = data; });
let syncEventSent = false;
OneCRLBlocklistClient.on("sync", ({ data }) => { syncEventSent = true; });
await OneCRLBlocklistClient.maybeSync(5000, startTime);
endHistogram = getUptakeTelemetrySnapshot(TELEMETRY_HISTOGRAM_KEY);
// since we only fixed the signature, and no data was changed, the sync result
// will be called with empty lists of created/updated/deleted.
equal(retrySyncData.current.length, 2);
equal(retrySyncData.created.length, 0);
equal(retrySyncData.updated.length, 0);
equal(retrySyncData.deleted.length, 0);
// since we only fixed the signature, and no data was changed, the sync event
// was not sent.
equal(syncEventSent, false);
// ensure that the failure count is incremented for a succesful sync with an
// (initial) bad signature - only SERVICES_SETTINGS_SYNC_SIG_FAIL should
@ -493,17 +490,14 @@ add_task(async function test_check_signatures() {
registerHandlers(badSigGoodOldResponses);
let oldChangesData;
OneCRLBlocklistClient.on("sync", ({ data }) => { oldChangesData = data; });
syncEventSent = false;
OneCRLBlocklistClient.on("sync", ({ data }) => { syncEventSent = true; });
await OneCRLBlocklistClient.maybeSync(5000, startTime);
// Local data was unchanged, since it was never than the one returned by the server.
equal(oldChangesData.current.length, 2);
equal(oldChangesData.created.length, 0);
equal(oldChangesData.updated.length, 0);
equal(oldChangesData.deleted.length, 0);
// Local data was unchanged, since it was never than the one returned by the server,
// thus the sync event is not sent.
equal(syncEventSent, false);
const badLocalContentGoodSigResponses = {
// In this test, we deliberately serve a bad signature initially. The