Bug 1620186 - Fetch from changeset endpoint r=glasserc

Differential Revision: https://phabricator.services.mozilla.com/D71570
This commit is contained in:
Mathieu Leplatre 2020-04-24 09:15:43 +00:00
Родитель 3c64c7a3b2
Коммит e95ee4d2f4
4 изменённых файлов: 214 добавлений и 238 удалений

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

@ -765,24 +765,17 @@ class RemoteSettingsClient extends EventEmitter {
options = {}
) {
const { retry = false } = options;
const since = retry || !localTimestamp ? undefined : `${localTimestamp}`;
// Fetch collection metadata and list of changes from server
// (or all records on retry).
const client = this.httpClient();
const [
// Fetch collection metadata and list of changes from server.
console.debug(
`Fetch changes from server (expected=${expectedTimestamp}, since=${since})`
);
const {
metadata,
{ data: remoteRecords, last_modified: remoteTimestamp },
] = await Promise.all([
client.getData({
query: { _expected: expectedTimestamp },
}),
client.listRecords({
filters: {
_expected: expectedTimestamp,
},
since: retry || !localTimestamp ? undefined : `${localTimestamp}`,
}),
]);
remoteTimestamp,
remoteRecords,
} = await this._fetchChangeset(expectedTimestamp, since);
// We build a sync result, based on remote changes.
const syncResult = {
@ -893,6 +886,36 @@ class RemoteSettingsClient extends EventEmitter {
return syncResult;
}
/**
* Fetch information from changeset endpoint.
*
* @param expectedTimestamp cache busting value
* @param since timestamp of last sync (optional)
*/
async _fetchChangeset(expectedTimestamp, since) {
const client = this.httpClient();
const {
metadata,
timestamp: remoteTimestamp,
changes: remoteRecords,
} = await client.execute(
{
path: `/buckets/${this.bucketName}/collections/${this.collectionName}/changeset`,
},
{
query: {
_expected: expectedTimestamp,
_since: since,
},
}
);
return {
remoteTimestamp,
metadata,
remoteRecords,
};
}
/**
* Use the filter func to filter the lists of changes obtained from synchronization,
* and return them along with the filtered list of local records.

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

@ -69,11 +69,7 @@ function run_test() {
handleResponse
);
server.registerPathHandler(
"/v1/buckets/main/collections/password-fields",
handleResponse
);
server.registerPathHandler(
"/v1/buckets/main/collections/password-fields/records",
"/v1/buckets/main/collections/password-fields/changeset",
handleResponse
);
server.registerPathHandler(
@ -81,15 +77,11 @@ function run_test() {
handleResponse
);
server.registerPathHandler(
"/v1/buckets/main/collections/language-dictionaries/records",
"/v1/buckets/main/collections/language-dictionaries/changeset",
handleResponse
);
server.registerPathHandler(
"/v1/buckets/main/collections/with-local-fields",
handleResponse
);
server.registerPathHandler(
"/v1/buckets/main/collections/with-local-fields/records",
"/v1/buckets/main/collections/with-local-fields/changeset",
handleResponse
);
server.registerPathHandler("/fake-x5u", handleResponse);
@ -910,26 +902,6 @@ function getSampleResponse(req, port) {
],
},
},
"GET:/v1/buckets/main/collections/password-fields": {
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: "1234"',
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({
data: {
id: "password-fields",
last_modified: 1234,
signature: {
signature: "abcdef",
x5u: `http://localhost:${port}/fake-x5u`,
},
},
}),
},
"GET:/fake-x5u": {
sampleHeaders: ["Content-Type: application/octet-stream"],
status: { status: 200, statusText: "OK" },
@ -940,7 +912,7 @@ ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL
wNuvFqc=
-----END CERTIFICATE-----`,
},
"GET:/v1/buckets/main/collections/password-fields/records?_expected=2000&_sort=-last_modified": {
"GET:/v1/buckets/main/collections/password-fields/changeset?_expected=2000": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -950,7 +922,16 @@ wNuvFqc=
],
status: { status: 200, statusText: "OK" },
responseBody: {
data: [
timestamp: 3000,
metadata: {
id: "password-fields",
last_modified: 1234,
signature: {
signature: "abcdef",
x5u: `http://localhost:${port}/fake-x5u`,
},
},
changes: [
{
id: "9d500963-d80e-3a91-6e74-66f3811b99cc",
last_modified: 3000,
@ -960,7 +941,7 @@ wNuvFqc=
],
},
},
"GET:/v1/buckets/main/collections/password-fields/records?_expected=3001&_sort=-last_modified&_since=3000": {
"GET:/v1/buckets/main/collections/password-fields/changeset?_expected=3001&_since=3000": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -970,7 +951,9 @@ wNuvFqc=
],
status: { status: 200, statusText: "OK" },
responseBody: {
data: [
metadata: {},
timestamp: 4000,
changes: [
{
id: "aabad965-e556-ffe7-4191-074f5dee3df3",
last_modified: 4000,
@ -986,7 +969,7 @@ wNuvFqc=
],
},
},
"GET:/v1/buckets/main/collections/password-fields/records?_expected=4001&_sort=-last_modified&_since=4000": {
"GET:/v1/buckets/main/collections/password-fields/changeset?_expected=4001&_since=4000": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -996,7 +979,9 @@ wNuvFqc=
],
status: { status: 200, statusText: "OK" },
responseBody: {
data: [
metadata: {},
timestamp: 5000,
changes: [
{
id: "aabad965-e556-ffe7-4191-074f5dee3df3",
deleted: true,
@ -1004,7 +989,7 @@ wNuvFqc=
],
},
},
"GET:/v1/buckets/main/collections/password-fields/records?_expected=10000&_sort=-last_modified&_since=9999": {
"GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10000&_since=9999": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -1018,7 +1003,7 @@ wNuvFqc=
error: "Service Unavailable",
},
},
"GET:/v1/buckets/main/collections/password-fields/records?_expected=10001&_sort=-last_modified&_since=10000": {
"GET:/v1/buckets/main/collections/password-fields/changeset?_expected=10001&_since=10000": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -1029,7 +1014,7 @@ wNuvFqc=
status: { status: 200, statusText: "OK" },
responseBody: "<invalid json",
},
"GET:/v1/buckets/main/collections/password-fields/records?_expected=11001&_sort=-last_modified&_since=11000": {
"GET:/v1/buckets/main/collections/password-fields/changeset?_expected=11001&_since=11000": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -1038,7 +1023,7 @@ wNuvFqc=
],
status: { status: 503, statusText: "Service Unavailable" },
responseBody: {
data: [
changes: [
{
id: "c4f021e3-f68c-4269-ad2a-d4ba87762b35",
last_modified: 4000,
@ -1083,7 +1068,7 @@ wNuvFqc=
],
},
},
"GET:/v1/buckets/main/collections/password-fields/records?_expected=1337&_sort=-last_modified": {
"GET:/v1/buckets/main/collections/password-fields/changeset?_expected=1337": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -1093,7 +1078,9 @@ wNuvFqc=
],
status: { status: 200, statusText: "OK" },
responseBody: {
data: [
metadata: {},
timestamp: 3000,
changes: [
{
id: "312cc78d-9c1f-4291-a4fa-a1be56f6cc69",
last_modified: 3000,
@ -1123,7 +1110,7 @@ wNuvFqc=
},
}),
},
"GET:/v1/buckets/main/collections/language-dictionaries/records": {
"GET:/v1/buckets/main/collections/language-dictionaries/changeset": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -1133,7 +1120,16 @@ wNuvFqc=
],
status: { status: 200, statusText: "OK" },
responseBody: {
data: [
timestamp: 5000000000000,
metadata: {
id: "language-dictionaries",
last_modified: 1234,
signature: {
signature: "xyz",
x5u: `http://localhost:${port}/fake-x5u`,
},
},
changes: [
{
id: "xx",
last_modified: 5000000000000,
@ -1152,27 +1148,7 @@ wNuvFqc=
],
},
},
"GET:/v1/buckets/main/collections/with-local-fields": {
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: "1234"',
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({
data: {
id: "with-local-fields",
last_modified: 1234,
signature: {
signature: "xyz",
x5u: `http://localhost:${port}/fake-x5u`,
},
},
}),
},
"GET:/v1/buckets/main/collections/with-local-fields/records?_expected=2000&_sort=-last_modified": {
"GET:/v1/buckets/main/collections/with-local-fields/changeset?_expected=2000": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -1182,7 +1158,16 @@ wNuvFqc=
],
status: { status: 200, statusText: "OK" },
responseBody: {
data: [
timestamp: 2000,
metadata: {
id: "with-local-fields",
last_modified: 1234,
signature: {
signature: "xyz",
x5u: `http://localhost:${port}/fake-x5u`,
},
},
changes: [
{
id: "c74279ce-fb0a-42a6-ae11-386b567a6119",
last_modified: 2000,
@ -1190,7 +1175,7 @@ wNuvFqc=
],
},
},
"GET:/v1/buckets/main/collections/with-local-fields/records?_expected=3000&_sort=-last_modified&_since=2000": {
"GET:/v1/buckets/main/collections/with-local-fields/changeset?_expected=3000&_since=2000": {
sampleHeaders: [
"Access-Control-Allow-Origin: *",
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
@ -1200,7 +1185,9 @@ wNuvFqc=
],
status: { status: 200, statusText: "OK" },
responseBody: {
data: [
timestamp: 3000,
metadata: {},
changes: [
{
id: "1f5c98b9-6d93-4c13-aa26-978b38695096",
last_modified: 3000,

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

@ -114,40 +114,11 @@ add_task(async function test_check_signatures() {
add_task(async function test_check_synchronization_with_signatures() {
const port = server.identity.primaryPort;
const x5u = `http://localhost:${port}/test_remote_settings_signatures/test_cert_chain.pem`;
// Telemetry reports.
const TELEMETRY_HISTOGRAM_KEY = client.identifier;
// a response to give the client when the cert chain is expected
function makeMetaResponseBody(lastModified, signature) {
return {
data: {
id: "signed",
last_modified: lastModified,
signature: {
x5u: `http://localhost:${port}/test_remote_settings_signatures/test_cert_chain.pem`,
public_key: "fake",
"content-signature": `x5u=http://localhost:${port}/test_remote_settings_signatures/test_cert_chain.pem;p384ecdsa=${signature}`,
signature_encoding: "rs_base64url",
signature,
hash_algorithm: "sha384",
ref: "1yryrnmzou5rf31ou80znpnq8n",
},
},
};
}
function makeMetaResponse(eTag, body, comment) {
return {
comment,
sampleHeaders: [
"Content-Type: application/json; charset=UTF-8",
`ETag: \"${eTag}\"`,
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify(body),
};
}
function registerHandlers(responses) {
function handleResponse(serverTimeMillis, request, response) {
const key = `${request.method}:${request.path}?${request.queryString}`;
@ -292,35 +263,28 @@ add_task(async function test_check_synchronization_with_signatures() {
'ETag: "1000"',
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({ data: [] }),
responseBody: JSON.stringify({
timestamp: 1000,
metadata: {
signature: {
x5u,
signature:
"vxuAg5rDCB-1pul4a91vqSBQRXJG_j7WOYUTswxRSMltdYmbhLRH8R8brQ9YKuNDF56F-w6pn4HWxb076qgKPwgcEBtUeZAO_RtaHXRkRUUgVzAr86yQL4-aJTbv3D6u",
},
},
changes: [],
}),
};
// Valid signature for empty collection.
const RESPONSE_BODY_META_EMPTY_SIG = makeMetaResponseBody(
1000,
"vxuAg5rDCB-1pul4a91vqSBQRXJG_j7WOYUTswxRSMltdYmbhLRH8R8brQ9YKuNDF56F-w6pn4HWxb076qgKPwgcEBtUeZAO_RtaHXRkRUUgVzAr86yQL4-aJTbv3D6u"
);
// The collection metadata containing the signature for the empty
// collection.
const RESPONSE_META_EMPTY_SIG = makeMetaResponse(
1000,
RESPONSE_BODY_META_EMPTY_SIG,
"RESPONSE_META_EMPTY_SIG"
);
// Here, we map request method and path to the available responses
const emptyCollectionResponses = {
"GET:/test_remote_settings_signatures/test_cert_chain.pem?": [
RESPONSE_CERT_CHAIN,
],
"GET:/v1/?": [RESPONSE_SERVER_SETTINGS],
"GET:/v1/buckets/main/collections/signed/records?_expected=1000&_sort=-last_modified": [
"GET:/v1/buckets/main/collections/signed/changeset?_expected=1000": [
RESPONSE_EMPTY_INITIAL,
],
"GET:/v1/buckets/main/collections/signed?_expected=1000": [
RESPONSE_META_EMPTY_SIG,
],
};
//
@ -362,28 +326,23 @@ add_task(async function test_check_synchronization_with_signatures() {
'ETag: "3000"',
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({ data: [RECORD2, RECORD1] }),
responseBody: JSON.stringify({
timestamp: 3000,
metadata: {
signature: {
x5u,
signature:
"dwhJeypadNIyzGj3QdI0KMRTPnHhFPF_j73mNrsPAHKMW46S2Ftf4BzsPMvPMB8h0TjDus13wo_R4l432DHe7tYyMIWXY0PBeMcoe5BREhFIxMxTsh9eGVXBD1e3UwRy",
},
},
changes: [RECORD2, RECORD1],
}),
};
const RESPONSE_BODY_META_TWO_ITEMS_SIG = makeMetaResponseBody(
3000,
"dwhJeypadNIyzGj3QdI0KMRTPnHhFPF_j73mNrsPAHKMW46S2Ftf4BzsPMvPMB8h0TjDus13wo_R4l432DHe7tYyMIWXY0PBeMcoe5BREhFIxMxTsh9eGVXBD1e3UwRy"
);
// A signature response for the collection containg RECORD1 and RECORD2
const RESPONSE_META_TWO_ITEMS_SIG = makeMetaResponse(
3000,
RESPONSE_BODY_META_TWO_ITEMS_SIG,
"RESPONSE_META_TWO_ITEMS_SIG"
);
const twoItemsResponses = {
"GET:/v1/buckets/main/collections/signed/records?_expected=3000&_sort=-last_modified&_since=1000": [
"GET:/v1/buckets/main/collections/signed/changeset?_expected=3000&_since=1000": [
RESPONSE_TWO_ADDED,
],
"GET:/v1/buckets/main/collections/signed?_expected=3000": [
RESPONSE_META_TWO_ITEMS_SIG,
],
};
registerHandlers(twoItemsResponses);
await client.maybeSync(3000);
@ -397,6 +356,8 @@ add_task(async function test_check_synchronization_with_signatures() {
//
// Check the collection with one addition and one removal has a valid
// signature
const THREE_ITEMS_SIG =
"MIEmNghKnkz12UodAAIc3q_Y4a3IJJ7GhHF4JYNYmm8avAGyPM9fYU7NzVo94pzjotG7vmtiYuHyIX2rTHTbT587w0LdRWxipgFd_PC1mHiwUyjFYNqBBG-kifYk7kEw";
// Remove RECORD1, add RECORD3
const RESPONSE_ONE_ADDED_ONE_REMOVED = {
@ -406,28 +367,22 @@ add_task(async function test_check_synchronization_with_signatures() {
'ETag: "4000"',
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({ data: [RECORD3, RECORD1_DELETION] }),
responseBody: JSON.stringify({
timestamp: 4000,
metadata: {
signature: {
x5u,
signature: THREE_ITEMS_SIG,
},
},
changes: [RECORD3, RECORD1_DELETION],
}),
};
const RESPONSE_BODY_META_THREE_ITEMS_SIG = makeMetaResponseBody(
4000,
"MIEmNghKnkz12UodAAIc3q_Y4a3IJJ7GhHF4JYNYmm8avAGyPM9fYU7NzVo94pzjotG7vmtiYuHyIX2rTHTbT587w0LdRWxipgFd_PC1mHiwUyjFYNqBBG-kifYk7kEw"
);
// signature response for the collection containing RECORD2 and RECORD3
const RESPONSE_META_THREE_ITEMS_SIG = makeMetaResponse(
4000,
RESPONSE_BODY_META_THREE_ITEMS_SIG,
"RESPONSE_META_THREE_ITEMS_SIG"
);
const oneAddedOneRemovedResponses = {
"GET:/v1/buckets/main/collections/signed/records?_expected=4000&_sort=-last_modified&_since=3000": [
"GET:/v1/buckets/main/collections/signed/changeset?_expected=4000&_since=3000": [
RESPONSE_ONE_ADDED_ONE_REMOVED,
],
"GET:/v1/buckets/main/collections/signed?_expected=4000": [
RESPONSE_META_THREE_ITEMS_SIG,
],
};
registerHandlers(oneAddedOneRemovedResponses);
await client.maybeSync(4000);
@ -449,22 +404,29 @@ add_task(async function test_check_synchronization_with_signatures() {
'ETag: "4000"',
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({ data: [] }),
responseBody: JSON.stringify({
timestamp: 4000,
metadata: {
signature: {
x5u,
signature: THREE_ITEMS_SIG,
},
},
changes: [],
}),
};
const noOpResponses = {
"GET:/v1/buckets/main/collections/signed/records?_expected=4100&_sort=-last_modified&_since=4000": [
"GET:/v1/buckets/main/collections/signed/changeset?_expected=4100&_since=4000": [
RESPONSE_EMPTY_NO_UPDATE,
],
"GET:/v1/buckets/main/collections/signed?_expected=4100": [
RESPONSE_META_THREE_ITEMS_SIG,
],
};
registerHandlers(noOpResponses);
await client.maybeSync(4100);
equal((await client.get()).length, 2);
console.info("---------------------------------------------------------");
//
// 5.
// - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3]
@ -484,38 +446,42 @@ add_task(async function test_check_synchronization_with_signatures() {
'ETag: "4000"',
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({ data: [RECORD2, RECORD3] }),
responseBody: JSON.stringify({
timestamp: 4000,
metadata: {
signature: {
x5u,
signature: THREE_ITEMS_SIG,
},
},
changes: [RECORD2, RECORD3],
}),
};
// Prepare a (deliberately) bad signature to check the collection state is
// reset if something is inconsistent
const RESPONSE_BODY_META_BAD_SIG = makeMetaResponseBody(
4000,
"aW52YWxpZCBzaWduYXR1cmUK"
);
const RESPONSE_META_BAD_SIG = makeMetaResponse(
4000,
RESPONSE_BODY_META_BAD_SIG,
"RESPONSE_META_BAD_SIG"
);
const RESPONSE_EMPTY_NO_UPDATE_BAD_SIG = {
...RESPONSE_EMPTY_NO_UPDATE,
responseBody: JSON.stringify({
timestamp: 4000,
metadata: {
signature: {
x5u,
signature: "aW52YWxpZCBzaWduYXR1cmUK",
},
},
changes: [],
}),
};
const badSigGoodSigResponses = {
// In this test, we deliberately serve a bad signature initially. The
// subsequent signature returned is a valid one for the three item
// collection.
"GET:/v1/buckets/main/collections/signed?_expected=5000": [
RESPONSE_META_BAD_SIG,
RESPONSE_META_THREE_ITEMS_SIG,
],
// The first collection state is the three item collection (since
// there's a sync with no updates) - but, since the signature is wrong,
// there was sync with no updates before) - but, since the signature is wrong,
// another request will be made...
"GET:/v1/buckets/main/collections/signed/records?_expected=5000&_sort=-last_modified&_since=4000": [
RESPONSE_EMPTY_NO_UPDATE,
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=4000": [
RESPONSE_EMPTY_NO_UPDATE_BAD_SIG,
],
// The next request is for the full collection. This will be checked against the valid signature
// - so the sync should succeed.
"GET:/v1/buckets/main/collections/signed/records?_expected=5000&_sort=-last_modified": [
// Subsequent signature returned is a valid one for the three item
// collection.
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [
RESPONSE_COMPLETE_INITIAL,
],
};
@ -558,23 +524,16 @@ add_task(async function test_check_synchronization_with_signatures() {
// - Sync will be no-op since local is equal to server, no changes to emit.
const badSigGoodOldResponses = {
// In this test, we deliberately serve a bad signature initially. The
// subsequent sitnature returned is a valid one for the three item
// collection.
"GET:/v1/buckets/main/collections/signed?_expected=5000": [
RESPONSE_META_BAD_SIG,
RESPONSE_META_EMPTY_SIG,
],
// The first collection state is the current state (since there's no update
// - but, since the signature is wrong, another request will be made)
"GET:/v1/buckets/main/collections/signed/records?_expected=5000&_sort=-last_modified&_since=4000": [
RESPONSE_EMPTY_NO_UPDATE,
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=4000": [
RESPONSE_EMPTY_NO_UPDATE_BAD_SIG,
],
// The next request is for the full collection. This will be
// checked against the valid signature and last_modified times will be
// compared. Sync should be a no-op, even though the signature is good,
// because the local collection is newer.
"GET:/v1/buckets/main/collections/signed/records?_expected=5000&_sort=-last_modified": [
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [
RESPONSE_EMPTY_INITIAL,
],
};
@ -603,17 +562,23 @@ add_task(async function test_check_synchronization_with_signatures() {
// Check that a tampered local DB will be overwritten and
// sync event contain the appropriate data.
const RESPONSE_COMPLETE_BAD_SIG = {
...RESPONSE_EMPTY_NO_UPDATE,
responseBody: JSON.stringify({
timestamp: 5000,
metadata: {
signature: {
x5u,
signature: "aW52YWxpZCBzaWduYXR1cmUK",
},
},
changes: [RECORD2, RECORD3],
}),
};
const badLocalContentGoodSigResponses = {
// In this test, we deliberately serve a bad signature initially. The
// subsequent signature returned is a valid one for the three item
// collection.
"GET:/v1/buckets/main/collections/signed?_expected=5000": [
RESPONSE_META_BAD_SIG,
RESPONSE_META_THREE_ITEMS_SIG,
],
// The next request is for the full collection. This will be checked
// against the valid signature - so the sync should succeed.
"GET:/v1/buckets/main/collections/signed/records?_expected=5000&_sort=-last_modified": [
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [
RESPONSE_COMPLETE_BAD_SIG,
RESPONSE_COMPLETE_INITIAL,
],
};
@ -659,7 +624,7 @@ add_task(async function test_check_synchronization_with_signatures() {
// Check that a failing signature throws after retry, and that sync changes
// are not applied.
const RESPONSE_ONLY_RECORD4 = {
const RESPONSE_ONLY_RECORD4_BAD_SIG = {
comment: "Delete RECORD3, create RECORD4",
sampleHeaders: [
"Content-Type: application/json; charset=UTF-8",
@ -667,7 +632,14 @@ add_task(async function test_check_synchronization_with_signatures() {
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({
data: [
timestamp: 6000,
metadata: {
signature: {
x5u,
signature: "wrong-sig-here-too",
},
},
changes: [
{
id: "f765df30-b2f1-42f6-9803-7bd5a07b5098",
last_modified: 6000,
@ -676,19 +648,11 @@ add_task(async function test_check_synchronization_with_signatures() {
}),
};
const allBadSigResponses = {
// In this test, we deliberately serve only a bad signature.
"GET:/v1/buckets/main/collections/signed?_expected=6000": [
RESPONSE_META_BAD_SIG,
"GET:/v1/buckets/main/collections/signed/changeset?_expected=6000&_since=4000": [
RESPONSE_EMPTY_NO_UPDATE_BAD_SIG,
],
// The first collection state is the three item collection (since
// there's a sync with no updates) - but, since the signature is wrong,
// another request will be made...
"GET:/v1/buckets/main/collections/signed/records?_expected=6000&_sort=-last_modified&_since=4000": [
RESPONSE_EMPTY_NO_UPDATE,
],
// The next request is for the full collection.
"GET:/v1/buckets/main/collections/signed/records?_expected=6000&_sort=-last_modified": [
RESPONSE_ONLY_RECORD4,
"GET:/v1/buckets/main/collections/signed/changeset?_expected=6000": [
RESPONSE_ONLY_RECORD4_BAD_SIG,
],
};
@ -747,24 +711,26 @@ add_task(async function test_check_synchronization_with_signatures() {
//
// Check that we don't apply changes when signature is missing in remote.
const RESPONSE_META_NO_SIG = {
const RESPONSE_NO_SIG = {
sampleHeaders: [
"Content-Type: application/json; charset=UTF-8",
`ETag: \"123456\"`,
],
status: { status: 200, statusText: "OK" },
responseBody: JSON.stringify({
data: {
metadata: {
last_modified: 123456,
},
changes: [],
timestamp: 123456,
}),
};
const missingSigResponses = {
// In this test, we deliberately serve metadata without the signature attribute.
// As if the collection was not signed.
"GET:/v1/buckets/main/collections/signed?_expected=6000": [
RESPONSE_META_NO_SIG,
"GET:/v1/buckets/main/collections/signed/changeset?_expected=6000": [
RESPONSE_NO_SIG,
],
};