1 // Copyright 2017 The Chromium Authors. All rights reserved.
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_service.h"
6 
7 #include <memory>
8 #include <string>
9 
10 #include "base/bind.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/test/scoped_feature_list.h"
13 #include "base/time/tick_clock.h"
14 #include "base/values.h"
15 #include "net/base/features.h"
16 #include "net/base/network_isolation_key.h"
17 #include "net/reporting/mock_persistent_reporting_store.h"
18 #include "net/reporting/reporting_browsing_data_remover.h"
19 #include "net/reporting/reporting_cache.h"
20 #include "net/reporting/reporting_context.h"
21 #include "net/reporting/reporting_endpoint.h"
22 #include "net/reporting/reporting_policy.h"
23 #include "net/reporting/reporting_report.h"
24 #include "net/reporting/reporting_service.h"
25 #include "net/reporting/reporting_test_util.h"
26 #include "net/test/test_with_task_environment.h"
27 #include "testing/gmock/include/gmock/gmock.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 
30 namespace net {
31 namespace {
32 
33 using CommandType = MockPersistentReportingStore::Command::Type;
34 
35 // The tests are parametrized on a boolean value which represents whether to use
36 // a MockPersistentReportingStore (if false, no store is used).
37 class ReportingServiceTest : public ::testing::TestWithParam<bool>,
38                              public WithTaskEnvironment {
39  protected:
40   const GURL kUrl_ = GURL("https://origin/path");
41   const GURL kUrl2_ = GURL("https://origin2/path");
42   const url::Origin kOrigin_ = url::Origin::Create(kUrl_);
43   const url::Origin kOrigin2_ = url::Origin::Create(kUrl2_);
44   const GURL kEndpoint_ = GURL("https://endpoint/");
45   const std::string kUserAgent_ = "Mozilla/1.0";
46   const std::string kGroup_ = "group";
47   const std::string kType_ = "type";
48   const NetworkIsolationKey kNik_ = NetworkIsolationKey(kOrigin_, kOrigin_);
49   const NetworkIsolationKey kNik2_ = NetworkIsolationKey(kOrigin2_, kOrigin2_);
50   const ReportingEndpointGroupKey kGroupKey_ =
51       ReportingEndpointGroupKey(kNik_, kOrigin_, kGroup_);
52   const ReportingEndpointGroupKey kGroupKey2_ =
53       ReportingEndpointGroupKey(kNik2_, kOrigin2_, kGroup_);
54 
ReportingServiceTest()55   ReportingServiceTest() {
56     feature_list_.InitAndEnableFeature(
57         features::kPartitionNelAndReportingByNetworkIsolationKey);
58     Init();
59   }
60 
61   // Initializes, or re-initializes, |service_| and its dependencies.
Init()62   void Init() {
63     if (GetParam())
64       store_ = std::make_unique<MockPersistentReportingStore>();
65     else
66       store_ = nullptr;
67 
68     auto test_context = std::make_unique<TestReportingContext>(
69         &clock_, &tick_clock_, ReportingPolicy(), store_.get());
70     context_ = test_context.get();
71 
72     service_ = ReportingService::CreateForTesting(std::move(test_context));
73   }
74 
75   // If the store exists, simulate finishing loading the store, which should
76   // make the rest of the test run synchronously.
FinishLoading(bool load_success)77   void FinishLoading(bool load_success) {
78     if (store_)
79       store_->FinishLoading(load_success);
80   }
81 
store()82   MockPersistentReportingStore* store() { return store_.get(); }
context()83   TestReportingContext* context() { return context_; }
service()84   ReportingService* service() { return service_.get(); }
85 
86  private:
87   base::test::ScopedFeatureList feature_list_;
88 
89   base::SimpleTestClock clock_;
90   base::SimpleTestTickClock tick_clock_;
91 
92   std::unique_ptr<MockPersistentReportingStore> store_;
93   TestReportingContext* context_;
94   std::unique_ptr<ReportingService> service_;
95 };
96 
TEST_P(ReportingServiceTest,QueueReport)97 TEST_P(ReportingServiceTest, QueueReport) {
98   service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
99                          std::make_unique<base::DictionaryValue>(), 0);
100   FinishLoading(true /* load_success */);
101 
102   std::vector<const ReportingReport*> reports;
103   context()->cache()->GetReports(&reports);
104   ASSERT_EQ(1u, reports.size());
105   EXPECT_EQ(kUrl_, reports[0]->url);
106   EXPECT_EQ(kNik_, reports[0]->network_isolation_key);
107   EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
108   EXPECT_EQ(kGroup_, reports[0]->group);
109   EXPECT_EQ(kType_, reports[0]->type);
110 }
111 
TEST_P(ReportingServiceTest,QueueReportSanitizeUrl)112 TEST_P(ReportingServiceTest, QueueReportSanitizeUrl) {
113   // Same as kUrl_ but with username, password, and fragment.
114   GURL url = GURL("https://username:password@origin/path#fragment");
115   service()->QueueReport(url, kNik_, kUserAgent_, kGroup_, kType_,
116                          std::make_unique<base::DictionaryValue>(), 0);
117   FinishLoading(true /* load_success */);
118 
119   std::vector<const ReportingReport*> reports;
120   context()->cache()->GetReports(&reports);
121   ASSERT_EQ(1u, reports.size());
122   EXPECT_EQ(kUrl_, reports[0]->url);
123   EXPECT_EQ(kNik_, reports[0]->network_isolation_key);
124   EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
125   EXPECT_EQ(kGroup_, reports[0]->group);
126   EXPECT_EQ(kType_, reports[0]->type);
127 }
128 
TEST_P(ReportingServiceTest,DontQueueReportInvalidUrl)129 TEST_P(ReportingServiceTest, DontQueueReportInvalidUrl) {
130   GURL url = GURL("https://");
131   // This does not trigger an attempt to load from the store because the url
132   // is immediately rejected as invalid.
133   service()->QueueReport(url, kNik_, kUserAgent_, kGroup_, kType_,
134                          std::make_unique<base::DictionaryValue>(), 0);
135 
136   std::vector<const ReportingReport*> reports;
137   context()->cache()->GetReports(&reports);
138   ASSERT_EQ(0u, reports.size());
139 }
140 
TEST_P(ReportingServiceTest,QueueReportNetworkIsolationKeyDisabled)141 TEST_P(ReportingServiceTest, QueueReportNetworkIsolationKeyDisabled) {
142   base::test::ScopedFeatureList feature_list;
143   feature_list.InitAndDisableFeature(
144       features::kPartitionNelAndReportingByNetworkIsolationKey);
145 
146   // Re-create the store, so it reads the new feature value.
147   Init();
148 
149   service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
150                          std::make_unique<base::DictionaryValue>(), 0);
151   FinishLoading(true /* load_success */);
152 
153   std::vector<const ReportingReport*> reports;
154   context()->cache()->GetReports(&reports);
155   ASSERT_EQ(1u, reports.size());
156 
157   // NetworkIsolationKey should be empty, instead of kNik_;
158   EXPECT_EQ(NetworkIsolationKey(), reports[0]->network_isolation_key);
159   EXPECT_NE(kNik_, reports[0]->network_isolation_key);
160 
161   EXPECT_EQ(kUrl_, reports[0]->url);
162   EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
163   EXPECT_EQ(kGroup_, reports[0]->group);
164   EXPECT_EQ(kType_, reports[0]->type);
165 }
166 
TEST_P(ReportingServiceTest,ProcessHeader)167 TEST_P(ReportingServiceTest, ProcessHeader) {
168   service()->ProcessHeader(kUrl_, kNik_,
169                            "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
170                                "\"}],"
171                                "\"group\":\"" +
172                                kGroup_ +
173                                "\","
174                                "\"max_age\":86400}");
175   FinishLoading(true /* load_success */);
176 
177   EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
178   EXPECT_TRUE(context()->cache()->GetEndpointForTesting(
179       ReportingEndpointGroupKey(kNik_, kOrigin_, kGroup_), kEndpoint_));
180 }
181 
TEST_P(ReportingServiceTest,ProcessHeaderPathAbsolute)182 TEST_P(ReportingServiceTest, ProcessHeaderPathAbsolute) {
183   service()->ProcessHeader(kUrl_, kNik_,
184                            "{\"endpoints\":[{\"url\":\"/path-absolute\"}],"
185                            "\"group\":\"" +
186                                kGroup_ +
187                                "\","
188                                "\"max_age\":86400}");
189   FinishLoading(true /* load_success */);
190 
191   EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
192 }
193 
TEST_P(ReportingServiceTest,ProcessHeader_TooLong)194 TEST_P(ReportingServiceTest, ProcessHeader_TooLong) {
195   const std::string header_too_long =
196       "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
197       "\"}],"
198       "\"group\":\"" +
199       kGroup_ +
200       "\","
201       "\"max_age\":86400," +
202       "\"junk\":\"" + std::string(32 * 1024, 'a') + "\"}";
203   // This does not trigger an attempt to load from the store because the header
204   // is immediately rejected as invalid.
205   service()->ProcessHeader(kUrl_, kNik_, header_too_long);
206 
207   EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
208 }
209 
TEST_P(ReportingServiceTest,ProcessHeader_TooDeep)210 TEST_P(ReportingServiceTest, ProcessHeader_TooDeep) {
211   const std::string header_too_deep = "{\"endpoints\":[{\"url\":\"" +
212                                       kEndpoint_.spec() +
213                                       "\"}],"
214                                       "\"group\":\"" +
215                                       kGroup_ +
216                                       "\","
217                                       "\"max_age\":86400," +
218                                       "\"junk\":[[[[[[[[[[]]]]]]]]]]}";
219   // This does not trigger an attempt to load from the store because the header
220   // is immediately rejected as invalid.
221   service()->ProcessHeader(kUrl_, kNik_, header_too_deep);
222 
223   EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
224 }
225 
TEST_P(ReportingServiceTest,ProcessHeaderNetworkIsolationKeyDisabled)226 TEST_P(ReportingServiceTest, ProcessHeaderNetworkIsolationKeyDisabled) {
227   base::test::ScopedFeatureList feature_list;
228   feature_list.InitAndDisableFeature(
229       features::kPartitionNelAndReportingByNetworkIsolationKey);
230 
231   // Re-create the store, so it reads the new feature value.
232   Init();
233 
234   service()->ProcessHeader(kUrl_, kNik_,
235                            "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
236                                "\"}],"
237                                "\"group\":\"" +
238                                kGroup_ +
239                                "\","
240                                "\"max_age\":86400}");
241   FinishLoading(true /* load_success */);
242 
243   EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
244   EXPECT_FALSE(context()->cache()->GetEndpointForTesting(
245       ReportingEndpointGroupKey(kNik_, kOrigin_, kGroup_), kEndpoint_));
246   EXPECT_TRUE(context()->cache()->GetEndpointForTesting(
247       ReportingEndpointGroupKey(NetworkIsolationKey(), kOrigin_, kGroup_),
248       kEndpoint_));
249 }
250 
TEST_P(ReportingServiceTest,WriteToStore)251 TEST_P(ReportingServiceTest, WriteToStore) {
252   if (!store())
253     return;
254 
255   MockPersistentReportingStore::CommandList expected_commands;
256 
257   // This first call to any public method triggers a load. The load will block
258   // until we call FinishLoading.
259   service()->ProcessHeader(kUrl_, kNik_,
260                            "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
261                                "\"}],"
262                                "\"group\":\"" +
263                                kGroup_ +
264                                "\","
265                                "\"max_age\":86400}");
266   expected_commands.emplace_back(CommandType::LOAD_REPORTING_CLIENTS);
267   EXPECT_THAT(store()->GetAllCommands(),
268               testing::UnorderedElementsAreArray(expected_commands));
269 
270   // Unblock the load. The will let the remaining calls to the service complete
271   // without blocking.
272   FinishLoading(true /* load_success */);
273   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
274                                  kGroupKey_, kEndpoint_);
275   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
276                                  kGroupKey_);
277   EXPECT_THAT(store()->GetAllCommands(),
278               testing::UnorderedElementsAreArray(expected_commands));
279 
280   service()->ProcessHeader(kUrl2_, kNik2_,
281                            "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
282                                "\"}],"
283                                "\"group\":\"" +
284                                kGroup_ +
285                                "\","
286                                "\"max_age\":86400}");
287   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
288                                  kGroupKey2_, kEndpoint_);
289   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
290                                  kGroupKey2_);
291   EXPECT_THAT(store()->GetAllCommands(),
292               testing::UnorderedElementsAreArray(expected_commands));
293 
294   service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
295                          std::make_unique<base::DictionaryValue>(), 0);
296   expected_commands.emplace_back(
297       CommandType::UPDATE_REPORTING_ENDPOINT_GROUP_ACCESS_TIME, kGroupKey_);
298   EXPECT_THAT(store()->GetAllCommands(),
299               testing::UnorderedElementsAreArray(expected_commands));
300 
301   service()->RemoveBrowsingData(ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS,
302                                 base::BindRepeating([](const GURL& url) {
303                                   return url.host() == "origin";
304                                 }));
305   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
306                                  kGroupKey_, kEndpoint_);
307   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
308                                  kGroupKey_);
309   expected_commands.emplace_back(CommandType::FLUSH);
310   EXPECT_THAT(store()->GetAllCommands(),
311               testing::UnorderedElementsAreArray(expected_commands));
312 
313   service()->RemoveAllBrowsingData(
314       ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS);
315   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
316                                  kGroupKey2_, kEndpoint_);
317   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
318                                  kGroupKey2_);
319   expected_commands.emplace_back(CommandType::FLUSH);
320   EXPECT_THAT(store()->GetAllCommands(),
321               testing::UnorderedElementsAreArray(expected_commands));
322 }
323 
TEST_P(ReportingServiceTest,WaitUntilLoadFinishesBeforeWritingToStore)324 TEST_P(ReportingServiceTest, WaitUntilLoadFinishesBeforeWritingToStore) {
325   if (!store())
326     return;
327 
328   MockPersistentReportingStore::CommandList expected_commands;
329 
330   // This first call to any public method triggers a load. The load will block
331   // until we call FinishLoading.
332   service()->ProcessHeader(kUrl_, kNik_,
333                            "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
334                                "\"}],"
335                                "\"group\":\"" +
336                                kGroup_ +
337                                "\","
338                                "\"max_age\":86400}");
339   expected_commands.emplace_back(CommandType::LOAD_REPORTING_CLIENTS);
340   EXPECT_THAT(store()->GetAllCommands(),
341               testing::UnorderedElementsAreArray(expected_commands));
342 
343   service()->ProcessHeader(kUrl2_, kNik2_,
344                            "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
345                                "\"}],"
346                                "\"group\":\"" +
347                                kGroup_ +
348                                "\","
349                                "\"max_age\":86400}");
350   EXPECT_THAT(store()->GetAllCommands(),
351               testing::UnorderedElementsAreArray(expected_commands));
352 
353   service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
354                          std::make_unique<base::DictionaryValue>(), 0);
355   EXPECT_THAT(store()->GetAllCommands(),
356               testing::UnorderedElementsAreArray(expected_commands));
357 
358   service()->RemoveBrowsingData(ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS,
359                                 base::BindRepeating([](const GURL& url) {
360                                   return url.host() == "origin";
361                                 }));
362   EXPECT_THAT(store()->GetAllCommands(),
363               testing::UnorderedElementsAreArray(expected_commands));
364 
365   service()->RemoveAllBrowsingData(
366       ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS);
367   EXPECT_THAT(store()->GetAllCommands(),
368               testing::UnorderedElementsAreArray(expected_commands));
369 
370   // Unblock the load. The will let the remaining calls to the service complete
371   // without blocking.
372   FinishLoading(true /* load_success */);
373   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
374                                  kGroupKey_, kEndpoint_);
375   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
376                                  kGroupKey2_, kEndpoint_);
377   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
378                                  kGroupKey_);
379   expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
380                                  kGroupKey2_);
381   expected_commands.emplace_back(
382       CommandType::UPDATE_REPORTING_ENDPOINT_GROUP_ACCESS_TIME, kGroupKey_);
383   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
384                                  kGroupKey_, kEndpoint_);
385   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
386                                  kGroupKey_);
387   expected_commands.emplace_back(CommandType::FLUSH);
388   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
389                                  kGroupKey2_, kEndpoint_);
390   expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
391                                  kGroupKey2_);
392   expected_commands.emplace_back(CommandType::FLUSH);
393   EXPECT_THAT(store()->GetAllCommands(),
394               testing::UnorderedElementsAreArray(expected_commands));
395 }
396 
397 INSTANTIATE_TEST_SUITE_P(ReportingServiceStoreTest,
398                          ReportingServiceTest,
399                          ::testing::Bool());
400 }  // namespace
401 }  // namespace net
402