// META: script=/resources/testdriver.js // META: script=/resources/testdriver-vendor.js // META: script=/common/get-host-info.sub.js // META: script=/common/utils.js // META: script=resources/ba-fledge-util.sub.js // META: script=resources/fledge-util.sub.js // META: script=third_party/cbor-js/cbor.js // META: script=/common/subset-tests.js // META: timeout=long // META: variant=?1-6 // META: variant=?7-last "use strict"; // These tests focus on the paggResponse field in AuctionConfig's // serverResponse, i.e. auctions involving private aggregation reporting. NOTE: // Due to debug mode being disabled for B&A's Private Aggregation reports, these // tests just exercise the code paths and ensure that correct number of reports // are sent -- they don't otherwise verify report content. // To better isolate from private aggregation tests run in parallel, // don't use the usual origin here. const MAIN_ORIGIN = OTHER_ORIGIN1; const MAIN_PATH = '/.well-known/private-aggregation/report-protected-audience'; const BUCKET_ONE = new Uint8Array([ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ]); function BigEndianInteger128ToUint8Array(val) { let buffer = new Uint8Array(16); for (let i = 15; i >= 0; i--) { buffer[i] = Number(val & 0xFFn); val >>= 8n; } return buffer; } function createSimplePerOriginPAggResponse( reportingOrigin = MAIN_ORIGIN, igIndex = 0, event = 'reserved.win', bucket = BUCKET_ONE, value = 10, filteringId = null) { let contribution = {}; if (bucket !== null) { contribution.bucket = bucket; } if (value !== null) { contribution.value = value; } if (filteringId !== null) { contribution.filteringId = filteringId; } return { 'reportingOrigin': reportingOrigin, 'igContributions': [{ 'igIndex': igIndex, 'eventContributions': [{'event': event, 'contributions': [contribution]}] }] }; } async function privateAggregationTestWithMutatedServerResponse( test, expectWin, paggResponse, timeout = 5000 /*ms*/, ownerOverride = null) { await resetReports(MAIN_ORIGIN + MAIN_PATH); let result = await BA.testWithMutatedServerResponse( test, expectWin, (msg, uuid) => { msg.paggResponse = paggResponse; }, (ig, uuid) => { ig.ads[0].renderURL = createRenderURL(uuid); }, ownerOverride); createAndNavigateFencedFrame(test, result); const reports = await pollReports(MAIN_PATH, timeout); return reports; } async function testInvalidPAggResponseFields( test, reportingOrigin = MAIN_ORIGIN, igIndex = 0, event = 'reserved.win', bucket = '1', value = 10, filteringId = null) { const paggResponse = [createSimplePerOriginPAggResponse( reportingOrigin, igIndex, event, bucket, value, filteringId)]; let reports = await privateAggregationTestWithMutatedServerResponse( test, /*expectWin=*/ true, paggResponse, /*timeout=*/ 5000, MAIN_ORIGIN); assert_equals(reports, null); } // The next few methods are modified from Chrome-specific // wpt_internal/private-aggregation/resources/utils.js const resetReports = url => { url = `${url}?clear_stash=true`; const options = { method: 'POST', mode: 'no-cors', }; return fetch(url, options); }; const delay = ms => new Promise(resolve => step_timeout(resolve, ms)); async function pollReports(path, wait_for = 1, timeout = 5000 /*ms*/) { const targetUrl = new URL(path, MAIN_ORIGIN); const endTime = performance.now() + timeout; const outReports = []; do { const response = await fetch(targetUrl); assert_true(response.ok, 'pollReports() fetch response should be OK.'); const reports = await response.json(); outReports.push(...reports); if (outReports.length >= wait_for) { break; } await delay(/*ms=*/ 100); } while (performance.now() < endTime); return outReports.length ? outReports : null; }; /** * Verifies that a report's aggregation_service_payloads has the expected * fields. Currently for B&A's PAgg reports, debug mode is disabled, so we * cannot check contributions in payload. */ const verifyAggregationServicePayloads = (aggregation_service_payloads) => { assert_equals(aggregation_service_payloads.length, 1); const payload_obj = aggregation_service_payloads[0]; assert_own_property(payload_obj, 'key_id'); assert_own_property(payload_obj, 'payload'); // Check the payload is base64 encoded. We do not decrypt the payload to // test its contents. atob(payload_obj.payload); // Check there are no extra keys assert_equals(Object.keys(payload_obj).length, expected_payload ? 3 : 2); }; /** * Verifies that a report has the expected fields. The `expected_payload` should * be undefined. */ const verifyReport = (report, reporting_origin) => { assert_own_property(report, 'shared_info'); let shared_info = JSON.parse(report.shared_info); assert_own_property(shared_info, 'reporting_origin'); assert_equals(shared_info.reporting_origin, reporting_origin); assert_own_property(report, 'aggregation_service_payloads'); assert_own_property(report, 'aggregation_coordinator_origin'); // TODO(qingxinwu): Maybe add tests for coordinator origin. assert_not_own_property(report, 'debug_key'); // Check there are no extra keys let expected_length = 3; assert_equals(Object.keys(report).length, expected_length); }; subsetTest(promise_test, async test => { await testInvalidPAggResponseFields(test, 'http://non-https.com'); }, 'Private aggregation - invalid reporting origin'); subsetTest( promise_test, async test => {await testInvalidPAggResponseFields(test, MAIN_ORIGIN, 100)}, 'Private aggregation - invalid index'); subsetTest(promise_test, async test => { await testInvalidPAggResponseFields( test, MAIN_ORIGIN, 0, 'reserved.not-supported'); }, 'Private aggregation - invalid event'); subsetTest(promise_test, async test => { await testInvalidPAggResponseFields( test, MAIN_ORIGIN, 0, 'reserved.win', /*bucket=*/ null); }, 'Private aggregation - missing required bucket'); subsetTest(promise_test, async test => { await testInvalidPAggResponseFields( test, MAIN_ORIGIN, 0, 'reserved.win', new Uint8Array([ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 ])); }, 'Private aggregation - bucket is bigger than 128 bits'); subsetTest(promise_test, async test => { await testInvalidPAggResponseFields( test, MAIN_ORIGIN, 0, 'reserved.win', BUCKET_ONE, /*value=*/ null); }, 'Private aggregation - missing required value'); subsetTest(promise_test, async test => { await testInvalidPAggResponseFields( test, MAIN_ORIGIN, 0, 'reserved.win', BUCKET_ONE, 10, 10000); }, 'Private aggregation - invalid filteringId'); subsetTest(promise_test, async test => { const paggResponse = [createSimplePerOriginPAggResponse()]; let reports = await privateAggregationTestWithMutatedServerResponse( test, /*expectWin=*/ true, paggResponse, /*timeout=*/ 6000, MAIN_ORIGIN); assert_equals(reports.length, 1); let report = JSON.parse(reports[0]); verifyReport(report, MAIN_ORIGIN); }, 'Private aggregation - successfully sent report'); // TODO(qingxinwu): may add a test for custom event type if possible. subsetTest(promise_test, async test => { const paggResponse = [{ 'reportingOrigin': MAIN_ORIGIN, 'igContributions': [{ 'igIndex': 0, 'eventContributions': [ { 'event': 'reserved.win', 'contributions': [{'value': 10}, {'bucket': BUCKET_ONE, 'value': 11}] }, { 'event': 'reserved.not-supported', 'contributions': [{'bucket': BigEndianInteger128ToUint8Array(2n), 'value': 22}] }, ] }] }]; let reports = await privateAggregationTestWithMutatedServerResponse( test, /*expectWin=*/ true, paggResponse, /*timeout=*/ 6000, MAIN_ORIGIN); assert_equals(reports.length, 1); let report = JSON.parse(reports[0]); verifyReport(report, MAIN_ORIGIN); }, 'Private aggregation - invalid contributions do not affect valid ones'); // TODO(qingxinwu): privateAggregation multi-seller.