зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1620186 - Fetch from changeset endpoint r=glasserc
Differential Revision: https://phabricator.services.mozilla.com/D71570
This commit is contained in:
Родитель
3c64c7a3b2
Коммит
e95ee4d2f4
|
@ -765,24 +765,17 @@ class RemoteSettingsClient extends EventEmitter {
|
||||||
options = {}
|
options = {}
|
||||||
) {
|
) {
|
||||||
const { retry = false } = options;
|
const { retry = false } = options;
|
||||||
|
const since = retry || !localTimestamp ? undefined : `${localTimestamp}`;
|
||||||
|
|
||||||
// Fetch collection metadata and list of changes from server
|
// Fetch collection metadata and list of changes from server.
|
||||||
// (or all records on retry).
|
console.debug(
|
||||||
const client = this.httpClient();
|
`Fetch changes from server (expected=${expectedTimestamp}, since=${since})`
|
||||||
const [
|
);
|
||||||
|
const {
|
||||||
metadata,
|
metadata,
|
||||||
{ data: remoteRecords, last_modified: remoteTimestamp },
|
remoteTimestamp,
|
||||||
] = await Promise.all([
|
remoteRecords,
|
||||||
client.getData({
|
} = await this._fetchChangeset(expectedTimestamp, since);
|
||||||
query: { _expected: expectedTimestamp },
|
|
||||||
}),
|
|
||||||
client.listRecords({
|
|
||||||
filters: {
|
|
||||||
_expected: expectedTimestamp,
|
|
||||||
},
|
|
||||||
since: retry || !localTimestamp ? undefined : `${localTimestamp}`,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// We build a sync result, based on remote changes.
|
// We build a sync result, based on remote changes.
|
||||||
const syncResult = {
|
const syncResult = {
|
||||||
|
@ -893,6 +886,36 @@ class RemoteSettingsClient extends EventEmitter {
|
||||||
return syncResult;
|
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,
|
* Use the filter func to filter the lists of changes obtained from synchronization,
|
||||||
* and return them along with the filtered list of local records.
|
* and return them along with the filtered list of local records.
|
||||||
|
|
|
@ -69,11 +69,7 @@ function run_test() {
|
||||||
handleResponse
|
handleResponse
|
||||||
);
|
);
|
||||||
server.registerPathHandler(
|
server.registerPathHandler(
|
||||||
"/v1/buckets/main/collections/password-fields",
|
"/v1/buckets/main/collections/password-fields/changeset",
|
||||||
handleResponse
|
|
||||||
);
|
|
||||||
server.registerPathHandler(
|
|
||||||
"/v1/buckets/main/collections/password-fields/records",
|
|
||||||
handleResponse
|
handleResponse
|
||||||
);
|
);
|
||||||
server.registerPathHandler(
|
server.registerPathHandler(
|
||||||
|
@ -81,15 +77,11 @@ function run_test() {
|
||||||
handleResponse
|
handleResponse
|
||||||
);
|
);
|
||||||
server.registerPathHandler(
|
server.registerPathHandler(
|
||||||
"/v1/buckets/main/collections/language-dictionaries/records",
|
"/v1/buckets/main/collections/language-dictionaries/changeset",
|
||||||
handleResponse
|
handleResponse
|
||||||
);
|
);
|
||||||
server.registerPathHandler(
|
server.registerPathHandler(
|
||||||
"/v1/buckets/main/collections/with-local-fields",
|
"/v1/buckets/main/collections/with-local-fields/changeset",
|
||||||
handleResponse
|
|
||||||
);
|
|
||||||
server.registerPathHandler(
|
|
||||||
"/v1/buckets/main/collections/with-local-fields/records",
|
|
||||||
handleResponse
|
handleResponse
|
||||||
);
|
);
|
||||||
server.registerPathHandler("/fake-x5u", 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": {
|
"GET:/fake-x5u": {
|
||||||
sampleHeaders: ["Content-Type: application/octet-stream"],
|
sampleHeaders: ["Content-Type: application/octet-stream"],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
|
@ -940,7 +912,7 @@ ZARKjbu1TuYQHf0fs+GwID8zeLc2zJL7UzcHFwwQ6Nda9OJN4uPAuC/BKaIpxCLL
|
||||||
wNuvFqc=
|
wNuvFqc=
|
||||||
-----END CERTIFICATE-----`,
|
-----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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -950,7 +922,16 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: {
|
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",
|
id: "9d500963-d80e-3a91-6e74-66f3811b99cc",
|
||||||
last_modified: 3000,
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -970,7 +951,9 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: {
|
responseBody: {
|
||||||
data: [
|
metadata: {},
|
||||||
|
timestamp: 4000,
|
||||||
|
changes: [
|
||||||
{
|
{
|
||||||
id: "aabad965-e556-ffe7-4191-074f5dee3df3",
|
id: "aabad965-e556-ffe7-4191-074f5dee3df3",
|
||||||
last_modified: 4000,
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -996,7 +979,9 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: {
|
responseBody: {
|
||||||
data: [
|
metadata: {},
|
||||||
|
timestamp: 5000,
|
||||||
|
changes: [
|
||||||
{
|
{
|
||||||
id: "aabad965-e556-ffe7-4191-074f5dee3df3",
|
id: "aabad965-e556-ffe7-4191-074f5dee3df3",
|
||||||
deleted: true,
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -1018,7 +1003,7 @@ wNuvFqc=
|
||||||
error: "Service Unavailable",
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -1029,7 +1014,7 @@ wNuvFqc=
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: "<invalid json",
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -1038,7 +1023,7 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 503, statusText: "Service Unavailable" },
|
status: { status: 503, statusText: "Service Unavailable" },
|
||||||
responseBody: {
|
responseBody: {
|
||||||
data: [
|
changes: [
|
||||||
{
|
{
|
||||||
id: "c4f021e3-f68c-4269-ad2a-d4ba87762b35",
|
id: "c4f021e3-f68c-4269-ad2a-d4ba87762b35",
|
||||||
last_modified: 4000,
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -1093,7 +1078,9 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: {
|
responseBody: {
|
||||||
data: [
|
metadata: {},
|
||||||
|
timestamp: 3000,
|
||||||
|
changes: [
|
||||||
{
|
{
|
||||||
id: "312cc78d-9c1f-4291-a4fa-a1be56f6cc69",
|
id: "312cc78d-9c1f-4291-a4fa-a1be56f6cc69",
|
||||||
last_modified: 3000,
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -1133,7 +1120,16 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: {
|
responseBody: {
|
||||||
data: [
|
timestamp: 5000000000000,
|
||||||
|
metadata: {
|
||||||
|
id: "language-dictionaries",
|
||||||
|
last_modified: 1234,
|
||||||
|
signature: {
|
||||||
|
signature: "xyz",
|
||||||
|
x5u: `http://localhost:${port}/fake-x5u`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
{
|
{
|
||||||
id: "xx",
|
id: "xx",
|
||||||
last_modified: 5000000000000,
|
last_modified: 5000000000000,
|
||||||
|
@ -1152,27 +1148,7 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"GET:/v1/buckets/main/collections/with-local-fields": {
|
"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",
|
|
||||||
"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": {
|
|
||||||
sampleHeaders: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -1182,7 +1158,16 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: {
|
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",
|
id: "c74279ce-fb0a-42a6-ae11-386b567a6119",
|
||||||
last_modified: 2000,
|
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: [
|
sampleHeaders: [
|
||||||
"Access-Control-Allow-Origin: *",
|
"Access-Control-Allow-Origin: *",
|
||||||
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
"Access-Control-Expose-Headers: Retry-After, Content-Length, Alert, Backoff",
|
||||||
|
@ -1200,7 +1185,9 @@ wNuvFqc=
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: {
|
responseBody: {
|
||||||
data: [
|
timestamp: 3000,
|
||||||
|
metadata: {},
|
||||||
|
changes: [
|
||||||
{
|
{
|
||||||
id: "1f5c98b9-6d93-4c13-aa26-978b38695096",
|
id: "1f5c98b9-6d93-4c13-aa26-978b38695096",
|
||||||
last_modified: 3000,
|
last_modified: 3000,
|
||||||
|
|
|
@ -114,40 +114,11 @@ add_task(async function test_check_signatures() {
|
||||||
add_task(async function test_check_synchronization_with_signatures() {
|
add_task(async function test_check_synchronization_with_signatures() {
|
||||||
const port = server.identity.primaryPort;
|
const port = server.identity.primaryPort;
|
||||||
|
|
||||||
|
const x5u = `http://localhost:${port}/test_remote_settings_signatures/test_cert_chain.pem`;
|
||||||
|
|
||||||
// Telemetry reports.
|
// Telemetry reports.
|
||||||
const TELEMETRY_HISTOGRAM_KEY = client.identifier;
|
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 registerHandlers(responses) {
|
||||||
function handleResponse(serverTimeMillis, request, response) {
|
function handleResponse(serverTimeMillis, request, response) {
|
||||||
const key = `${request.method}:${request.path}?${request.queryString}`;
|
const key = `${request.method}:${request.path}?${request.queryString}`;
|
||||||
|
@ -292,35 +263,28 @@ add_task(async function test_check_synchronization_with_signatures() {
|
||||||
'ETag: "1000"',
|
'ETag: "1000"',
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
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
|
// Here, we map request method and path to the available responses
|
||||||
const emptyCollectionResponses = {
|
const emptyCollectionResponses = {
|
||||||
"GET:/test_remote_settings_signatures/test_cert_chain.pem?": [
|
"GET:/test_remote_settings_signatures/test_cert_chain.pem?": [
|
||||||
RESPONSE_CERT_CHAIN,
|
RESPONSE_CERT_CHAIN,
|
||||||
],
|
],
|
||||||
"GET:/v1/?": [RESPONSE_SERVER_SETTINGS],
|
"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,
|
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"',
|
'ETag: "3000"',
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
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 = {
|
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,
|
RESPONSE_TWO_ADDED,
|
||||||
],
|
],
|
||||||
"GET:/v1/buckets/main/collections/signed?_expected=3000": [
|
|
||||||
RESPONSE_META_TWO_ITEMS_SIG,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
registerHandlers(twoItemsResponses);
|
registerHandlers(twoItemsResponses);
|
||||||
await client.maybeSync(3000);
|
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
|
// Check the collection with one addition and one removal has a valid
|
||||||
// signature
|
// signature
|
||||||
|
const THREE_ITEMS_SIG =
|
||||||
|
"MIEmNghKnkz12UodAAIc3q_Y4a3IJJ7GhHF4JYNYmm8avAGyPM9fYU7NzVo94pzjotG7vmtiYuHyIX2rTHTbT587w0LdRWxipgFd_PC1mHiwUyjFYNqBBG-kifYk7kEw";
|
||||||
|
|
||||||
// Remove RECORD1, add RECORD3
|
// Remove RECORD1, add RECORD3
|
||||||
const RESPONSE_ONE_ADDED_ONE_REMOVED = {
|
const RESPONSE_ONE_ADDED_ONE_REMOVED = {
|
||||||
|
@ -406,28 +367,22 @@ add_task(async function test_check_synchronization_with_signatures() {
|
||||||
'ETag: "4000"',
|
'ETag: "4000"',
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
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 = {
|
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,
|
RESPONSE_ONE_ADDED_ONE_REMOVED,
|
||||||
],
|
],
|
||||||
"GET:/v1/buckets/main/collections/signed?_expected=4000": [
|
|
||||||
RESPONSE_META_THREE_ITEMS_SIG,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
registerHandlers(oneAddedOneRemovedResponses);
|
registerHandlers(oneAddedOneRemovedResponses);
|
||||||
await client.maybeSync(4000);
|
await client.maybeSync(4000);
|
||||||
|
@ -449,22 +404,29 @@ add_task(async function test_check_synchronization_with_signatures() {
|
||||||
'ETag: "4000"',
|
'ETag: "4000"',
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: JSON.stringify({ data: [] }),
|
responseBody: JSON.stringify({
|
||||||
|
timestamp: 4000,
|
||||||
|
metadata: {
|
||||||
|
signature: {
|
||||||
|
x5u,
|
||||||
|
signature: THREE_ITEMS_SIG,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
changes: [],
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const noOpResponses = {
|
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,
|
RESPONSE_EMPTY_NO_UPDATE,
|
||||||
],
|
],
|
||||||
"GET:/v1/buckets/main/collections/signed?_expected=4100": [
|
|
||||||
RESPONSE_META_THREE_ITEMS_SIG,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
registerHandlers(noOpResponses);
|
registerHandlers(noOpResponses);
|
||||||
await client.maybeSync(4100);
|
await client.maybeSync(4100);
|
||||||
|
|
||||||
equal((await client.get()).length, 2);
|
equal((await client.get()).length, 2);
|
||||||
|
|
||||||
|
console.info("---------------------------------------------------------");
|
||||||
//
|
//
|
||||||
// 5.
|
// 5.
|
||||||
// - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3]
|
// - collection: [RECORD2, RECORD3] -> [RECORD2, RECORD3]
|
||||||
|
@ -484,38 +446,42 @@ add_task(async function test_check_synchronization_with_signatures() {
|
||||||
'ETag: "4000"',
|
'ETag: "4000"',
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
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
|
const RESPONSE_EMPTY_NO_UPDATE_BAD_SIG = {
|
||||||
// reset if something is inconsistent
|
...RESPONSE_EMPTY_NO_UPDATE,
|
||||||
const RESPONSE_BODY_META_BAD_SIG = makeMetaResponseBody(
|
responseBody: JSON.stringify({
|
||||||
4000,
|
timestamp: 4000,
|
||||||
"aW52YWxpZCBzaWduYXR1cmUK"
|
metadata: {
|
||||||
);
|
signature: {
|
||||||
const RESPONSE_META_BAD_SIG = makeMetaResponse(
|
x5u,
|
||||||
4000,
|
signature: "aW52YWxpZCBzaWduYXR1cmUK",
|
||||||
RESPONSE_BODY_META_BAD_SIG,
|
},
|
||||||
"RESPONSE_META_BAD_SIG"
|
},
|
||||||
);
|
changes: [],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
const badSigGoodSigResponses = {
|
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
|
// 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...
|
// another request will be made...
|
||||||
"GET:/v1/buckets/main/collections/signed/records?_expected=5000&_sort=-last_modified&_since=4000": [
|
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=4000": [
|
||||||
RESPONSE_EMPTY_NO_UPDATE,
|
RESPONSE_EMPTY_NO_UPDATE_BAD_SIG,
|
||||||
],
|
],
|
||||||
// The next request is for the full collection. This will be checked against the valid signature
|
// Subsequent signature returned is a valid one for the three item
|
||||||
// - so the sync should succeed.
|
// collection.
|
||||||
"GET:/v1/buckets/main/collections/signed/records?_expected=5000&_sort=-last_modified": [
|
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [
|
||||||
RESPONSE_COMPLETE_INITIAL,
|
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.
|
// - Sync will be no-op since local is equal to server, no changes to emit.
|
||||||
|
|
||||||
const badSigGoodOldResponses = {
|
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
|
// The first collection state is the current state (since there's no update
|
||||||
// - but, since the signature is wrong, another request will be made)
|
// - 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": [
|
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000&_since=4000": [
|
||||||
RESPONSE_EMPTY_NO_UPDATE,
|
RESPONSE_EMPTY_NO_UPDATE_BAD_SIG,
|
||||||
],
|
],
|
||||||
// The next request is for the full collection. This will be
|
// The next request is for the full collection. This will be
|
||||||
// checked against the valid signature and last_modified times 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,
|
// compared. Sync should be a no-op, even though the signature is good,
|
||||||
// because the local collection is newer.
|
// 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,
|
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
|
// Check that a tampered local DB will be overwritten and
|
||||||
// sync event contain the appropriate data.
|
// 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 = {
|
const badLocalContentGoodSigResponses = {
|
||||||
// In this test, we deliberately serve a bad signature initially. The
|
"GET:/v1/buckets/main/collections/signed/changeset?_expected=5000": [
|
||||||
// subsequent signature returned is a valid one for the three item
|
RESPONSE_COMPLETE_BAD_SIG,
|
||||||
// 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": [
|
|
||||||
RESPONSE_COMPLETE_INITIAL,
|
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
|
// Check that a failing signature throws after retry, and that sync changes
|
||||||
// are not applied.
|
// are not applied.
|
||||||
|
|
||||||
const RESPONSE_ONLY_RECORD4 = {
|
const RESPONSE_ONLY_RECORD4_BAD_SIG = {
|
||||||
comment: "Delete RECORD3, create RECORD4",
|
comment: "Delete RECORD3, create RECORD4",
|
||||||
sampleHeaders: [
|
sampleHeaders: [
|
||||||
"Content-Type: application/json; charset=UTF-8",
|
"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" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: JSON.stringify({
|
responseBody: JSON.stringify({
|
||||||
data: [
|
timestamp: 6000,
|
||||||
|
metadata: {
|
||||||
|
signature: {
|
||||||
|
x5u,
|
||||||
|
signature: "wrong-sig-here-too",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
changes: [
|
||||||
{
|
{
|
||||||
id: "f765df30-b2f1-42f6-9803-7bd5a07b5098",
|
id: "f765df30-b2f1-42f6-9803-7bd5a07b5098",
|
||||||
last_modified: 6000,
|
last_modified: 6000,
|
||||||
|
@ -676,19 +648,11 @@ add_task(async function test_check_synchronization_with_signatures() {
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
const allBadSigResponses = {
|
const allBadSigResponses = {
|
||||||
// In this test, we deliberately serve only a bad signature.
|
"GET:/v1/buckets/main/collections/signed/changeset?_expected=6000&_since=4000": [
|
||||||
"GET:/v1/buckets/main/collections/signed?_expected=6000": [
|
RESPONSE_EMPTY_NO_UPDATE_BAD_SIG,
|
||||||
RESPONSE_META_BAD_SIG,
|
|
||||||
],
|
],
|
||||||
// The first collection state is the three item collection (since
|
"GET:/v1/buckets/main/collections/signed/changeset?_expected=6000": [
|
||||||
// there's a sync with no updates) - but, since the signature is wrong,
|
RESPONSE_ONLY_RECORD4_BAD_SIG,
|
||||||
// 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,
|
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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.
|
// Check that we don't apply changes when signature is missing in remote.
|
||||||
|
|
||||||
const RESPONSE_META_NO_SIG = {
|
const RESPONSE_NO_SIG = {
|
||||||
sampleHeaders: [
|
sampleHeaders: [
|
||||||
"Content-Type: application/json; charset=UTF-8",
|
"Content-Type: application/json; charset=UTF-8",
|
||||||
`ETag: \"123456\"`,
|
`ETag: \"123456\"`,
|
||||||
],
|
],
|
||||||
status: { status: 200, statusText: "OK" },
|
status: { status: 200, statusText: "OK" },
|
||||||
responseBody: JSON.stringify({
|
responseBody: JSON.stringify({
|
||||||
data: {
|
metadata: {
|
||||||
last_modified: 123456,
|
last_modified: 123456,
|
||||||
},
|
},
|
||||||
|
changes: [],
|
||||||
|
timestamp: 123456,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
const missingSigResponses = {
|
const missingSigResponses = {
|
||||||
// In this test, we deliberately serve metadata without the signature attribute.
|
// In this test, we deliberately serve metadata without the signature attribute.
|
||||||
// As if the collection was not signed.
|
// As if the collection was not signed.
|
||||||
"GET:/v1/buckets/main/collections/signed?_expected=6000": [
|
"GET:/v1/buckets/main/collections/signed/changeset?_expected=6000": [
|
||||||
RESPONSE_META_NO_SIG,
|
RESPONSE_NO_SIG,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче