Skip to content

Commit 95148e4

Browse files
Garrett TanzerChromium LUCI CQ
authored andcommitted
Fenced frames: Add reporting to custom destination URLs [4/N]
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}
1 parent 2126493 commit 95148e4

8 files changed

+389
-48
lines changed

content/browser/fenced_frame/fenced_frame_browsertest.cc

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4746,7 +4746,11 @@ class FencedFrameReportEventBrowserTest
47464746
*web_contents()->GetBrowserContext()),
47474747
/*main_frame_origin=*/
47484748
web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
4749-
/*winner_origin=*/url::Origin::Create(GURL("https://a.test")));
4749+
/*winner_origin=*/url::Origin::Create(GURL("https://a.test")),
4750+
/*allowed_reporting_origins=*/
4751+
{{url::Origin::Create(https_server()->GetURL("a.test", "/")),
4752+
url::Origin::Create(https_server()->GetURL("b.test", "/")),
4753+
url::Origin::Create(https_server()->GetURL("c.test", "/"))}});
47504754
}
47514755

47524756
// A helper function for specifying reportEvent tests. Each step consists of a
@@ -4829,7 +4833,8 @@ class FencedFrameReportEventBrowserTest
48294833
blink::FencedFrame::ReportingDestination::kBuyer,
48304834
{
48314835
{"click", reporting_url},
4832-
});
4836+
},
4837+
/*reporting_ad_macro_map=*/FencedFrameReporter::ReportingMacroMap());
48334838
// Set empty reporting url for seller.
48344839
fenced_frame_reporter->OnUrlMappingReady(
48354840
blink::FencedFrame::ReportingDestination::kSeller, {{"click", GURL()}});

content/browser/fenced_frame/fenced_frame_config.cc

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,33 @@ GURL GenerateUrnUuid() {
2121
base::Uuid::GenerateRandomV4().AsLowercaseString());
2222
}
2323

