Skip to content

Commit

Permalink
Fenced frames: Add reporting to custom destination URLs [4/N]
Browse files Browse the repository at this point in the history
Finish reportEvent to custom destination URLs:
* If the destination origin isn't an allowed origin, disallow all future
  reports to custom destination URLs from this FencedFrameReporter.
* Substitute macros into the destination URL.

WICG/turtledove#477

Change-Id: I222c94bf41d559b549a108834abb485ba0787e43
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4753243
Reviewed-by: Shivani Sharma <[email protected]>
Reviewed-by: Qingxin Wu <[email protected]>
Commit-Queue: Garrett Tanzer <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1184195}
  • Loading branch information
Garrett Tanzer authored and Chromium LUCI CQ committed Aug 16, 2023
1 parent 2126493 commit 95148e4
Show file tree
Hide file tree
Showing 8 changed files with 389 additions and 48 deletions.
9 changes: 7 additions & 2 deletions content/browser/fenced_frame/fenced_frame_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4746,7 +4746,11 @@ class FencedFrameReportEventBrowserTest
*web_contents()->GetBrowserContext()),
/*main_frame_origin=*/
web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
/*winner_origin=*/url::Origin::Create(GURL("https://a.test")));
/*winner_origin=*/url::Origin::Create(GURL("https://a.test")),
/*allowed_reporting_origins=*/
{{url::Origin::Create(https_server()->GetURL("a.test", "/")),
url::Origin::Create(https_server()->GetURL("b.test", "/")),
url::Origin::Create(https_server()->GetURL("c.test", "/"))}});
}

// A helper function for specifying reportEvent tests. Each step consists of a
Expand Down Expand Up @@ -4829,7 +4833,8 @@ class FencedFrameReportEventBrowserTest
blink::FencedFrame::ReportingDestination::kBuyer,
{
{"click", reporting_url},
});
},
/*reporting_ad_macro_map=*/FencedFrameReporter::ReportingMacroMap());
// Set empty reporting url for seller.
fenced_frame_reporter->OnUrlMappingReady(
blink::FencedFrame::ReportingDestination::kSeller, {{"click", GURL()}});
Expand Down
27 changes: 27 additions & 0 deletions content/browser/fenced_frame/fenced_frame_config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,33 @@ GURL GenerateUrnUuid() {
base::Uuid::GenerateRandomV4().AsLowercaseString());
}

std::string SubstituteMappedStrings(
const std::string& input,
const std::vector<std::pair<std::string, std::string>>& substitutions) {
std::vector<std::string> output_vec;
size_t input_idx = 0;
while (input_idx < input.size()) {
size_t replace_idx = input.size();
size_t replace_end_idx = input.size();
std::pair<std::string, std::string> const* next_replacement = nullptr;
for (const auto& substitution : substitutions) {
size_t found_idx = input.find(substitution.first, input_idx);
if (found_idx < replace_idx) {
replace_idx = found_idx;
replace_end_idx = found_idx + substitution.first.size();
next_replacement = &substitution;
}
}
output_vec.push_back(input.substr(input_idx, replace_idx - input_idx));
if (replace_idx < input.size()) {
output_vec.push_back(next_replacement->second);
}
// move input index to after what we replaced (or end of string).
input_idx = replace_end_idx;
}
return base::StrCat(output_vec);
}

