blob: ad685732f0251232cc4bfe21796e0b632240ee26 [file] [log] [blame]
// 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 <utility>
#include "base/check.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/attribution_reporting/destination_set.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_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/storable_source.h"
#include "content/browser/attribution_reporting/store_source_result.h"
#include "net/base/schemeful_site.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
namespace content {
namespace {
using EventLevelResult = ::content::AttributionTrigger::EventLevelResult;
using AggregatableResult = ::content::AttributionTrigger::AggregatableResult;
constexpr char kAttributionDestination[] = "attribution_destination";
enum class DebugDataType {
kSourceDestinationLimit,
kSourceNoised,
kSourceStorageLimit,
kSourceSuccess,
kSourceUnknownError,
kTriggerNoMatchingSource,
kTriggerAttributionsPerSourceDestinationLimit,
kTriggerNoMatchingFilterData,
kTriggerReportingOriginLimit,
kTriggerEventDeduplicated,
kTriggerEventNoMatchingConfigurations,
kTriggerEventNoise,
kTriggerEventLowPriority,
kTriggerEventExcessiveReports,
kTriggerEventStorageLimit,
kTriggerEventReportWindowPassed,
kTriggerAggregateDeduplicated,
kTriggerAggregateNoContributions,
kTriggerAggregateInsufficientBudget,
kTriggerAggregateStorageLimit,
kTriggerAggregateReportWindowPassed,
// TODO(crbug.com/1442939): Add an interop test for this.
kTriggerAggregateExcessiveReports,
kTriggerUnknownError,
};
absl::optional<DebugDataType> DataTypeIfCookieSet(DebugDataType data_type,
bool is_debug_cookie_set) {
return is_debug_cookie_set ? absl::make_optional(data_type) : absl::nullopt;
}
absl::optional<DebugDataType> GetReportDataType(StorableSource::Result result,
bool is_debug_cookie_set) {
switch (result) {
case StorableSource::Result::kProhibitedByBrowserPolicy:
return absl::nullopt;
case StorableSource::Result::kSuccess:
// `kSourceSuccess` is sent for unattributed reporting origin limit to
// mitigate the security concerns on reporting this error. Because
// `kExcessiveReportingOrigins` is thrown based on information across
// reporting origins, reporting on it would violate the same-origin policy.
case StorableSource::Result::kExcessiveReportingOrigins:
return DataTypeIfCookieSet(DebugDataType::kSourceSuccess,
is_debug_cookie_set);
case StorableSource::Result::kInsufficientUniqueDestinationCapacity:
return DebugDataType::kSourceDestinationLimit;
case StorableSource::Result::kSuccessNoised:
return DataTypeIfCookieSet(DebugDataType::kSourceNoised,
is_debug_cookie_set);
case StorableSource::Result::kInsufficientSourceCapacity:
return DataTypeIfCookieSet(DebugDataType::kSourceStorageLimit,
is_debug_cookie_set);
case StorableSource::Result::kInternalError:
return DataTypeIfCookieSet(DebugDataType::kSourceUnknownError,
is_debug_cookie_set);
}
}
absl::optional<DebugDataType> GetReportDataType(EventLevelResult result,
bool is_debug_cookie_set) {
switch (result) {
case EventLevelResult::kSuccess:
case EventLevelResult::kProhibitedByBrowserPolicy:
case EventLevelResult::kSuccessDroppedLowerPriority:
case EventLevelResult::kNotRegistered:
return absl::nullopt;
case EventLevelResult::kInternalError:
return DataTypeIfCookieSet(DebugDataType::kTriggerUnknownError,
is_debug_cookie_set);
case EventLevelResult::kNoCapacityForConversionDestination:
return DataTypeIfCookieSet(DebugDataType::kTriggerEventStorageLimit,
is_debug_cookie_set);
case EventLevelResult::kExcessiveReportingOrigins:
return DataTypeIfCookieSet(DebugDataType::kTriggerReportingOriginLimit,
is_debug_cookie_set);
case EventLevelResult::kNoMatchingImpressions:
return DataTypeIfCookieSet(DebugDataType::kTriggerNoMatchingSource,
is_debug_cookie_set);
case EventLevelResult::kExcessiveAttributions:
return DataTypeIfCookieSet(
DebugDataType::kTriggerAttributionsPerSourceDestinationLimit,
is_debug_cookie_set);
case EventLevelResult::kNoMatchingSourceFilterData:
return DataTypeIfCookieSet(DebugDataType::kTriggerNoMatchingFilterData,
is_debug_cookie_set);
case EventLevelResult::kDeduplicated:
return DataTypeIfCookieSet(DebugDataType::kTriggerEventDeduplicated,
is_debug_cookie_set);
case EventLevelResult::kNoMatchingConfigurations:
return DataTypeIfCookieSet(
DebugDataType::kTriggerEventNoMatchingConfigurations,
is_debug_cookie_set);
case EventLevelResult::kDroppedForNoise:
case EventLevelResult::kFalselyAttributedSource:
return DataTypeIfCookieSet(DebugDataType::kTriggerEventNoise,
is_debug_cookie_set);
case EventLevelResult::kPriorityTooLow:
return DataTypeIfCookieSet(DebugDataType::kTriggerEventLowPriority,
is_debug_cookie_set);
case EventLevelResult::kExcessiveReports:
return DataTypeIfCookieSet(DebugDataType::kTriggerEventExcessiveReports,
is_debug_cookie_set);
case EventLevelResult::kReportWindowPassed:
return DataTypeIfCookieSet(DebugDataType::kTriggerEventReportWindowPassed,
is_debug_cookie_set);
}
}
absl::optional<DebugDataType> GetReportDataType(AggregatableResult result,
bool is_debug_cookie_set) {
switch (result) {
case AggregatableResult::kSuccess:
case AggregatableResult::kNotRegistered:
case AggregatableResult::kProhibitedByBrowserPolicy:
return absl::nullopt;
case AggregatableResult::kInternalError:
return DataTypeIfCookieSet(DebugDataType::kTriggerUnknownError,
is_debug_cookie_set);
case AggregatableResult::kNoCapacityForConversionDestination:
return DataTypeIfCookieSet(DebugDataType::kTriggerAggregateStorageLimit,
is_debug_cookie_set);
case AggregatableResult::kExcessiveReportingOrigins:
return DataTypeIfCookieSet(DebugDataType::kTriggerReportingOriginLimit,
is_debug_cookie_set);
case AggregatableResult::kNoMatchingImpressions:
return DataTypeIfCookieSet(DebugDataType::kTriggerNoMatchingSource,
is_debug_cookie_set);
case AggregatableResult::kExcessiveAttributions:
return DataTypeIfCookieSet(
DebugDataType::kTriggerAttributionsPerSourceDestinationLimit,
is_debug_cookie_set);
case AggregatableResult::kNoMatchingSourceFilterData:
return DataTypeIfCookieSet(DebugDataType::kTriggerNoMatchingFilterData,
is_debug_cookie_set);
case AggregatableResult::kDeduplicated:
return DataTypeIfCookieSet(DebugDataType::kTriggerAggregateDeduplicated,
is_debug_cookie_set);
case AggregatableResult::kNoHistograms:
return DataTypeIfCookieSet(
DebugDataType::kTriggerAggregateNoContributions, is_debug_cookie_set);
case AggregatableResult::kInsufficientBudget:
return DataTypeIfCookieSet(
DebugDataType::kTriggerAggregateInsufficientBudget,
is_debug_cookie_set);
case AggregatableResult::kReportWindowPassed:
return DataTypeIfCookieSet(
DebugDataType::kTriggerAggregateReportWindowPassed,
is_debug_cookie_set);
case AggregatableResult::kExcessiveReports:
return DataTypeIfCookieSet(
DebugDataType::kTriggerAggregateExcessiveReports,
is_debug_cookie_set);
}
}
std::string SerializeReportDataType(DebugDataType data_type) {
switch (data_type) {
case DebugDataType::kSourceDestinationLimit:
return "source-destination-limit";
case DebugDataType::kSourceNoised:
return "source-noised";
case DebugDataType::kSourceStorageLimit:
return "source-storage-limit";
case DebugDataType::kSourceSuccess:
return "source-success";
case DebugDataType::kSourceUnknownError:
return "source-unknown-error";
case DebugDataType::kTriggerNoMatchingSource:
return "trigger-no-matching-source";
case DebugDataType::kTriggerAttributionsPerSourceDestinationLimit:
return "trigger-attributions-per-source-destination-limit";
case DebugDataType::kTriggerNoMatchingFilterData:
return "trigger-no-matching-filter-data";
case DebugDataType::kTriggerReportingOriginLimit:
return "trigger-reporting-origin-limit";
case DebugDataType::kTriggerEventDeduplicated:
return "trigger-event-deduplicated";
case DebugDataType::kTriggerEventNoMatchingConfigurations:
return "trigger-event-no-matching-configurations";
case DebugDataType::kTriggerEventNoise:
return "trigger-event-noise";
case DebugDataType::kTriggerEventLowPriority:
return "trigger-event-low-priority";
case DebugDataType::kTriggerEventExcessiveReports:
return "trigger-event-excessive-reports";
case DebugDataType::kTriggerEventStorageLimit:
return "trigger-event-storage-limit";
case DebugDataType::kTriggerEventReportWindowPassed:
return "trigger-event-report-window-passed";
case DebugDataType::kTriggerAggregateDeduplicated:
return "trigger-aggregate-deduplicated";
case DebugDataType::kTriggerAggregateNoContributions:
return "trigger-aggregate-no-contributions";
case DebugDataType::kTriggerAggregateInsufficientBudget:
return "trigger-aggregate-insufficient-budget";
case DebugDataType::kTriggerAggregateStorageLimit:
return "trigger-aggregate-storage-limit";
case DebugDataType::kTriggerAggregateReportWindowPassed:
return "trigger-aggregate-report-window-passed";
case DebugDataType::kTriggerAggregateExcessiveReports:
return "trigger-aggregate-excessive-reports";
case DebugDataType::kTriggerUnknownError:
return "trigger-unknown-error";
}
}
void SetSourceData(base::Value::Dict& data_body,
uint64_t source_event_id,
const net::SchemefulSite& source_site,
absl::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));
}
}
template <typename T>
void SetLimit(base::Value::Dict& data_body, absl::optional<T> limit) {
DCHECK(limit.has_value());
data_body.Set("limit", base::NumberToString(*limit));
}
base::Value::Dict GetReportDataBody(DebugDataType data_type,
const StorableSource& source,
const StoreSourceResult& result) {
DCHECK(!source.is_within_fenced_frame());
const attribution_reporting::SourceRegistration& registration =
source.registration();
base::Value::Dict data_body;
data_body.Set(kAttributionDestination, registration.destination_set.ToJson());
SetSourceData(data_body, registration.source_event_id,
source.common_info().source_site(), registration.debug_key);
switch (data_type) {
case DebugDataType::kSourceDestinationLimit:
SetLimit(data_body,
result.max_destinations_per_source_site_reporting_origin);
break;
case DebugDataType::kSourceStorageLimit:
SetLimit(data_body, result.max_sources_per_origin);
break;
case DebugDataType::kSourceNoised:
case DebugDataType::kSourceSuccess:
case DebugDataType::kSourceUnknownError:
break;
case DebugDataType::kTriggerNoMatchingSource:
case DebugDataType::kTriggerAttributionsPerSourceDestinationLimit:
case DebugDataType::kTriggerNoMatchingFilterData:
case DebugDataType::kTriggerReportingOriginLimit:
case DebugDataType::kTriggerEventDeduplicated:
case DebugDataType::kTriggerEventNoMatchingConfigurations:
case DebugDataType::kTriggerEventNoise:
case DebugDataType::kTriggerEventLowPriority:
case DebugDataType::kTriggerEventExcessiveReports:
case DebugDataType::kTriggerEventStorageLimit:
case DebugDataType::kTriggerEventReportWindowPassed:
case DebugDataType::kTriggerAggregateDeduplicated:
case DebugDataType::kTriggerAggregateNoContributions:
case DebugDataType::kTriggerAggregateInsufficientBudget:
case DebugDataType::kTriggerAggregateStorageLimit:
case DebugDataType::kTriggerAggregateReportWindowPassed:
case DebugDataType::kTriggerAggregateExcessiveReports:
case DebugDataType::kTriggerUnknownError:
NOTREACHED();
return base::Value::Dict();
}
return data_body;
}
// `original_report_time` must be non-null when `data_type`'s body will contain
// a `scheduled_report_time` field, which is only true for certain event-level
// failures that use the entire body of the report that would have been stored
// if attribution had succeeded.
base::Value::Dict GetReportDataBody(DebugDataType data_type,
const AttributionTrigger& trigger,
const CreateReportResult& result,
base::Time* original_report_time) {
base::Value::Dict data_body;
data_body.Set(kAttributionDestination,
net::SchemefulSite(trigger.destination_origin()).Serialize());
if (absl::optional<uint64_t> debug_key = trigger.registration().debug_key) {
data_body.Set("trigger_debug_key", base::NumberToString(*debug_key));
}
if (const absl::optional<StoredSource>& source = result.source()) {
SetSourceData(data_body, source->source_event_id(),
source->common_info().source_site(), source->debug_key());
}
switch (data_type) {
case DebugDataType::kTriggerNoMatchingSource:
case DebugDataType::kTriggerNoMatchingFilterData:
case DebugDataType::kTriggerEventDeduplicated:
case DebugDataType::kTriggerEventNoMatchingConfigurations:
case DebugDataType::kTriggerEventNoise:
case DebugDataType::kTriggerEventReportWindowPassed:
case DebugDataType::kTriggerAggregateDeduplicated:
case DebugDataType::kTriggerAggregateNoContributions:
case DebugDataType::kTriggerAggregateReportWindowPassed:
case DebugDataType::kTriggerUnknownError:
break;
case DebugDataType::kTriggerAttributionsPerSourceDestinationLimit:
SetLimit(data_body, result.limits().rate_limits_max_attributions);
break;
case DebugDataType::kTriggerAggregateInsufficientBudget:
SetLimit(data_body, result.limits().aggregatable_budget_per_source);
break;
case DebugDataType::kTriggerAggregateExcessiveReports:
SetLimit(data_body, result.limits().max_aggregatable_reports_per_source);
break;
case DebugDataType::kTriggerReportingOriginLimit:
SetLimit(data_body,
result.limits().rate_limits_max_attribution_reporting_origins);
break;
case DebugDataType::kTriggerEventStorageLimit:
SetLimit(data_body,
result.limits().max_event_level_reports_per_destination);
break;
case DebugDataType::kTriggerAggregateStorageLimit:
SetLimit(data_body,
result.limits().max_aggregatable_reports_per_destination);
break;
case DebugDataType::kTriggerEventLowPriority:
case DebugDataType::kTriggerEventExcessiveReports:
DCHECK(result.dropped_event_level_report());
DCHECK(original_report_time);
*original_report_time =
result.dropped_event_level_report()->initial_report_time();
return result.dropped_event_level_report()->ReportBody();
case DebugDataType::kSourceDestinationLimit:
case DebugDataType::kSourceNoised:
case DebugDataType::kSourceStorageLimit:
case DebugDataType::kSourceSuccess:
case DebugDataType::kSourceUnknownError:
NOTREACHED();
return base::Value::Dict();
}
return data_body;
}
base::Value::Dict GetReportData(DebugDataType type, base::Value::Dict body) {
base::Value::Dict dict;
dict.Set("type", SerializeReportDataType(type));
dict.Set("body", std::move(body));
return dict;
}
GURL ReportURL(const attribution_reporting::SuitableOrigin& reporting_origin) {
static constexpr char kPath[] =
"/.well-known/attribution-reporting/debug/verbose";
GURL::Replacements replacements;
replacements.SetPathStr(kPath);
return reporting_origin->GetURL().ReplaceComponents(replacements);
}
} // namespace
// static
absl::optional<AttributionDebugReport> AttributionDebugReport::Create(
const StorableSource& source,
bool is_debug_cookie_set,
const StoreSourceResult& result) {
if (!source.registration().debug_reporting ||
source.is_within_fenced_frame()) {
return absl::nullopt;
}
absl::optional<DebugDataType> data_type =
GetReportDataType(result.status, is_debug_cookie_set);
if (!data_type) {
return absl::nullopt;
}
base::Value::List report_body;
report_body.Append(
GetReportData(*data_type, GetReportDataBody(*data_type, source, result)));
return AttributionDebugReport(std::move(report_body),
source.common_info().reporting_origin(),
/*original_report_time=*/base::Time());
}
// static
absl::optional<AttributionDebugReport> AttributionDebugReport::Create(
const AttributionTrigger& trigger,
bool is_debug_cookie_set,
const CreateReportResult& result) {
if (!trigger.registration().debug_reporting ||
trigger.is_within_fenced_frame()) {
return absl::nullopt;
}
base::Value::List report_body;
base::Time original_report_time;
absl::optional<DebugDataType> event_level_data_type =
GetReportDataType(result.event_level_status(), is_debug_cookie_set);
if (event_level_data_type) {
report_body.Append(
GetReportData(*event_level_data_type,
GetReportDataBody(*event_level_data_type, trigger, result,
&original_report_time)));
}
if (absl::optional<DebugDataType> aggregatable_data_type =
GetReportDataType(result.aggregatable_status(), is_debug_cookie_set);
aggregatable_data_type &&
aggregatable_data_type != event_level_data_type) {
report_body.Append(GetReportData(
*aggregatable_data_type,
GetReportDataBody(*aggregatable_data_type, trigger, result,
/*original_report_time=*/nullptr)));
}
if (report_body.empty()) {
return absl::nullopt;
}
return AttributionDebugReport(
std::move(report_body), trigger.reporting_origin(), original_report_time);
}
AttributionDebugReport::AttributionDebugReport(
base::Value::List report_body,
const attribution_reporting::SuitableOrigin& reporting_origin,
base::Time original_report_time)
: report_body_(std::move(report_body)),
report_url_(ReportURL(reporting_origin)),
original_report_time_(original_report_time) {
DCHECK(!report_body_.empty());
}
AttributionDebugReport::~AttributionDebugReport() = default;
AttributionDebugReport::AttributionDebugReport(AttributionDebugReport&&) =
default;
AttributionDebugReport& AttributionDebugReport::operator=(
AttributionDebugReport&&) = default;
} // namespace content