0

Wire up trusted KVv2 scoring signals in seller worklet

Add trusted scoring signals KVv2 support in bidder worklet and tests.

Bug: 337917489
Change-Id: I99d90c810e37cb6cace57b00c6f1eeae966a1403
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5897162
Commit-Queue: Tianyang Xu <xtlsheep@google.com>
Reviewed-by: danakj <danakj@chromium.org>
Reviewed-by: Russ Hamilton <behamilton@google.com>
Cr-Commit-Position: refs/heads/main@{#1362639}
This commit is contained in:
Tianyang Xu
2024-10-01 20:17:26 +00:00
committed by Chromium LUCI CQ
parent 904c1b45b4
commit 803109d61f
11 changed files with 231 additions and 17 deletions

@ -107,7 +107,8 @@ class TestAuctionProcessManager
const url::Origin& top_window_origin,
auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
permissions_policy_state,
std::optional<uint16_t> experiment_id) override {
std::optional<uint16_t> experiment_id,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override {
NOTREACHED_IN_MIGRATION();
}

@ -525,7 +525,7 @@ void AuctionWorkletManager::WorkletOwner::OnProcessAssigned(
worklet_info_.signals_url, worklet_manager_->top_window_origin(),
GetAuctionWorkletPermissionsPolicyState(delegate->GetFrame(),
worklet_info_.script_url),
worklet_info_.experiment_group_id);
worklet_info_.experiment_group_id, /*public_key=*/nullptr);
seller_worklet_.set_disconnect_with_reason_handler(base::BindOnce(
&WorkletOwner::OnWorkletDisconnected, base::Unretained(this)));
break;

@ -647,7 +647,8 @@ class MockAuctionProcessManager
const url::Origin& top_window_origin,
auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
permissions_policy_state,
std::optional<uint16_t> experiment_group_id) override {
std::optional<uint16_t> experiment_group_id,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override {
DCHECK(!seller_worklet_);
// Make sure this request came over the right pipe.

@ -641,7 +641,8 @@ void MockAuctionProcessManager::LoadSellerWorklet(
const url::Origin& top_window_origin,
auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
permissions_policy_state,
std::optional<uint16_t> experiment_group_id) {
std::optional<uint16_t> experiment_group_id,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) {
EXPECT_EQ(0u, seller_worklets_.count(script_source_url));
// Make sure this request came over the right pipe.

@ -471,7 +471,8 @@ class MockAuctionProcessManager
const url::Origin& top_window_origin,
auction_worklet::mojom::AuctionWorkletPermissionsPolicyStatePtr
permissions_policy_state,
std::optional<uint16_t> experiment_group_id) override;
std::optional<uint16_t> experiment_group_id,
auction_worklet::mojom::TrustedSignalsPublicKeyPtr public_key) override;
// Set the expected timeout for an interest group with the specified name,
// when it's received by a bidder worklet's FinishGenerateBid() method. Must

@ -303,7 +303,8 @@ void AuctionWorkletServiceImpl::LoadSellerWorklet(
const std::optional<GURL>& trusted_scoring_signals_url,
const url::Origin& top_window_origin,
mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
std::optional<uint16_t> experiment_group_id) {
std::optional<uint16_t> experiment_group_id,
mojom::TrustedSignalsPublicKeyPtr public_key) {
std::vector<scoped_refptr<AuctionV8Helper>> v8_helpers;
for (size_t i = 0; i < auction_seller_v8_helper_holders_.size(); ++i) {
v8_helpers.push_back(auction_seller_v8_helper_holders_[i]->V8Helper());
@ -317,6 +318,7 @@ void AuctionWorkletServiceImpl::LoadSellerWorklet(
std::move(auction_network_events_handler), decision_logic_url,
trusted_scoring_signals_url, top_window_origin,
std::move(permissions_policy_state), experiment_group_id,
std::move(public_key),
base::BindRepeating(
&AuctionWorkletServiceImpl::GetNextSellerWorkletThreadIndex,
base::Unretained(this)));

@ -88,7 +88,8 @@ class CONTENT_EXPORT AuctionWorkletServiceImpl
const std::optional<GURL>& trusted_scoring_signals_url,
const url::Origin& top_window_origin,
mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
std::optional<uint16_t> experiment_group_id) override;
std::optional<uint16_t> experiment_group_id,
mojom::TrustedSignalsPublicKeyPtr public_key) override;
// Returns an index in the seller thread pool, where the corresponding V8
// thread will be used to execute the next task.

@ -172,5 +172,6 @@ interface AuctionWorkletService {
url.mojom.Url? trusted_scoring_signals_url,
url.mojom.Origin top_window_origin,
AuctionWorkletPermissionsPolicyState permissions_policy_state,
uint16? experiment_group_id);
uint16? experiment_group_id,
TrustedSignalsPublicKey? public_key);
};

