From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- .../protected-audience-surface-failure.https.html | 70 +++++++ .../protected-audience-surface-success.https.html | 98 ++++++++++ .../resources/protected-audience-helper-module.js | 209 +++++++++++++++++++++ ...rotected_audience_event_level_report_handler.py | 43 +++++ .../tests/private-aggregation/resources/reports.py | 88 +++++++++ .../resources/shared-storage-helper-module.js | 23 +++ .../tests/private-aggregation/resources/util.js | 24 +++ ...ared-storage-permissions-policy-none.https.html | 23 +++ ...rage-permissions-policy-none.https.html.headers | 1 + .../shared-storage-surface-context-id.https.html | 73 +++++++ .../shared-storage-surface-failure-2.https.html | 26 +++ .../shared-storage-surface-failure.https.html | 74 ++++++++ .../shared-storage-surface-success-2.https.html | 27 +++ .../shared-storage-surface-success.https.html | 71 +++++++ 14 files changed, 850 insertions(+) create mode 100644 testing/web-platform/tests/private-aggregation/protected-audience-surface-failure.https.html create mode 100644 testing/web-platform/tests/private-aggregation/protected-audience-surface-success.https.html create mode 100644 testing/web-platform/tests/private-aggregation/resources/protected-audience-helper-module.js create mode 100644 testing/web-platform/tests/private-aggregation/resources/protected_audience_event_level_report_handler.py create mode 100644 testing/web-platform/tests/private-aggregation/resources/reports.py create mode 100644 testing/web-platform/tests/private-aggregation/resources/shared-storage-helper-module.js create mode 100644 testing/web-platform/tests/private-aggregation/resources/util.js create mode 100644 testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html create mode 100644 testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html.headers create mode 100644 testing/web-platform/tests/private-aggregation/shared-storage-surface-context-id.https.html create mode 100644 testing/web-platform/tests/private-aggregation/shared-storage-surface-failure-2.https.html create mode 100644 testing/web-platform/tests/private-aggregation/shared-storage-surface-failure.https.html create mode 100644 testing/web-platform/tests/private-aggregation/shared-storage-surface-success-2.https.html create mode 100644 testing/web-platform/tests/private-aggregation/shared-storage-surface-success.https.html (limited to 'testing/web-platform/tests/private-aggregation') diff --git a/testing/web-platform/tests/private-aggregation/protected-audience-surface-failure.https.html b/testing/web-platform/tests/private-aggregation/protected-audience-surface-failure.https.html new file mode 100644 index 0000000000..b512afc25d --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/protected-audience-surface-failure.https.html @@ -0,0 +1,70 @@ + + + + + + + + + + diff --git a/testing/web-platform/tests/private-aggregation/protected-audience-surface-success.https.html b/testing/web-platform/tests/private-aggregation/protected-audience-surface-success.https.html new file mode 100644 index 0000000000..1d89032109 --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/protected-audience-surface-success.https.html @@ -0,0 +1,98 @@ + + + + + + + + + + diff --git a/testing/web-platform/tests/private-aggregation/resources/protected-audience-helper-module.js b/testing/web-platform/tests/private-aggregation/resources/protected-audience-helper-module.js new file mode 100644 index 0000000000..be4f01379e --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/resources/protected-audience-helper-module.js @@ -0,0 +1,209 @@ +// This file is adapted from /fledge/tentative/resources/fledge-util.js, +// removing unnecessary logic and modifying to allow it to be run in the +// private-aggregation directory. + +"use strict;" + +const FULL_URL = window.location.href; +let BASE_URL = FULL_URL.substring(0, FULL_URL.lastIndexOf('/') + 1) +const BASE_PATH = (new URL(BASE_URL)).pathname; +const DEFAULT_INTEREST_GROUP_NAME = 'default name'; + +// Use python script files under fledge directory +const FLEDGE_DIR = '/fledge/tentative/'; +const FLEDGE_BASE_URL = BASE_URL.replace(BASE_PATH, FLEDGE_DIR); + +// Sleep method that waits for prescribed number of milliseconds. +const sleep = ms => new Promise(resolve => step_timeout(resolve, ms)); + +// Generates a UUID by token. +function generateUuid() { + let uuid = token(); + return uuid; +} + +// Creates a URL that will be sent to the handler. +// `uuid` is used to identify the stash shard to use. +// `operate` is used to set action as write or read. +// `report` is used to carry the message for write requests. +function createReportingURL(uuid, operation, report = 'default-report') { + let url = new URL(`${window.location.origin}${BASE_PATH}resources/protected_audience_event_level_report_handler.py`); + url.searchParams.append('uuid', uuid); + url.searchParams.append('operation', operation); + + if (report) + url.searchParams.append('report', report); + + return url.toString(); +} + +function createWritingURL(uuid, report) { + return createReportingURL(uuid, 'write'); +} + +function createReadingURL(uuid) { + return createReportingURL(uuid, 'read'); +} + +async function waitForObservedReports(uuid, expectedNumReports, timeout = 5000 /*ms*/) { + expectedReports = Array(expectedNumReports).fill('default-report'); + const reportURL = createReadingURL(uuid); + let startTime = performance.now(); + + while (performance.now() - startTime < timeout) { + let response = await fetch(reportURL, { credentials: 'omit', mode: 'cors' }); + let actualReports = await response.json(); + + // If expected number of reports have been observed, compare with list of + // all expected reports and exit. + if (actualReports.length == expectedReports.length) { + assert_array_equals(actualReports.sort(), expectedReports); + return; + } + + await sleep(/*ms=*/ 100); + } + assert_unreached("Report fetching timed out: " + uuid); +} + +// Creates a bidding script with the provided code in the method bodies. The +// bidding script's generateBid() method will return a bid of 9 for the first +// ad, after the passed in code in the "generateBid" input argument has been +// run, unless it returns something or throws. +// +// The default reportWin() method is empty. +function createBiddingScriptURL(params = {}) { + let url = new URL(`${FLEDGE_BASE_URL}resources/bidding-logic.sub.py`); + if (params.generateBid) + url.searchParams.append('generateBid', params.generateBid); + if (params.reportWin) + url.searchParams.append('reportWin', params.reportWin); + if (params.error) + url.searchParams.append('error', params.error); + if (params.bid) + url.searchParams.append('bid', params.bid); + return url.toString(); +} + +// Creates a decision script with the provided code in the method bodies. The +// decision script's scoreAd() method will reject ads with renderURLs that +// don't ends with "uuid", and will return a score equal to the bid, after the +// passed in code in the "scoreAd" input argument has been run, unless it +// returns something or throws. +// +// The default reportResult() method is empty. +function createDecisionScriptURL(uuid, params = {}) { + let url = new URL(`${FLEDGE_BASE_URL}resources/decision-logic.sub.py`); + url.searchParams.append('uuid', uuid); + if (params.scoreAd) + url.searchParams.append('scoreAd', params.scoreAd); + if (params.reportResult) + url.searchParams.append('reportResult', params.reportResult); + if (params.error) + url.searchParams.append('error', params.error); + return url.toString(); +} + +// Creates a renderURL for an ad that runs the passed in "script". "uuid" has +// no effect, beyond making the URL distinct between tests, and being verified +// by the decision logic script before accepting a bid. "uuid" is expected to +// be last. +function createRenderURL(uuid, script) { + let url = new URL(`${FLEDGE_BASE_URL}resources/fenced-frame.sub.py`); + if (script) + url.searchParams.append('script', script); + url.searchParams.append('uuid', uuid); + return url.toString(); +} + +// Joins an interest group that, by default, is owned by the current frame's +// origin, is named DEFAULT_INTEREST_GROUP_NAME, has a bidding script that +// issues a bid of 9 with a renderURL of "https://not.checked.test/${uuid}". +// `interestGroupOverrides` is required to override fields in the joined +// interest group. +async function joinInterestGroup(test, uuid, interestGroupOverrides) { + const INTEREST_GROUP_LIFETIME_SECS = 60; + + let interestGroup = { + owner: window.location.origin, + name: DEFAULT_INTEREST_GROUP_NAME, + ads: [{renderURL: createRenderURL(uuid)}], + ...interestGroupOverrides + }; + + await navigator.joinAdInterestGroup(interestGroup, + INTEREST_GROUP_LIFETIME_SECS); + test.add_cleanup( + async () => {await navigator.leaveAdInterestGroup(interestGroup)}); +} + +// Runs a FLEDGE auction and returns the result. `auctionConfigOverrides` is +// required to override fields in the auction configuration. +async function runBasicFledgeAuction(test, uuid, auctionConfigOverrides) { + let auctionConfig = { + seller: window.location.origin, + interestGroupBuyers: [window.location.origin], + resolveToConfig: true, + ...auctionConfigOverrides + }; + return await navigator.runAdAuction(auctionConfig); +} + +// Calls runBasicFledgeAuction(), expecting the auction to have a winner. +// Creates a fenced frame that will be destroyed on completion of "test", and +// navigates it to the URN URL returned by the auction. Does not wait for the +// fenced frame to finish loading, since there's no API that can do that. +async function runBasicFledgeAuctionAndNavigate(test, uuid, + auctionConfigOverrides) { + let config = await runBasicFledgeAuction(test, uuid, auctionConfigOverrides); + assert_true(config instanceof FencedFrameConfig, + `Wrong value type returned from auction: ${config.constructor.type}`); + + let fencedFrame = document.createElement('fencedframe'); + fencedFrame.mode = 'opaque-ads'; + fencedFrame.config = config; + document.body.appendChild(fencedFrame); + test.add_cleanup(() => { document.body.removeChild(fencedFrame); }); +} + +// Joins an interest group and runs an auction, expecting no winner to be +// returned. "testConfig" can optionally modify the interest group or +// auctionConfig. +async function runBasicFledgeTestExpectingNoWinner(test, testConfig) { + const uuid = generateUuid(test); + await joinInterestGroup(test, uuid, testConfig.interestGroupOverrides); + let result = await runBasicFledgeAuction( + test, uuid, testConfig.auctionConfigOverrides); + assert_true(result === null, 'Auction unexpectedly had a winner'); +} + +// Test helper for report phase of auctions that lets the caller specify the +// body of scoreAd(), reportResult(), generateBid() and reportWin(), as well as +// additional arguments to be passed to joinAdInterestGroup() and runAdAuction() +async function runReportTest(test, uuid, codeToInsert, + expectedNumReports = 0, overrides = {}) { + let generateBid = codeToInsert.generateBid; + let scoreAd = codeToInsert.scoreAd; + let reportWin = codeToInsert.reportWin; + let reportResult = codeToInsert.reportResult; + + let extraInterestGroupOverrides = overrides.joinAdInterestGroup || {} + let extraAuctionConfigOverrides = overrides.runAdAuction || {} + + let interestGroupOverrides = { + biddingLogicURL: createBiddingScriptURL({ generateBid, reportWin }), + ...extraInterestGroupOverrides + }; + let auctionConfigOverrides = { + decisionLogicURL: createDecisionScriptURL( + uuid, { scoreAd, reportResult }), + ...extraAuctionConfigOverrides + } + + await joinInterestGroup(test, uuid, interestGroupOverrides); + await runBasicFledgeAuctionAndNavigate(test, uuid, auctionConfigOverrides); + + if (expectedNumReports) { + await waitForObservedReports(uuid, expectedNumReports); + } +} diff --git a/testing/web-platform/tests/private-aggregation/resources/protected_audience_event_level_report_handler.py b/testing/web-platform/tests/private-aggregation/resources/protected_audience_event_level_report_handler.py new file mode 100644 index 0000000000..c77b9e19c8 --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/resources/protected_audience_event_level_report_handler.py @@ -0,0 +1,43 @@ +"""Handler to receive message from protected audience worklets, such as +sendReportTo() and forDebuggingOnly.reportAdAuctionWin(). + +This handler only supports read and write operations from the URL parameters. +""" + +import json +from typing import List, Tuple + +from wptserve.request import Request +from wptserve.response import Response + +Header = Tuple[str, str] +ResponseTuple = Tuple[int, List[Header], str] + +def main(request: Request, response: Response) -> ResponseTuple: + operation = request.GET.first(b"operation").decode('utf-8') + uuid = request.GET.first(b"uuid").decode('utf-8') + if operation == "read": + with request.server.stash.lock: + stash_reports = request.server.stash.take(key=uuid) + if stash_reports is None: + stash_reports = [] + else: + request.server.stash.put(key=uuid, value=stash_reports) + + return 200, [("Content-Type", "application/json")], json.dumps(stash_reports) + elif operation == "write": + report = request.GET.first(b"report").decode('utf-8') + + if report is None: + return 400, [("Content-Type", "application/json")], json.dumps({'error': 'Missing report.', 'uuid': uuid}) + + with request.server.stash.lock: + stash_reports = request.server.stash.take(key=uuid) + if stash_reports is None: + stash_reports = [] + stash_reports.append(report) + request.server.stash.put(key=uuid, value=stash_reports) + + return 200, [("Content-Type", "application/json")], json.dumps({'msg': 'Recorded report ' + uuid}) + else: + return 400, [("Content-Type", "application/json")], json.dumps({'error': 'Invalid operation.', 'uuid': uuid}) diff --git a/testing/web-platform/tests/private-aggregation/resources/reports.py b/testing/web-platform/tests/private-aggregation/resources/reports.py new file mode 100644 index 0000000000..0f6fd8255a --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/resources/reports.py @@ -0,0 +1,88 @@ +"""Methods for the report-shared-storage and report-protected-audience endpoints (including debug endpoints)""" +import json +from typing import List, Optional, Tuple, Union +import urllib.parse + +from wptserve.request import Request +from wptserve.stash import Stash +from wptserve.utils import isomorphic_decode, isomorphic_encode + +# Arbitrary key used to access the reports in the stash. +REPORTS_KEY = "9d285691-4386-45ad-9a79-d2ec29557bfe" + +CLEAR_STASH_AS_BYTES = isomorphic_encode("clear_stash") + +Header = Tuple[str, str] +Status = Union[int, Tuple[int, str]] +Response = Tuple[Status, List[Header], str] + +def get_request_origin(request: Request) -> str: + return "%s://%s" % (request.url_parts.scheme, + request.url_parts.netloc) + +def handle_post_request(request: Request) -> Response: + """Handles POST request for reports. + + Retrieves the report from the request body and stores the report in the + stash. If clear_stash is specified in the query params, clears the stash. + """ + if request.GET.get(CLEAR_STASH_AS_BYTES): + clear_stash(request.server.stash) + return 200, [], "Stash successfully cleared." + + store_report(request.server.stash, get_request_origin(request), + request.body.decode("utf-8")) + return 200, [], "" + + +def handle_get_request(request: Request) -> Response: + """Handles GET request for reports. + + Retrieves and returns all reports from the stash. + """ + headers = [("Content-Type", "application/json")] + reports = take_reports(request.server.stash, get_request_origin(request)) + headers.append(("Access-Control-Allow-Origin", "*")) + return 200, headers, json.dumps(reports) + + +def store_report(stash: Stash, origin: str, report: str) -> None: + """Stores the report in the stash. Report here is a JSON.""" + with stash.lock: + reports_dict = stash.take(REPORTS_KEY) + if not reports_dict: + reports_dict = {} + reports = reports_dict.get(origin, []) + reports.append(report) + reports_dict[origin] = reports + stash.put(REPORTS_KEY, reports_dict) + return None + +def clear_stash(stash: Stash) -> None: + "Clears the stash." + stash.take(REPORTS_KEY) + return None + +def take_reports(stash: Stash, origin: str) -> List[str]: + """Takes all the reports from the stash and returns them.""" + with stash.lock: + reports_dict = stash.take(REPORTS_KEY) + if not reports_dict: + reports_dict = {} + + reports = reports_dict.pop(origin, []) + stash.put(REPORTS_KEY, reports_dict) + return reports + + +def handle_request(request: Request) -> Response: + """Handles request to get or store reports.""" + if request.method == "POST": + return handle_post_request(request) + if request.method == "GET": + return handle_get_request(request) + + return (405, "Method Not Allowed"), [("Content-Type", "application/json")], json.dumps({ + "code": 405, + "message": "Only GET or POST methods are supported." + }) diff --git a/testing/web-platform/tests/private-aggregation/resources/shared-storage-helper-module.js b/testing/web-platform/tests/private-aggregation/resources/shared-storage-helper-module.js new file mode 100644 index 0000000000..f5a8533d0f --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/resources/shared-storage-helper-module.js @@ -0,0 +1,23 @@ +// Copyright 2023 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +class ContributeToHistogramOperation { + async run(urls, data) { + if (data.enableDebugMode) { + privateAggregation.enableDebugMode(data.enableDebugModeArgs); + + if (data.enableDebugModeExtraTime) { + privateAggregation.enableDebugMode(data.enableDebugModeArgs); + } + } + for (const contribution of data.contributions) { + privateAggregation.contributeToHistogram(contribution); + } + + // If an error occurs, the default URL will be picked instead. + return 1; + } +} + +register('contribute-to-histogram', ContributeToHistogramOperation); diff --git a/testing/web-platform/tests/private-aggregation/resources/util.js b/testing/web-platform/tests/private-aggregation/resources/util.js new file mode 100644 index 0000000000..24e156446f --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/resources/util.js @@ -0,0 +1,24 @@ +// Execute Private Aggregation functions in shared storage worklet given +// `paa_data`, and expect that success/failure result is `expected_error`. +async function VerifyContributeToHistogram(paa_data, expected_error) { + const ancestor_key = token(); + let url0 = generateURL("/shared-storage/resources/frame0.html", + [ancestor_key]); + let url1 = generateURL("/shared-storage/resources/frame1.html", + [ancestor_key]); + + await addModuleOnce("/private-aggregation/resources/shared-storage-helper-module.js"); + + let select_url_result = await sharedStorage.selectURL( + "contribute-to-histogram", [{url: url0}, {url: url1}], + {data: paa_data, keepAlive: true}); + + attachFencedFrame(select_url_result, 'opaque-ads'); + const result = await nextValueFromServer(ancestor_key); + + if (expected_error) { + assert_equals(result, "frame0_loaded"); + } else { + assert_equals(result, "frame1_loaded"); + } +} diff --git a/testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html b/testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html new file mode 100644 index 0000000000..3593ed71ea --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html.headers b/testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html.headers new file mode 100644 index 0000000000..8781910151 --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/shared-storage-permissions-policy-none.https.html.headers @@ -0,0 +1 @@ +Permissions-Policy: private-aggregation=() diff --git a/testing/web-platform/tests/private-aggregation/shared-storage-surface-context-id.https.html b/testing/web-platform/tests/private-aggregation/shared-storage-surface-context-id.https.html new file mode 100644 index 0000000000..3b0e1b3d74 --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/shared-storage-surface-context-id.https.html @@ -0,0 +1,73 @@ + + + + + + + + + + + diff --git a/testing/web-platform/tests/private-aggregation/shared-storage-surface-failure-2.https.html b/testing/web-platform/tests/private-aggregation/shared-storage-surface-failure-2.https.html new file mode 100644 index 0000000000..b39ecd8d74 --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/shared-storage-surface-failure-2.https.html @@ -0,0 +1,26 @@ + + + + + + + + + + + diff --git a/testing/web-platform/tests/private-aggregation/shared-storage-surface-failure.https.html b/testing/web-platform/tests/private-aggregation/shared-storage-surface-failure.https.html new file mode 100644 index 0000000000..9dc62b1bb7 --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/shared-storage-surface-failure.https.html @@ -0,0 +1,74 @@ + + + + + + + + + + + diff --git a/testing/web-platform/tests/private-aggregation/shared-storage-surface-success-2.https.html b/testing/web-platform/tests/private-aggregation/shared-storage-surface-success-2.https.html new file mode 100644 index 0000000000..8203fd11ea --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/shared-storage-surface-success-2.https.html @@ -0,0 +1,27 @@ + + + + + + + + + + + diff --git a/testing/web-platform/tests/private-aggregation/shared-storage-surface-success.https.html b/testing/web-platform/tests/private-aggregation/shared-storage-surface-success.https.html new file mode 100644 index 0000000000..7012180c71 --- /dev/null +++ b/testing/web-platform/tests/private-aggregation/shared-storage-surface-success.https.html @@ -0,0 +1,71 @@ + + + + + + + + + + + -- cgit v1.2.3