Check allowed reporting origins attestation at join/update time.
If any origin in IG ads' allowedReportingOrigins is not attested, do not join or update the interest group. Bug: b/291749337 Change-Id: Ib9cd3681804c4818786db7c2044aecab2e8f003b Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4753792 Reviewed-by: Russ Hamilton <behamilton@google.com> Commit-Queue: Qingxin Wu <qingxinwu@google.com> Cr-Commit-Position: refs/heads/main@{#1181700}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
6debc1fec5
commit
db80710acf
content/browser/interest_group
@ -89,6 +89,21 @@ bool IsAdRequestValid(const blink::mojom::AdRequestConfig& config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AreAllowedReportingOriginsAttested(
|
||||
BrowserContext* browser_context,
|
||||
const std::vector<url::Origin>& origins) {
|
||||
for (const auto& origin : origins) {
|
||||
if (!GetContentClient()
|
||||
->browser()
|
||||
->IsPrivacySandboxReportingDestinationAttested(
|
||||
browser_context, origin,
|
||||
PrivacySandboxInvokingAPI::kProtectedAudience)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AdAuctionServiceImpl::BiddingAndAuctionDataConstructionState::
|
||||
@ -133,10 +148,16 @@ void AdAuctionServiceImpl::JoinInterestGroup(
|
||||
updated_group.expiry = max_expiry;
|
||||
}
|
||||
|
||||
// `base::Unretained` is safe here since the `BrowserContext` owns the
|
||||
// `StoragePartition` that owns the interest group manager.
|
||||
GetInterestGroupManager().CheckPermissionsAndJoinInterestGroup(
|
||||
std::move(updated_group), main_frame_url_, origin(),
|
||||
GetFrame()->GetNetworkIsolationKey(), report_result_only,
|
||||
*GetFrameURLLoaderFactory(), std::move(callback));
|
||||
*GetFrameURLLoaderFactory(),
|
||||
base::BindRepeating(
|
||||
&AreAllowedReportingOriginsAttested,
|
||||
base::Unretained(render_frame_host().GetBrowserContext())),
|
||||
std::move(callback));
|
||||
}
|
||||
|
||||
void AdAuctionServiceImpl::LeaveInterestGroup(
|
||||
@ -225,8 +246,14 @@ void AdAuctionServiceImpl::UpdateAdInterestGroups() {
|
||||
ContentBrowserClient::InterestGroupApiOperation::kUpdate, origin())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// `base::Unretained` is safe here since the `BrowserContext` owns the
|
||||
// `StoragePartition` that owns the interest group manager.
|
||||
GetInterestGroupManager().UpdateInterestGroupsOfOwner(
|
||||
origin(), GetClientSecurityState());
|
||||
origin(), GetClientSecurityState(),
|
||||
base::BindRepeating(
|
||||
&AreAllowedReportingOriginsAttested,
|
||||
base::Unretained(render_frame_host().GetBrowserContext())));
|
||||
}
|
||||
|
||||
void AdAuctionServiceImpl::CreateAuctionNonce(
|
||||
@ -320,6 +347,9 @@ void AdAuctionServiceImpl::RunAdAuction(
|
||||
base::Unretained(this)),
|
||||
base::BindRepeating(&AdAuctionServiceImpl::GetAdAuctionPageData,
|
||||
base::Unretained(this)),
|
||||
base::BindRepeating(
|
||||
&AreAllowedReportingOriginsAttested,
|
||||
base::Unretained(render_frame_host().GetBrowserContext())),
|
||||
std::move(abort_receiver),
|
||||
base::BindOnce(
|
||||
&AdAuctionServiceImpl::OnAuctionComplete, base::Unretained(this),
|
||||
|
@ -806,11 +806,10 @@ class AdAuctionServiceImplTest : public RenderViewHostTestHarness {
|
||||
// Creates a new AdAuctionServiceImpl and use it to try and join
|
||||
// `interest_group`. Waits for the operation to signal completion.
|
||||
//
|
||||
// Creates a new AdAuctionServiceImpl with each call so the RFH
|
||||
// can be navigated between different sites. And
|
||||
// AdAuctionServiceImpl only handles one site (cross site navs use
|
||||
// different AdAuctionServices, and generally use different
|
||||
// RFHs as well).
|
||||
// Creates a new AdAuctionServiceImpl with each call so the RFH can be
|
||||
// navigated between different sites. And AdAuctionServiceImpl only handles
|
||||
// one site (cross site navs use different AdAuctionServices, and generally
|
||||
// use different RFHs as well).
|
||||
//
|
||||
// If `rfh` is nullptr, uses the main frame.
|
||||
void JoinInterestGroupAndFlush(const blink::InterestGroup& interest_group,
|
||||
@ -1135,6 +1134,31 @@ TEST_F(AdAuctionServiceImplTest, JoinInterestGroupDisallowedUrls) {
|
||||
EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
|
||||
}
|
||||
|
||||
// Attempt to join an interest group whose allowed reporting origins are not all
|
||||
// attested. No join should happen.
|
||||
TEST_F(AdAuctionServiceImplTest,
|
||||
JoinInterestGroupNotAttestedAllowedReportingOrigins) {
|
||||
const url::Origin kNotAttestedOrigin =
|
||||
url::Origin::Create(GURL("https://a.test"));
|
||||
content_browser_client_.SetAllowList({kOriginG});
|
||||
// Test `bidding_url`.
|
||||
blink::InterestGroup interest_group = CreateInterestGroup();
|
||||
interest_group.ads.emplace();
|
||||
std::vector<url::Origin> allowed_reporting_origins = {kOriginG,
|
||||
kNotAttestedOrigin};
|
||||
blink::InterestGroup::Ad ad(
|
||||
/*render_url=*/GURL("https://example.com/render"),
|
||||
/*metadata=*/absl::nullopt,
|
||||
/*size_group=*/absl::nullopt,
|
||||
/*buyer_reporting_id=*/absl::nullopt,
|
||||
/*buyer_and_seller_reporting_id=*/absl::nullopt,
|
||||
/*ad_render_id=*/absl::nullopt,
|
||||
/*allowed_reporting_origins=*/std::move(allowed_reporting_origins));
|
||||
interest_group.ads->emplace_back(std::move(ad));
|
||||
JoinInterestGroupAndFlush(interest_group);
|
||||
EXPECT_EQ(0, GetJoinCount(kOriginA, kInterestGroupName));
|
||||
}
|
||||
|
||||
// Attempt to join an interest group whose size is very large. No join should
|
||||
// happen -- it should fail and close the pipe.
|
||||
TEST_F(AdAuctionServiceImplTest, JoinMassiveInterestGroupFails) {
|
||||
@ -1207,6 +1231,7 @@ TEST_F(AdAuctionServiceImplTest, FixExpiryOnJoin) {
|
||||
|
||||
// The server JSON updates all fields that can be updated.
|
||||
TEST_F(AdAuctionServiceImplTest, UpdateAllUpdatableFields) {
|
||||
content_browser_client_.SetAllowList({kOriginF, kOriginG});
|
||||
// TODO(caraitto): Remove camelCase sellerCapabilities fields when no longer
|
||||
// supported.
|
||||
network_responder_->RegisterUpdateResponse(
|
||||
@ -1265,7 +1290,7 @@ TEST_F(AdAuctionServiceImplTest, UpdateAllUpdatableFields) {
|
||||
interest_group.trusted_bidding_signals_keys.emplace();
|
||||
interest_group.trusted_bidding_signals_keys->push_back("key1");
|
||||
interest_group.ads.emplace();
|
||||
std::vector<url::Origin> allowed_reporting_origins = {kOriginG};
|
||||
std::vector<url::Origin> allowed_reporting_origins = {kOriginF};
|
||||
blink::InterestGroup::Ad ad(
|
||||
/*render_url=*/GURL("https://example.com/render"),
|
||||
/*metadata=*/"{\"ad\":\"metadata\",\"here\":[1,2,3]}",
|
||||
@ -1273,7 +1298,7 @@ TEST_F(AdAuctionServiceImplTest, UpdateAllUpdatableFields) {
|
||||
/*buyer_reporting_id=*/"old_brid",
|
||||
/*buyer_and_seller_reporting_id=*/"old_shrid",
|
||||
/*ad_render_id=*/"123abc",
|
||||
/*allowed_reporting_origins*/ std::move(allowed_reporting_origins));
|
||||
/*allowed_reporting_origins=*/std::move(allowed_reporting_origins));
|
||||
interest_group.ads->emplace_back(std::move(ad));
|
||||
interest_group.ad_components.emplace();
|
||||
blink::InterestGroup::Ad ad_component(
|
||||
@ -2155,6 +2180,53 @@ TEST_F(AdAuctionServiceImplTest, UpdateInvalidFieldCancelsAllUpdates) {
|
||||
}
|
||||
}
|
||||
|
||||
// The `ads` field is valid, but one of its allowed reporting origins is not
|
||||
// attested. The entire update should get cancelled.
|
||||
TEST_F(AdAuctionServiceImplTest,
|
||||
UpdateNotAttestedAllowedReportingOriginsCancelsAllUpdates) {
|
||||
blink::InterestGroup interest_group = CreateInterestGroup();
|
||||
interest_group.update_url = kUpdateUrlA;
|
||||
interest_group.bidding_url = kBiddingLogicUrlA;
|
||||
interest_group.trusted_bidding_signals_url = kTrustedBiddingSignalsUrlA;
|
||||
interest_group.trusted_bidding_signals_keys.emplace();
|
||||
interest_group.trusted_bidding_signals_keys->push_back("key1");
|
||||
interest_group.ads.emplace();
|
||||
blink::InterestGroup::Ad ad(
|
||||
/*render_url=*/GURL("https://example.com/render"),
|
||||
/*metadata=*/"{\"ad\":\"metadata\",\"here\":[1,2,3]}");
|
||||
interest_group.ads->emplace_back(std::move(ad));
|
||||
JoinInterestGroupAndFlush(interest_group);
|
||||
EXPECT_EQ(1, GetJoinCount(kOriginA, kInterestGroupName));
|
||||
|
||||
content_browser_client_.SetAllowList({kOriginG});
|
||||
network_responder_->RegisterUpdateResponse(
|
||||
kUpdateUrlPath, base::StringPrintf(R"({
|
||||
"biddingLogicUrl": "%s/interest_group/new_bidding_logic.js",
|
||||
"ads": [{"renderURL": "https://test.com",
|
||||
"metadata": {"new_a": "b"},
|
||||
"allowedReportingOrigins": ["https://a.test", "https://g.test"]
|
||||
}]
|
||||
})",
|
||||
kOriginStringA));
|
||||
|
||||
UpdateInterestGroupNoFlush();
|
||||
task_environment()->RunUntilIdle();
|
||||
|
||||
// Check that the ads and bidding logic URL didn't change.
|
||||
std::vector<StorageInterestGroup> groups =
|
||||
GetInterestGroupsForOwner(kOriginA);
|
||||
ASSERT_EQ(groups.size(), 1u);
|
||||
const auto& group = groups[0].interest_group;
|
||||
ASSERT_TRUE(group.ads.has_value());
|
||||
ASSERT_EQ(group.ads->size(), 1u);
|
||||
EXPECT_EQ(group.ads.value()[0].render_url.spec(),
|
||||
"https://example.com/render");
|
||||
EXPECT_EQ(group.ads.value()[0].metadata,
|
||||
"{\"ad\":\"metadata\",\"here\":[1,2,3]}");
|
||||
EXPECT_FALSE(group.ads.value()[0].allowed_reporting_origins.has_value());
|
||||
EXPECT_EQ(group.bidding_url, kBiddingLogicUrlA);
|
||||
}
|
||||
|
||||
// The `priority` field is not a valid number. The entire update should get
|
||||
// cancelled, since updates are atomic.
|
||||
TEST_F(AdAuctionServiceImplTest, UpdateInvalidPriorityCancelsAllUpdates) {
|
||||
@ -2905,6 +2977,117 @@ TEST_F(AdAuctionServiceImplTest, UpdateRateLimitedAfterBadUpdateResponse) {
|
||||
"https://example.com/new_render");
|
||||
}
|
||||
|
||||
// Just like AdAuctionServiceImplTest.UpdateRateLimitedAfterBadUpdateResponse,
|
||||
// except server returns a valid JSON response for update but with un-enrolled
|
||||
// allowedReportingOrigins.
|
||||
//
|
||||
// Join an interest group.
|
||||
// Set up update to fail (return server response with un-enrolled origins).
|
||||
// Update interest group fails.
|
||||
// Change update response to different value that will succeed.
|
||||
// Update does nothing (rate limited).
|
||||
// Advance to just before rate limit drops (which for bad response is the longer
|
||||
// "successful" duration), update does nothing (rate limited).
|
||||
// Advance after time limit. Update should work.
|
||||
TEST_F(AdAuctionServiceImplTest,
|
||||
UpdateRateLimitedAfterGotNotAttestedAllowedReportingOrigins) {
|
||||
content_browser_client_.SetAllowList({kOriginB});
|
||||
network_responder_->RegisterUpdateResponse(kUpdateUrlPath,
|
||||
R"({
|
||||
"ads": [{"renderURL": "https://example.com/new_render",
|
||||
"allowedReportingOrigins": ["https://a.test"]
|
||||
}]
|
||||
})");
|
||||
|
||||
blink::InterestGroup interest_group = CreateInterestGroup();
|
||||
// Set a long expiration delta so that we can advance to the next rate limit
|
||||
// period without the interest group expiring.
|
||||
interest_group.expiry = base::Time::Now() + base::Days(30);
|
||||
interest_group.update_url = kUpdateUrlA;
|
||||
interest_group.ads.emplace();
|
||||
blink::InterestGroup::Ad ad(
|
||||
/*render_url=*/GURL("https://example.com/render"),
|
||||
/*metadata=*/absl::nullopt);
|
||||
interest_group.ads->emplace_back(std::move(ad));
|
||||
JoinInterestGroupAndFlush(interest_group);
|
||||
EXPECT_EQ(1, GetJoinCount(kOriginA, kInterestGroupName));
|
||||
|
||||
UpdateInterestGroupNoFlush();
|
||||
task_environment()->RunUntilIdle();
|
||||
|
||||
// The first update fails, nothing changes.
|
||||
std::vector<StorageInterestGroup> groups =
|
||||
GetInterestGroupsForOwner(kOriginA);
|
||||
ASSERT_EQ(groups.size(), 1u);
|
||||
const auto& group = groups[0].interest_group;
|
||||
ASSERT_TRUE(group.ads.has_value());
|
||||
ASSERT_EQ(group.ads->size(), 1u);
|
||||
EXPECT_EQ(group.ads.value()[0].render_url.spec(),
|
||||
"https://example.com/render");
|
||||
EXPECT_FALSE(group.ads.value()[0].allowed_reporting_origins.has_value());
|
||||
|
||||
// Change the allowedReportingOrigins to attested origins and try updating
|
||||
// again.
|
||||
network_responder_->RegisterUpdateResponse(kUpdateUrlPath, R"({
|
||||
"ads": [{"renderURL": "https://example.com/new_render",
|
||||
"allowedReportingOrigins": ["https://b.test"]
|
||||
}]
|
||||
})");
|
||||
UpdateInterestGroupNoFlush();
|
||||
task_environment()->RunUntilIdle();
|
||||
|
||||
// The update does nothing due to rate limiting, nothing changes.
|
||||
std::vector<StorageInterestGroup> groups2 =
|
||||
GetInterestGroupsForOwner(kOriginA);
|
||||
ASSERT_EQ(groups2.size(), 1u);
|
||||
const auto& group2 = groups2[0].interest_group;
|
||||
ASSERT_TRUE(group2.ads.has_value());
|
||||
ASSERT_EQ(group2.ads->size(), 1u);
|
||||
EXPECT_EQ(group.ads.value()[0].render_url.spec(),
|
||||
"https://example.com/render");
|
||||
EXPECT_FALSE(group.ads.value()[0].allowed_reporting_origins.has_value());
|
||||
|
||||
// Advance time to just before end of rate limit period. Update should still
|
||||
// do nothing due to rate limiting. Invalid responses use the longer
|
||||
// "successful" backoff period.
|
||||
task_environment()->FastForwardBy(
|
||||
InterestGroupStorage::kUpdateSucceededBackoffPeriod - base::Seconds(1));
|
||||
|
||||
UpdateInterestGroupNoFlush();
|
||||
task_environment()->RunUntilIdle();
|
||||
|
||||
// The update does nothing due to rate limiting, nothing changes.
|
||||
std::vector<StorageInterestGroup> groups3 =
|
||||
GetInterestGroupsForOwner(kOriginA);
|
||||
ASSERT_EQ(groups3.size(), 1u);
|
||||
const auto& group3 = groups3[0].interest_group;
|
||||
ASSERT_TRUE(group3.ads.has_value());
|
||||
ASSERT_EQ(group3.ads->size(), 1u);
|
||||
EXPECT_EQ(group.ads.value()[0].render_url.spec(),
|
||||
"https://example.com/render");
|
||||
EXPECT_FALSE(group.ads.value()[0].allowed_reporting_origins.has_value());
|
||||
|
||||
// Advance time to just after end of rate limit period. Update should now
|
||||
// succeed.
|
||||
task_environment()->FastForwardBy(base::Seconds(2));
|
||||
|
||||
UpdateInterestGroupNoFlush();
|
||||
task_environment()->RunUntilIdle();
|
||||
|
||||
// The update changes the database contents.
|
||||
std::vector<StorageInterestGroup> groups4 =
|
||||
GetInterestGroupsForOwner(kOriginA);
|
||||
ASSERT_EQ(groups4.size(), 1u);
|
||||
const auto& group4 = groups4[0].interest_group;
|
||||
ASSERT_TRUE(group4.ads.has_value());
|
||||
ASSERT_EQ(group4.ads->size(), 1u);
|
||||
EXPECT_EQ(group4.ads.value()[0].render_url.spec(),
|
||||
"https://example.com/new_render");
|
||||
std::vector<url::Origin> allowed_reporting_origins = {kOriginB};
|
||||
EXPECT_EQ(group4.ads.value()[0].allowed_reporting_origins,
|
||||
allowed_reporting_origins);
|
||||
}
|
||||
|
||||
// Join an interest group.
|
||||
// Make interest group update fail with net::ERR_CONNECTION_RESET.
|
||||
// Update interest group fails.
|
||||
@ -4388,8 +4571,10 @@ function scoreAd(
|
||||
}
|
||||
)";
|
||||
|
||||
content_browser_client_.SetAllowList({kOriginG});
|
||||
network_responder_->RegisterUpdateResponse(kUpdateUrlPath, R"({
|
||||
"ads": [{"renderURL": "https://example.com/new_render"
|
||||
"ads": [{"renderURL": "https://example.com/new_render",
|
||||
"allowedReportingOrigins": ["https://g.test"]
|
||||
}]
|
||||
})");
|
||||
|
||||
@ -4426,6 +4611,9 @@ function scoreAd(
|
||||
ASSERT_EQ(a_group.ads->size(), 1u);
|
||||
EXPECT_EQ(a_group.ads.value()[0].render_url.spec(),
|
||||
"https://example.com/new_render");
|
||||
std::vector<url::Origin> allowed_reporting_origins = {kOriginG};
|
||||
EXPECT_EQ(a_group.ads.value()[0].allowed_reporting_origins,
|
||||
allowed_reporting_origins);
|
||||
}
|
||||
|
||||
// Like UpdatesInterestGroupsAfterSuccessfulAuction, but the auction fails
|
||||
|
@ -78,6 +78,7 @@ std::unique_ptr<AuctionRunner> AuctionRunner::CreateAndStart(
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
|
||||
IsInterestGroupApiAllowedCallback is_interest_group_api_allowed_callback,
|
||||
GetAdAuctionPageDataCallback get_page_data_callback,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
mojo::PendingReceiver<AbortableAdAuction> abort_receiver,
|
||||
RunAuctionCallback callback) {
|
||||
std::unique_ptr<AuctionRunner> instance(new AuctionRunner(
|
||||
@ -88,8 +89,8 @@ std::unique_ptr<AuctionRunner> AuctionRunner::CreateAndStart(
|
||||
frame_origin, ukm_source_id, std::move(client_security_state),
|
||||
std::move(url_loader_factory),
|
||||
std::move(is_interest_group_api_allowed_callback),
|
||||
std::move(get_page_data_callback), std::move(abort_receiver),
|
||||
std::move(callback)));
|
||||
std::move(get_page_data_callback), std::move(attestation_callback),
|
||||
std::move(abort_receiver), std::move(callback)));
|
||||
instance->StartAuction();
|
||||
return instance;
|
||||
}
|
||||
@ -462,6 +463,7 @@ AuctionRunner::AuctionRunner(
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
|
||||
IsInterestGroupApiAllowedCallback is_interest_group_api_allowed_callback,
|
||||
GetAdAuctionPageDataCallback get_page_data_callback,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
mojo::PendingReceiver<AbortableAdAuction> abort_receiver,
|
||||
RunAuctionCallback callback)
|
||||
: interest_group_manager_(interest_group_manager),
|
||||
@ -474,6 +476,7 @@ AuctionRunner::AuctionRunner(
|
||||
is_interest_group_api_allowed_callback_(
|
||||
is_interest_group_api_allowed_callback),
|
||||
get_page_data_callback_(get_page_data_callback),
|
||||
attestation_callback_(attestation_callback),
|
||||
abort_receiver_(this, std::move(abort_receiver)),
|
||||
kanon_mode_(kanon_mode),
|
||||
owned_auction_config_(
|
||||
@ -611,7 +614,7 @@ void AuctionRunner::UpdateInterestGroupsPostAuction() {
|
||||
});
|
||||
|
||||
interest_group_manager_->UpdateInterestGroupsOfOwners(
|
||||
update_owners, client_security_state_.Clone());
|
||||
update_owners, client_security_state_.Clone(), attestation_callback_);
|
||||
}
|
||||
|
||||
void AuctionRunner::NotifyPromiseResolved(
|
||||
|
@ -93,6 +93,9 @@ class CONTENT_EXPORT AuctionRunner : public blink::mojom::AbortableAdAuction {
|
||||
using GetAdAuctionPageDataCallback =
|
||||
base::RepeatingCallback<AdAuctionPageData*()>;
|
||||
|
||||
using AreReportingOriginsAttestedCallback =
|
||||
base::RepeatingCallback<bool(const std::vector<url::Origin>&)>;
|
||||
|
||||
// Creates an entire FLEDGE auction. Single-use object.
|
||||
//
|
||||
// Arguments: `auction_worklet_manager`, `interest_group_manager`,
|
||||
@ -121,6 +124,10 @@ class CONTENT_EXPORT AuctionRunner : public blink::mojom::AbortableAdAuction {
|
||||
// seller origins, and those for which it returns false will not be allowed
|
||||
// to participate in the auction.
|
||||
//
|
||||
// `attestation_callback` will be called on all interest group
|
||||
// updates' ad allowed reporting origins, and those updates which the
|
||||
// callback returns false will not update the interest group.
|
||||
//
|
||||
// `callback` is invoked on auction completion. It should synchronously
|
||||
// destroy this AuctionRunner object. `callback` won't be invoked until
|
||||
// after CreateAndStart() returns.
|
||||
@ -139,6 +146,7 @@ class CONTENT_EXPORT AuctionRunner : public blink::mojom::AbortableAdAuction {
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
|
||||
IsInterestGroupApiAllowedCallback is_interest_group_api_allowed_callback,
|
||||
GetAdAuctionPageDataCallback get_page_data_callback,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
mojo::PendingReceiver<AbortableAdAuction> abort_receiver,
|
||||
RunAuctionCallback callback);
|
||||
|
||||
@ -215,6 +223,7 @@ class CONTENT_EXPORT AuctionRunner : public blink::mojom::AbortableAdAuction {
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
|
||||
IsInterestGroupApiAllowedCallback is_interest_group_api_allowed_callback,
|
||||
GetAdAuctionPageDataCallback get_page_data_callback,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
mojo::PendingReceiver<AbortableAdAuction> abort_receiver,
|
||||
RunAuctionCallback callback);
|
||||
|
||||
@ -282,6 +291,8 @@ class CONTENT_EXPORT AuctionRunner : public blink::mojom::AbortableAdAuction {
|
||||
|
||||
GetAdAuctionPageDataCallback get_page_data_callback_;
|
||||
|
||||
AreReportingOriginsAttestedCallback attestation_callback_;
|
||||
|
||||
mojo::Receiver<blink::mojom::AbortableAdAuction> abort_receiver_;
|
||||
|
||||
// Configuration.
|
||||
|
@ -1721,6 +1721,15 @@ class AuctionRunnerTest : public RenderViewHostTestHarness,
|
||||
|
||||
auction_run_loop_ = std::make_unique<base::RunLoop>();
|
||||
abortable_ad_auction_.reset();
|
||||
|
||||
// The callback to check attestation is not tested at this layer.
|
||||
// AdAuctionServiceImplTest tests it.
|
||||
base::RepeatingCallback<bool(const std::vector<url::Origin>&)>
|
||||
attestation_callback = base::BindRepeating(
|
||||
[](BrowserContext* browser_context,
|
||||
const std::vector<url::Origin>& origins) { return true; },
|
||||
base::Unretained(browser_context()));
|
||||
|
||||
auction_runner_ = AuctionRunner::CreateAndStart(
|
||||
auction_worklet_manager_.get(), interest_group_manager_.get(),
|
||||
/*browser_context=*/browser_context(), &private_aggregation_manager_,
|
||||
@ -1730,6 +1739,7 @@ class AuctionRunnerTest : public RenderViewHostTestHarness,
|
||||
IsInterestGroupApiAllowedCallback(), base::BindLambdaForTesting([&]() {
|
||||
return ad_auction_page_data_.get();
|
||||
}),
|
||||
std::move(attestation_callback),
|
||||
abortable_ad_auction_.BindNewPipeAndPassReceiver(),
|
||||
base::BindOnce(&AuctionRunnerTest::OnAuctionComplete,
|
||||
base::Unretained(this)));
|
||||
|
@ -3185,13 +3185,15 @@ IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
|
||||
}
|
||||
|
||||
IN_PROC_BROWSER_TEST_F(InterestGroupBrowserTest,
|
||||
JoinInterestGroupValidAllowedReportingOrigins) {
|
||||
JoinInterestGroupValidAttestedAllowedReportingOrigins) {
|
||||
GURL url = https_server_->GetURL("a.test", "/echo");
|
||||
auto origin = url::Origin::Create(url);
|
||||
ASSERT_TRUE(NavigateToURL(shell(), url));
|
||||
|
||||
std::vector<url::Origin> allowed_reporting_origins = {
|
||||
url::Origin::Create(GURL("https://report.test"))};
|
||||
url::Origin other_origin =
|
||||
url::Origin::Create(https_server_->GetURL("b.test", "/echo"));
|
||||
content_browser_client_->AddToAllowList({other_origin});
|
||||
std::vector<url::Origin> allowed_reporting_origins = {other_origin};
|
||||
EXPECT_EQ(
|
||||
kSuccess,
|
||||
JoinInterestGroupAndVerify(
|
||||
|
@ -173,6 +173,7 @@ void InterestGroupManagerImpl::CheckPermissionsAndJoinInterestGroup(
|
||||
const net::NetworkIsolationKey& network_isolation_key,
|
||||
bool report_result_only,
|
||||
network::mojom::URLLoaderFactory& url_loader_factory,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
blink::mojom::AdAuctionService::JoinInterestGroupCallback callback) {
|
||||
url::Origin interest_group_owner = group.owner;
|
||||
permissions_checker_.CheckPermissions(
|
||||
@ -181,7 +182,8 @@ void InterestGroupManagerImpl::CheckPermissionsAndJoinInterestGroup(
|
||||
base::BindOnce(
|
||||
&InterestGroupManagerImpl::OnJoinInterestGroupPermissionsChecked,
|
||||
base::Unretained(this), std::move(group), joining_url,
|
||||
report_result_only, std::move(callback)));
|
||||
report_result_only, std::move(attestation_callback),
|
||||
std::move(callback)));
|
||||
}
|
||||
|
||||
void InterestGroupManagerImpl::CheckPermissionsAndLeaveInterestGroup(
|
||||
@ -228,16 +230,18 @@ void InterestGroupManagerImpl::LeaveInterestGroup(
|
||||
|
||||
void InterestGroupManagerImpl::UpdateInterestGroupsOfOwner(
|
||||
const url::Origin& owner,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state) {
|
||||
update_manager_.UpdateInterestGroupsOfOwner(owner,
|
||||
std::move(client_security_state));
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback) {
|
||||
update_manager_.UpdateInterestGroupsOfOwner(
|
||||
owner, std::move(client_security_state), std::move(callback));
|
||||
}
|
||||
|
||||
void InterestGroupManagerImpl::UpdateInterestGroupsOfOwners(
|
||||
base::span<url::Origin> owners,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state) {
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback) {
|
||||
update_manager_.UpdateInterestGroupsOfOwners(
|
||||
owners, std::move(client_security_state));
|
||||
owners, std::move(client_security_state), std::move(callback));
|
||||
}
|
||||
|
||||
void InterestGroupManagerImpl::RecordInterestGroupBids(
|
||||
@ -465,6 +469,7 @@ void InterestGroupManagerImpl::OnJoinInterestGroupPermissionsChecked(
|
||||
blink::InterestGroup group,
|
||||
const GURL& joining_url,
|
||||
bool report_result_only,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
blink::mojom::AdAuctionService::JoinInterestGroupCallback callback,
|
||||
bool can_join) {
|
||||
// Invoke callback before calling JoinInterestGroup(), which posts a task to
|
||||
@ -475,8 +480,20 @@ void InterestGroupManagerImpl::OnJoinInterestGroupPermissionsChecked(
|
||||
// invoking the callback may potentially leak whether the user was previously
|
||||
// in the InterestGroup through timing differences.
|
||||
std::move(callback).Run(/*failed_well_known_check=*/!can_join);
|
||||
if (!report_result_only && can_join)
|
||||
|
||||
// All ads' allowed reporting origins must be attested. Otherwise don't join.
|
||||
if (!report_result_only && can_join) {
|
||||
if (group.ads) {
|
||||
for (const auto& ad : *group.ads) {
|
||||
if (ad.allowed_reporting_origins) {
|
||||
if (!attestation_callback.Run(ad.allowed_reporting_origins.value())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
JoinInterestGroup(std::move(group), joining_url);
|
||||
}
|
||||
}
|
||||
|
||||
void InterestGroupManagerImpl::OnLeaveInterestGroupPermissionsChecked(
|
||||
|
@ -57,6 +57,9 @@ class InterestGroupStorage;
|
||||
// as it performs blocking file IO when backed by on-disk storage.
|
||||
class CONTENT_EXPORT InterestGroupManagerImpl : public InterestGroupManager {
|
||||
public:
|
||||
using AreReportingOriginsAttestedCallback =
|
||||
base::RepeatingCallback<bool(const std::vector<url::Origin>&)>;
|
||||
|
||||
// Controls how auction worklets will be run. kDedicated will use
|
||||
// fully-isolated utility processes solely for worklet. kInRenderer will
|
||||
// re-use regular renderers following the normal site isolation policy.
|
||||
@ -133,6 +136,7 @@ class CONTENT_EXPORT InterestGroupManagerImpl : public InterestGroupManager {
|
||||
const net::NetworkIsolationKey& network_isolation_key,
|
||||
bool report_result_only,
|
||||
network::mojom::URLLoaderFactory& url_loader_factory,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
blink::mojom::AdAuctionService::JoinInterestGroupCallback callback);
|
||||
|
||||
// Same as CheckPermissionsAndJoinInterestGroup(), except for a leave
|
||||
@ -159,14 +163,16 @@ class CONTENT_EXPORT InterestGroupManagerImpl : public InterestGroupManager {
|
||||
// load or validate are skipped, but other updates will proceed.
|
||||
void UpdateInterestGroupsOfOwner(
|
||||
const url::Origin& owner,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state);
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback);
|
||||
// Like UpdateInterestGroupsOfOwner(), but handles multiple interest group
|
||||
// owners.
|
||||
//
|
||||
// The list is shuffled in-place to ensure fairness.
|
||||
void UpdateInterestGroupsOfOwners(
|
||||
base::span<url::Origin> owners,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state);
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback);
|
||||
|
||||
// For testing *only*; changes the maximum amount of time that the update
|
||||
// process can run before it gets cancelled for taking too long.
|
||||
@ -370,6 +376,7 @@ class CONTENT_EXPORT InterestGroupManagerImpl : public InterestGroupManager {
|
||||
blink::InterestGroup group,
|
||||
const GURL& joining_url,
|
||||
bool report_result_only,
|
||||
AreReportingOriginsAttestedCallback attestation_callback,
|
||||
blink::mojom::AdAuctionService::JoinInterestGroupCallback callback,
|
||||
bool can_join);
|
||||
void OnLeaveInterestGroupPermissionsChecked(
|
||||
|
@ -578,18 +578,21 @@ InterestGroupUpdateManager::~InterestGroupUpdateManager() = default;
|
||||
|
||||
void InterestGroupUpdateManager::UpdateInterestGroupsOfOwner(
|
||||
const url::Origin& owner,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state) {
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback) {
|
||||
attestation_callback_ = std::move(callback);
|
||||
owners_to_update_.Enqueue(owner, std::move(client_security_state));
|
||||
MaybeContinueUpdatingCurrentOwner();
|
||||
}
|
||||
|
||||
void InterestGroupUpdateManager::UpdateInterestGroupsOfOwners(
|
||||
base::span<url::Origin> owners,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state) {
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback) {
|
||||
// Shuffle the list of interest group owners for fairness.
|
||||
base::RandomShuffle(owners.begin(), owners.end());
|
||||
for (const url::Origin& owner : owners) {
|
||||
UpdateInterestGroupsOfOwner(owner, client_security_state.Clone());
|
||||
UpdateInterestGroupsOfOwner(owner, client_security_state.Clone(), callback);
|
||||
}
|
||||
}
|
||||
|
||||
@ -780,6 +783,17 @@ void InterestGroupUpdateManager::DidUpdateInterestGroupsOfOwnerJsonParse(
|
||||
ReportUpdateFailed(group_key, UpdateDelayType::kParseFailure);
|
||||
return;
|
||||
}
|
||||
if (interest_group_update->ads) {
|
||||
for (const auto& ad : *interest_group_update->ads) {
|
||||
if (ad.allowed_reporting_origins) {
|
||||
if (!attestation_callback_.Run(ad.allowed_reporting_origins.value())) {
|
||||
// Treat this the same way as a parse failure.
|
||||
ReportUpdateFailed(group_key, UpdateDelayType::kParseFailure);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
UpdateInterestGroup(group_key, std::move(*interest_group_update));
|
||||
}
|
||||
|
||||
|
@ -47,6 +47,9 @@ class InterestGroupManagerImpl;
|
||||
// updated recently.
|
||||
class CONTENT_EXPORT InterestGroupUpdateManager {
|
||||
public:
|
||||
using AreReportingOriginsAttestedCallback =
|
||||
base::RepeatingCallback<bool(const std::vector<url::Origin>&)>;
|
||||
|
||||
// `manager` should be the InterestGroupManagerImpl that owns this
|
||||
// InterestGroupManager.
|
||||
InterestGroupUpdateManager(
|
||||
@ -59,7 +62,8 @@ class CONTENT_EXPORT InterestGroupUpdateManager {
|
||||
// load or validate are skipped, but other updates will proceed.
|
||||
void UpdateInterestGroupsOfOwner(
|
||||
const url::Origin& owner,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state);
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback);
|
||||
|
||||
// Like UpdateInterestGroupsOfOwner(), but handles multiple interest group
|
||||
// owners.
|
||||
@ -67,7 +71,8 @@ class CONTENT_EXPORT InterestGroupUpdateManager {
|
||||
// The list is shuffled in-place to ensure fairness.
|
||||
void UpdateInterestGroupsOfOwners(
|
||||
base::span<url::Origin> owners,
|
||||
network::mojom::ClientSecurityStatePtr client_security_state);
|
||||
network::mojom::ClientSecurityStatePtr client_security_state,
|
||||
AreReportingOriginsAttestedCallback callback);
|
||||
|
||||
// For testing *only*; changes the maximum amount of time that the update
|
||||
// process can run before it gets cancelled for taking too long.
|
||||
@ -252,6 +257,9 @@ class CONTENT_EXPORT InterestGroupUpdateManager {
|
||||
// destroyed.
|
||||
UrlLoadersList url_loaders_;
|
||||
|
||||
// For checking if all allowed reporting origins are attested.
|
||||
AreReportingOriginsAttestedCallback attestation_callback_;
|
||||
|
||||
// TODO(crbug.com/1186444): Do we need to test InterestGroupManager
|
||||
// destruction during update? If so, how?
|
||||
base::WeakPtrFactory<InterestGroupUpdateManager> weak_factory_{this};
|
||||
|
Reference in New Issue
Block a user