24+
std::string SubstituteMappedStrings(
25+
const std::string& input,
26+
const std::vector<std::pair<std::string, std::string>>& substitutions) {
27+
std::vector<std::string> output_vec;
28+
size_t input_idx = 0;
29+
while (input_idx < input.size()) {
30+
size_t replace_idx = input.size();
31+
size_t replace_end_idx = input.size();
32+
std::pair<std::string, std::string> const* next_replacement = nullptr;
33+
for (const auto& substitution : substitutions) {
34+
size_t found_idx = input.find(substitution.first, input_idx);
35+
if (found_idx < replace_idx) {
36+
replace_idx = found_idx;
37+
replace_end_idx = found_idx + substitution.first.size();
38+
next_replacement = &substitution;
39+
}
40+
}
41+
output_vec.push_back(input.substr(input_idx, replace_idx - input_idx));
42+
if (replace_idx < input.size()) {
43+
output_vec.push_back(next_replacement->second);
44+
}
45+
// move input index to after what we replaced (or end of string).
46+
input_idx = replace_end_idx;
47+
}
48+
return base::StrCat(output_vec);
49+
}
50+
2451
namespace {
2552

2653
std::vector<std::pair<GURL, FencedFrameConfig>>

content/browser/fenced_frame/fenced_frame_config.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,15 @@ class FencedFrameURLMapping;
9393
extern const char kUrnUuidPrefix[];
9494
GURL CONTENT_EXPORT GenerateUrnUuid();
9595

96+
// Returns a new string based on input where the matching substrings have been
97+
// replaced with the corresponding substitutions. This function avoids repeated
98+
// string operations by building the output based on all substitutions, one
99+
// substitution at a time. This effectively performs all substitutions
100+
// simultaneously, with the earliest match in the input taking precedence.
101+
std::string SubstituteMappedStrings(
102+
const std::string& input,
103+
const std::vector<std::pair<std::string, std::string>>& substitutions);
104+
96105
using AdAuctionData = blink::FencedFrame::AdAuctionData;
97106
using DeprecatedFencedFrameMode = blink::FencedFrame::DeprecatedFencedFrameMode;
98107
using SharedStorageBudgetMetadata =

content/browser/fenced_frame/fenced_frame_reporter.cc

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include "content/browser/devtools/network_service_devtools_observer.h"
3232
#include "content/browser/devtools/protocol/network_handler.h"
3333
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
34+
#include "content/browser/fenced_frame/fenced_frame_config.h"
3435
#include "content/browser/interest_group/interest_group_pa_report_util.h"
3536
#include "content/browser/private_aggregation/private_aggregation_budget_key.h"
3637
#include "content/browser/private_aggregation/private_aggregation_manager.h"
@@ -307,7 +308,8 @@ bool FencedFrameReporter::SendReport(
307308
// the map is empty, can't send a request. An entry with a null (not empty)
308309
// map means the map is pending, and is handled below.
309310
if (it == reporting_metadata_.end() ||
310-
(it->second.reporting_url_map && it->second.reporting_url_map->empty())) {
311+
(absl::holds_alternative<DestinationEnumEvent>(event_variant) &&
312+
it->second.reporting_url_map && it->second.reporting_url_map->empty())) {
311313
error_message = base::StrCat(
312314
{"This frame did not register reporting metadata for destination '",
313315
ReportingDestinationAsString(reporting_destination), "'."});
@@ -389,7 +391,7 @@ bool FencedFrameReporter::SendReportInternal(
389391
return false;
390392
}
391393

392-
// Validate the reporting url.
394+
// Validate the reporting URL.
393395
url = url_iter->second;
394396
if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) {
395397
error_message = base::StrCat(
@@ -401,12 +403,84 @@ bool FencedFrameReporter::SendReportInternal(
401403
return false;
402404
}
403405
} else {
404-
CHECK(absl::holds_alternative<DestinationURLEvent>(event_variant));
405406
// Since the event references a destination URL, use it directly.
406-
// The URL should have been validated previously, to be a valid HTTPS url.
407-
// TODO(gtanzer): Substitute macros from the reporting metadata as needed.
408-
// TODO(gtanzer): Check whether the url is an allowlisted origin.
409-
url = absl::get<DestinationURLEvent>(event_variant).url;
407+
// The URL should have been validated previously, to be a valid HTTPS URL.
408+
CHECK(absl::holds_alternative<DestinationURLEvent>(event_variant));
409+
410+
// Check that reportEvent to custom destination URLs with macro
411+
// substitution is allowed in this context. (i.e., The macro map has a
412+
// value.)
413+
if (!reporting_destination_info.reporting_ad_macro_map.has_value()) {
414+
error_message =
415+
"This frame attempted to send a report to a custom destination URL "
416+
"with macro substitution, which is not supported by the API that "
417+
"created this frame's fenced frame config.";
418+
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
419+
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
420+
return false;
421+
}
422+
423+
// If there is no allowlist, or the allowlist is empty, provide a more
424+
// specific error message.
425+
if (!allowed_reporting_origins_.has_value() ||
426+
allowed_reporting_origins_->empty()) {
427+
error_message =
428+
"This frame attempted to send a report to a custom destination URL "
429+
"with macro substitution, but no origins are allowed by its "
430+
"allowlist.";
431+
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
432+
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
433+
return false;
434+
}
435+
436+
// If the origin allowlist has previously been violated, this feature is
437+
// disabled for the lifetime of the FencedFrameReporter. This prevents
438+
// an interest group from encoding cross-site data about a user in binary
439+
// with its choices of allowed/disallowed origins.
440+
if (attempted_custom_url_report_to_disallowed_origin_) {
441+
error_message =
442+
"This frame attempted to send a report to a custom destination URL "
443+
"with macro substitution, but this functionality is disabled because "
444+
"a request was previously attempted to a disallowed origin.";
445+
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
446+
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
447+
return false;
448+
}
449+
450+
// Substitute macros in the specified URL using the macro map.
451+
// TODO(qingxinwu): Lift these changes up out of FencedFrameReporter into
452+
// the code that constructs the reporting ad macro map.
453+
std::vector<std::pair<std::string, std::string>> macro_map;
454+
for (const auto& entry :
455+
reporting_destination_info.reporting_ad_macro_map.value()) {
456+
macro_map.emplace_back("${" + entry.first + "}", entry.second);
457+
}
458+
url = GURL(SubstituteMappedStrings(
459+
absl::get<DestinationURLEvent>(event_variant).url.spec(), macro_map));
460+
url::Origin destination_origin = url::Origin::Create(url);
461+
462+
// Check whether the destination URL has an allowed origin.
463+
bool is_allowed_origin = false;
464+
for (auto& origin : allowed_reporting_origins_.value()) {
465+
if (origin.IsSameOriginWith(destination_origin)) {
466+
is_allowed_origin = true;
467+
break;
468+
}
469+
}
470+
471+
// If the destination URL has a disallowed origin, disable this feature for
472+
// the lifetime of the FencedFrameReporter and return.
473+
if (!is_allowed_origin) {
474+
attempted_custom_url_report_to_disallowed_origin_ = true;
475+
error_message =
476+
"This frame attempted to send a report to a custom destination URL "
477+
"with macro substitution to a disallowed origin. No further reports "
478+
"to custom destination URLs will be allowed for this fenced frame "
479+
"config.";
480+
console_message_level = blink::mojom::ConsoleMessageLevel::kError;
481+
NotifyFencedFrameReportingBeaconFailed(attribution_reporting_data);
482+
return false;
483+
}
410484
}
411485

412486
if (!GetContentClient()

content/browser/fenced_frame/fenced_frame_reporter.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,14 @@ class CONTENT_EXPORT FencedFrameReporter
377377
// Origins allowed to receive macro expanded reports.
378378
const absl::optional<std::vector<url::Origin>> allowed_reporting_origins_;
379379

380+
// Whether there has been an attempt to send a custom destination url with
381+
// macro substitution report to a disallowed origin (according to
382+
// `allowed_reporting_origins_`). Once this occurs, custom destination url
383+
// reports will be disabled for the remainder of the FencedFrameReporter's
384+
// lifetime. This prevents an interest group from encoding cross-site data
385+
// about a user in binary with its choices of allowed/disallowed origins.
386+
bool attempted_custom_url_report_to_disallowed_origin_ = false;
387+
380388
// Private aggregation requests for non-reserved event types registered in
381389
// bidder worklets, keyed by event type.
382390
// OnForEventPrivateAggregationRequestsReceived() builds this map up.

0 commit comments

Comments
 (0)