| // Copyright 2020 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_report.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/numerics/checked_math.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/values.h" |
| #include "components/attribution_reporting/aggregatable_trigger_config.h" |
| #include "components/attribution_reporting/destination_set.h" |
| #include "components/attribution_reporting/source_type.h" |
| #include "components/attribution_reporting/suitable_origin.h" |
| #include "content/browser/attribution_reporting/aggregatable_attribution_utils.h" |
| #include "content/browser/attribution_reporting/common_source_info.h" |
| #include "content/browser/attribution_reporting/stored_source.h" |
| #include "third_party/abseil-cpp/absl/functional/overload.h" |
| #include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #include "url/url_canon.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using ::attribution_reporting::SuitableOrigin; |
| |
| void PopulateReportBody(base::Value::Dict& dict, |
| const AttributionReport::AggregatableData& data) { |
| if (const auto& assembled_report = data.assembled_report(); |
| assembled_report.has_value()) { |
| dict = assembled_report->GetAsJson(); |
| } else { |
| // This generally should only be called when displaying the report |
| // for debugging/internals. |
| dict.Set("shared_info", "not generated prior to send"); |
| dict.Set("aggregation_service_payloads", "not generated prior to send"); |
| } |
| |
| if (const auto& trigger_context_id = |
| data.aggregatable_trigger_config().trigger_context_id(); |
| trigger_context_id.has_value()) { |
| dict.Set("trigger_context_id", *trigger_context_id); |
| } |
| } |
| |
| } // namespace |
| |
| AttributionReport::EventLevelData::EventLevelData(uint32_t trigger_data, |
| int64_t priority, |
| const StoredSource& source) |
| : trigger_data(trigger_data), |
| priority(priority), |
| source_origin(source.common_info().source_origin()), |
| destinations(source.destination_sites()), |
| source_event_id(source.source_event_id()), |
| source_type(source.common_info().source_type()), |
| randomized_response_rate(source.randomized_response_rate()), |
| attributed_truthfully(source.attribution_logic() == |
| StoredSource::AttributionLogic::kTruthfully) {} |
| |
| AttributionReport::EventLevelData::EventLevelData(const EventLevelData&) = |
| default; |
| |
| AttributionReport::EventLevelData& AttributionReport::EventLevelData::operator=( |
| const EventLevelData&) = default; |
| |
| AttributionReport::EventLevelData::EventLevelData(EventLevelData&&) = default; |
| |
| AttributionReport::EventLevelData& AttributionReport::EventLevelData::operator=( |
| EventLevelData&&) = default; |
| |
| AttributionReport::EventLevelData::~EventLevelData() = default; |
| |
| AttributionReport::AggregatableData::AggregatableData( |
| std::optional<SuitableOrigin> aggregation_coordinator_origin, |
| attribution_reporting::AggregatableTriggerConfig |
| aggregatable_trigger_config, |
| base::Time source_time, |
| std::vector<blink::mojom::AggregatableReportHistogramContribution> |
| contributions, |
| std::optional<attribution_reporting::SuitableOrigin> source_origin) |
| : aggregation_coordinator_origin_( |
| std::move(aggregation_coordinator_origin)), |
| aggregatable_trigger_config_(std::move(aggregatable_trigger_config)), |
| source_time_(source_time), |
| contributions_(std::move(contributions)), |
| source_origin_(std::move(source_origin)) { |
| CHECK(source_origin_.has_value() || contributions_.empty()); |
| } |
| |
| AttributionReport::AggregatableData::AggregatableData(const AggregatableData&) = |
| default; |
| |
| AttributionReport::AggregatableData& |
| AttributionReport::AggregatableData::operator=(const AggregatableData&) = |
| default; |
| |
| AttributionReport::AggregatableData::AggregatableData(AggregatableData&&) = |
| default; |
| |
| AttributionReport::AggregatableData& |
| AttributionReport::AggregatableData::operator=(AggregatableData&&) = default; |
| |
| void AttributionReport::AggregatableData::SetContributions( |
| std::vector<blink::mojom::AggregatableReportHistogramContribution> |
| contributions) { |
| CHECK(source_origin_.has_value()); |
| CHECK(!contributions.empty()); |
| contributions_ = std::move(contributions); |
| } |
| |
| void AttributionReport::AggregatableData::SetAssembledReport( |
| std::optional<AggregatableReport> assembled_report) { |
| CHECK(!assembled_report_.has_value()); |
| assembled_report_ = std::move(assembled_report); |
| } |
| |
| AttributionReport::AggregatableData::~AggregatableData() = default; |
| |
| base::CheckedNumeric<int64_t> |
| AttributionReport::AggregatableData::BudgetRequired() const { |
| return GetTotalAggregatableValues(contributions_); |
| } |
| |
| AttributionReport::AttributionReport( |
| AttributionInfo attribution_info, |
| Id id, |
| base::Time report_time, |
| base::Time initial_report_time, |
| base::Uuid external_report_id, |
| int failed_send_attempts, |
| Data data, |
| attribution_reporting::SuitableOrigin reporting_origin, |
| std::optional<uint64_t> source_debug_key) |
| : attribution_info_(std::move(attribution_info)), |
| id_(id), |
| report_time_(report_time), |
| initial_report_time_(initial_report_time), |
| external_report_id_(std::move(external_report_id)), |
| failed_send_attempts_(failed_send_attempts), |
| data_(std::move(data)), |
| reporting_origin_(std::move(reporting_origin)), |
| source_debug_key_(source_debug_key) { |
| CHECK(external_report_id_.is_valid()); |
| CHECK_GE(failed_send_attempts_, 0); |
| } |
| |
| AttributionReport::AttributionReport(const AttributionReport&) = default; |
| |
| AttributionReport& AttributionReport::operator=(const AttributionReport&) = |
| default; |
| |
| AttributionReport::AttributionReport(AttributionReport&&) = default; |
| |
| AttributionReport& AttributionReport::operator=(AttributionReport&&) = default; |
| |
| AttributionReport::~AttributionReport() = default; |
| |
| GURL AttributionReport::ReportURL(bool debug) const { |
| static constexpr char kBasePath[] = "/.well-known/attribution-reporting/"; |
| static constexpr char kDebugPath[] = "debug/"; |
| |
| const char* endpoint_path; |
| switch (GetReportType()) { |
| case Type::kEventLevel: |
| endpoint_path = "report-event-attribution"; |
| break; |
| case Type::kAggregatableAttribution: |
| case Type::kNullAggregatable: |
| endpoint_path = "report-aggregate-attribution"; |
| break; |
| } |
| |
| std::string path = |
| base::StrCat({kBasePath, debug ? kDebugPath : "", endpoint_path}); |
| |
| GURL::Replacements replacements; |
| replacements.SetPathStr(path); |
| return reporting_origin_->GetURL().ReplaceComponents(replacements); |
| } |
| |
| base::Value::Dict AttributionReport::ReportBody() const { |
| base::Value::Dict dict; |
| |
| std::visit( |
| absl::Overload{ |
| [&](const EventLevelData& data) { |
| dict.Set("attribution_destination", data.destinations.ToJson()); |
| |
| // The API denotes these values as strings; a `uint64_t` cannot be |
| // put in a dict as an integer in order to be opaque to various API |
| // configurations. |
| dict.Set("source_event_id", |
| base::NumberToString(data.source_event_id)); |
| |
| dict.Set("trigger_data", base::NumberToString(data.trigger_data)); |
| |
| dict.Set("source_type", |
| attribution_reporting::SourceTypeName(data.source_type)); |
| |
| dict.Set("report_id", external_report_id_.AsLowercaseString()); |
| |
| // Round to 7 digits of precision, which allows us to express binary |
| // randomized response with epsilon = 14 without rounding to 0 |
| // (0.00000166305 -> 0.0000017). |
| double rounded_rate = |
| round(data.randomized_response_rate * 10000000) / 10000000.0; |
| dict.Set("randomized_trigger_rate", rounded_rate); |
| |
| dict.Set("scheduled_report_time", |
| base::NumberToString( |
| (initial_report_time_ - base::Time::UnixEpoch()) |
| .InSeconds())); |
| }, |
| |
| [&](const AggregatableData& data) { PopulateReportBody(dict, data); }, |
| }, |
| data_); |
| |
| if (CanDebuggingBeEnabled()) { |
| CHECK(source_debug_key_.has_value()); |
| std::optional<uint64_t> trigger_debug_key = attribution_info_.debug_key; |
| CHECK(trigger_debug_key.has_value()); |
| dict.Set("source_debug_key", base::NumberToString(*source_debug_key_)); |
| dict.Set("trigger_debug_key", base::NumberToString(*trigger_debug_key)); |
| } |
| |
| return dict; |
| } |
| |
| AttributionReport::Type AttributionReport::GetReportType() const { |
| return std::visit( |
| absl::Overload{ |
| [](const EventLevelData&) { |
| return AttributionReport::Type::kEventLevel; |
| }, |
| |
| [](const AggregatableData& data) { |
| return data.is_null() |
| ? AttributionReport::Type::kNullAggregatable |
| : AttributionReport::Type::kAggregatableAttribution; |
| }, |
| }, |
| data_); |
| } |
| |
| void AttributionReport::set_report_time(base::Time report_time) { |
| report_time_ = report_time; |
| } |
| |
| // static |
| std::optional<base::Time> AttributionReport::MinReportTime( |
| std::optional<base::Time> a, |
| std::optional<base::Time> b) { |
| if (!a.has_value()) { |
| return b; |
| } |
| |
| if (!b.has_value()) { |
| return a; |
| } |
| |
| return std::min(*a, *b); |
| } |
| |
| const SuitableOrigin& AttributionReport::GetSourceOrigin() const { |
| return std::visit( |
| absl::Overload{ |
| [](const AttributionReport::EventLevelData& data) |
| -> const SuitableOrigin& { return data.source_origin; }, |
| [&](const AttributionReport::AggregatableData& data) |
| -> const SuitableOrigin& { |
| if (data.source_origin().has_value()) { |
| return *data.source_origin(); |
| } |
| return attribution_info_.context_origin; |
| }, |
| }, |
| data_); |
| } |
| |
| bool AttributionReport::CanDebuggingBeEnabled() const { |
| return attribution_info_.debug_key.has_value() && |
| source_debug_key_.has_value(); |
| } |
| |
| } // namespace content |