summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/fledge/tentative/resources
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /testing/web-platform/tests/fledge/tentative/resources
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/fledge/tentative/resources')
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/additional-bids.py64
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/ed25519.py289
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js210
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py2
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py2
-rw-r--r--testing/web-platform/tests/fledge/tentative/resources/worklet-helpers.js4
6 files changed, 505 insertions, 66 deletions
diff --git a/testing/web-platform/tests/fledge/tentative/resources/additional-bids.py b/testing/web-platform/tests/fledge/tentative/resources/additional-bids.py
index 060606b41d..721909a045 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/additional-bids.py
+++ b/testing/web-platform/tests/fledge/tentative/resources/additional-bids.py
@@ -13,6 +13,7 @@ with a value of b"?1"; this entrypoint otherwise returns a 400 response.
import json
import base64
+import fledge.tentative.resources.ed25519 as ed25519
import fledge.tentative.resources.fledge_http_server_util as fledge_http_server_util
@@ -20,6 +21,57 @@ class BadRequestError(Exception):
pass
+def _generate_signature(message, base64_encoded_secret_key):
+ """Returns a signature entry for a signed additional bid.
+
+ Args:
+ base64_encoded_secret_key: base64-encoded Ed25519 key with which to sign
+ the message. From this secret key, the public key can be deduced, which
+ becomes part of the signature entry.
+ message: The additional bid text (or other text if generating an invalid
+ signature) to sign.
+ """
+ secret_key = base64.b64decode(base64_encoded_secret_key.encode("utf-8"))
+ public_key = ed25519.publickey_unsafe(secret_key)
+ signature = ed25519.signature_unsafe(
+ message.encode("utf-8"), secret_key, public_key)
+ return {
+ "key": base64.b64encode(public_key).decode("utf-8"),
+ "signature": base64.b64encode(signature).decode("utf-8")
+ }
+
+
+def _sign_additional_bid(additional_bid_string,
+ secret_keys_for_valid_signatures,
+ secret_keys_for_invalid_signatures):
+ """Returns a signed additional bid given an additional bid and secret keys.
+
+ Args:
+ additional_bid_string: string representation of the additional bid
+ secret_keys_for_valid_signatures: a list of strings, each a base64-encoded
+ Ed25519 secret key with which to sign the additional bid
+ secret_keys_for_invalid_signatures: a list of strings, each a base64-encoded
+ Ed25519 secret key with which to incorrectly sign the additional bid
+ """
+ signatures = []
+ signatures.extend(
+ _generate_signature(additional_bid_string, secret_key)
+ for secret_key in secret_keys_for_valid_signatures)
+
+ # For invalid signatures, we use the correct secret key to sign a different
+ # message - the additional bid prepended by 'invalid' - so that the signature
+ # is a structually valid signature but can't be used to verify the additional
+ # bid.
+ signatures.extend(
+ _generate_signature("invalid" + additional_bid_string, secret_key)
+ for secret_key in secret_keys_for_invalid_signatures)
+
+ return json.dumps({
+ "bid": additional_bid_string,
+ "signatures": signatures
+ })
+
+
def main(request, response):
try:
if fledge_http_server_util.handle_cors_headers_and_preflight(request, response):
@@ -34,14 +86,16 @@ def main(request, response):
if not additional_bids:
raise BadRequestError("Missing 'additionalBids' parameter")
for additional_bid in json.loads(additional_bids):
- additional_bid_string = json.dumps(additional_bid)
+ # Each additional bid may have associated testMetadata. Remove this from
+ # the additional bid and use it to adjust the behavior of this handler.
+ test_metadata = additional_bid.pop("testMetadata", {})
auction_nonce = additional_bid.get("auctionNonce", None)
if not auction_nonce:
raise BadRequestError("Additional bid missing required 'auctionNonce' field")
- signed_additional_bid = json.dumps({
- "bid": additional_bid_string,
- "signatures": []
- })
+ signed_additional_bid = _sign_additional_bid(
+ json.dumps(additional_bid),
+ test_metadata.get("secretKeysForValidSignatures", []),
+ test_metadata.get("secretKeysForInvalidSignatures", []))
additional_bid_header_value = (auction_nonce.encode("utf-8") + b":" +
base64.b64encode(signed_additional_bid.encode("utf-8")))
response.headers.append(b"Ad-Auction-Additional-Bid", additional_bid_header_value)
diff --git a/testing/web-platform/tests/fledge/tentative/resources/ed25519.py b/testing/web-platform/tests/fledge/tentative/resources/ed25519.py
new file mode 100644
index 0000000000..53e548ab8e
--- /dev/null
+++ b/testing/web-platform/tests/fledge/tentative/resources/ed25519.py
@@ -0,0 +1,289 @@
+# ed25519.py - Optimized version of the reference implementation of Ed25519
+#
+# Written in 2011? by Daniel J. Bernstein <djb@cr.yp.to>
+# 2013 by Donald Stufft <donald@stufft.io>
+# 2013 by Alex Gaynor <alex.gaynor@gmail.com>
+# 2013 by Greg Price <price@mit.edu>
+#
+# To the extent possible under law, the author(s) have dedicated all copyright
+# and related and neighboring rights to this software to the public domain
+# worldwide. This software is distributed without any warranty.
+#
+# You should have received a copy of the CC0 Public Domain Dedication along
+# with this software. If not, see
+# <http://creativecommons.org/publicdomain/zero/1.0/>.
+#
+# Downloaded from https://raw.githubusercontent.com/pyca/ed25519/main/ed25519.py
+# on April 1, 2024.
+
+"""
+NB: This code is not safe for use with secret keys or secret data.
+The only safe use of this code is for verifying signatures on public messages.
+
+Functions for computing the public key of a secret key and for signing
+a message are included, namely publickey_unsafe and signature_unsafe,
+for testing purposes only.
+
+The root of the problem is that Python's long-integer arithmetic is
+not designed for use in cryptography. Specifically, it may take more
+or less time to execute an operation depending on the values of the
+inputs, and its memory access patterns may also depend on the inputs.
+This opens it to timing and cache side-channel attacks which can
+disclose data to an attacker. We rely on Python's long-integer
+arithmetic, so we cannot handle secrets without risking their disclosure.
+"""
+
+import hashlib
+
+
+__version__ = "1.0.dev0"
+
+
+b = 256
+q = 2**255 - 19
+l = 2**252 + 27742317777372353535851937790883648493
+
+
+def H(m):
+ return hashlib.sha512(m).digest()
+
+
+def pow2(x, p):
+ """== pow(x, 2**p, q)"""
+ while p > 0:
+ x = x * x % q
+ p -= 1
+ return x
+
+
+def inv(z):
+ r"""$= z^{-1} \mod q$, for z != 0"""
+ # Adapted from curve25519_athlon.c in djb's Curve25519.
+ z2 = z * z % q # 2
+ z9 = pow2(z2, 2) * z % q # 9
+ z11 = z9 * z2 % q # 11
+ z2_5_0 = (z11 * z11) % q * z9 % q # 31 == 2^5 - 2^0
+ z2_10_0 = pow2(z2_5_0, 5) * z2_5_0 % q # 2^10 - 2^0
+ z2_20_0 = pow2(z2_10_0, 10) * z2_10_0 % q # ...
+ z2_40_0 = pow2(z2_20_0, 20) * z2_20_0 % q
+ z2_50_0 = pow2(z2_40_0, 10) * z2_10_0 % q
+ z2_100_0 = pow2(z2_50_0, 50) * z2_50_0 % q
+ z2_200_0 = pow2(z2_100_0, 100) * z2_100_0 % q
+ z2_250_0 = pow2(z2_200_0, 50) * z2_50_0 % q # 2^250 - 2^0
+ return pow2(z2_250_0, 5) * z11 % q # 2^255 - 2^5 + 11 = q - 2
+
+
+d = -121665 * inv(121666) % q
+I = pow(2, (q - 1) // 4, q)
+
+
+def xrecover(y):
+ xx = (y * y - 1) * inv(d * y * y + 1)
+ x = pow(xx, (q + 3) // 8, q)
+
+ if (x * x - xx) % q != 0:
+ x = (x * I) % q
+
+ if x % 2 != 0:
+ x = q - x
+
+ return x
+
+
+By = 4 * inv(5)
+Bx = xrecover(By)
+B = (Bx % q, By % q, 1, (Bx * By) % q)
+ident = (0, 1, 1, 0)
+
+
+def edwards_add(P, Q):
+ # This is formula sequence 'addition-add-2008-hwcd-3' from
+ # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
+ (x1, y1, z1, t1) = P
+ (x2, y2, z2, t2) = Q
+
+ a = (y1 - x1) * (y2 - x2) % q
+ b = (y1 + x1) * (y2 + x2) % q
+ c = t1 * 2 * d * t2 % q
+ dd = z1 * 2 * z2 % q
+ e = b - a
+ f = dd - c
+ g = dd + c
+ h = b + a
+ x3 = e * f
+ y3 = g * h
+ t3 = e * h
+ z3 = f * g
+
+ return (x3 % q, y3 % q, z3 % q, t3 % q)
+
+
+def edwards_double(P):
+ # This is formula sequence 'dbl-2008-hwcd' from
+ # http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html
+ (x1, y1, z1, t1) = P
+
+ a = x1 * x1 % q
+ b = y1 * y1 % q
+ c = 2 * z1 * z1 % q
+ # dd = -a
+ e = ((x1 + y1) * (x1 + y1) - a - b) % q
+ g = -a + b # dd + b
+ f = g - c
+ h = -a - b # dd - b
+ x3 = e * f
+ y3 = g * h
+ t3 = e * h
+ z3 = f * g
+
+ return (x3 % q, y3 % q, z3 % q, t3 % q)
+
+
+def scalarmult(P, e):
+ if e == 0:
+ return ident
+ Q = scalarmult(P, e // 2)
+ Q = edwards_double(Q)
+ if e & 1:
+ Q = edwards_add(Q, P)
+ return Q
+
+
+# Bpow[i] == scalarmult(B, 2**i)
+Bpow = []
+
+
+def make_Bpow():
+ P = B
+ for i in range(253):
+ Bpow.append(P)
+ P = edwards_double(P)
+
+
+make_Bpow()
+
+
+def scalarmult_B(e):
+ """
+ Implements scalarmult(B, e) more efficiently.
+ """
+ # scalarmult(B, l) is the identity
+ e = e % l
+ P = ident
+ for i in range(253):
+ if e & 1:
+ P = edwards_add(P, Bpow[i])
+ e = e // 2
+ assert e == 0, e
+ return P
+
+
+def encodeint(y):
+ bits = [(y >> i) & 1 for i in range(b)]
+ return bytes(
+ [sum([bits[i * 8 + j] << j for j in range(8)]) for i in range(b // 8)]
+ )
+
+
+def encodepoint(P):
+ (x, y, z, t) = P
+ zi = inv(z)
+ x = (x * zi) % q
+ y = (y * zi) % q
+ bits = [(y >> i) & 1 for i in range(b - 1)] + [x & 1]
+ return bytes(
+ [sum([bits[i * 8 + j] << j for j in range(8)]) for i in range(b // 8)]
+ )
+
+
+def bit(h, i):
+ return (h[i // 8] >> (i % 8)) & 1
+
+
+def publickey_unsafe(sk):
+ """
+ Not safe to use with secret keys or secret data.
+
+ See module docstring. This function should be used for testing only.
+ """
+ h = H(sk)
+ a = 2 ** (b - 2) + sum(2**i * bit(h, i) for i in range(3, b - 2))
+ A = scalarmult_B(a)
+ return encodepoint(A)
+
+
+def Hint(m):
+ h = H(m)
+ return sum(2**i * bit(h, i) for i in range(2 * b))
+
+
+def signature_unsafe(m, sk, pk):
+ """
+ Not safe to use with secret keys or secret data.
+
+ See module docstring. This function should be used for testing only.
+ """
+ h = H(sk)
+ a = 2 ** (b - 2) + sum(2**i * bit(h, i) for i in range(3, b - 2))
+ r = Hint(bytes([h[j] for j in range(b // 8, b // 4)]) + m)
+ R = scalarmult_B(r)
+ S = (r + Hint(encodepoint(R) + pk + m) * a) % l
+ return encodepoint(R) + encodeint(S)
+
+
+def isoncurve(P):
+ (x, y, z, t) = P
+ return (
+ z % q != 0
+ and x * y % q == z * t % q
+ and (y * y - x * x - z * z - d * t * t) % q == 0
+ )
+
+
+def decodeint(s):
+ return sum(2**i * bit(s, i) for i in range(0, b))
+
+
+def decodepoint(s):
+ y = sum(2**i * bit(s, i) for i in range(0, b - 1))
+ x = xrecover(y)
+ if x & 1 != bit(s, b - 1):
+ x = q - x
+ P = (x, y, 1, (x * y) % q)
+ if not isoncurve(P):
+ raise ValueError("decoding point that is not on curve")
+ return P
+
+
+class SignatureMismatch(Exception):
+ pass
+
+
+def checkvalid(s, m, pk):
+ """
+ Not safe to use when any argument is secret.
+
+ See module docstring. This function should be used only for
+ verifying public signatures of public messages.
+ """
+ if len(s) != b // 4:
+ raise ValueError("signature length is wrong")
+
+ if len(pk) != b // 8:
+ raise ValueError("public-key length is wrong")
+
+ R = decodepoint(s[: b // 8])
+ A = decodepoint(pk)
+ S = decodeint(s[b // 8 : b // 4])
+ h = Hint(encodepoint(R) + pk + m)
+
+ (x1, y1, z1, t1) = P = scalarmult_B(S)
+ (x2, y2, z2, t2) = Q = edwards_add(R, scalarmult(A, h))
+
+ if (
+ not isoncurve(P)
+ or not isoncurve(Q)
+ or (x1 * z2 - x2 * z1) % q != 0
+ or (y1 * z2 - y2 * z1) % q != 0
+ ):
+ raise SignatureMismatch("signature does not pass verification")
diff --git a/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js b/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js
index 5819357e29..7be02e34ff 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js
+++ b/testing/web-platform/tests/fledge/tentative/resources/fledge-util.sub.js
@@ -177,7 +177,7 @@ async function waitForObservedRequestsIgnoreDebugOnlyReports(
function createBiddingScriptURL(params = {}) {
let origin = params.origin ? params.origin : new URL(BASE_URL).origin;
let url = new URL(`${origin}${RESOURCE_PATH}bidding-logic.sub.py`);
- // These checks use "==" to ignore null and not provided arguments, while
+ // These checks use "!=" to ignore null and not provided arguments, while
// treating '' as a valid argument.
if (params.generateBid != null)
url.searchParams.append('generateBid', params.generateBid);
@@ -213,7 +213,7 @@ function createDecisionScriptURL(uuid, params = {}) {
let origin = params.origin ? params.origin : new URL(BASE_URL).origin;
let url = new URL(`${origin}${RESOURCE_PATH}decision-logic.sub.py`);
url.searchParams.append('uuid', uuid);
- // These checks use "==" to ignore null and not provided arguments, while
+ // These checks use "!=" to ignore null and not provided arguments, while
// treating '' as a valid argument.
if (params.scoreAd != null)
url.searchParams.append('scoreAd', params.scoreAd);
@@ -230,8 +230,8 @@ function createDecisionScriptURL(uuid, params = {}) {
// be last. "signalsParams" also has no effect, but is used by
// trusted-scoring-signals.py to affect the response.
function createRenderURL(uuid, script, signalsParams, origin) {
- // These checks use "==" to ignore null and not provided arguments, while
- // treating '' as a valid argument.
+ // These checks use "==" and "!=" to ignore null and not provided
+ // arguments, while treating '' as a valid argument.
if (origin == null)
origin = new URL(BASE_URL).origin;
let url = new URL(`${origin}${RESOURCE_PATH}fenced-frame.sub.py`);
@@ -260,6 +260,15 @@ function createInterestGroupForOrigin(uuid, origin,
};
}
+// Waits for the join command to complete. Adds cleanup command to `test` to
+// leave the interest group when the test completes.
+async function joinInterestGroupWithoutDefaults(test, interestGroup,
+ durationSeconds = 60) {
+ await navigator.joinAdInterestGroup(interestGroup, durationSeconds);
+ test.add_cleanup(
+ async () => { await navigator.leaveAdInterestGroup(interestGroup); });
+}
+
// 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}",
@@ -271,12 +280,33 @@ function createInterestGroupForOrigin(uuid, origin,
// interest group.
async function joinInterestGroup(test, uuid, interestGroupOverrides = {},
durationSeconds = 60) {
- let interestGroup = createInterestGroupForOrigin(uuid, window.location.origin,
- interestGroupOverrides);
-
- await navigator.joinAdInterestGroup(interestGroup, durationSeconds);
- test.add_cleanup(
- async () => { await navigator.leaveAdInterestGroup(interestGroup) });
+ await joinInterestGroupWithoutDefaults(
+ test, createInterestGroupForOrigin(
+ uuid, window.location.origin, interestGroupOverrides),
+ durationSeconds);
+}
+
+// Joins a negative interest group with the specified owner, name, and
+// additionalBidKey. Because these are the only valid fields for a negative
+// interest groups, this function doesn't expose an 'overrides' parameter.
+// Adds cleanup command to `test` to leave the interest group when the test
+// completes.
+async function joinNegativeInterestGroup(
+ test, owner, name, additionalBidKey) {
+ let interestGroup = {
+ owner: owner,
+ name: name,
+ additionalBidKey: additionalBidKey
+ };
+ if (owner !== window.location.origin) {
+ let iframe = await createIframe(test, owner, 'join-ad-interest-group');
+ await runInFrame(
+ test, iframe,
+ `await joinInterestGroupWithoutDefaults(` +
+ `test_instance, ${JSON.stringify(interestGroup)})`);
+ } else {
+ await joinInterestGroupWithoutDefaults(test_instance, interestGroup);
+ }
}
// Similar to joinInterestGroup, but leaves the interest group instead.
@@ -487,6 +517,17 @@ async function runReportTest(test, uuid, codeToInsert, expectedReportURLs,
await waitForObservedRequests(uuid, expectedReportURLs);
}
+// Helper function for running a standard test of the additional bid and
+// negative targeting features. This helper verifies that the auction produces a
+// winner. It takes the following arguments:
+// - test/uuid: the test object and uuid from the test case (see generateUuid)
+// - buyers: array of strings, each a domain for a buyer participating in this
+// auction
+// - actionNonce: string, the auction nonce for this auction, typically
+// retrieved from a prior call to navigator.createAuctionNonce
+// - highestScoringOtherBid: the amount of the second-highest bid,
+// or zero if there's no second-highest bid
+// - winningAdditionalBidId: the label of the winning bid
async function runAdditionalBidTest(test, uuid, buyers, auctionNonce,
additionalBidsPromise,
highestScoringOtherBid,
@@ -516,7 +557,7 @@ async function runInFrame(test, child_window, script, param) {
let promise = new Promise(function(resolve, reject) {
function WaitForMessage(event) {
- if (event.data.messageUuid != messageUuid)
+ if (event.data.messageUuid !== messageUuid)
return;
receivedResponse = event.data;
if (event.data.result === 'success') {
@@ -548,7 +589,7 @@ async function createFrame(test, origin, is_iframe = true, permissions = null) {
`${origin}${RESOURCE_PATH}subordinate-frame.sub.html?uuid=${frameUuid}`;
let promise = new Promise(function(resolve, reject) {
function WaitForMessage(event) {
- if (event.data.messageUuid != frameUuid)
+ if (event.data.messageUuid !== frameUuid)
return;
if (event.data.result === 'load complete') {
resolve();
@@ -662,79 +703,130 @@ function directFromSellerSignalsValidatorCode(uuid, expectedSellerSignals,
return {
// Seller worklets
scoreAd:
- `if (directFromSellerSignals === null ||
+ `if (directFromSellerSignals == null ||
directFromSellerSignals.sellerSignals !== ${expectedSellerSignals} ||
directFromSellerSignals.auctionSignals !== ${expectedAuctionSignals} ||
- Object.keys(directFromSellerSignals).length != 2) {
+ Object.keys(directFromSellerSignals).length !== 2) {
throw 'Failed to get expected directFromSellerSignals in scoreAd(): ' +
JSON.stringify(directFromSellerSignals);
}`,
reportResultSuccessCondition:
- `directFromSellerSignals !== null &&
+ `directFromSellerSignals != null &&
directFromSellerSignals.sellerSignals === ${expectedSellerSignals} &&
directFromSellerSignals.auctionSignals === ${expectedAuctionSignals} &&
- Object.keys(directFromSellerSignals).length == 2`,
+ Object.keys(directFromSellerSignals).length === 2`,
reportResult:
`sendReportTo("${createSellerReportURL(uuid)}");`,
// Bidder worklets
generateBid:
- `if (directFromSellerSignals === null ||
+ `if (directFromSellerSignals == null ||
directFromSellerSignals.perBuyerSignals !== ${expectedPerBuyerSignals} ||
directFromSellerSignals.auctionSignals !== ${expectedAuctionSignals} ||
- Object.keys(directFromSellerSignals).length != 2) {
+ Object.keys(directFromSellerSignals).length !== 2) {
throw 'Failed to get expected directFromSellerSignals in generateBid(): ' +
JSON.stringify(directFromSellerSignals);
}`,
reportWinSuccessCondition:
- `directFromSellerSignals !== null &&
+ `directFromSellerSignals != null &&
directFromSellerSignals.perBuyerSignals === ${expectedPerBuyerSignals} &&
directFromSellerSignals.auctionSignals === ${expectedAuctionSignals} &&
- Object.keys(directFromSellerSignals).length == 2`,
+ Object.keys(directFromSellerSignals).length === 2`,
reportWin:
`sendReportTo("${createBidderReportURL(uuid)}");`,
};
}
-// Creates an additional bid with the given parameters. This additional bid
-// specifies a biddingLogicURL that provides an implementation of
-// reportAdditionalBidWin that triggers a sendReportTo() to the bidder report
-// URL of the winning additional bid. Additional bids are described in more
-// detail at
-// https://github.com/WICG/turtledove/blob/main/FLEDGE.md#6-additional-bids.
-function createAdditionalBid(uuid, auctionNonce, seller, buyer, interestGroupName, bidAmount,
- additionalBidOverrides = {}) {
- return {
- interestGroup: {
- name: interestGroupName,
- biddingLogicURL: createBiddingScriptURL(
- {
- origin: buyer,
- reportAdditionalBidWin: `sendReportTo("${createBidderReportURL(uuid, interestGroupName)}");`
- }),
- owner: buyer
- },
- bid: {
- ad: ['metadata'],
- bid: bidAmount,
- render: createRenderURL(uuid)
- },
- auctionNonce: auctionNonce,
- seller: seller,
- ...additionalBidOverrides
+let additionalBidHelper = function() {
+ // Creates an additional bid with the given parameters. This additional bid
+ // specifies a biddingLogicURL that provides an implementation of
+ // reportAdditionalBidWin that triggers a sendReportTo() to the bidder report
+ // URL of the winning additional bid. Additional bids are described in more
+ // detail at
+ // https://github.com/WICG/turtledove/blob/main/FLEDGE.md#6-additional-bids.
+ function createAdditionalBid(uuid, auctionNonce, seller, buyer, interestGroupName, bidAmount,
+ additionalBidOverrides = {}) {
+ return {
+ interestGroup: {
+ name: interestGroupName,
+ biddingLogicURL: createBiddingScriptURL(
+ {
+ origin: buyer,
+ reportAdditionalBidWin: `sendReportTo("${createBidderReportURL(uuid, interestGroupName)}");`
+ }),
+ owner: buyer
+ },
+ bid: {
+ ad: ['metadata'],
+ bid: bidAmount,
+ render: createRenderURL(uuid)
+ },
+ auctionNonce: auctionNonce,
+ seller: seller,
+ ...additionalBidOverrides
+ };
}
-}
-// Fetch some number of additional bid from a seller and verify that the
-// 'Ad-Auction-Additional-Bid' header is not visible in this JavaScript context.
-// The `additionalBids` parameter is a list of additional bids.
-async function fetchAdditionalBids(seller, additionalBids) {
- const url = new URL(`${seller}${RESOURCE_PATH}additional-bids.py`);
- url.searchParams.append('additionalBids', JSON.stringify(additionalBids));
- const response = await fetch(url.href, {adAuctionHeaders: true});
+ // Gets the testMetadata for an additional bid, initializing it if needed.
+ function getAndMaybeInitializeTestMetadata(additionalBid) {
+ if (additionalBid.testMetadata === undefined) {
+ additionalBid.testMetadata = {};
+ }
+ return additionalBid.testMetadata;
+ }
- assert_equals(response.status, 200, 'Failed to fetch additional bid: ' + await response.text());
- assert_false(
- response.headers.has('Ad-Auction-Additional-Bid'),
- 'Header "Ad-Auction-Additional-Bid" should not be available in JavaScript context.');
-}
+ // Tells the additional bid endpoint to correctly sign the additional bid with
+ // the given secret keys before returning that as a signed additional bid.
+ function signWithSecretKeys(additionalBid, secretKeys) {
+ getAndMaybeInitializeTestMetadata(additionalBid).
+ secretKeysForValidSignatures = secretKeys;
+ }
+
+ // Tells the additional bid endpoint to incorrectly sign the additional bid with
+ // the given secret keys before returning that as a signed additional bid. This
+ // is used for testing the behavior when the auction encounters an invalid
+ // signature.
+ function incorrectlySignWithSecretKeys(additionalBid, secretKeys) {
+ getAndMaybeInitializeTestMetadata(additionalBid).
+ secretKeysForInvalidSignatures = secretKeys;
+ }
+
+ // Adds a single negative interest group to an additional bid, as described at:
+ // https://github.com/WICG/turtledove/blob/main/FLEDGE.md#622-how-additional-bids-specify-their-negative-interest-groups
+ function addNegativeInterestGroup(additionalBid, negativeInterestGroup) {
+ additionalBid["negativeInterestGroup"] = negativeInterestGroup;
+ }
+
+ // Adds multiple negative interest groups to an additional bid, as described at:
+ // https://github.com/WICG/turtledove/blob/main/FLEDGE.md#622-how-additional-bids-specify-their-negative-interest-groups
+ function addNegativeInterestGroups(additionalBid, negativeInterestGroups,
+ joiningOrigin) {
+ additionalBid["negativeInterestGroups"] = {
+ joiningOrigin: joiningOrigin,
+ interestGroupNames: negativeInterestGroups
+ };
+ }
+
+ // Fetch some number of additional bid from a seller and verify that the
+ // 'Ad-Auction-Additional-Bid' header is not visible in this JavaScript context.
+ // The `additionalBids` parameter is a list of additional bids.
+ async function fetchAdditionalBids(seller, additionalBids) {
+ const url = new URL(`${seller}${RESOURCE_PATH}additional-bids.py`);
+ url.searchParams.append('additionalBids', JSON.stringify(additionalBids));
+ const response = await fetch(url.href, {adAuctionHeaders: true});
+
+ assert_equals(response.status, 200, 'Failed to fetch additional bid: ' + await response.text());
+ assert_false(
+ response.headers.has('Ad-Auction-Additional-Bid'),
+ 'Header "Ad-Auction-Additional-Bid" should not be available in JavaScript context.');
+ }
+
+ return {
+ createAdditionalBid: createAdditionalBid,
+ signWithSecretKeys: signWithSecretKeys,
+ incorrectlySignWithSecretKeys: incorrectlySignWithSecretKeys,
+ addNegativeInterestGroup: addNegativeInterestGroup,
+ addNegativeInterestGroups: addNegativeInterestGroups,
+ fetchAdditionalBids: fetchAdditionalBids
+ };
+}();
diff --git a/testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py b/testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py
index 45bede2c45..f9ca9031f1 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py
+++ b/testing/web-platform/tests/fledge/tentative/resources/trusted-bidding-signals.py
@@ -110,6 +110,8 @@ def main(request, response):
value = request.GET.first(b"slotSize", b"not-found").decode("ASCII")
elif key == "allSlotsRequestedSizes":
value = request.GET.first(b"allSlotsRequestedSizes", b"not-found").decode("ASCII")
+ elif key == "url":
+ value = request.url
responseBody["keys"][key] = value
if "data-version" in interestGroupNames:
diff --git a/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py b/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py
index eccef5e762..ce53e76295 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py
+++ b/testing/web-platform/tests/fledge/tentative/resources/trusted-scoring-signals.py
@@ -122,6 +122,8 @@ def main(request, response):
value = request.GET.first(b"hostname", b"not-found").decode("ASCII")
elif signalsParam == "headers":
value = fledge_http_server_util.headers_to_ascii(request.headers)
+ elif signalsParam == "url":
+ value = request.url
if addValue:
if urlList["type"] not in responseBody:
responseBody[urlList["type"]] = {}
diff --git a/testing/web-platform/tests/fledge/tentative/resources/worklet-helpers.js b/testing/web-platform/tests/fledge/tentative/resources/worklet-helpers.js
index 2147a026ae..0bac1b99a9 100644
--- a/testing/web-platform/tests/fledge/tentative/resources/worklet-helpers.js
+++ b/testing/web-platform/tests/fledge/tentative/resources/worklet-helpers.js
@@ -11,10 +11,10 @@ function deepEquals(a, b) {
return a === b;
let aKeys = Object.keys(a);
- if (aKeys.length != Object.keys(b).length)
+ if (aKeys.length !== Object.keys(b).length)
return false;
for (let key of aKeys) {
- if (a.hasOwnProperty(key) != b.hasOwnProperty(key) ||
+ if (a.hasOwnProperty(key) !== b.hasOwnProperty(key) ||
!deepEquals(a[key], b[key])) {
return false;
}