namespace {

std::vector<std::pair<GURL, FencedFrameConfig>>
Expand Down
9 changes: 9 additions & 0 deletions content/browser/fenced_frame/fenced_frame_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ class FencedFrameURLMapping;
extern const char kUrnUuidPrefix[];
GURL CONTENT_EXPORT GenerateUrnUuid();

// Returns a new string based on input where the matching substrings have been
// replaced with the corresponding substitutions. This function avoids repeated
// string operations by building the output based on all substitutions, one
// substitution at a time. This effectively performs all substitutions
// simultaneously, with the earliest match in the input taking precedence.
std::string SubstituteMappedStrings(
const std::string& input,
const std::vector<std::pair<std::string, std::string>>& substitutions);

using AdAuctionData = blink::FencedFrame::AdAuctionData;
using DeprecatedFencedFrameMode = blink::FencedFrame::DeprecatedFencedFrameMode;
using SharedStorageBudgetMetadata =
Expand Down
88 changes: 81 additions & 7 deletions content/browser/fenced_frame/fenced_frame_reporter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "content/browser/devtools/network_service_devtools_observer.h"
#include "content/browser/devtools/protocol/network_handler.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/fenced_frame/fenced_frame_config.h"
#include "content/browser/interest_group/interest_group_pa_report_util.h"
#include "content/browser/private_aggregation/private_aggregation_budget_key.h"
#include "content/browser/private_aggregation/private_aggregation_manager.h"
Expand Down Expand Up @@ -307,7 +308,8 @@ bool FencedFrameReporter::SendReport(
// the map is empty, can't send a request. An entry with a null (not empty)
// map means the map is pending, and is handled below.
if (it == reporting_metadata_.end() ||
(it->second.reporting_url_map && it->second.reporting_url_map->empty())) {
(absl::holds_alternative<DestinationEnumEvent>(event_variant) &&
it->second.reporting_url_map && it->second.reporting_url_map->empty())) {
error_message = base::StrCat(
{"This frame did not register reporting metadata for destination '",
ReportingDestinationAsString(reporting_destination), "'."});
Expand Down Expand Up @@ -389,7 +391,7 @@ bool FencedFrameReporter::SendReportInternal(
return false;
}

// Validate the reporting url.
// Validate the reporting URL.
url = url_iter->second;
if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
error_message = base::StrCat(
Expand All @@ -401,12 +403,84 @@ bool FencedFrameReporter::SendReportInternal(
return false;
}
} else {
CHECK(absl::holds_alternative<DestinationURLEvent>(event_variant));
// Since the event references a destination URL, use it directly.
// The URL should have been validated previously, to be a valid HTTPS url.
// TODO(gtanzer): Substitute macros from the reporting metadata as needed.
// TODO(gtanzer): Check whether the url is an allowlisted origin.
url = absl::get<DestinationURLEvent>(event_variant).url;
// The URL should have been validated previously, to be a valid HTTPS URL.
CHECK(absl::holds_alternative<DestinationURLEvent>(event_variant));

// Check that reportEvent to custom destination URLs with macro
// substitution is allowed in this context. (i.e., The macro map has a
// value.)
if (!reporting_destination_info.reporting_ad_macro_map.has_value()) {
error_message =
"This frame attempted to send a report to a custom destination URL "
"with macro substitution, which is not supported by the API that "
"created this frame's fenced frame config.";
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
return false;
}

// If there is no allowlist, or the allowlist is empty, provide a more
// specific error message.
if (!allowed_reporting_origins_.has_value() ||
allowed_reporting_origins_->empty()) {
error_message =
"This frame attempted to send a report to a custom destination URL "
"with macro substitution, but no origins are allowed by its "
"allowlist.";
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
return false;
}

// If the origin allowlist has previously been violated, this feature is
// disabled for the lifetime of the FencedFrameReporter. This prevents
// an interest group from encoding cross-site data about a user in binary
// with its choices of allowed/disallowed origins.
if (attempted_custom_url_report_to_disallowed_origin_) {
error_message =
"This frame attempted to send a report to a custom destination URL "
"with macro substitution, but this functionality is disabled because "
"a request was previously attempted to a disallowed origin.";
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
return false;
}

// Substitute macros in the specified URL using the macro map.
// TODO(qingxinwu): Lift these changes up out of FencedFrameReporter into
// the code that constructs the reporting ad macro map.
std::vector<std::pair<std::string, std::string>> macro_map;
for (const auto& entry :
reporting_destination_info.reporting_ad_macro_map.value()) {
macro_map.emplace_back("${" + entry.first + "}", entry.second);
}
url = GURL(SubstituteMappedStrings(
absl::get<DestinationURLEvent>(event_variant).url.spec(), macro_map));
url::Origin destination_origin = url::Origin::Create(url);

// Check whether the destination URL has an allowed origin.
bool is_allowed_origin = false;
for (auto& origin : allowed_reporting_origins_.value()) {
if (origin.IsSameOriginWith(destination_origin)) {
is_allowed_origin = true;
break;
}
}

// If the destination URL has a disallowed origin, disable this feature for
// the lifetime of the FencedFrameReporter and return.
if (!is_allowed_origin) {
attempted_custom_url_report_to_disallowed_origin_ = true;
error_message =
"This frame attempted to send a report to a custom destination URL "
"with macro substitution to a disallowed origin. No further reports "
"to custom destination URLs will be allowed for this fenced frame "
"config.";
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
return false;
}
}

if (!GetContentClient()
Expand Down
8 changes: 8 additions & 0 deletions content/browser/fenced_frame/fenced_frame_reporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,14 @@ class CONTENT_EXPORT FencedFrameReporter
// Origins allowed to receive macro expanded reports.
const absl::optional<std::vector<url::Origin>> allowed_reporting_origins_;

// Whether there has been an attempt to send a custom destination url with
// macro substitution report to a disallowed origin (according to
// `allowed_reporting_origins_`). Once this occurs, custom destination url
// reports will be disabled for the remainder of the FencedFrameReporter's
// lifetime. This prevents an interest group from encoding cross-site data
// about a user in binary with its choices of allowed/disallowed origins.
bool attempted_custom_url_report_to_disallowed_origin_ = false;

// Private aggregation requests for non-reserved event types registered in
// bidder worklets, keyed by event type.
// OnForEventPrivateAggregationRequestsReceived() builds this map up.
Expand Down

0 comments on commit 95148e4

Please sign in to comment.