| // 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/interop/runner.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/check.h" |
| #include "base/containers/flat_map.h" |
| #include "base/containers/flat_set.h" |
| #include "base/containers/span.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/bind.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ref.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "base/task/updateable_sequenced_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/thread_annotations.h" |
| #include "base/time/time.h" |
| #include "base/types/expected.h" |
| #include "base/types/expected_macros.h" |
| #include "base/values.h" |
| #include "components/aggregation_service/aggregation_coordinator_utils.h" |
| #include "components/attribution_reporting/attribution_scopes_data.h" |
| #include "components/attribution_reporting/eligibility.h" |
| #include "components/attribution_reporting/event_level_epsilon.h" |
| #include "components/attribution_reporting/max_event_level_reports.h" |
| #include "components/attribution_reporting/privacy_math.h" |
| #include "components/attribution_reporting/registration_eligibility.mojom-forward.h" |
| #include "components/attribution_reporting/source_type.mojom-forward.h" |
| #include "components/attribution_reporting/test_utils.h" |
| #include "content/browser/aggregation_service/aggregatable_report.h" |
| #include "content/browser/aggregation_service/aggregation_service_impl.h" |
| #include "content/browser/aggregation_service/aggregation_service_test_utils.h" |
| #include "content/browser/aggregation_service/public_key.h" |
| #include "content/browser/attribution_reporting/attribution_background_registrations_id.h" |
| #include "content/browser/attribution_reporting/attribution_data_host_manager.h" |
| #include "content/browser/attribution_reporting/attribution_features.h" |
| #include "content/browser/attribution_reporting/attribution_manager_impl.h" |
| #include "content/browser/attribution_reporting/attribution_os_level_manager.h" |
| #include "content/browser/attribution_reporting/attribution_report.h" |
| #include "content/browser/attribution_reporting/attribution_report_network_sender.h" |
| #include "content/browser/attribution_reporting/attribution_reporting.mojom.h" |
| #include "content/browser/attribution_reporting/attribution_resolver_delegate_impl.h" |
| #include "content/browser/attribution_reporting/attribution_suitable_context.h" |
| #include "content/browser/attribution_reporting/interop/parser.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/global_routing_id.h" |
| #include "content/public/browser/network_service_instance.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_utils.h" |
| #include "content/test/test_content_browser_client.h" |
| #include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h" |
| #include "services/network/network_service.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/attribution.mojom.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "services/network/test/test_utils.h" |
| #include "third_party/abseil-cpp/absl/functional/overload.h" |
| #include "third_party/blink/public/common/features.h" |
| #include "third_party/blink/public/common/tokens/tokens.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| using ::attribution_reporting::RandomizedResponse; |
| using ::attribution_reporting::mojom::RegistrationEligibility; |
| using ::network::mojom::AttributionReportingEligibility; |
| |
| using AttributionReportingOperation = |
| ::content::ContentBrowserClient::AttributionReportingOperation; |
| |
| constexpr int64_t kNavigationId(-1); |
| |
| const GlobalRenderFrameHostId kFrameId = {0, 1}; |
| |
| base::TimeDelta TimeOffset(base::Time time_origin) { |
| return time_origin - base::Time::UnixEpoch(); |
| } |
| |
| class Adjuster : public ReportBodyAdjuster { |
| public: |
| Adjuster(base::Time time_origin, |
| const aggregation_service::TestHpkeKey& hpke_key) |
| : time_origin_(time_origin), hpke_key_(hpke_key) {} |
| |
| ~Adjuster() override = default; |
| |
| private: |
| void AdjustEventLevel(base::Value::Dict& report_body) override { |
| AdjustTime(report_body, "scheduled_report_time"); |
| } |
| |
| void AdjustAggregatable(base::Value::Dict& report_body) override { |
| AdjustAggregatableReportSharedInfo(report_body); |
| } |
| |
| void AdjustAggregatableDebug(base::Value::Dict& report_body) override { |
| AdjustAggregatableReportSharedInfo(report_body); |
| } |
| |
| void AdjustAggregatableReportSharedInfo(base::Value::Dict& report_body) { |
| std::string* shared_info = report_body.FindString("shared_info"); |
| CHECK(shared_info); |
| |
| std::optional<base::Value::Dict> shared_info_dict = |
| base::JSONReader::ReadDict(*shared_info, base::JSON_PARSE_RFC); |
| CHECK(shared_info_dict); |
| |
| AdjustTime(*shared_info_dict, "scheduled_report_time"); |
| |
| // When source registration time is excluded from the report, its value is |
| // set to "0". |
| AdjustTime(*shared_info_dict, "source_registration_time", |
| /*skip_adjust_value=*/"0"); |
| |
| std::string adjusted_shared_info; |
| base::JSONWriter::Write(*shared_info_dict, &adjusted_shared_info); |
| |
| // The payloads were encrypted with the original shared info, therefore |
| // need to be re-encrypted with the adjusted shared info. |
| base::Value::List* payloads = |
| report_body.FindList("aggregation_service_payloads"); |
| CHECK(payloads); |
| |
| for (base::Value& payload : *payloads) { |
| std::string* payload_str = payload.GetDict().FindString("payload"); |
| CHECK(payload_str); |
| |
| std::optional<std::vector<uint8_t>> encrypted_payload = |
| base::Base64Decode(*payload_str); |
| CHECK(encrypted_payload.has_value()); |
| |
| // Decrypt with the original shared info. |
| std::vector<uint8_t> decrypted_payload = |
| aggregation_service::DecryptPayloadWithHpke( |
| *encrypted_payload, hpke_key_->full_hpke_key(), *shared_info); |
| |
| // Re-encrypt with the adjusted shared info. |
| std::string authenticated_info_str = base::StrCat( |
| {AggregatableReport::kDomainSeparationPrefix, adjusted_shared_info}); |
| *payload_str = |
| base::Base64Encode(EncryptAggregatableReportPayloadWithHpke( |
| decrypted_payload, hpke_key_->GetPublicKey().key, |
| base::as_byte_span(authenticated_info_str))); |
| } |
| |
| *shared_info = std::move(adjusted_shared_info); |
| } |
| |
| // Adjust the field that contains a string encoding seconds from the UNIX |
| // epoch. It needs to be adjusted relative to the simulator's origin time in |
| // order for test output to be consistent. |
| void AdjustTime(base::Value::Dict& dict, |
| std::string_view key, |
| std::string_view skip_adjust_value = "") { |
| if (std::string* str = dict.FindString(key); |
| str && *str != skip_adjust_value) { |
| if (int64_t seconds; base::StringToInt64(*str, &seconds)) { |
| *str = base::NumberToString(seconds - |
| TimeOffset(time_origin_).InSeconds()); |
| } |
| } |
| } |
| |
| const base::Time time_origin_; |
| const base::raw_ref<const aggregation_service::TestHpkeKey> hpke_key_; |
| }; |
| |
| AttributionInteropOutput::Report MakeReport( |
| const network::ResourceRequest& req, |
| const base::Time time_origin, |
| const aggregation_service::TestHpkeKey& hpke_key) { |
| std::optional<base::Value> value = |
| base::JSONReader::Read(network::GetUploadData(req), base::JSON_PARSE_RFC); |
| CHECK(value.has_value()); |
| |
| Adjuster adjuster(time_origin, hpke_key); |
| MaybeAdjustReportBody(req.url, *value, adjuster); |
| |
| return AttributionInteropOutput::Report( |
| base::Time::Now() - TimeOffset(time_origin), req.url, *std::move(value)); |
| } |
| |
| class AttributionInteropContentBrowserClient : public TestContentBrowserClient { |
| public: |
| explicit AttributionInteropContentBrowserClient( |
| const std::vector<AttributionSimulationEvent>& events) { |
| std::vector<base::Time> times; |
| for (const auto& event : events) { |
| if (const auto* data = |
| std::get_if<AttributionSimulationEvent::Response>(&event.data); |
| data && data->debug_permission) { |
| times.push_back(event.time); |
| } |
| } |
| debug_allowed_.replace(std::move(times)); |
| } |
| |
| private: |
| bool IsAttributionReportingOperationAllowed( |
| content::BrowserContext* browser_context, |
| AttributionReportingOperation operation, |
| content::RenderFrameHost* rfh, |
| const url::Origin* source_origin, |
| const url::Origin* destination_origin, |
| const url::Origin* reporting_origin, |
| bool* can_bypass) override { |
| switch (operation) { |
| case AttributionReportingOperation::kSource: |
| case AttributionReportingOperation::kSourceVerboseDebugReport: |
| case AttributionReportingOperation::kTrigger: |
| case AttributionReportingOperation::kTriggerVerboseDebugReport: |
| case AttributionReportingOperation::kOsSource: |
| case AttributionReportingOperation::kOsSourceVerboseDebugReport: |
| case AttributionReportingOperation::kOsTrigger: |
| case AttributionReportingOperation::kOsTriggerVerboseDebugReport: |
| case AttributionReportingOperation::kSourceAggregatableDebugReport: |
| case AttributionReportingOperation::kTriggerAggregatableDebugReport: |
| case AttributionReportingOperation::kReport: |
| case AttributionReportingOperation::kAny: |
| return true; |
| case AttributionReportingOperation::kSourceTransitionalDebugReporting: |
| case AttributionReportingOperation::kOsSourceTransitionalDebugReporting: |
| case AttributionReportingOperation::kTriggerTransitionalDebugReporting: |
| case AttributionReportingOperation::kOsTriggerTransitionalDebugReporting: |
| return debug_allowed_.contains(base::Time::Now()); |
| } |
| } |
| |
| base::flat_set<base::Time> debug_allowed_; |
| }; |
| |
| class ControllableStorageDelegate : public AttributionResolverDelegateImpl { |
| public: |
| explicit ControllableStorageDelegate(AttributionInteropRun& run) |
| : AttributionResolverDelegateImpl(AttributionNoiseMode::kNone, |
| AttributionDelayMode::kDefault, |
| run.config.attribution_config) { |
| std::vector<std::pair<base::Time, RandomizedResponse>> responses; |
| std::vector<std::pair<base::Time, base::flat_set<int>>> |
| null_aggregatable_reports_days; |
| for (auto& event : run.events) { |
| if (auto* data = |
| std::get_if<AttributionSimulationEvent::Response>(&event.data)) { |
| if (data->randomized_response.has_value()) { |
| responses.emplace_back( |
| event.time, |
| std::exchange(data->randomized_response, std::nullopt)); |
| } |
| if (!data->null_aggregatable_reports_days.empty()) { |
| null_aggregatable_reports_days.emplace_back( |
| event.time, |
| std::exchange(data->null_aggregatable_reports_days, {})); |
| } |
| } |
| } |
| randomized_responses_.replace(std::move(responses)); |
| null_aggregatable_reports_days_.replace( |
| std::move(null_aggregatable_reports_days)); |
| } |
| |
| ~ControllableStorageDelegate() override = default; |
| |
| ControllableStorageDelegate(const ControllableStorageDelegate&) = delete; |
| ControllableStorageDelegate& operator=(const ControllableStorageDelegate&) = |
| delete; |
| |
| ControllableStorageDelegate(ControllableStorageDelegate&&) = delete; |
| ControllableStorageDelegate& operator=(ControllableStorageDelegate&&) = |
| delete; |
| |
| private: |
| // AttributionResolverDelegateImpl: |
| GetRandomizedResponseResult GetRandomizedResponse( |
| const attribution_reporting::mojom::SourceType source_type, |
| const attribution_reporting::TriggerDataSet& trigger_data, |
| const attribution_reporting::EventReportWindows& event_report_windows, |
| const attribution_reporting::MaxEventLevelReports max_event_level_reports, |
| const attribution_reporting::EventLevelEpsilon epsilon, |
| const std::optional<attribution_reporting::AttributionScopesData>& |
| scopes_data) override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ASSIGN_OR_RETURN(auto response_data, |
| AttributionResolverDelegateImpl::GetRandomizedResponse( |
| source_type, trigger_data, event_report_windows, |
| max_event_level_reports, epsilon, scopes_data)); |
| |
| auto it = randomized_responses_.find(base::Time::Now()); |
| if (it == randomized_responses_.end()) { |
| return response_data; |
| } |
| |
| // Avoid crashing in `AttributionStorageSql::StoreSource()` by returning an |
| // arbitrary error here, which will manifest as unexpected test output. |
| if (!attribution_reporting::IsValid(it->second, trigger_data, |
| event_report_windows, |
| max_event_level_reports)) { |
| LOG(ERROR) << "invalid randomized response with trigger_data=" |
| << trigger_data; |
| return base::unexpected(attribution_reporting::RandomizedResponseError:: |
| kExceedsChannelCapacityLimit); |
| } |
| |
| response_data.response() = std::exchange(it->second, std::nullopt); |
| return response_data; |
| } |
| |
| std::optional<AttributionResolverDelegate::OfflineReportDelayConfig> |
| GetOfflineReportDelayConfig() const override { |
| return OfflineReportDelayConfig{ |
| .min = base::Minutes(5), |
| .max = base::Minutes(5), |
| }; |
| } |
| |
| bool GenerateNullAggregatableReportForLookbackDay( |
| int lookback_day, |
| attribution_reporting::mojom::SourceRegistrationTimeConfig |
| source_registration_time_config) const override { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| bool ret = AttributionResolverDelegateImpl:: |
| GenerateNullAggregatableReportForLookbackDay( |
| lookback_day, source_registration_time_config); |
| auto it = null_aggregatable_reports_days_.find(base::Time::Now()); |
| if (it != null_aggregatable_reports_days_.end()) { |
| ret = it->second.contains(lookback_day); |
| } |
| return ret; |
| } |
| |
| base::flat_map<base::Time, RandomizedResponse> randomized_responses_ |
| GUARDED_BY_CONTEXT(sequence_checker_); |
| |
| base::flat_map<base::Time, base::flat_set<int>> |
| null_aggregatable_reports_days_ GUARDED_BY_CONTEXT(sequence_checker_); |
| }; |
| |
| void Handle(const AttributionSimulationEvent::StartRequest& event, |
| AttributionManager& manager) { |
| std::optional<RegistrationEligibility> eligibility = |
| attribution_reporting::GetRegistrationEligibility(event.eligibility); |
| if (!eligibility.has_value()) { |
| return; |
| } |
| |
| auto suitable_context = AttributionSuitableContext::CreateForTesting( |
| event.context_origin, event.fenced, kFrameId, |
| /*last_navigation_id=*/kNavigationId); |
| |
| auto& data_host_manager = *manager.GetDataHostManager(); |
| std::optional<blink::AttributionSrcToken> attribution_src_token; |
| if (event.eligibility == AttributionReportingEligibility::kNavigationSource) { |
| attribution_src_token.emplace(); |
| data_host_manager.NotifyNavigationWithBackgroundRegistrationsWillStart( |
| *attribution_src_token, |
| /*background_registrations_count=*/1); |
| data_host_manager.NotifyNavigationRegistrationStarted( |
| suitable_context, *attribution_src_token, kNavigationId, |
| /*devtools_request_id=*/""); |
| data_host_manager.NotifyNavigationRegistrationCompleted( |
| *attribution_src_token); |
| } |
| |
| manager.GetDataHostManager()->NotifyBackgroundRegistrationStarted( |
| BackgroundRegistrationsId(event.request_id), std::move(suitable_context), |
| *eligibility, attribution_src_token, |
| /*devtools_request_id=*/""); |
| } |
| |
| void Handle(const AttributionSimulationEvent::Response& event, |
| AttributionManager& manager) { |
| manager.GetDataHostManager()->NotifyBackgroundRegistrationData( |
| BackgroundRegistrationsId(event.request_id), event.response_headers.get(), |
| event.url); |
| } |
| |
| void Handle(const AttributionSimulationEvent::EndRequest& event, |
| AttributionManager& manager) { |
| manager.GetDataHostManager()->NotifyBackgroundRegistrationCompleted( |
| BackgroundRegistrationsId(event.request_id)); |
| } |
| |
| void Handle(const AttributionSimulationEvent::Navigation& event, |
| AttributionManager& manager) { |
| manager.UpdateLastNavigationTime(base::Time::Now()); |
| } |
| |
| void FastForwardUntilReportsConsumed(AttributionManager& manager, |
| BrowserTaskEnvironment& task_environment) { |
| while (true) { |
| auto delta = base::TimeDelta::Min(); |
| base::RunLoop run_loop; |
| |
| manager.GetPendingReportsForInternalUse( |
| /*limit=*/-1, |
| base::BindLambdaForTesting([&](std::vector<AttributionReport> reports) { |
| auto it = std::ranges::max_element(reports, /*comp=*/{}, |
| &AttributionReport::report_time); |
| if (it != reports.end()) { |
| delta = it->report_time() - base::Time::Now(); |
| } |
| run_loop.Quit(); |
| })); |
| |
| run_loop.Run(); |
| |
| if (delta.is_negative()) { |
| task_environment.FastForwardBy(base::TimeDelta()); |
| break; |
| } |
| task_environment.FastForwardBy(delta); |
| } |
| } |
| |
| } // namespace |
| |
| base::expected<AttributionInteropOutput, std::string> |
| RunAttributionInteropSimulation( |
| AttributionInteropRun run, |
| const aggregation_service::TestHpkeKey& hpke_key) { |
| if (run.events.empty()) { |
| return AttributionInteropOutput(); |
| } |
| |
| DCHECK(std::ranges::is_sorted(run.events, /*comp=*/{}, |
| &AttributionSimulationEvent::time)); |
| |
| std::vector<base::test::FeatureRefAndParams> enabled_features( |
| {{blink::features::kKeepAliveInBrowserMigration, {}}, |
| {blink::features::kAttributionReportingInBrowserMigration, {}}}); |
| |
| std::optional<AttributionOsLevelManager::ScopedApiStateForTesting> |
| scoped_api_state; |
| if (run.config.needs_cross_app_web) { |
| scoped_api_state.emplace(AttributionOsLevelManager::ApiState::kEnabled); |
| } |
| |
| if (run.config.needs_retry_after_new_navigation) { |
| enabled_features.push_back(base::test::FeatureRefAndParams( // IN-TEST |
| kAttributionReportNavigationBasedRetry, |
| {{"navigation_retry_attempt", |
| *run.config.needs_retry_after_new_navigation}})); |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list; |
| scoped_feature_list.InitWithFeaturesAndParameters(enabled_features, |
| /*disabled_features=*/{}); |
| |
| attribution_reporting::ScopedMaxEventLevelEpsilonForTesting |
| scoped_max_event_level_epsilon(run.config.max_event_level_epsilon); |
| |
| attribution_reporting::ScopedMaxTriggerStateCardinalityForTesting |
| scoped_max_trigger_state_cardinality( |
| run.config.max_trigger_state_cardinality); |
| |
| // Prerequisites for using an environment with mock time. |
| BrowserTaskEnvironment task_environment( |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME); |
| TestBrowserContext browser_context; |
| |
| GetNetworkService(); |
| // Wait for the Network Service to initialize on the IO thread. |
| RunAllPendingInMessageLoop(content::BrowserThread::IO); |
| // Disable metrics updater to avoid test timeouts. |
| network::NetworkService::GetNetworkServiceForTesting() |
| ->ResetMetricsUpdaterForTesting(); // IN-TEST |
| |
| // Ensure that `time_origin` has a whole number of days to make |
| // `AdjustEventLevelBody()` and `AdjustAggregatableReportSharedInfo()` time |
| // calculations robust against sub-second-precision report times and rounding, |
| // which otherwise cannot be recovered because the `scheduled_report_time` |
| // field has second precision and `source_registration_time` is rounded to |
| // whole day. |
| { |
| const base::Time non_whole_day = base::Time::Now(); |
| |
| base::Time::Exploded exploded; |
| non_whole_day.UTCExplode(&exploded); |
| DCHECK(exploded.HasValidValues()); |
| exploded.millisecond = 0; |
| exploded.second = 0; |
| exploded.minute = 0; |
| exploded.hour = 0; |
| |
| base::Time whole_day; |
| bool ok = base::Time::FromUTCExploded(exploded, &whole_day); |
| DCHECK(ok); |
| |
| task_environment.FastForwardBy((whole_day + base::Days(1)) - non_whole_day); |
| } |
| |
| const base::Time time_origin = base::Time::Now(); |
| |
| for (auto& event : run.events) { |
| event.time = time_origin + (event.time - base::Time::UnixEpoch()); |
| } |
| |
| const base::Time min_event_time = run.events.front().time; |
| |
| task_environment.FastForwardBy(min_event_time - time_origin); |
| |
| auto* storage_partition = static_cast<StoragePartitionImpl*>( |
| browser_context.GetDefaultStoragePartition()); |
| |
| AttributionInteropContentBrowserClient browser_client(run.events); |
| ScopedContentBrowserClientSetting setting(&browser_client); |
| |
| AttributionInteropOutput output; |
| |
| network::TestURLLoaderFactory test_url_loader_factory; |
| test_url_loader_factory.SetInterceptor( |
| base::BindLambdaForTesting([&](const network::ResourceRequest& req) { |
| output.reports.emplace_back(MakeReport(req, time_origin, hpke_key)); |
| test_url_loader_factory.AddResponse(req.url.spec(), /*content=*/""); |
| })); |
| |
| // Speed-up parsing in `AttributionDataHostManagerImpl`. |
| data_decoder::test::InProcessDataDecoder in_process_data_decoder; |
| |
| auto storage_task_runner = |
| base::ThreadPool::CreateUpdateableSequencedTaskRunner( |
| {base::TaskPriority::BEST_EFFORT, base::MayBlock(), |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN, |
| base::ThreadPolicy::MUST_USE_FOREGROUND}); |
| |
| auto manager = AttributionManagerImpl::CreateForTesting( |
| // Avoid creating an on-disk sqlite DB. |
| /*user_data_directory=*/base::FilePath(), |
| /*special_storage_policy=*/nullptr, |
| std::make_unique<ControllableStorageDelegate>(run), |
| std::make_unique<AttributionReportNetworkSender>( |
| test_url_loader_factory.GetSafeWeakWrapper()), |
| std::make_unique<NoOpAttributionOsLevelManager>(), storage_partition, |
| storage_task_runner); |
| |
| for (const auto& origin : run.config.aggregation_coordinator_origins) { |
| // TODO: Consider using a different public key for each origin. |
| static_cast<AggregationServiceImpl*>( |
| storage_partition->GetAggregationService()) |
| ->SetPublicKeysForTesting( // IN-TEST |
| GetAggregationServiceProcessingUrl(origin), |
| PublicKeyset({hpke_key.GetPublicKey()}, |
| /*fetch_time=*/base::Time::Now(), |
| /*expiry_time=*/base::Time::Max())); |
| } |
| |
| ::aggregation_service::ScopedAggregationCoordinatorAllowlistForTesting |
| scoped_aggregation_coordinators( |
| std::move(run.config.aggregation_coordinator_origins)); |
| |
| for (const auto& event : run.events) { |
| task_environment.FastForwardBy(event.time - base::Time::Now()); |
| std::visit( |
| absl::Overload{ |
| [&](const AttributionSimulationEvent::Connection& event) { |
| if (!event.connected) { |
| test_url_loader_factory.SetInterceptor( |
| base::BindLambdaForTesting([&](const network:: |
| ResourceRequest& req) { |
| test_url_loader_factory.AddResponse( |
| req.url, network::mojom::URLResponseHead::New(), |
| /*content=*/"", |
| network::URLLoaderCompletionStatus( |
| net::ERR_INTERNET_DISCONNECTED), |
| network::TestURLLoaderFactory::Redirects(), |
| network::TestURLLoaderFactory::ResponseProduceFlags:: |
| kSendHeadersOnNetworkError); |
| })); |
| } else { |
| test_url_loader_factory.SetInterceptor( |
| base::BindLambdaForTesting( |
| [&](const network::ResourceRequest& req) { |
| output.reports.emplace_back( |
| MakeReport(req, time_origin, hpke_key)); |
| test_url_loader_factory.AddResponse(req.url.spec(), |
| /*content=*/""); |
| })); |
| } |
| }, |
| [&](const auto& data) { Handle(data, *manager); }}, |
| event.data); |
| } |
| |
| FastForwardUntilReportsConsumed(*manager, task_environment); |
| |
| return output; |
| } |
| |
| void MaybeAdjustReportBody(const GURL& url, |
| base::Value& payload, |
| ReportBodyAdjuster& adjuster) { |
| if (base::EndsWith(url.path_piece(), "/report-aggregate-attribution")) { |
| if (base::Value::Dict* dict = payload.GetIfDict()) { |
| adjuster.AdjustAggregatable(*dict); |
| } |
| } else if (base::EndsWith(url.path_piece(), "/report-event-attribution")) { |
| if (base::Value::Dict* dict = payload.GetIfDict()) { |
| adjuster.AdjustEventLevel(*dict); |
| } |
| } else if (url.path_piece() == |
| "/.well-known/attribution-reporting/debug/verbose") { |
| if (base::Value::List* list = payload.GetIfList()) { |
| for (auto& item : *list) { |
| base::Value::Dict* dict = item.GetIfDict(); |
| if (!dict) { |
| continue; |
| } |
| |
| const std::string* debug_data_type = dict->FindString("type"); |
| base::Value::Dict* body = dict->FindDict("body"); |
| if (debug_data_type && body) { |
| adjuster.AdjustVerboseDebug(*debug_data_type, *body); |
| } |
| } |
| } |
| } else if (url.path_piece() == |
| "/.well-known/attribution-reporting/debug/" |
| "report-aggregate-debug") { |
| if (base::Value::Dict* dict = payload.GetIfDict()) { |
| adjuster.AdjustAggregatableDebug(*dict); |
| } |
| } |
| } |
| |
| void ReportBodyAdjuster::AdjustVerboseDebug(std::string_view debug_data_type, |
| base::Value::Dict& body) { |
| if (debug_data_type == "trigger-event-excessive-reports" || |
| debug_data_type == "trigger-event-low-priority") { |
| AdjustEventLevel(body); |
| } |
| } |
| |
| } // namespace content |