| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/attribution_reporting/attribution_debug_report.h" |
| |
| #include <stdint.h> |
| |
| #include <optional> |
| #include <string_view> |
| #include <utility> |
| #include <variant> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/feature_list.h" |
| #include "base/functional/function_ref.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/notreached.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "components/attribution_reporting/constants.h" |
| #include "components/attribution_reporting/debug_types.h" |
| #include "components/attribution_reporting/debug_types.mojom.h" |
| #include "components/attribution_reporting/destination_set.h" |
| #include "components/attribution_reporting/os_registration.h" |
| #include "components/attribution_reporting/registration_header_error.h" |
| #include "components/attribution_reporting/source_registration.h" |
| #include "components/attribution_reporting/suitable_origin.h" |
| #include "components/attribution_reporting/trigger_registration.h" |
| #include "content/browser/attribution_reporting/attribution_constants.h" |
| #include "content/browser/attribution_reporting/attribution_features.h" |
| #include "content/browser/attribution_reporting/attribution_reporting.mojom.h" |
| #include "content/browser/attribution_reporting/attribution_trigger.h" |
| #include "content/browser/attribution_reporting/common_source_info.h" |
| #include "content/browser/attribution_reporting/create_report_result.h" |
| #include "content/browser/attribution_reporting/os_registration.h" |
| #include "content/browser/attribution_reporting/storable_source.h" |
| #include "content/browser/attribution_reporting/store_source_result.h" |
| #include "net/base/schemeful_site.h" |
| #include "third_party/abseil-cpp/absl/functional/overload.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using ::attribution_reporting::mojom::DebugDataType; |
| |
| constexpr char kAttributionDestination[] = "attribution_destination"; |
| |
| struct DebugDataTypeAndBody { |
| DebugDataType debug_data_type; |
| base::Value::Dict body; |
| |
| explicit DebugDataTypeAndBody(DebugDataType debug_data_type, |
| base::Value limit = base::Value(), |
| base::Value::Dict body = base::Value::Dict()) |
| : debug_data_type(debug_data_type), body(std::move(body)) { |
| if (!limit.is_none()) { |
| this->body.Set("limit", std::move(limit)); |
| } |
| } |
| }; |
| |
| template <typename T> |
| base::Value GetLimit(T limit) { |
| return base::Value(base::NumberToString(limit)); |
| } |
| |
| std::optional<DebugDataTypeAndBody> GetReportDataBody( |
| const StoreSourceResult& result) { |
| const auto make_report_body = [&](DebugDataType type, |
| base::Value limit = base::Value()) { |
| base::Value::Dict body; |
| if (result.destination_limit().has_value()) { |
| body.Set("source_destination_limit", |
| GetLimit(result.destination_limit().value())); |
| } |
| |
| return std::make_optional<DebugDataTypeAndBody>(type, std::move(limit), |
| std::move(body)); |
| }; |
| |
| return std::visit( |
| absl::Overload{ |
| [](StoreSourceResult::ProhibitedByBrowserPolicy) { |
| return std::optional<DebugDataTypeAndBody>(); |
| }, |
| [&](std::variant<StoreSourceResult::Success, |
| // `kSourceSuccess` is sent for a few errors as well |
| // to mitigate the security concerns on reporting |
| // these errors. Because these errors are thrown |
| // based on information across reporting origins, |
| // reporting on them would violate the same-origin |
| // policy. |
| StoreSourceResult::ExcessiveReportingOrigins, |
| StoreSourceResult::DestinationGlobalLimitReached>) { |
| return make_report_body(result.is_noised() |
| ? DebugDataType::kSourceNoised |
| : DebugDataType::kSourceSuccess); |
| }, |
| [&](StoreSourceResult::InsufficientUniqueDestinationCapacity v) { |
| return make_report_body(DebugDataType::kSourceDestinationLimit, |
| GetLimit(v.limit)); |
| }, |
| [&](std::variant<StoreSourceResult::DestinationReportingLimitReached, |
| StoreSourceResult::DestinationBothLimitsReached> v) { |
| return make_report_body( |
| DebugDataType::kSourceDestinationRateLimit, |
| std::visit([](auto v) { return GetLimit(v.limit); }, v)); |
| }, |
| [&](StoreSourceResult::DestinationPerDayReportingLimitReached v) { |
| return make_report_body( |
| DebugDataType::kSourceDestinationPerDayRateLimit, |
| GetLimit(v.limit)); |
| }, |
| [&](StoreSourceResult::InsufficientSourceCapacity v) { |
| return make_report_body(DebugDataType::kSourceStorageLimit, |
| GetLimit(v.limit)); |
| }, |
| [&](StoreSourceResult::InternalError) { |
| return make_report_body(DebugDataType::kSourceUnknownError); |
| }, |
| [&](StoreSourceResult::ReportingOriginsPerSiteLimitReached v) { |
| return make_report_body( |
| DebugDataType::kSourceReportingOriginPerSiteLimit, |
| GetLimit(v.limit)); |
| }, |
| [&](StoreSourceResult::ExceedsMaxChannelCapacity v) { |
| return make_report_body(DebugDataType::kSourceChannelCapacityLimit, |
| base::Value(v.limit)); |
| }, |
| [&](StoreSourceResult::ExceedsMaxScopesChannelCapacity v) { |
| return make_report_body( |
| DebugDataType::kSourceScopesChannelCapacityLimit, |
| base::Value(v.limit)); |
| }, |
| [&](StoreSourceResult::ExceedsMaxTriggerStateCardinality v) { |
| return make_report_body( |
| DebugDataType::kSourceTriggerStateCardinalityLimit, |
| GetLimit(v.limit)); |
| }, |
| [&](StoreSourceResult::ExceedsMaxEventStatesLimit v) { |
| return make_report_body(DebugDataType::kSourceMaxEventStatesLimit, |
| GetLimit(v.limit)); |
| }, |
| }, |
| result.result()); |
| } |
| |
| std::optional<DebugDataTypeAndBody> GetReportDataTypeAndLimit( |
| const CreateReportResult::EventLevel& result) { |
| return std::visit( |
| absl::Overload{ |
| [](const CreateReportResult::EventLevelSuccess&) { |
| return std::optional<DebugDataTypeAndBody>(); |
| }, |
| [](CreateReportResult::ProhibitedByBrowserPolicy) { |
| return std::optional<DebugDataTypeAndBody>(); |
| }, |
| [](CreateReportResult::NotRegistered) { |
| return std::optional<DebugDataTypeAndBody>(); |
| }, |
| [](CreateReportResult::InternalError) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerUnknownError); |
| }, |
| [](CreateReportResult::NoCapacityForConversionDestination v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventStorageLimit, GetLimit(v.max)); |
| }, |
| [](CreateReportResult::ExcessiveReportingOrigins v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerReportingOriginLimit, GetLimit(v.max)); |
| }, |
| [](CreateReportResult::NoMatchingImpressions) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerNoMatchingSource); |
| }, |
| [](CreateReportResult::ExcessiveAttributions v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType:: |
| kTriggerEventAttributionsPerSourceDestinationLimit, |
| GetLimit(v.max)); |
| }, |
| [](CreateReportResult::NoMatchingSourceFilterData) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerNoMatchingFilterData); |
| }, |
| [](CreateReportResult::Deduplicated) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventDeduplicated); |
| }, |
| [](CreateReportResult::NoMatchingConfigurations) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventNoMatchingConfigurations); |
| }, |
| [](CreateReportResult::NeverAttributedSource) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventNoise); |
| }, |
| [](CreateReportResult::FalselyAttributedSource) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventNoise); |
| }, |
| [](const CreateReportResult::PriorityTooLow&) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventLowPriority); |
| }, |
| [](const CreateReportResult::ExcessiveEventLevelReports&) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventExcessiveReports); |
| }, |
| [](CreateReportResult::ReportWindowNotStarted) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventReportWindowNotStarted); |
| }, |
| [](CreateReportResult::ReportWindowPassed) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventReportWindowPassed); |
| }, |
| [](CreateReportResult::NoMatchingTriggerData) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerEventNoMatchingTriggerData); |
| }, |
| }, |
| result); |
| } |
| |
| std::optional<DebugDataTypeAndBody> GetReportDataTypeAndLimit( |
| const CreateReportResult::Aggregatable& result) { |
| return std::visit( |
| absl::Overload{ |
| [](const CreateReportResult::AggregatableSuccess&) { |
| return std::optional<DebugDataTypeAndBody>(); |
| }, |
| [](CreateReportResult::NotRegistered) { |
| return std::optional<DebugDataTypeAndBody>(); |
| }, |
| [](CreateReportResult::ProhibitedByBrowserPolicy) { |
| return std::optional<DebugDataTypeAndBody>(); |
| }, |
| [](CreateReportResult::InternalError) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerUnknownError); |
| }, |
| [](CreateReportResult::NoCapacityForConversionDestination v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerAggregateStorageLimit, GetLimit(v.max)); |
| }, |
| [](CreateReportResult::ExcessiveReportingOrigins v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerReportingOriginLimit, GetLimit(v.max)); |
| }, |
| [](CreateReportResult::NoMatchingImpressions) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerNoMatchingSource); |
| }, |
| [](CreateReportResult::ExcessiveAttributions v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType:: |
| kTriggerAggregateAttributionsPerSourceDestinationLimit, |
| GetLimit(v.max)); |
| }, |
| [](CreateReportResult::NoMatchingSourceFilterData) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerNoMatchingFilterData); |
| }, |
| [](CreateReportResult::Deduplicated) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerAggregateDeduplicated); |
| }, |
| [](CreateReportResult::NoHistograms) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerAggregateNoContributions); |
| }, |
| [](CreateReportResult::InsufficientBudget) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerAggregateInsufficientBudget, |
| GetLimit(attribution_reporting::kMaxAggregatableValue)); |
| }, |
| [](const CreateReportResult::InsufficientNamedBudget& v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerAggregateInsufficientNamedBudget, |
| GetLimit(v.budget), base::Value::Dict().Set("name", v.name)); |
| }, |
| [](CreateReportResult::ReportWindowPassed) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerAggregateReportWindowPassed); |
| }, |
| [](CreateReportResult::ExcessiveAggregatableReports v) { |
| return std::make_optional<DebugDataTypeAndBody>( |
| DebugDataType::kTriggerAggregateExcessiveReports, |
| GetLimit(v.max)); |
| }, |
| }, |
| result); |
| } |
| |
| void SetSourceData(base::Value::Dict& data_body, |
| uint64_t source_event_id, |
| const net::SchemefulSite& source_site, |
| std::optional<uint64_t> source_debug_key) { |
| data_body.Set("source_event_id", base::NumberToString(source_event_id)); |
| data_body.Set("source_site", source_site.Serialize()); |
| if (source_debug_key) { |
| data_body.Set("source_debug_key", base::NumberToString(*source_debug_key)); |
| } |
| } |
| |
| base::Value::Dict GetReportDataBody(DebugDataTypeAndBody data, |
| const CreateReportResult& result) { |
| if (data.debug_data_type == DebugDataType::kTriggerEventExcessiveReports || |
| data.debug_data_type == DebugDataType::kTriggerEventLowPriority) { |
| CHECK(result.dropped_event_level_report()); |
| return result.dropped_event_level_report()->ReportBody(); |
| } |
| |
| data.body.Set( |
| kAttributionDestination, |
| net::SchemefulSite(result.trigger().destination_origin()).Serialize()); |
| |
| if (std::optional<uint64_t> debug_key = |
| result.trigger().registration().debug_key) { |
| data.body.Set("trigger_debug_key", base::NumberToString(*debug_key)); |
| } |
| |
| if (const std::optional<StoredSource>& source = result.source()) { |
| SetSourceData(data.body, source->source_event_id(), |
| source->common_info().source_site(), source->debug_key()); |
| } |
| |
| return std::move(data.body); |
| } |
| |
| base::Value::Dict GetReportData(DebugDataType type, base::Value::Dict body) { |
| return base::Value::Dict() |
| .Set("type", attribution_reporting::SerializeDebugDataType(type)) |
| .Set("body", std::move(body)); |
| } |
| |
| void RecordVerboseDebugReportType(DebugDataType type) { |
| base::UmaHistogramEnumeration("Conversions.SentVerboseDebugReportType4", |
| type); |
| } |
| |
| } // namespace |
| |
| GURL AttributionDebugReport::ReportUrl() const { |
| static constexpr char kPath[] = |
| "/.well-known/attribution-reporting/debug/verbose"; |
| |
| GURL::Replacements replacements; |
| replacements.SetPathStr(kPath); |
| return reporting_origin_->GetURL().ReplaceComponents(replacements); |
| } |
| |
| // static |
| std::optional<AttributionDebugReport> AttributionDebugReport::Create( |
| base::FunctionRef<bool()> is_operation_allowed, |
| const StoreSourceResult& result) { |
| const StorableSource& source = result.source(); |
| if (!source.registration().debug_reporting || |
| !source.common_info().cookie_based_debug_allowed() || |
| source.is_within_fenced_frame() || !is_operation_allowed()) { |
| return std::nullopt; |
| } |
| |
| std::optional<DebugDataTypeAndBody> data = GetReportDataBody(result); |
| if (!data) { |
| return std::nullopt; |
| } |
| |
| RecordVerboseDebugReportType(data->debug_data_type); |
| |
| const attribution_reporting::SourceRegistration& registration = |
| source.registration(); |
| |
| data->body.Set(kAttributionDestination, |
| registration.destination_set.ToJson()); |
| SetSourceData(data->body, registration.source_event_id, |
| source.common_info().source_site(), registration.debug_key); |
| |
| return AttributionDebugReport( |
| base::Value::List::with_capacity(1).Append( |
| GetReportData(data->debug_data_type, std::move(data->body))), |
| source.common_info().reporting_origin()); |
| } |
| |
| // static |
| std::optional<AttributionDebugReport> AttributionDebugReport::Create( |
| base::FunctionRef<bool()> is_operation_allowed, |
| bool cookie_based_debug_allowed, |
| const CreateReportResult& result) { |
| if (!result.trigger().registration().debug_reporting || |
| !cookie_based_debug_allowed || |
| result.trigger().is_within_fenced_frame() || !is_operation_allowed()) { |
| return std::nullopt; |
| } |
| |
| if (result.source() && |
| !result.source()->common_info().cookie_based_debug_allowed()) { |
| return std::nullopt; |
| } |
| |
| base::Value::List report_body; |
| |
| std::optional<DebugDataType> event_level_type; |
| if (std::optional<DebugDataTypeAndBody> event_level_data_type_limit = |
| GetReportDataTypeAndLimit(result.event_level_result())) { |
| event_level_type = event_level_data_type_limit->debug_data_type; |
| report_body.Append(GetReportData( |
| *event_level_type, |
| GetReportDataBody(*std::move(event_level_data_type_limit), result))); |
| RecordVerboseDebugReportType(*event_level_type); |
| } |
| |
| if (std::optional<DebugDataTypeAndBody> aggregatable_data_type_limit = |
| GetReportDataTypeAndLimit(result.aggregatable_result()); |
| aggregatable_data_type_limit && |
| aggregatable_data_type_limit->debug_data_type != event_level_type) { |
| DebugDataType aggregatable_type = |
| aggregatable_data_type_limit->debug_data_type; |
| report_body.Append(GetReportData( |
| aggregatable_type, |
| GetReportDataBody(*std::move(aggregatable_data_type_limit), result))); |
| RecordVerboseDebugReportType(aggregatable_type); |
| } |
| |
| if (report_body.empty()) { |
| return std::nullopt; |
| } |
| |
| return AttributionDebugReport(std::move(report_body), |
| result.trigger().reporting_origin()); |
| } |
| |
| // static |
| std::optional<AttributionDebugReport> AttributionDebugReport::Create( |
| const OsRegistration& registration, |
| size_t item_index, |
| base::FunctionRef<bool(const url::Origin&)> is_operation_allowed) { |
| CHECK_LT(item_index, registration.registration_items.size()); |
| const auto& registration_item = registration.registration_items[item_index]; |
| if (!registration_item.debug_reporting || |
| registration.is_within_fenced_frame) { |
| return std::nullopt; |
| } |
| |
| auto registration_origin = |
| attribution_reporting::SuitableOrigin::Create(registration_item.url); |
| if (!registration_origin.has_value() || |
| !is_operation_allowed(*registration_origin)) { |
| return std::nullopt; |
| } |
| |
| DebugDataType data_type; |
| switch (registration.GetType()) { |
| case attribution_reporting::mojom::RegistrationType::kSource: |
| data_type = DebugDataType::kOsSourceDelegated; |
| break; |
| case attribution_reporting::mojom::RegistrationType::kTrigger: |
| data_type = DebugDataType::kOsTriggerDelegated; |
| break; |
| } |
| |
| RecordVerboseDebugReportType(data_type); |
| |
| return AttributionDebugReport( |
| base::Value::List::with_capacity(1).Append(GetReportData( |
| data_type, |
| base::Value::Dict() |
| .Set( |
| "context_site", |
| net::SchemefulSite(registration.top_level_origin).Serialize()) |
| .Set("registration_url", registration_item.url.spec()))), |
| *std::move(registration_origin)); |
| } |
| |
| std::optional<AttributionDebugReport> AttributionDebugReport::Create( |
| attribution_reporting::SuitableOrigin reporting_origin, |
| attribution_reporting::RegistrationHeaderError error, |
| const attribution_reporting::SuitableOrigin& context_origin, |
| bool is_within_fenced_frame, |
| base::FunctionRef<bool(const url::Origin&)> is_operation_allowed) { |
| if (is_within_fenced_frame || !is_operation_allowed(*reporting_origin)) { |
| return std::nullopt; |
| } |
| |
| constexpr DebugDataType kDataType = DebugDataType::kHeaderParsingError; |
| |
| RecordVerboseDebugReportType(kDataType); |
| |
| return AttributionDebugReport( |
| base::Value::List::with_capacity(1).Append(GetReportData( |
| kDataType, base::Value::Dict() |
| .Set("context_site", |
| net::SchemefulSite(context_origin).Serialize()) |
| .Set("header", error.HeaderName()) |
| .Set("value", std::move(error.header_value)))), |
| std::move(reporting_origin)); |
| } |
| |
| AttributionDebugReport::AttributionDebugReport( |
| base::Value::List report_body, |
| attribution_reporting::SuitableOrigin reporting_origin) |
| : report_body_(std::move(report_body)), |
| reporting_origin_(std::move(reporting_origin)) { |
| CHECK(!report_body_.empty()); |
| } |
| |
| AttributionDebugReport::~AttributionDebugReport() = default; |
| |
| AttributionDebugReport::AttributionDebugReport(AttributionDebugReport&&) = |
| default; |
| |
| AttributionDebugReport& AttributionDebugReport::operator=( |
| AttributionDebugReport&&) = default; |
| |
| } // namespace content |