Skip to content

Commit

Permalink
Change Ad k-anonymity key to include group owner and bidding script
Browse files Browse the repository at this point in the history
Bug: 1234419
Change-Id: I8e58d22ccd2fcb6d1c96fe0bc3cc095eab178531
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4023026
Auto-Submit: Russ Hamilton <behamilton@google.com>
Reviewed-by: Maks Orlovich <morlovich@chromium.org>
Reviewed-by: Dominic Farolino <dom@chromium.org>
Commit-Queue: Dominic Farolino <dom@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1073443}
  • Loading branch information
brusshamilton authored and Chromium LUCI CQ committed Nov 18, 2022
1 parent 5b8d18e commit d931e78
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 102 deletions.
4 changes: 3 additions & 1 deletion content/browser/interest_group/auction_runner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ void AuctionRunner::OnReportingPhaseComplete(

interest_group_manager_->RecordInterestGroupWin(winning_group_key,
ad_metadata);
interest_group_manager_->RegisterAdAsWon(auction_.top_bid()->bid->render_url);
interest_group_manager_->RegisterAdAsWon(
*auction_.top_bid()->bid->interest_group,
*auction_.top_bid()->bid->bid_ad);

std::vector<GURL> debug_win_report_urls;
std::vector<GURL> debug_loss_report_urls;
Expand Down
2 changes: 1 addition & 1 deletion content/browser/interest_group/interest_group_auction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ class InterestGroupAuction::BuyerHelper
base::Time start_time = auction_->auction_start_time_;

std::vector<std::pair<GURL, bool>> kanon_entries;
for (const auto& ad_kanon : storage_interest_group.ads_kanon) {
for (const auto& ad_kanon : storage_interest_group.bidding_ads_kanon) {
bool is_kanon =
ad_kanon.is_k_anonymous &&
(ad_kanon.last_updated + kKAnonymityExpiration < start_time);
Expand Down
37 changes: 34 additions & 3 deletions content/browser/interest_group/interest_group_auction_reporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@
#include "content/browser/fenced_frame/fenced_frame_url_mapping.h"
#include "content/browser/interest_group/auction_worklet_manager.h"
#include "content/browser/interest_group/interest_group_auction.h"
#include "content/browser/interest_group/interest_group_k_anonymity_manager.h"
#include "content/browser/interest_group/interest_group_storage.h"
#include "content/services/auction_worklet/public/mojom/bidder_worklet.mojom.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom-forward.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
#include "content/services/auction_worklet/public/mojom/seller_worklet.mojom.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/interest_group/interest_group.h"
#include "third_party/blink/public/mojom/fenced_frame/fenced_frame.mojom-shared.h"
#include "third_party/blink/public/mojom/interest_group/interest_group_types.mojom.h"
Expand Down Expand Up @@ -293,10 +295,39 @@ void InterestGroupAuctionReporter::OnBidderWorkletReceived(
*auction_config,
winning_bid_info_.storage_interest_group->interest_group.owner);

std::string group_name =
winning_bid_info_.storage_interest_group->interest_group.name;
// if k-anonymity enforcement is on we can only reveal the winning interest
// group name in reportWin if the winning ad's reporting_ads_kanon entry is
// k-anonymous. Otherwise we simply provide the empty string instead of the
// group name.
if (base::FeatureList::IsEnabled(
blink::features::kFledgeConsiderKAnonymity) &&
base::FeatureList::IsEnabled(blink::features::kFledgeEnforceKAnonymity)) {
auto chosen_ad = base::ranges::find(
*winning_bid_info_.storage_interest_group->interest_group.ads,
winning_bid_info_.render_url,
[](const blink::InterestGroup::Ad& ad) { return ad.render_url; });
CHECK(chosen_ad !=
winning_bid_info_.storage_interest_group->interest_group.ads->end());
std::string reporting_key = KAnonKeyForAdNameReporting(
winning_bid_info_.storage_interest_group->interest_group, *chosen_ad);
auto kanon = base::ranges::find(
winning_bid_info_.storage_interest_group->reporting_ads_kanon,
reporting_key, [](const StorageInterestGroup::KAnonymityData& data) {
return data.key;
});
if (kanon == winning_bid_info_.storage_interest_group->reporting_ads_kanon
.end() ||
!kanon->is_k_anonymous) {
group_name = "";
}
}

bidder_worklet_handle_->GetBidderWorklet()->ReportWin(
winning_bid_info_.storage_interest_group->interest_group.name,
auction_config->non_shared_params.auction_signals, per_buyer_signals,
signals_for_winner, winning_bid_info_.render_url, winning_bid_info_.bid,
group_name, auction_config->non_shared_params.auction_signals,
per_buyer_signals, signals_for_winner, winning_bid_info_.render_url,
winning_bid_info_.bid,
/*browser_signal_highest_scoring_other_bid=*/
seller_info.highest_scoring_other_bid,
seller_info.highest_scoring_other_bid_owner.has_value() &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "content/browser/interest_group/interest_group_k_anonymity_manager.h"

#include "base/bind.h"
#include "base/containers/contains.h"
#include "base/time/time.h"
#include "content/browser/interest_group/interest_group_manager_impl.h"

Expand All @@ -14,6 +15,29 @@ std::string KAnonKeyFor(const url::Origin& owner, const std::string& name) {
return owner.GetURL().spec() + '\n' + name;
}

std::string KAnonKeyForAdBid(const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad) {
DCHECK(group.ads);
DCHECK(base::Contains(*group.ads, ad) ||
(group.ad_components && base::Contains(*group.ad_components, ad)));
DCHECK(group.bidding_url);
return group.owner.GetURL().spec() + '\n' +
group.bidding_url.value_or(GURL()).spec() + '\n' +
group.bidding_wasm_helper_url.value_or(GURL()).spec() + '\n' +
ad.render_url.spec();
}

std::string KAnonKeyForAdNameReporting(const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad) {
DCHECK(group.ads);
DCHECK(base::Contains(*group.ads, ad));
DCHECK(group.bidding_url);
return group.owner.GetURL().spec() + '\n' + group.name + '\n' +
group.bidding_url.value_or(GURL()).spec() + '\n' +
group.bidding_wasm_helper_url.value_or(GURL()).spec() + '\n' +
ad.render_url.spec();
}

InterestGroupKAnonymityManager::InterestGroupKAnonymityManager(
InterestGroupManagerImpl* interest_group_manager,
KAnonymityServiceDelegate* k_anonymity_service)
Expand Down Expand Up @@ -47,7 +71,12 @@ void InterestGroupKAnonymityManager::QueryKAnonymityForInterestGroup(
}
}

for (const auto& ad : storage_group.ads_kanon) {
for (const auto& ad : storage_group.bidding_ads_kanon) {
if (ad.last_updated < check_time - min_wait) {
ids_to_query.push_back(ad.key);
}
}
for (const auto& ad : storage_group.reporting_ads_kanon) {
if (ad.last_updated < check_time - min_wait) {
ids_to_query.push_back(ad.key);
}
Expand Down Expand Up @@ -90,8 +119,11 @@ void InterestGroupKAnonymityManager::RegisterInterestGroupAsJoined(
RegisterIDAsJoined(group.daily_update_url->spec());
}

void InterestGroupKAnonymityManager::RegisterAdAsWon(const GURL& render_url) {
RegisterIDAsJoined(render_url.spec());
void InterestGroupKAnonymityManager::RegisterAdAsWon(
const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad) {
RegisterIDAsJoined(KAnonKeyForAdBid(group, ad));
RegisterIDAsJoined(KAnonKeyForAdNameReporting(group, ad));
// TODO(behamilton): Consider proactively starting a query here to improve the
// speed that browsers see new ads. We will likely want to rate limit this
// somehow though.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ class InterestGroupManagerImpl;
std::string CONTENT_EXPORT KAnonKeyFor(const url::Origin& owner,
const std::string& name);

// Calculates the k-anonymity key for an Ad that is used for bidding.
std::string CONTENT_EXPORT KAnonKeyForAdBid(const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad);
// Calculates the k-anonymity key for an Ad that is used for reporting the
// interest group name.
std::string CONTENT_EXPORT
KAnonKeyForAdNameReporting(const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad);

// Manages k-anonymity updates. Checks last updated times in the database
// to limit updates (joins and queries) to once per day. Called by the
// InterestGroupManagerImpl for interest group k-anonymity updates. Calls
Expand All @@ -44,7 +53,8 @@ class InterestGroupKAnonymityManager {

// Notify the k-anonymity service that this ad won an auction. Internally this
// calls RegisterIDAsJoined().
void RegisterAdAsWon(const GURL& render_url);
void RegisterAdAsWon(const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad);

private:
// Callback from k-anonymity service QuerySets(). Saves the updated results to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ constexpr base::TimeDelta kJoinInterval = base::Hours(1);
constexpr base::TimeDelta kQueryInterval = base::Hours(2);

constexpr char kAdURL[] = "https://www.foo.com/ad1.html";
constexpr char kBiddingURL[] = "https://www.example.com/bidding_logic";
constexpr char kUpdateURL[] = "https://www.example.com/update";

class TestKAnonymityServiceDelegate : public KAnonymityServiceDelegate {
Expand Down Expand Up @@ -65,6 +66,7 @@ blink::InterestGroup MakeInterestGroup(url::Origin owner, std::string name) {
group.expiry = base::Time::Now() + base::Days(1);
group.owner = owner;
group.name = name;
group.bidding_url = GURL(kBiddingURL);
group.daily_update_url = GURL(kUpdateURL);
group.ads.emplace();
group.ads->push_back(blink::InterestGroup::Ad(GURL(kAdURL), /*metadata=*/""));
Expand Down Expand Up @@ -95,7 +97,7 @@ class InterestGroupKAnonymityManagerTest : public testing::Test {
return result;
}

absl::optional<base::Time> getLastReported(InterestGroupManagerImpl* manager,
absl::optional<base::Time> GetLastReported(InterestGroupManagerImpl* manager,
std::string key) {
absl::optional<base::Time> result;
base::RunLoop run_loop;
Expand All @@ -109,6 +111,13 @@ class InterestGroupKAnonymityManagerTest : public testing::Test {
return result;
}

absl::optional<base::Time> GetLastAdReported(
InterestGroupManagerImpl* manager,
const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad) {
return GetLastReported(manager, KAnonKeyForAdBid(group, ad));
}

std::unique_ptr<InterestGroupManagerImpl> CreateManager(
bool has_error = false) {
delegate_ = std::make_unique<TestKAnonymityServiceDelegate>(has_error);
Expand Down Expand Up @@ -179,7 +188,7 @@ TEST_F(InterestGroupKAnonymityManagerTest, QueueUpdatePerformsJoinSetForGroup) {
std::string group_update_url = kUpdateURL;

auto manager = CreateManager();
EXPECT_EQ(base::Time::Min(), getLastReported(manager.get(), group_name_url));
EXPECT_EQ(base::Time::Min(), GetLastReported(manager.get(), group_name_url));
EXPECT_FALSE(getGroup(manager.get(), owner, name));
base::Time before_join = base::Time::Now();

Expand All @@ -192,12 +201,12 @@ TEST_F(InterestGroupKAnonymityManagerTest, QueueUpdatePerformsJoinSetForGroup) {
EXPECT_TRUE(getGroup(manager.get(), owner, name));

absl::optional<base::Time> group_name_reported =
getLastReported(manager.get(), group_name_url);
GetLastReported(manager.get(), group_name_url);
ASSERT_TRUE(group_name_reported);
EXPECT_LE(before_join, group_name_reported);

absl::optional<base::Time> update_url_reported =
getLastReported(manager.get(), kUpdateURL);
GetLastReported(manager.get(), kUpdateURL);
ASSERT_TRUE(update_url_reported);
EXPECT_LE(before_join, update_url_reported);

Expand All @@ -211,60 +220,66 @@ TEST_F(InterestGroupKAnonymityManagerTest, QueueUpdatePerformsJoinSetForGroup) {

// Second update shouldn't change anything.
EXPECT_EQ(group_name_reported,
getLastReported(manager.get(), group_name_url));
EXPECT_EQ(update_url_reported, getLastReported(manager.get(), kUpdateURL));
GetLastReported(manager.get(), group_name_url));
EXPECT_EQ(update_url_reported, GetLastReported(manager.get(), kUpdateURL));

task_environment().FastForwardBy(kJoinInterval);

// Updated more than GetJoinInterval() ago, so update.
manager->QueueKAnonymityUpdateForInterestGroup(*maybe_group);
task_environment().RunUntilIdle();
EXPECT_LT(update_url_reported, getLastReported(manager.get(), kUpdateURL));
EXPECT_LT(update_url_reported, GetLastReported(manager.get(), kUpdateURL));
}

TEST_F(InterestGroupKAnonymityManagerTest, RegisterAdAsWonPerformsJoinSet) {
const GURL top_frame = GURL("https://www.example.com/foo");
const url::Origin owner = url::Origin::Create(top_frame);
const std::string name = "foo";
blink::InterestGroup group = MakeInterestGroup(owner, "foo");
group.bidding_url = GURL("https://www.example.com/bidding.js");

auto manager = CreateManager();
EXPECT_FALSE(getGroup(manager.get(), owner, name));
EXPECT_EQ(base::Time::Min(), getLastReported(manager.get(), kAdURL));
EXPECT_EQ(base::Time::Min(),
GetLastAdReported(manager.get(), group, group.ads.value()[0]));

manager->JoinInterestGroup(MakeInterestGroup(owner, "foo"), top_frame);
manager->JoinInterestGroup(group, top_frame);
// The group *must* exist when JoinInterestGroup returns.
ASSERT_TRUE(getGroup(manager.get(), owner, name));

// k-anonymity would happens here.
task_environment().FastForwardBy(base::Minutes(1));

// Ads are *not* reported as part of joining an interest group.
absl::optional<base::Time> reported = getLastReported(manager.get(), kAdURL);
absl::optional<base::Time> reported =
GetLastAdReported(manager.get(), group, group.ads.value()[0]);
EXPECT_EQ(base::Time::Min(), reported);

base::Time before_mark_ad = base::Time::Now();
manager->RegisterAdAsWon(GURL(kAdURL));
manager->RegisterAdAsWon(group, group.ads.value()[0]);

// k-anonymity update happens here.
task_environment().FastForwardBy(base::Minutes(1));

reported = getLastReported(manager.get(), kAdURL);
reported = GetLastAdReported(manager.get(), group, group.ads.value()[0]);
EXPECT_LE(before_mark_ad, reported);
ASSERT_TRUE(reported);
base::Time last_reported = *reported;

manager->RegisterAdAsWon(GURL(kAdURL));
manager->RegisterAdAsWon(group, group.ads.value()[0]);
task_environment().FastForwardBy(base::Minutes(1));

// Second update shouldn't have changed the update time (too recent).
EXPECT_EQ(last_reported, getLastReported(manager.get(), kAdURL));
EXPECT_EQ(last_reported,
GetLastAdReported(manager.get(), group, group.ads.value()[0]));

task_environment().FastForwardBy(kJoinInterval);

// Updated more than 24 hours ago, so update.
manager->RegisterAdAsWon(GURL(kAdURL));
manager->RegisterAdAsWon(group, group.ads.value()[0]);
task_environment().RunUntilIdle();
EXPECT_LT(last_reported, getLastReported(manager.get(), kAdURL));
EXPECT_LT(last_reported,
GetLastAdReported(manager.get(), group, group.ads.value()[0]));
}

TEST_F(InterestGroupKAnonymityManagerTest, HandlesServerErrors) {
Expand All @@ -290,7 +305,7 @@ TEST_F(InterestGroupKAnonymityManagerTest, HandlesServerErrors) {
// values below.

absl::optional<base::Time> group_name_reported =
getLastReported(manager.get(), kUpdateURL);
GetLastReported(manager.get(), kUpdateURL);
ASSERT_TRUE(group_name_reported);

// TODO(behamilton): Change this once we expect the server to be stable.
Expand Down Expand Up @@ -359,13 +374,15 @@ TEST_F(InterestGroupKAnonymityManagerTestWithMock,
const url::Origin owner = url::Origin::Create(top_frame);
const GURL ad1 = GURL(kAdURL);

blink::InterestGroup group1 = MakeInterestGroup(owner, "foo");

// Join one group twice, and an overlapping group once.
manager->JoinInterestGroup(MakeInterestGroup(owner, "foo"), top_frame);
manager->JoinInterestGroup(MakeInterestGroup(owner, "foo"), top_frame);
manager->JoinInterestGroup(group1, top_frame);
manager->JoinInterestGroup(group1, top_frame);
manager->JoinInterestGroup(MakeInterestGroup(owner, "bar"), top_frame);

manager->RegisterAdAsWon(ad1);
manager->RegisterAdAsWon(ad1);
manager->RegisterAdAsWon(group1, group1.ads.value()[0]);
manager->RegisterAdAsWon(group1, group1.ads.value()[0]);

// k-anonymity update happens here.
task_environment().FastForwardBy(base::Minutes(1));
Expand Down
6 changes: 4 additions & 2 deletions content/browser/interest_group/interest_group_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,10 @@ void InterestGroupManagerImpl::RecordInterestGroupWin(
.WithArgs(group_key, std::move(ad_json));
}

void InterestGroupManagerImpl::RegisterAdAsWon(const GURL& render_url) {
k_anonymity_manager_->RegisterAdAsWon(render_url);
void InterestGroupManagerImpl::RegisterAdAsWon(
const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad) {
k_anonymity_manager_->RegisterAdAsWon(group, ad);
}

void InterestGroupManagerImpl::GetInterestGroup(
Expand Down
7 changes: 4 additions & 3 deletions content/browser/interest_group/interest_group_manager_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,10 @@ class CONTENT_EXPORT InterestGroupManagerImpl : public InterestGroupManager {
void RecordInterestGroupWin(const blink::InterestGroupKey& group_key,
const std::string& ad_json);

// Reports the AD URL to the k-anonymity service. Should be called when FLEDGE
// selects and ad.
void RegisterAdAsWon(const GURL& render_url);
// Reports the ad to the k-anonymity service. Should be called when FLEDGE
// selects an ad.
void RegisterAdAsWon(const blink::InterestGroup& group,
const blink::InterestGroup::Ad& ad);

// Gets a single interest group.
void GetInterestGroup(
Expand Down

0 comments on commit d931e78

Please sign in to comment.