0

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:
Qingxin Wu
2023-08-09 21:20:36 +00:00
committed by Chromium LUCI CQ
parent 6debc1fec5
commit db80710acf
10 changed files with 320 additions and 30 deletions

@ -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};