blob: 8901a46c4ee668ec131b1f9bb3ceda28b6a64e4c [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/private_aggregation/private_aggregation_host.h"
#include <iterator>
#include <string>
#include <utility>
#include <vector>
#include "base/callback.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/guid.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "base/values.h"
#include "components/aggregation_service/aggregation_service.mojom.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/private_aggregation/private_aggregation_budget_key.h"
#include "content/browser/private_aggregation/private_aggregation_utils.h"
#include "content/common/aggregatable_report.mojom.h"
#include "content/common/private_aggregation_host.mojom.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_switches.h"
#include "mojo/public/cpp/bindings/message.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/origin.h"
namespace content {
namespace {
// Helper to add a random delay to reports being sent. This delay is picked
// uniformly at random from the range [10 minutes, 1 hour).
// TODO(alexmt): Consider making this configurable for easier testing.
base::Time GetScheduledReportTime(base::Time report_issued_time) {
return report_issued_time + base::Minutes(10) +
base::RandDouble() * base::Minutes(50);
}
void RecordSendHistogramReportResultHistogram(
PrivateAggregationHost::SendHistogramReportResult result) {
base::UmaHistogramEnumeration(
"PrivacySandbox.PrivateAggregation.Host.SendHistogramReportResult",
result);
}
} // namespace
struct PrivateAggregationHost::ReceiverContext {
url::Origin worklet_origin;
url::Origin top_frame_origin;
PrivateAggregationBudgetKey::Api api_for_budgeting;
};
PrivateAggregationHost::PrivateAggregationHost(
base::RepeatingCallback<void(AggregatableReportRequest,
PrivateAggregationBudgetKey)>
on_report_request_received,
BrowserContext* browser_context)
: should_not_delay_reports_(
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kPrivateAggregationDeveloperMode)),
on_report_request_received_(std::move(on_report_request_received)),
browser_context_(*browser_context) {
DCHECK(!on_report_request_received_.is_null());
}
PrivateAggregationHost::~PrivateAggregationHost() = default;
bool PrivateAggregationHost::BindNewReceiver(
url::Origin worklet_origin,
url::Origin top_frame_origin,
PrivateAggregationBudgetKey::Api api_for_budgeting,
mojo::PendingReceiver<mojom::PrivateAggregationHost> pending_receiver) {
if (!network::IsOriginPotentiallyTrustworthy(worklet_origin)) {
// Let the pending receiver be destroyed as it goes out of scope so none of
// its requests are processed.
return false;
}
receiver_set_.Add(
this, std::move(pending_receiver),
ReceiverContext{.worklet_origin = std::move(worklet_origin),
.top_frame_origin = std::move(top_frame_origin),
.api_for_budgeting = api_for_budgeting});
return true;
}
void PrivateAggregationHost::SendHistogramReport(
std::vector<mojom::AggregatableReportHistogramContributionPtr>
contribution_ptrs,
mojom::AggregationServiceMode aggregation_mode,
mojom::DebugModeDetailsPtr debug_mode_details) {
const url::Origin& reporting_origin =
receiver_set_.current_context().worklet_origin;
DCHECK(network::IsOriginPotentiallyTrustworthy(reporting_origin));
if (!GetContentClient()->browser()->IsPrivateAggregationAllowed(
&*browser_context_, receiver_set_.current_context().top_frame_origin,
reporting_origin)) {
RecordSendHistogramReportResultHistogram(
SendHistogramReportResult::kApiDisabledInSettings);
return;
}
// Null pointers should fail mojo validation.
DCHECK(base::ranges::none_of(
contribution_ptrs,
[](const mojom::AggregatableReportHistogramContributionPtr&
contribution_ptr) { return contribution_ptr.is_null(); }));
DCHECK(!debug_mode_details.is_null());
if (contribution_ptrs.size() > kMaxNumberOfContributions) {
// TODO(crbug.com/1323324): Add histograms for monitoring failures here,
// possibly broken out by failure reason.
mojo::ReportBadMessage("Too many contributions");
RecordSendHistogramReportResultHistogram(
SendHistogramReportResult::kTooManyContributions);
return;
}
std::vector<mojom::AggregatableReportHistogramContribution> contributions;
contributions.reserve(contribution_ptrs.size());
base::ranges::transform(
contribution_ptrs, std::back_inserter(contributions),
[](const mojom::AggregatableReportHistogramContributionPtr&
contribution_ptr) { return std::move(*contribution_ptr); });
AggregationServicePayloadContents payload_contents(
AggregationServicePayloadContents::Operation::kHistogram,
std::move(contributions), aggregation_mode,
::aggregation_service::mojom::AggregationCoordinator::kDefault);
base::Time now = base::Time::Now();
AggregatableReportSharedInfo shared_info(
/*scheduled_report_time=*/should_not_delay_reports_
? now
: GetScheduledReportTime(
/*report_issued_time=*/now),
/*report_id=*/base::GUID::GenerateRandomV4(), reporting_origin,
debug_mode_details->is_enabled
? AggregatableReportSharedInfo::DebugMode::kEnabled
: AggregatableReportSharedInfo::DebugMode::kDisabled,
/*additional_fields=*/base::Value::Dict(),
/*api_version=*/kApiReportVersion,
/*api_identifier=*/
private_aggregation::GetApiIdentifier(
receiver_set_.current_context().api_for_budgeting));
std::string reporting_path = private_aggregation::GetReportingPath(
receiver_set_.current_context().api_for_budgeting,
/*is_immediate_debug_report=*/false);
absl::optional<uint64_t> debug_key;
if (!debug_mode_details->debug_key.is_null()) {
if (!debug_mode_details->is_enabled) {
mojo::ReportBadMessage("Debug key present but debug mode is not enabled");
RecordSendHistogramReportResultHistogram(
SendHistogramReportResult::kDebugKeyPresentWithoutDebugMode);
return;
}
debug_key = debug_mode_details->debug_key->value;
}
absl::optional<AggregatableReportRequest> report_request =
AggregatableReportRequest::Create(std::move(payload_contents),
std::move(shared_info),
std::move(reporting_path), debug_key);
if (!report_request.has_value()) {
// TODO(crbug.com/1323324): Add histograms for monitoring failures here,
// possibly broken out by failure reason.
mojo::ReportBadMessage("Invalid report request parameters");
RecordSendHistogramReportResultHistogram(
SendHistogramReportResult::kReportRequestCreationFailed);
return;
}
absl::optional<PrivateAggregationBudgetKey> budget_key =
PrivateAggregationBudgetKey::Create(
/*origin=*/reporting_origin, /*api_invocation_time=*/now,
/*api=*/receiver_set_.current_context().api_for_budgeting);
// The origin should be potentially trustworthy.
DCHECK(budget_key.has_value());
on_report_request_received_.Run(std::move(report_request.value()),
std::move(budget_key.value()));
RecordSendHistogramReportResultHistogram(SendHistogramReportResult::kSuccess);
}
} // namespace content