1 // Copyright 2017 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/reporting/reporting_delivery_agent.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <set>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "base/check.h"
15 #include "base/containers/contains.h"
16 #include "base/functional/bind.h"
17 #include "base/json/json_writer.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/time/tick_clock.h"
22 #include "base/timer/timer.h"
23 #include "base/values.h"
24 #include "net/base/isolation_info.h"
25 #include "net/base/network_anonymization_key.h"
26 #include "net/base/url_util.h"
27 #include "net/reporting/reporting_cache.h"
28 #include "net/reporting/reporting_cache_observer.h"
29 #include "net/reporting/reporting_context.h"
30 #include "net/reporting/reporting_delegate.h"
31 #include "net/reporting/reporting_endpoint_manager.h"
32 #include "net/reporting/reporting_report.h"
33 #include "net/reporting/reporting_target_type.h"
34 #include "net/reporting/reporting_uploader.h"
35 #include "url/gurl.h"
36 #include "url/origin.h"
37
38 namespace net {
39
40 namespace {
41
42 using ReportList =
43 std::vector<raw_ptr<const ReportingReport, VectorExperimental>>;
44 using ReportingUploadHeaderType =
45 ReportingDeliveryAgent::ReportingUploadHeaderType;
46
RecordReportingUploadHeaderType(ReportingUploadHeaderType header_type)47 void RecordReportingUploadHeaderType(ReportingUploadHeaderType header_type) {
48 base::UmaHistogramEnumeration("Net.Reporting.UploadHeaderType", header_type);
49 }
50
SerializeReports(const ReportList & reports,base::TimeTicks now)51 std::string SerializeReports(const ReportList& reports, base::TimeTicks now) {
52 base::Value::List reports_value;
53
54 for (const ReportingReport* report : reports) {
55 base::Value::Dict report_value;
56
57 report_value.Set("age", base::saturated_cast<int>(
58 (now - report->queued).InMilliseconds()));
59 report_value.Set("type", report->type);
60 report_value.Set("url", report->url.spec());
61 report_value.Set("user_agent", report->user_agent);
62 report_value.Set("body", report->body.Clone());
63
64 reports_value.Append(std::move(report_value));
65 }
66
67 std::string json_out;
68 bool json_written = base::JSONWriter::Write(reports_value, &json_out);
69 DCHECK(json_written);
70 return json_out;
71 }
72
CompareReportGroupKeys(const ReportingReport * lhs,const ReportingReport * rhs)73 bool CompareReportGroupKeys(const ReportingReport* lhs,
74 const ReportingReport* rhs) {
75 return lhs->GetGroupKey() < rhs->GetGroupKey();
76 }
77
78 // Each Delivery corresponds to one upload URLRequest.
79 class Delivery {
80 public:
81 // The target of a delivery. All reports uploaded together must share the
82 // same values for these parameters.
83 // Note that |origin| here (which matches the report's |origin|) is not
84 // necessarily the same as the |origin| of the ReportingEndpoint's group key
85 // (if the endpoint is configured to include subdomains). Reports with
86 // different group keys can be in the same delivery, as long as the NAK,
87 // report origin and reporting source are the same, and they all get assigned
88 // to the same endpoint URL.
89 // |isolation_info| is the IsolationInfo struct associated with the reporting
90 // endpoint, and is used to determine appropriate credentials for the upload.
91 // |network_anonymization_key| is the NAK from the ReportingEndpoint, which
92 // may have been cleared in the ReportingService if reports are not being
93 // partitioned by NAK. (This is why a separate parameter is used here, rather
94 // than simply using the computed NAK from |isolation_info|.)
95 struct Target {
Targetnet::__anond21231a50111::Delivery::Target96 Target(const IsolationInfo& isolation_info,
97 const NetworkAnonymizationKey& network_anonymization_key,
98 const url::Origin& origin,
99 const GURL& endpoint_url,
100 const std::optional<base::UnguessableToken> reporting_source,
101 ReportingTargetType target_type)
102 : isolation_info(isolation_info),
103 network_anonymization_key(network_anonymization_key),
104 origin(origin),
105 endpoint_url(endpoint_url),
106 reporting_source(reporting_source),
107 target_type(target_type) {
108 DCHECK(network_anonymization_key.IsEmpty() ||
109 network_anonymization_key ==
110 isolation_info.network_anonymization_key());
111 }
112
113 ~Target() = default;
114
operator <net::__anond21231a50111::Delivery::Target115 bool operator<(const Target& other) const {
116 // Note that sorting by NAK here is required for V0 reports; V1 reports
117 // should not need this (but it doesn't hurt). We can remove that as a
118 // comparison key when V0 reporting endpoints are removed.
119 return std::tie(network_anonymization_key, origin, endpoint_url,
120 reporting_source, target_type) <
121 std::tie(other.network_anonymization_key, other.origin,
122 other.endpoint_url, other.reporting_source,
123 other.target_type);
124 }
125
126 IsolationInfo isolation_info;
127 NetworkAnonymizationKey network_anonymization_key;
128 url::Origin origin;
129 GURL endpoint_url;
130 std::optional<base::UnguessableToken> reporting_source;
131 ReportingTargetType target_type;
132 };
133
Delivery(const Target & target)134 explicit Delivery(const Target& target) : target_(target) {}
135
136 ~Delivery() = default;
137
138 // Add the developer reports in [reports_begin, reports_end) into this
139 // delivery. Modify the report counter for the |endpoint| to which this
140 // delivery is destined.
AddDeveloperReports(const ReportingEndpoint & endpoint,const ReportList::const_iterator reports_begin,const ReportList::const_iterator reports_end)141 void AddDeveloperReports(const ReportingEndpoint& endpoint,
142 const ReportList::const_iterator reports_begin,
143 const ReportList::const_iterator reports_end) {
144 DCHECK(reports_begin != reports_end);
145 DCHECK(endpoint.group_key.network_anonymization_key ==
146 network_anonymization_key());
147 DCHECK(endpoint.group_key.origin.has_value());
148 DCHECK(IsSubdomainOf(
149 target_.origin.host() /* subdomain */,
150 endpoint.group_key.origin.value().host() /* superdomain */));
151 DCHECK_EQ(ReportingTargetType::kDeveloper, target_.target_type);
152 DCHECK_EQ(endpoint.group_key.target_type, target_.target_type);
153 for (auto report_it = reports_begin; report_it != reports_end;
154 ++report_it) {
155 DCHECK_EQ((*reports_begin)->GetGroupKey(), (*report_it)->GetGroupKey());
156 DCHECK((*report_it)->network_anonymization_key ==
157 network_anonymization_key());
158 DCHECK_EQ(url::Origin::Create((*report_it)->url), target_.origin);
159 DCHECK_EQ((*report_it)->group, endpoint.group_key.group_name);
160 // Report origin is equal to, or a subdomain of, the endpoint
161 // configuration's origin.
162 DCHECK(IsSubdomainOf(
163 (*report_it)->url.host_piece() /* subdomain */,
164 endpoint.group_key.origin.value().host() /* superdomain */));
165 DCHECK_EQ((*report_it)->target_type, target_.target_type);
166 }
167
168 reports_per_group_[endpoint.group_key] +=
169 std::distance(reports_begin, reports_end);
170 reports_.insert(reports_.end(), reports_begin, reports_end);
171 }
172
173 // Add the enterprise reports in [reports_begin, reports_end) into this
174 // delivery. Modify the report counter for the |endpoint| to which this
175 // delivery is destined.
AddEnterpriseReports(const ReportingEndpoint & endpoint,const ReportList::const_iterator reports_begin,const ReportList::const_iterator reports_end)176 void AddEnterpriseReports(const ReportingEndpoint& endpoint,
177 const ReportList::const_iterator reports_begin,
178 const ReportList::const_iterator reports_end) {
179 DCHECK(reports_begin != reports_end);
180 DCHECK_EQ(ReportingTargetType::kEnterprise, target_.target_type);
181 DCHECK_EQ(endpoint.group_key.target_type, target_.target_type);
182 for (auto report_it = reports_begin; report_it != reports_end;
183 ++report_it) {
184 DCHECK_EQ((*reports_begin)->GetGroupKey(), (*report_it)->GetGroupKey());
185 DCHECK_EQ((*report_it)->group, endpoint.group_key.group_name);
186 DCHECK_EQ((*report_it)->target_type, target_.target_type);
187 }
188
189 reports_per_group_[endpoint.group_key] +=
190 std::distance(reports_begin, reports_end);
191 reports_.insert(reports_.end(), reports_begin, reports_end);
192 }
193
194 // Records statistics for reports after an upload has completed.
195 // Either removes successfully delivered reports, or increments the failure
196 // counter if delivery was unsuccessful.
ProcessOutcome(ReportingCache * cache,bool success)197 void ProcessOutcome(ReportingCache* cache, bool success) {
198 for (const auto& group_name_and_count : reports_per_group_) {
199 cache->IncrementEndpointDeliveries(group_name_and_count.first,
200 target_.endpoint_url,
201 group_name_and_count.second, success);
202 }
203 if (success) {
204 ReportingUploadHeaderType upload_type =
205 target_.reporting_source.has_value()
206 ? ReportingUploadHeaderType::kReportingEndpoints
207 : ReportingUploadHeaderType::kReportTo;
208 for (size_t i = 0; i < reports_.size(); ++i) {
209 RecordReportingUploadHeaderType(upload_type);
210 }
211 cache->RemoveReports(reports_, /* delivery_success */ true);
212 } else {
213 cache->IncrementReportsAttempts(reports_);
214 }
215 }
216
network_anonymization_key() const217 const NetworkAnonymizationKey& network_anonymization_key() const {
218 return target_.network_anonymization_key;
219 }
endpoint_url() const220 const GURL& endpoint_url() const { return target_.endpoint_url; }
reports() const221 const ReportList& reports() const { return reports_; }
222
223 private:
224 const Target target_;
225 ReportList reports_;
226
227 // Used to track statistics for each ReportingEndpoint.
228 // The endpoint is uniquely identified by the key in conjunction with
229 // |target_.endpoint_url|. See ProcessOutcome().
230 std::map<ReportingEndpointGroupKey, int> reports_per_group_;
231 };
232
233 class ReportingDeliveryAgentImpl : public ReportingDeliveryAgent,
234 public ReportingCacheObserver {
235 public:
ReportingDeliveryAgentImpl(ReportingContext * context,const RandIntCallback & rand_callback)236 ReportingDeliveryAgentImpl(ReportingContext* context,
237 const RandIntCallback& rand_callback)
238 : context_(context),
239 timer_(std::make_unique<base::OneShotTimer>()),
240 endpoint_manager_(
241 ReportingEndpointManager::Create(&context->policy(),
242 &context->tick_clock(),
243 context->delegate(),
244 context->cache(),
245 rand_callback)) {
246 context_->AddCacheObserver(this);
247 }
248
249 ReportingDeliveryAgentImpl(const ReportingDeliveryAgentImpl&) = delete;
250 ReportingDeliveryAgentImpl& operator=(const ReportingDeliveryAgentImpl&) =
251 delete;
252
253 // ReportingDeliveryAgent implementation:
254
~ReportingDeliveryAgentImpl()255 ~ReportingDeliveryAgentImpl() override {
256 context_->RemoveCacheObserver(this);
257 }
258
SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer)259 void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer) override {
260 DCHECK(!timer_->IsRunning());
261 timer_ = std::move(timer);
262 }
263
SendReportsForSource(base::UnguessableToken reporting_source)264 void SendReportsForSource(base::UnguessableToken reporting_source) override {
265 DCHECK(!reporting_source.is_empty());
266 ReportList reports =
267 cache()->GetReportsToDeliverForSource(reporting_source);
268 if (reports.empty())
269 return;
270 DoSendReports(std::move(reports));
271 }
272
273 // ReportingCacheObserver implementation:
OnReportsUpdated()274 void OnReportsUpdated() override {
275 if (CacheHasReports() && !timer_->IsRunning()) {
276 SendReports();
277 StartTimer();
278 }
279 }
280
281 private:
CacheHasReports()282 bool CacheHasReports() {
283 ReportList reports;
284 context_->cache()->GetReports(&reports);
285 return !reports.empty();
286 }
287
StartTimer()288 void StartTimer() {
289 timer_->Start(FROM_HERE, policy().delivery_interval,
290 base::BindOnce(&ReportingDeliveryAgentImpl::OnTimerFired,
291 base::Unretained(this)));
292 }
293
OnTimerFired()294 void OnTimerFired() {
295 if (CacheHasReports()) {
296 SendReports();
297 StartTimer();
298 }
299 }
300
SendReports()301 void SendReports() {
302 ReportList reports = cache()->GetReportsToDeliver();
303 if (reports.empty())
304 return;
305 DoSendReports(std::move(reports));
306 }
307
SendReportsForTesting()308 void SendReportsForTesting() override { SendReports(); }
309
DoSendReports(ReportList reports)310 void DoSendReports(ReportList reports) {
311 // First determine which origins we're allowed to upload reports about.
312 std::set<url::Origin> report_origins;
313 for (const ReportingReport* report : reports) {
314 report_origins.insert(url::Origin::Create(report->url));
315 }
316 delegate()->CanSendReports(
317 std::move(report_origins),
318 base::BindOnce(&ReportingDeliveryAgentImpl::OnSendPermissionsChecked,
319 weak_factory_.GetWeakPtr(), std::move(reports)));
320 }
321
OnSendPermissionsChecked(ReportList reports,std::set<url::Origin> allowed_report_origins)322 void OnSendPermissionsChecked(ReportList reports,
323 std::set<url::Origin> allowed_report_origins) {
324 DCHECK(!reports.empty());
325 std::map<Delivery::Target, std::unique_ptr<Delivery>> deliveries;
326
327 // Sort by group key
328 std::sort(reports.begin(), reports.end(), &CompareReportGroupKeys);
329
330 // Iterate over "buckets" of reports with the same group key.
331 for (auto bucket_it = reports.begin(); bucket_it != reports.end();) {
332 auto bucket_start = bucket_it;
333 // Set the iterator to the beginning of the next group bucket.
334 bucket_it = std::upper_bound(bucket_it, reports.end(), *bucket_it,
335 &CompareReportGroupKeys);
336
337 // Skip this group if we don't have origin permissions for this origin.
338 const ReportingEndpointGroupKey& report_group_key =
339 (*bucket_start)->GetGroupKey();
340 // If the origin is nullopt, this should be an enterprise target.
341 if (!report_group_key.origin.has_value()) {
342 DCHECK_EQ(ReportingTargetType::kEnterprise,
343 report_group_key.target_type);
344 } else if (!base::Contains(allowed_report_origins,
345 report_group_key.origin.value())) {
346 continue;
347 }
348
349 // Skip this group if there is already a pending upload for it.
350 // We don't allow multiple concurrent uploads for the same group.
351 if (base::Contains(pending_groups_, report_group_key))
352 continue;
353
354 // Find an endpoint to deliver these reports to.
355 const ReportingEndpoint endpoint =
356 endpoint_manager_->FindEndpointForDelivery(report_group_key);
357 // TODO(chlily): Remove reports for which there are no valid delivery
358 // endpoints.
359 if (!endpoint)
360 continue;
361
362 pending_groups_.insert(report_group_key);
363
364 IsolationInfo isolation_info =
365 cache()->GetIsolationInfoForEndpoint(endpoint);
366
367 // Add the reports to the appropriate delivery.
368 Delivery::Target target(
369 isolation_info, report_group_key.network_anonymization_key,
370 (report_group_key.origin.has_value() ? report_group_key.origin.value()
371 : url::Origin()),
372 endpoint.info.url, endpoint.group_key.reporting_source,
373 endpoint.group_key.target_type);
374 auto delivery_it = deliveries.find(target);
375 if (delivery_it == deliveries.end()) {
376 bool inserted;
377 auto new_delivery = std::make_unique<Delivery>(target);
378 std::tie(delivery_it, inserted) =
379 deliveries.emplace(std::move(target), std::move(new_delivery));
380 DCHECK(inserted);
381 }
382 switch (target.target_type) {
383 case ReportingTargetType::kDeveloper:
384 delivery_it->second->AddDeveloperReports(endpoint, bucket_start,
385 bucket_it);
386 break;
387 case ReportingTargetType::kEnterprise:
388 delivery_it->second->AddEnterpriseReports(endpoint, bucket_start,
389 bucket_it);
390 break;
391 }
392 }
393
394 // Keep track of which of these reports we don't queue for delivery; we'll
395 // need to mark them as not-pending.
396 std::set<const ReportingReport*> undelivered_reports(reports.begin(),
397 reports.end());
398
399 // Start an upload for each delivery.
400 for (auto& target_and_delivery : deliveries) {
401 const Delivery::Target& target = target_and_delivery.first;
402 std::unique_ptr<Delivery>& delivery = target_and_delivery.second;
403
404 int max_depth = 0;
405 for (const ReportingReport* report : delivery->reports()) {
406 undelivered_reports.erase(report);
407 max_depth = std::max(report->depth, max_depth);
408 }
409
410 std::string upload_data =
411 SerializeReports(delivery->reports(), tick_clock().NowTicks());
412
413 // TODO: Calculate actual max depth.
414 uploader()->StartUpload(
415 target.origin, target.endpoint_url, target.isolation_info,
416 upload_data, max_depth,
417 /*eligible_for_credentials=*/target.reporting_source.has_value(),
418 base::BindOnce(&ReportingDeliveryAgentImpl::OnUploadComplete,
419 weak_factory_.GetWeakPtr(), std::move(delivery)));
420 }
421
422 cache()->ClearReportsPending(
423 {undelivered_reports.begin(), undelivered_reports.end()});
424 }
425
OnUploadComplete(std::unique_ptr<Delivery> delivery,ReportingUploader::Outcome outcome)426 void OnUploadComplete(std::unique_ptr<Delivery> delivery,
427 ReportingUploader::Outcome outcome) {
428 bool success = outcome == ReportingUploader::Outcome::SUCCESS;
429 delivery->ProcessOutcome(cache(), success);
430
431 endpoint_manager_->InformOfEndpointRequest(
432 delivery->network_anonymization_key(), delivery->endpoint_url(),
433 success);
434
435 // TODO(chlily): This leaks information across NAKs. If the endpoint URL is
436 // configured for both NAK1 and NAK2, and it responds with a 410 on a NAK1
437 // connection, then the change in configuration will be detectable on a NAK2
438 // connection.
439 // TODO(rodneyding): Handle Remove endpoint for Reporting-Endpoints header.
440 if (outcome == ReportingUploader::Outcome::REMOVE_ENDPOINT)
441 cache()->RemoveEndpointsForUrl(delivery->endpoint_url());
442
443 for (const ReportingReport* report : delivery->reports()) {
444 pending_groups_.erase(report->GetGroupKey());
445 }
446
447 cache()->ClearReportsPending(delivery->reports());
448 }
449
policy() const450 const ReportingPolicy& policy() const { return context_->policy(); }
tick_clock() const451 const base::TickClock& tick_clock() const { return context_->tick_clock(); }
delegate()452 ReportingDelegate* delegate() { return context_->delegate(); }
cache()453 ReportingCache* cache() { return context_->cache(); }
uploader()454 ReportingUploader* uploader() { return context_->uploader(); }
455
456 raw_ptr<ReportingContext> context_;
457
458 std::unique_ptr<base::OneShotTimer> timer_;
459
460 // Tracks endpoint groups for which there is a pending delivery running.
461 std::set<ReportingEndpointGroupKey> pending_groups_;
462
463 std::unique_ptr<ReportingEndpointManager> endpoint_manager_;
464
465 base::WeakPtrFactory<ReportingDeliveryAgentImpl> weak_factory_{this};
466 };
467
468 } // namespace
469
470 // static
Create(ReportingContext * context,const RandIntCallback & rand_callback)471 std::unique_ptr<ReportingDeliveryAgent> ReportingDeliveryAgent::Create(
472 ReportingContext* context,
473 const RandIntCallback& rand_callback) {
474 return std::make_unique<ReportingDeliveryAgentImpl>(context, rand_callback);
475 }
476
477 ReportingDeliveryAgent::~ReportingDeliveryAgent() = default;
478
479 } // namespace net
480