blob: 9e941867c53657f59e005d9f51fa9734ab59f286 [file] [log] [blame]
// 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_internals_handler_impl.h"
#include <stdint.h>
#include <algorithm>
#include <iterator>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/containers/to_vector.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/attribution_reporting/aggregation_keys.h"
#include "components/attribution_reporting/os_registration.h"
#include "components/attribution_reporting/parsing_utils.h"
#include "components/attribution_reporting/source_registration.h"
#include "components/attribution_reporting/suitable_origin.h"
#include "components/attribution_reporting/trigger_config.h"
#include "components/attribution_reporting/trigger_registration.h"
#include "content/browser/attribution_reporting/aggregatable_debug_report.h"
#include "content/browser/attribution_reporting/aggregatable_named_budget_pair.h"
#include "content/browser/attribution_reporting/attribution_debug_report.h"
#include "content/browser/attribution_reporting/attribution_info.h"
#include "content/browser/attribution_reporting/attribution_internals.mojom.h"
#include "content/browser/attribution_reporting/attribution_manager.h"
#include "content/browser/attribution_reporting/attribution_report.h"
#include "content/browser/attribution_reporting/attribution_reporting.mojom-forward.h"
#include "content/browser/attribution_reporting/attribution_trigger.h"
#include "content/browser/attribution_reporting/attribution_utils.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/send_result.h"
#include "content/browser/attribution_reporting/storable_source.h"
#include "content/browser/attribution_reporting/stored_source.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/common/content_client.h"
#include "net/base/net_errors.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "third_party/abseil-cpp/absl/numeric/int128.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
namespace {
using Attributability =
::attribution_internals::mojom::WebUISource::Attributability;
using Empty = ::attribution_internals::mojom::Empty;
using ReportStatus = ::attribution_internals::mojom::ReportStatus;
using ReportStatusPtr = ::attribution_internals::mojom::ReportStatusPtr;
using ::attribution_internals::mojom::WebUIAggregatableDebugReport;
using ::attribution_internals::mojom::WebUIDebugReport;
std::string SerializeBudgetsMap(
const StoredSource::AggregatableNamedBudgets& map) {
base::Value::Dict dict;
for (const auto& [key, value] : map) {
base::Value::Dict inner_dict;
inner_dict.Set("original_budget", value.original_budget());
inner_dict.Set("remaining_budget", value.remaining_budget());
dict.Set(key, std::move(inner_dict));
}
return SerializeAttributionJson(dict, /*pretty_print=*/true);
}
attribution_internals::mojom::WebUISourcePtr WebUISource(
const StoredSource& source,
Attributability attributability) {
const CommonSourceInfo& common_info = source.common_info();
return attribution_internals::mojom::WebUISource::New(
*source.source_id(), source.source_event_id(),
common_info.source_origin(), source.destination_sites(),
common_info.reporting_origin(),
source.source_time().InMillisecondsFSinceUnixEpoch(),
source.expiry_time().InMillisecondsFSinceUnixEpoch(),
source.event_report_windows(),
base::ToVector(source.trigger_data().trigger_data()),
source.max_event_level_reports(),
source.aggregatable_report_window_time().InMillisecondsFSinceUnixEpoch(),
common_info.source_type(), source.priority(), source.debug_key(),
source.dedup_keys(), source.filter_data(),
base::MakeFlatMap<std::string, std::string>(
source.aggregation_keys().keys(), {},
[](const auto& key) {
return std::make_pair(
key.first,
attribution_reporting::HexEncodeAggregationKey(key.second));
}),
source.remaining_aggregatable_attribution_budget(),
source.aggregatable_dedup_keys(), source.trigger_data_matching(),
source.event_level_epsilon(),
source.common_info().cookie_based_debug_allowed(),
source.remaining_aggregatable_debug_budget(),
attribution_reporting::HexEncodeAggregationKey(
source.aggregatable_debug_key_piece()),
source.attribution_scopes_data().has_value()
? SerializeAttributionJson(source.attribution_scopes_data()->ToJson(),
/*pretty_print=*/true)
: "null",
SerializeBudgetsMap(source.aggregatable_named_budgets()),
attributability);
}
std::vector<attribution_internals::mojom::WebUISourcePtr> ToWebUISources(
const std::vector<StoredSource>& active_sources) {
return base::ToVector(active_sources, [](const StoredSource& source) {
Attributability attributability;
switch (source.attribution_logic()) {
case StoredSource::AttributionLogic::kTruthfully:
attributability = Attributability::kAttributable;
break;
case StoredSource::AttributionLogic::kNever:
attributability = Attributability::kNoisedNever;
break;
case StoredSource::AttributionLogic::kFalsely:
attributability = Attributability::kNoisedFalsely;
break;
}
if (attributability == Attributability::kAttributable) {
switch (source.active_state()) {
case StoredSource::ActiveState::kActive:
attributability = Attributability::kAttributable;
break;
case StoredSource::ActiveState::kReachedEventLevelAttributionLimit:
attributability = Attributability::kReachedEventLevelAttributionLimit;
break;
case StoredSource::ActiveState::kInactive:
NOTREACHED();
}
}
return WebUISource(source, attributability);
});
}
attribution_internals::mojom::WebUIReportPtr WebUIReport(
const AttributionReport& report,
bool is_debug_report,
ReportStatusPtr status) {
namespace ai_mojom = attribution_internals::mojom;
const AttributionInfo& attribution_info = report.attribution_info();
ai_mojom::WebUIReportDataPtr data = std::visit(
absl::Overload{
[](const AttributionReport::EventLevelData& event_level_data) {
return ai_mojom::WebUIReportData::NewEventLevelData(
ai_mojom::WebUIReportEventLevelData::New(
event_level_data.priority,
event_level_data.attributed_truthfully));
},
[](const AttributionReport::AggregatableData& aggregatable_data) {
std::vector<ai_mojom::AggregatableHistogramContributionPtr>
contributions;
if (aggregatable_data.is_null()) {
contributions.push_back(
ai_mojom::AggregatableHistogramContribution::New(
attribution_reporting::HexEncodeAggregationKey(0),
/*value=*/0,
/*filtering_id=*/0));
} else {
contributions = base::ToVector(
aggregatable_data.contributions(),
[](const auto& contribution) {
return ai_mojom::AggregatableHistogramContribution::New(
attribution_reporting::HexEncodeAggregationKey(
contribution.bucket),
base::checked_cast<uint32_t>(contribution.value),
contribution.filtering_id.value_or(0));
});
}
return ai_mojom::WebUIReportData::NewAggregatableAttributionData(
ai_mojom::WebUIReportAggregatableAttributionData::New(
std::move(contributions),
aggregatable_data.aggregation_coordinator_origin()
? aggregatable_data.aggregation_coordinator_origin()
->Serialize()
: "",
aggregatable_data.is_null()));
},
},
report.data());
return attribution_internals::mojom::WebUIReport::New(
report.id(), report.ReportURL(is_debug_report),
/*trigger_time=*/attribution_info.time.InMillisecondsFSinceUnixEpoch(),
/*report_time=*/report.report_time().InMillisecondsFSinceUnixEpoch(),
SerializeAttributionJson(report.ReportBody(), /*pretty_print=*/true),
std::move(status), std::move(data));
}
std::vector<attribution_internals::mojom::WebUIReportPtr> ToWebUIReports(
const std::vector<AttributionReport>& pending_reports) {
return base::ToVector(pending_reports, [](const AttributionReport& report) {
return WebUIReport(report, /*is_debug_report=*/false,
ReportStatus::NewPending(Empty::New()));
});
}
attribution_internals::mojom::NetworkStatusPtr NetworkStatus(int status) {
return status >= 0
? attribution_internals::mojom::NetworkStatus::NewHttpResponseCode(
status)
: attribution_internals::mojom::NetworkStatus::NewNetworkError(
net::ErrorToShortString(status));
}
} // namespace
AttributionInternalsHandlerImpl::AttributionInternalsHandlerImpl(
WebUI* web_ui,
mojo::PendingRemote<attribution_internals::mojom::Observer> observer,
mojo::PendingReceiver<attribution_internals::mojom::Handler> handler)
: web_ui_(raw_ref<WebUI>::from_ptr(web_ui)),
observer_(std::move(observer)),
handler_(this, std::move(handler)) {
if (auto* manager =
AttributionManager::FromWebContents(web_ui_->GetWebContents())) {
manager_observation_.Observe(manager);
observer_.set_disconnect_handler(
base::BindOnce(&AttributionInternalsHandlerImpl::OnObserverDisconnected,
base::Unretained(this)));
OnSourcesChanged();
OnReportsChanged();
}
}
AttributionInternalsHandlerImpl::~AttributionInternalsHandlerImpl() = default;
void AttributionInternalsHandlerImpl::IsAttributionReportingEnabled(
attribution_internals::mojom::Handler::IsAttributionReportingEnabledCallback
callback) {
content::WebContents* contents = web_ui_->GetWebContents();
bool attribution_reporting_enabled =
AttributionManager::FromWebContents(contents) &&
GetContentClient()->browser()->IsAttributionReportingOperationAllowed(
contents->GetBrowserContext(),
ContentBrowserClient::AttributionReportingOperation::kAny,
/*rfh=*/nullptr, /*source_origin=*/nullptr,
/*destination_origin=*/nullptr, /*reporting_origin=*/nullptr,
/*can_bypass=*/nullptr);
std::move(callback).Run(
attribution_reporting_enabled,
static_cast<WebContentsImpl*>(contents)->GetAttributionSupport());
}
void AttributionInternalsHandlerImpl::OnDebugModeChanged(bool debug_mode) {
observer_->OnDebugModeChanged(debug_mode);
}
void AttributionInternalsHandlerImpl::OnSourcesChanged() {
if (AttributionManager* manager =
AttributionManager::FromWebContents(web_ui_->GetWebContents())) {
manager->GetActiveSourcesForWebUI(base::BindOnce(
[](base::WeakPtr<AttributionInternalsHandlerImpl> handler,
std::vector<StoredSource> sources) {
if (handler) {
handler->observer_->OnSourcesChanged(ToWebUISources(sources));
}
},
weak_ptr_factory_.GetWeakPtr()));
}
}
void AttributionInternalsHandlerImpl::OnReportsChanged() {
if (AttributionManager* manager =
AttributionManager::FromWebContents(web_ui_->GetWebContents())) {
manager->GetPendingReportsForInternalUse(
/*limit=*/1000,
base::BindOnce(
[](base::WeakPtr<AttributionInternalsHandlerImpl> handler,
std::vector<AttributionReport> reports) {
if (handler) {
handler->observer_->OnReportsChanged(ToWebUIReports(reports));
}
},
weak_ptr_factory_.GetWeakPtr()));
}
}
void AttributionInternalsHandlerImpl::SendReport(
AttributionReport::Id id,
attribution_internals::mojom::Handler::SendReportCallback callback) {
if (AttributionManager* manager =
AttributionManager::FromWebContents(web_ui_->GetWebContents())) {
manager->SendReportForWebUI(id, std::move(callback));
} else {
std::move(callback).Run();
}
}
void AttributionInternalsHandlerImpl::ClearStorage(
attribution_internals::mojom::Handler::ClearStorageCallback callback) {
if (AttributionManager* manager =
AttributionManager::FromWebContents(web_ui_->GetWebContents())) {
manager->ClearData(base::Time::Min(), base::Time::Max(),
/*filter=*/base::NullCallback(),
/*filter_builder=*/nullptr,
/*delete_rate_limit_data=*/true, std::move(callback));
} else {
std::move(callback).Run();
}
}
namespace {
using WebUISourceRegistration =
::attribution_internals::mojom::WebUISourceRegistration;
attribution_internals::mojom::WebUIRegistrationPtr GetRegistration(
base::Time time,
const attribution_reporting::SuitableOrigin& context_origin,
const attribution_reporting::SuitableOrigin& reporting_origin,
std::string registration_json,
std::optional<uint64_t> cleared_debug_key) {
auto reg = attribution_internals::mojom::WebUIRegistration::New();
reg->time = time.InMillisecondsFSinceUnixEpoch();
reg->context_origin = context_origin;
reg->reporting_origin = reporting_origin;
reg->registration_json = std::move(registration_json);
reg->cleared_debug_key = cleared_debug_key;
return reg;
}
} // namespace
void AttributionInternalsHandlerImpl::OnSourceHandled(
const StorableSource& source,
base::Time source_time,
std::optional<uint64_t> cleared_debug_key,
attribution_reporting::mojom::StoreSourceResult result) {
auto web_ui_source = WebUISourceRegistration::New();
web_ui_source->registration =
GetRegistration(source_time, source.common_info().source_origin(),
source.common_info().reporting_origin(),
SerializeAttributionJson(source.registration().ToJson(),
/*pretty_print=*/true),
cleared_debug_key);
web_ui_source->type = source.common_info().source_type();
web_ui_source->status = std::move(result);
observer_->OnSourceHandled(std::move(web_ui_source));
}
void AttributionInternalsHandlerImpl::OnReportSent(
const AttributionReport& report,
bool is_debug_report,
const SendResult& info) {
ReportStatusPtr status = std::visit(
absl::Overload{
[](SendResult::Sent sent) {
return ReportStatus::NewNetworkStatus(NetworkStatus(sent.status));
},
[](SendResult::Expired) {
return ReportStatus::NewExpired(Empty::New());
},
[](SendResult::Dropped) {
return ReportStatus::NewProhibitedByBrowserPolicy(Empty::New());
},
[](SendResult::AssemblyFailure) {
return ReportStatus::NewFailedToAssemble(Empty::New());
},
},
info.result);
observer_->OnReportHandled(
WebUIReport(report, is_debug_report, std::move(status)));
}
void AttributionInternalsHandlerImpl::OnDebugReportSent(
const AttributionDebugReport& report,
int status,
base::Time time) {
auto web_report = WebUIDebugReport::New();
web_report->url = report.ReportUrl();
web_report->time = time.InMillisecondsFSinceUnixEpoch();
web_report->body =
SerializeAttributionJson(report.ReportBody(), /*pretty_print=*/true);
web_report->status = NetworkStatus(status);
observer_->OnDebugReportSent(std::move(web_report));
}
void AttributionInternalsHandlerImpl::OnAggregatableDebugReportSent(
const AggregatableDebugReport& report,
base::ValueView report_body,
attribution_reporting::mojom::ProcessAggregatableDebugReportResult
process_result,
const SendAggregatableDebugReportResult& send_result) {
auto web_report = WebUIAggregatableDebugReport::New();
web_report->url = report.ReportUrl();
web_report->time =
report.scheduled_report_time().InMillisecondsFSinceUnixEpoch();
web_report->body =
SerializeAttributionJson(report_body, /*pretty_print=*/true);
web_report->process_result = process_result;
web_report->send_result = std::visit(
absl::Overload{
[](const SendAggregatableDebugReportResult::Sent& sent) {
return attribution_internals::mojom::
SendAggregatableDebugReportResult::NewNetworkStatus(
NetworkStatus(sent.status));
},
[](const SendAggregatableDebugReportResult::AssemblyFailed&) {
return attribution_internals::mojom::
SendAggregatableDebugReportResult::NewAssemblyFailed(
Empty::New());
},
},
send_result.result);
observer_->OnAggregatableDebugReportSent(std::move(web_report));
}
void AttributionInternalsHandlerImpl::OnOsRegistration(
base::Time time,
const attribution_reporting::OsRegistrationItem& registration,
const url::Origin& top_level_origin,
attribution_reporting::mojom::RegistrationType type,
bool is_debug_key_allowed,
attribution_reporting::mojom::OsRegistrationResult result) {
auto web_ui_os_registration =
attribution_internals::mojom::WebUIOsRegistration::New();
web_ui_os_registration->time =
time.InMillisecondsFSinceUnixEpochIgnoringNull();
web_ui_os_registration->registration_url = registration.url;
web_ui_os_registration->top_level_origin = top_level_origin;
web_ui_os_registration->is_debug_key_allowed = is_debug_key_allowed;
web_ui_os_registration->debug_reporting = registration.debug_reporting;
web_ui_os_registration->type = type;
web_ui_os_registration->result = result;
observer_->OnOsRegistration(std::move(web_ui_os_registration));
}
void AttributionInternalsHandlerImpl::OnTriggerHandled(
const std::optional<uint64_t> cleared_debug_key,
const CreateReportResult& result) {
const AttributionTrigger& trigger = result.trigger();
const attribution_reporting::TriggerRegistration& registration =
trigger.registration();
auto web_ui_trigger = attribution_internals::mojom::WebUITrigger::New();
web_ui_trigger->registration =
GetRegistration(result.trigger_time(), trigger.destination_origin(),
trigger.reporting_origin(),
SerializeAttributionJson(registration.ToJson(),
/*pretty_print=*/true),
cleared_debug_key);
web_ui_trigger->event_level_result = result.event_level_status();
web_ui_trigger->aggregatable_result = result.aggregatable_status();
observer_->OnTriggerHandled(std::move(web_ui_trigger));
if (const AttributionReport* report = result.replaced_event_level_report()) {
CHECK_EQ(
result.event_level_status(),
AttributionTrigger::EventLevelResult::kSuccessDroppedLowerPriority);
CHECK(result.new_event_level_report());
observer_->OnReportHandled(
WebUIReport(*report, /*is_debug_report=*/false,
ReportStatus::NewReplacedByHigherPriorityReport(
result.new_event_level_report()
->external_report_id()
.AsLowercaseString())));
}
}
void AttributionInternalsHandlerImpl::OnObserverDisconnected() {
manager_observation_.Reset();
}
} // namespace content