From 8617d6719c970d0434d9557dfb1aff5bdae7eac3 Mon Sep 17 00:00:00 2001 From: Russ Hamilton Date: Tue, 7 Jan 2025 09:14:14 -0800 Subject: [PATCH] Add support for Ad-Auction-Result-Nonce header for PA B&A Add support for the alternate authorization flow for Protected Audiences Bidding and Auction response (as described in https://github.com/WICG/turtledove/pull/1233). This feature is behind the FledgeBiddingAndAuctionNonceSupport feature flag which is going to be enabled by default (for a waterfall rollout in M133). Bug:385128725 Change-Id: Id3c622241c82ed0b71037bfeb1ca5432cd6e66dc --- .../resources/authorize-server-response.py | 11 +- .../tentative/resources/ba-fledge-util.sub.js | 9 ++ .../tentative/server-response.https.window.js | 112 +++++++++++++++--- 3 files changed, 113 insertions(+), 19 deletions(-) diff --git a/fledge/tentative/resources/authorize-server-response.py b/fledge/tentative/resources/authorize-server-response.py index 303d2efae5f13a0..83f46d9d68bb3dc 100644 --- a/fledge/tentative/resources/authorize-server-response.py +++ b/fledge/tentative/resources/authorize-server-response.py @@ -1,6 +1,11 @@ def main(request, response): response.status = (200, b"OK") response.headers.set(b"Content-Type", b"text/plain") - hash_list = request.GET.get_list(b"hashes") - response.headers.set(b"Ad-Auction-Result", - b",".join(hash_list)) + if b"hashes" in request.GET: + hash_list = request.GET.get_list(b"hashes") + response.headers.set(b"Ad-Auction-Result", + b",".join(hash_list)) + if b"nonces" in request.GET: + nonce_list = request.GET.get_list(b"nonces") + response.headers.set(b"Ad-Auction-Result-Nonce", + b",".join(nonce_list)) diff --git a/fledge/tentative/resources/ba-fledge-util.sub.js b/fledge/tentative/resources/ba-fledge-util.sub.js index e03a4056ca2042f..4e78a04c8d51a18 100644 --- a/fledge/tentative/resources/ba-fledge-util.sub.js +++ b/fledge/tentative/resources/ba-fledge-util.sub.js @@ -360,6 +360,15 @@ BA.authorizeServerResponseHashes = async function(hashes) { await fetch(authorizeURL, {adAuctionHeaders: true}); }; +// Authorizes each serverResponse nonce in `nonces` to be used for +// B&A auction result. +BA.authorizeServerResponseNonces = async function(nonces) { + let authorizeURL = + new URL('resources/authorize-server-response.py', window.location); + authorizeURL.searchParams.append('nonces', nonces.join(',')); + await fetch(authorizeURL, {adAuctionHeaders: true}); +}; + BA.configureCoordinator = async function() { // This is async in hope it can eventually use testdriver to configure this. return 'https://{{hosts[][]}}'; diff --git a/fledge/tentative/server-response.https.window.js b/fledge/tentative/server-response.https.window.js index 2205500dd5b7c32..9806b8d9b5df4c1 100644 --- a/fledge/tentative/server-response.https.window.js +++ b/fledge/tentative/server-response.https.window.js @@ -6,22 +6,22 @@ // META: script=third_party/cbor-js/cbor.js // META: script=/common/subset-tests.js // META: timeout=long -// META: variant=?1-4 -// META: variant=?5-8 -// META: variant=?9-12 -// META: variant=?13-16 -// META: variant=?17-20 -// META: variant=?21-24 -// META: variant=?25-28 -// META: variant=?29-32 -// META: variant=?33-36 -// META: variant=?37-40 -// META: variant=?41-44 -// META: variant=?45-48 -// META: variant=?49-52 -// META: variant=?53-56 -// META: variant=?57-60 -// META: variant=?61-64 +// META: variant=?1-6 +// META: variant=?7-10 +// META: variant=?11-14 +// META: variant=?15-18 +// META: variant=?19-22 +// META: variant=?23-26 +// META: variant=?27-30 +// META: variant=?31-34 +// META: variant=?35-38 +// META: variant=?39-42 +// META: variant=?43-46 +// META: variant=?47-50 +// META: variant=?51-54 +// META: variant=?55-58 +// META: variant=?59-62 +// META: variant=?63-66 // These tests focus on the serverResponse field in AuctionConfig, e.g. // auctions involving bidding and auction services. @@ -68,6 +68,86 @@ subsetTest(promise_test, async test => { await waitForObservedRequests(uuid, [adA]); }, 'Basic B&A auction'); +subsetTest(promise_test, async test => { + const uuid = generateUuid(test); + const adA = createTrackerURL(window.location.origin, uuid, 'track_get', 'a'); + const adB = createTrackerURL(window.location.origin, uuid, 'track_get', 'b'); + const adsArray = + [{renderURL: adA, adRenderId: 'a'}, {renderURL: adB, adRenderId: 'b'}]; + await joinInterestGroup(test, uuid, {ads: adsArray}); + + const result = await navigator.getInterestGroupAdAuctionData({ + coordinatorOrigin: await BA.configureCoordinator(), + seller: window.location.origin + }); + assert_true(result.requestId !== null); + assert_true(result.request.length > 0); + + let decoded = await BA.decodeInterestGroupData(result.request); + + let serverResponseMsg = { + 'nonce': uuid, + 'biddingGroups': {}, + 'adRenderURL': adsArray[0].renderURL, + 'interestGroupName': DEFAULT_INTEREST_GROUP_NAME, + 'interestGroupOwner': window.location.origin, + }; + serverResponseMsg.biddingGroups[window.location.origin] = [0]; + + let serverResponse = + await BA.encodeServerResponse(serverResponseMsg, decoded); + + let hashString = await BA.payloadHash(serverResponse); + await BA.authorizeServerResponseNonces([uuid]); + + let auctionResult = await navigator.runAdAuction({ + 'seller': window.location.origin, + 'requestId': result.requestId, + 'serverResponse': serverResponse, + 'resolveToConfig': true, + }); + expectSuccess(auctionResult); + createAndNavigateFencedFrame(test, auctionResult); + await waitForObservedRequests(uuid, [adA]); +}, 'Basic B&A auction - nonces'); + +subsetTest(promise_test, async test => { + const uuid = generateUuid(test); + const adA = createTrackerURL(window.location.origin, uuid, 'track_get', 'a'); + const adB = createTrackerURL(window.location.origin, uuid, 'track_get', 'b'); + const adsArray = + [{renderURL: adA, adRenderId: 'a'}, {renderURL: adB, adRenderId: 'b'}]; + await joinInterestGroup(test, uuid, {ads: adsArray}); + + const result = await navigator.getInterestGroupAdAuctionData({ + coordinatorOrigin: await BA.configureCoordinator(), + seller: window.location.origin + }); + assert_true(result.requestId !== null); + assert_true(result.request.length > 0); + + let decoded = await BA.decodeInterestGroupData(result.request); + + let serverResponseMsg = { + 'biddingGroups': {}, + 'adRenderURL': adsArray[0].renderURL, + 'interestGroupName': DEFAULT_INTEREST_GROUP_NAME, + 'interestGroupOwner': window.location.origin, + }; + serverResponseMsg.biddingGroups[window.location.origin] = [0]; + + let serverResponse = + await BA.encodeServerResponse(serverResponseMsg, decoded); + + let auctionResult = await navigator.runAdAuction({ + 'seller': window.location.origin, + 'requestId': result.requestId, + 'serverResponse': serverResponse, + 'resolveToConfig': true, + }); + expectNoWinner(auctionResult); +}, 'Basic B&A auction - not authorized'); + subsetTest(promise_test, async test => { const uuid = generateUuid(test); const adA = createTrackerURL(window.location.origin, uuid, 'track_get', 'a');