@ -432,6 +432,7 @@ SellerWorklet::SellerWorklet(
const url::Origin& top_window_origin,
mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
std::optional<uint16_t> experiment_group_id,
mojom::TrustedSignalsPublicKeyPtr public_key,
GetNextThreadIndexCallback get_next_thread_index_callback)
: url_loader_factory_(std::move(pending_url_loader_factory)),
script_source_url_(decision_logic_url),
@ -475,7 +476,7 @@ SellerWorklet::SellerWorklet(
*trusted_scoring_signals_url,
/*experiment_group_id=*/experiment_group_id,
/*trusted_bidding_signals_slot_size_param=*/std::string(),
/*public_key=*/nullptr,
std::move(public_key),
v8_helpers_[get_next_thread_index_callback_.Run()].get())
: nullptr);
trusted_signals_relation_ = ClassifyTrustedSignals(
@ -553,6 +554,7 @@ void SellerWorklet::ScoreAd(
score_ad_task->component_expect_bid_currency = component_expect_bid_currency;
score_ad_task->browser_signal_interest_group_owner =
browser_signal_interest_group_owner;
score_ad_task->bidder_joining_origin = bidder_joining_origin;
score_ad_task->browser_signal_render_url = browser_signal_render_url;
score_ad_task->browser_signal_selected_buyer_and_seller_reporting_id =
browser_signal_selected_buyer_and_seller_reporting_id;
@ -2012,14 +2014,29 @@ void SellerWorklet::StartFetchingSignalsForTask(
SignalsOriginRelation::kSameOriginSignals ||
trusted_signals_relation_ ==
SignalsOriginRelation::kPermittedCrossOriginSignals);
score_ad_task->trusted_scoring_signals_request =
trusted_signals_request_manager_->RequestScoringSignals(
score_ad_task->browser_signal_render_url,
score_ad_task->browser_signal_ad_components,
score_ad_task->auction_ad_config_non_shared_params
.max_trusted_scoring_signals_url_length,
base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded,
base::Unretained(this), score_ad_task));
if (trusted_signals_request_manager_->HasPublicKey()) {
DCHECK(base::FeatureList::IsEnabled(
blink::features::kFledgeTrustedSignalsKVv2Support));
score_ad_task->trusted_scoring_signals_request =
trusted_signals_request_manager_->RequestKVv2ScoringSignals(
score_ad_task->browser_signal_render_url,
score_ad_task->browser_signal_ad_components,
score_ad_task->browser_signal_interest_group_owner,
score_ad_task->bidder_joining_origin,
base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded,
base::Unretained(this), score_ad_task));
} else {
score_ad_task->trusted_scoring_signals_request =
trusted_signals_request_manager_->RequestScoringSignals(
score_ad_task->browser_signal_render_url,
score_ad_task->browser_signal_ad_components,
score_ad_task->auction_ad_config_non_shared_params
.max_trusted_scoring_signals_url_length,
base::BindOnce(&SellerWorklet::OnTrustedScoringSignalsDownloaded,
base::Unretained(this), score_ad_task));
}
}
void SellerWorklet::OnTrustedScoringSignalsDownloaded(

@ -101,6 +101,7 @@ class CONTENT_EXPORT SellerWorklet : public mojom::SellerWorklet {
const url::Origin& top_window_origin,
mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state,
std::optional<uint16_t> experiment_group_id,
mojom::TrustedSignalsPublicKeyPtr public_key,
GetNextThreadIndexCallback next_thread_index_callback);
explicit SellerWorklet(const SellerWorklet&) = delete;
@ -201,6 +202,7 @@ class CONTENT_EXPORT SellerWorklet : public mojom::SellerWorklet {
mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller;
std::optional<blink::AdCurrency> component_expect_bid_currency;
url::Origin browser_signal_interest_group_owner;
url::Origin bidder_joining_origin;
GURL browser_signal_render_url;
std::optional<std::string>
browser_signal_selected_buyer_and_seller_reporting_id;
@ -607,6 +609,7 @@ class CONTENT_EXPORT SellerWorklet : public mojom::SellerWorklet {
mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory_;
const GURL script_source_url_;
mojom::TrustedSignalsPublicKeyPtr public_key_;
// Populated only if `this` was created with a non-null
// `trusted_scoring_signals_url`.

@ -30,9 +30,12 @@
#include "base/test/values_test_util.h"
#include "base/test/with_feature_override.h"
#include "base/time/time.h"
#include "components/cbor/writer.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "content/services/auction_worklet/public/cpp/cbor_test_util.h"
#include "content/services/auction_worklet/public/cpp/real_time_reporting.h"
#include "content/services/auction_worklet/public/mojom/auction_network_events_handler.mojom.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom-forward.h"
#include "content/services/auction_worklet/public/mojom/auction_worklet_service.mojom.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
#include "content/services/auction_worklet/public/mojom/real_time_reporting.mojom.h"
@ -45,6 +48,7 @@
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/bindings/unique_receiver_set.h"
#include "net/http/http_status_code.h"
#include "net/third_party/quiche/src/quiche/oblivious_http/oblivious_http_gateway.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -81,6 +85,20 @@ const char kTrustedScoringSignalsResponse[] = R"(
}
)";
const uint8_t kTestPrivateKey[] = {
0xff, 0x1f, 0x47, 0xb1, 0x68, 0xb6, 0xb9, 0xea, 0x65, 0xf7, 0x97,
0x4f, 0xf2, 0x2e, 0xf2, 0x36, 0x94, 0xe2, 0xf6, 0xb6, 0x8d, 0x66,
0xf3, 0xa7, 0x64, 0x14, 0x28, 0xd4, 0x45, 0x35, 0x01, 0x8f,
};
const uint8_t kTestPublicKey[] = {
0xa1, 0x5f, 0x40, 0x65, 0x86, 0xfa, 0xc4, 0x7b, 0x99, 0x59, 0x70,
0xf1, 0x85, 0xd9, 0xd8, 0x91, 0xc7, 0x4d, 0xcf, 0x1e, 0xb9, 0x1a,
0x7d, 0x50, 0xa5, 0x8b, 0x01, 0x68, 0x3e, 0x60, 0x05, 0x2d,
};
const uint8_t kKeyId = 0xFF;
// Creates a seller script with scoreAd() returning the specified expression.
// Allows using scoreAd() arguments, arbitrary values, incorrect types, etc.
std::string CreateScoreAdScript(const std::string& raw_return_value,
@ -263,6 +281,7 @@ class SellerWorkletTest : public testing::Test {
/*private_aggregation_allowed=*/true,
/*shared_storage_allowed=*/false);
experiment_group_id_ = std::nullopt;
public_key_ = nullptr;
browser_signals_other_seller_.reset();
component_expect_bid_currency_ = std::nullopt;
browser_signal_interest_group_owner_ =
@ -779,6 +798,7 @@ class SellerWorkletTest : public testing::Test {
auction_network_events_handler_.CreateRemote(), decision_logic_url_,
trusted_scoring_signals_url_, top_window_origin_,
permissions_policy_state_.Clone(), experiment_group_id_,
public_key_ ? public_key_.Clone() : nullptr,
base::BindRepeating(&SellerWorkletTest::GetNextThreadIndex,
base::Unretained(this)));
@ -866,6 +886,7 @@ class SellerWorkletTest : public testing::Test {
url::Origin top_window_origin_;
mojom::AuctionWorkletPermissionsPolicyStatePtr permissions_policy_state_;
std::optional<uint16_t> experiment_group_id_;
mojom::TrustedSignalsPublicKeyPtr public_key_;
mojom::ComponentAuctionOtherSellerPtr browser_signals_other_seller_;
std::optional<blink::AdCurrency> component_expect_bid_currency_;
url::Origin browser_signal_interest_group_owner_;
@ -6120,6 +6141,171 @@ TEST_F(SellerWorkletSharedStorageAPIEnabledTest,
}
}
class SellerWorkletKVv2Test : public SellerWorkletTest {
public:
SellerWorkletKVv2Test() {
scoped_feature_list_.InitWithFeatures(
{blink::features::kFledgeTrustedSignalsKVv2Support}, {});
public_key_ = mojom::TrustedSignalsPublicKey::New(
std::string(reinterpret_cast<const char*>(&kTestPublicKey[0]),
sizeof(kTestPublicKey)),
kKeyId);
}
protected:
static std::string GenerateResponseBody(
const std::string& request_body,
const std::string& response_json_content) {
// Decrypt the request.
auto response_key_config = quiche::ObliviousHttpHeaderKeyConfig::Create(
kKeyId, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, EVP_HPKE_HKDF_SHA256,
EVP_HPKE_AES_256_GCM);
CHECK(response_key_config.ok()) << response_key_config.status();
auto ohttp_gateway =
quiche::ObliviousHttpGateway::Create(
std::string(reinterpret_cast<const char*>(&kTestPrivateKey[0]),
sizeof(kTestPrivateKey)),
response_key_config.value())
.value();
auto received_request = ohttp_gateway.DecryptObliviousHttpRequest(
request_body, kTrustedSignalsKVv2EncryptionRequestMediaType);
CHECK(received_request.ok()) << received_request.status();
// Build response body.
cbor::Value::MapValue compression_group;
compression_group.try_emplace(cbor::Value("compressionGroupId"),
cbor::Value(0));
compression_group.try_emplace(cbor::Value("ttlMs"), cbor::Value(100));
compression_group.try_emplace(
cbor::Value("content"),
cbor::Value(test::ToCborVector(response_json_content)));
cbor::Value::ArrayValue compression_groups;
compression_groups.emplace_back(std::move(compression_group));
cbor::Value::MapValue body_map;
body_map.try_emplace(cbor::Value("compressionGroups"),
cbor::Value(std::move(compression_groups)));
cbor::Value body_value(std::move(body_map));
std::optional<std::vector<uint8_t>> maybe_body_bytes =
cbor::Writer::Write(body_value);
CHECK(maybe_body_bytes);
std::string response_body = test::CreateKVv2ResponseBody(
base::as_string_view(maybe_body_bytes.value()));
auto response_context =
std::move(received_request).value().ReleaseContext();
// Encrypt the response body.
auto maybe_response = ohttp_gateway.CreateObliviousHttpResponse(
response_body, response_context,
kTrustedSignalsKVv2EncryptionResponseMediaType);
CHECK(maybe_response.ok()) << maybe_response.status();
return maybe_response->EncapsulateAndSerialize();
}
base::test::ScopedFeatureList scoped_feature_list_;
};
TEST_F(SellerWorkletKVv2Test, ScoreAdTrustedScoringSignals) {
const char kJson[] =
R"([{
"id": 0,
"dataVersion": 101,
"keyGroupOutputs": [
{
"tags": [
"renderUrls"
],
"keyValues": {
"https://bar.test/": {
"value": "1"
}
}
},
{
"tags": [
"adComponentRenderUrls"
],
"keyValues": {
"https://barsub.test/": {
"value": "2"
},
"https://foosub.test/": {
"value": "[3]"
}
}
}
]
}])";
const char kValidate[] = R"(
const expected = '{"renderURL":{"https://bar.test/":1},' +
'"renderUrl":{"https://bar.test/":1},"adComponentRenderURLs":' +
'{"https://barsub.test/":2,"https://foosub.test/":[3]},' +
'"adComponentRenderUrls":{"https://barsub.test/":2,' +
'"https://foosub.test/":[3]}}'
const actual = JSON.stringify(trustedScoringSignals);
if (actual === expected)
return 2;
throw actual + "!" + expected;
)";
const base::TimeDelta kDelay = base::Milliseconds(135);
trusted_scoring_signals_url_ =
GURL("https://url.test/trusted_scoring_signals");
browser_signal_render_url_ = GURL("https://bar.test/");
browser_signal_ad_components_ = {GURL("https://barsub.test/"),
GURL("https://foosub.test/")};
AddJavascriptResponse(
&url_loader_factory_, decision_logic_url_,
CreateScoreAdScript(/*raw_return_value=*/"1", kValidate));
auto seller_worklet = CreateWorklet();
ASSERT_TRUE(seller_worklet);
base::RunLoop run_loop;
RunScoreAdOnWorkletAsync(
seller_worklet.get(), /*expected_score=*/2,
/*expected_errors=*/{}, mojom::ComponentAuctionModifiedBidParamsPtr(),
/*expected_data_version=*/101,
/*expected_debug_loss_report_url=*/std::nullopt,
/*expected_debug_win_report_url=*/std::nullopt,
mojom::RejectReason::kNotAvailable,
/*expected_pa_requests=*/{},
/*expected_real_time_contributions=*/{},
/*expected_bid_in_seller_currency=*/std::nullopt,
/*expected_score_ad_timeout=*/false,
/*expected_signals_fetch_latency=*/kDelay,
/*expected_code_ready_latency=*/std::nullopt, run_loop.QuitClosure());
task_environment_.FastForwardBy(kDelay);
// Decrypt request and encrypt response.
ASSERT_EQ(1, url_loader_factory_.NumPending());
const network::ResourceRequest* pending_request;
ASSERT_TRUE(url_loader_factory_.IsPending(
trusted_scoring_signals_url_->spec(), &pending_request));
std::string request_body =
std::string(pending_request->request_body->elements()
->at(0)
.As<network::DataElementBytes>()
.AsStringPiece());
std::string response_body = GenerateResponseBody(request_body, kJson);
std::string headers =
base::StringPrintf("%s\nContent-Type: %s", kAllowFledgeHeader,
"message/ad-auction-trusted-signals-request");
AddResponse(&url_loader_factory_, trusted_scoring_signals_url_.value(),
kAdAuctionTrustedSignalsMimeType,
/*charset=*/std::nullopt, response_body, headers);
run_loop.Run();
}
class SellerWorkletTwoThreadsSharedStorageAPIEnabledTest
: public SellerWorkletSharedStorageAPIEnabledTest {
public: