• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 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/device_bound_sessions/session_inclusion_rules.h"
6 
7 #include <initializer_list>
8 
9 #include "base/strings/string_util.h"
10 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
11 #include "net/device_bound_sessions/proto/storage.pb.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "url/gurl.h"
14 #include "url/origin.h"
15 
16 namespace net::device_bound_sessions {
17 
18 namespace {
19 
20 using Result = SessionInclusionRules::InclusionResult;
21 
22 // These tests depend on the registry_controlled_domains code, so assert ahead
23 // of time that the eTLD+1 is what we expect, for clarity and to avoid confusing
24 // test failures.
25 #define ASSERT_DOMAIN_AND_REGISTRY(origin, expected_domain_and_registry)      \
26   {                                                                           \
27     ASSERT_EQ(                                                                \
28         registry_controlled_domains::GetDomainAndRegistry(                    \
29             origin, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES), \
30         expected_domain_and_registry)                                         \
31         << "Unexpected domain and registry.";                                 \
32   }
33 
34 struct EvaluateUrlTestCase {
35   const char* url;
36   Result expected_result;
37 };
38 
CheckEvaluateUrlTestCases(const SessionInclusionRules & inclusion_rules,std::initializer_list<EvaluateUrlTestCase> test_cases)39 void CheckEvaluateUrlTestCases(
40     const SessionInclusionRules& inclusion_rules,
41     std::initializer_list<EvaluateUrlTestCase> test_cases) {
42   for (const auto& test_case : test_cases) {
43     SCOPED_TRACE(test_case.url);
44     EXPECT_EQ(inclusion_rules.EvaluateRequestUrl(GURL(test_case.url)),
45               test_case.expected_result);
46   }
47 }
48 
49 struct AddUrlRuleTestCase {
50   Result rule_type;
51   const char* host_pattern;
52   const char* path_prefix;
53   bool expected_is_added;
54 };
55 
CheckAddUrlRuleTestCases(SessionInclusionRules & inclusion_rules,std::initializer_list<AddUrlRuleTestCase> test_cases)56 void CheckAddUrlRuleTestCases(
57     SessionInclusionRules& inclusion_rules,
58     std::initializer_list<AddUrlRuleTestCase> test_cases) {
59   for (const auto& test_case : test_cases) {
60     SCOPED_TRACE(base::JoinString(
61         {test_case.host_pattern, test_case.path_prefix}, ", "));
62     bool is_added = inclusion_rules.AddUrlRuleIfValid(
63         test_case.rule_type, test_case.host_pattern, test_case.path_prefix);
64     EXPECT_EQ(is_added, test_case.expected_is_added);
65   }
66 }
67 
TEST(SessionInclusionRulesTest,DefaultConstructorMatchesNothing)68 TEST(SessionInclusionRulesTest, DefaultConstructorMatchesNothing) {
69   SessionInclusionRules inclusion_rules;
70   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
71 
72   EXPECT_EQ(Result::kExclude,
73             inclusion_rules.EvaluateRequestUrl(GURL("https://origin.test")));
74   EXPECT_EQ(Result::kExclude, inclusion_rules.EvaluateRequestUrl(GURL()));
75 }
76 
TEST(SessionInclusionRulesTest,DefaultIncludeOriginMayNotIncludeSite)77 TEST(SessionInclusionRulesTest, DefaultIncludeOriginMayNotIncludeSite) {
78   url::Origin subdomain_origin =
79       url::Origin::Create(GURL("https://some.site.test"));
80 
81   ASSERT_DOMAIN_AND_REGISTRY(subdomain_origin, "site.test");
82 
83   SessionInclusionRules inclusion_rules{subdomain_origin};
84   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
85 
86   CheckEvaluateUrlTestCases(
87       inclusion_rules, {// URL not valid.
88                         {"", Result::kExclude},
89                         // Origins match.
90                         {"https://some.site.test", Result::kInclude},
91                         // Path is allowed.
92                         {"https://some.site.test/path", Result::kInclude},
93                         // Not same scheme.
94                         {"http://some.site.test", Result::kExclude},
95                         // Not same host (same-site subdomain).
96                         {"https://some.other.site.test", Result::kExclude},
97                         // Not same host (superdomain).
98                         {"https://site.test", Result::kExclude},
99                         // Unrelated site.
100                         {"https://unrelated.test", Result::kExclude},
101                         // Not same port.
102                         {"https://some.site.test:8888", Result::kExclude}});
103 }
104 
TEST(SessionInclusionRulesTest,DefaultIncludeOriginThoughMayIncludeSite)105 TEST(SessionInclusionRulesTest, DefaultIncludeOriginThoughMayIncludeSite) {
106   url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
107 
108   ASSERT_DOMAIN_AND_REGISTRY(root_site_origin, "site.test");
109 
110   SessionInclusionRules inclusion_rules{root_site_origin};
111   EXPECT_TRUE(inclusion_rules.may_include_site_for_testing());
112 
113   // All expectations are as above. Even though including the site is allowed,
114   // because the origin's host is its root eTLD+1, it is still limited to a
115   // default origin inclusion_rules because it did not set include_site.
116   CheckEvaluateUrlTestCases(inclusion_rules,
117                             {// URL not valid.
118                              {"", Result::kExclude},
119                              // Origins match.
120                              {"https://site.test", Result::kInclude},
121                              // Path is allowed.
122                              {"https://site.test/path", Result::kInclude},
123                              // Not same scheme.
124                              {"http://site.test", Result::kExclude},
125                              // Not same host (same-site subdomain).
126                              {"https://other.site.test", Result::kExclude},
127                              // Not same host (superdomain).
128                              {"https://test", Result::kExclude},
129                              // Unrelated site.
130                              {"https://unrelated.test", Result::kExclude},
131                              // Not same port.
132                              {"https://site.test:8888", Result::kExclude}});
133 }
134 
TEST(SessionInclusionRulesTest,IncludeSiteAttemptedButNotAllowed)135 TEST(SessionInclusionRulesTest, IncludeSiteAttemptedButNotAllowed) {
136   url::Origin subdomain_origin =
137       url::Origin::Create(GURL("https://some.site.test"));
138 
139   ASSERT_DOMAIN_AND_REGISTRY(subdomain_origin, "site.test");
140 
141   SessionInclusionRules inclusion_rules{subdomain_origin};
142   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
143 
144   // Only the origin is included.
145   CheckEvaluateUrlTestCases(inclusion_rules,
146                             {{"https://some.site.test", Result::kInclude},
147                              {"https://other.site.test", Result::kExclude}});
148 
149   // This shouldn't do anything.
150   inclusion_rules.SetIncludeSite(true);
151   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
152 
153   // Still only the origin is included.
154   CheckEvaluateUrlTestCases(inclusion_rules,
155                             {{"https://some.site.test", Result::kInclude},
156                              {"https://other.site.test", Result::kExclude}});
157 }
158 
TEST(SessionInclusionRulesTest,IncludeSite)159 TEST(SessionInclusionRulesTest, IncludeSite) {
160   url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
161 
162   ASSERT_DOMAIN_AND_REGISTRY(root_site_origin, "site.test");
163 
164   SessionInclusionRules inclusion_rules{root_site_origin};
165   EXPECT_TRUE(inclusion_rules.may_include_site_for_testing());
166 
167   inclusion_rules.SetIncludeSite(true);
168 
169   CheckEvaluateUrlTestCases(
170       inclusion_rules, {// URL not valid.
171                         {"", Result::kExclude},
172                         // Origins match.
173                         {"https://site.test", Result::kInclude},
174                         // Path is allowed.
175                         {"https://site.test/path", Result::kInclude},
176                         // Not same scheme (site is schemeful).
177                         {"http://site.test", Result::kExclude},
178                         // Same-site subdomain is allowed.
179                         {"https://some.site.test", Result::kInclude},
180                         {"https://some.other.site.test", Result::kInclude},
181                         // Not same host (superdomain).
182                         {"https://test", Result::kExclude},
183                         // Unrelated site.
184                         {"https://unrelated.test", Result::kExclude},
185                         // Other port is allowed because whole site is included.
186                         {"https://site.test:8888", Result::kInclude}});
187 }
188 
TEST(SessionInclusionRulesTest,AddUrlRuleToOriginOnly)189 TEST(SessionInclusionRulesTest, AddUrlRuleToOriginOnly) {
190   url::Origin subdomain_origin =
191       url::Origin::Create(GURL("https://some.site.test"));
192 
193   ASSERT_DOMAIN_AND_REGISTRY(subdomain_origin, "site.test");
194 
195   SessionInclusionRules inclusion_rules{subdomain_origin};
196   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
197 
198   // Only the origin is allowed, since the setting origin is not the root
199   // eTLD+1. The only acceptable rules are limited to the origin/same host.
200   CheckAddUrlRuleTestCases(
201       inclusion_rules,
202       {// Host pattern equals origin's host. Path is valid.
203        {Result::kExclude, "some.site.test", "/static", true},
204        // Add an opposite rule to check later.
205        {Result::kInclude, "some.site.test", "/static/included", true},
206        // Path not valid.
207        {Result::kExclude, "some.site.test", "NotAPath", false},
208        // Other host patterns not accepted.
209        {Result::kExclude, "*.site.test", "/", false},
210        {Result::kExclude, "unrelated.test", "/", false},
211        {Result::kExclude, "site.test", "/", false},
212        {Result::kExclude, "other.site.test", "/", false},
213        {Result::kExclude, "https://some.site.test", "/", false},
214        {Result::kExclude, "some.site.test:443", "/", false}});
215 
216   EXPECT_EQ(inclusion_rules.num_url_rules_for_testing(), 2u);
217 
218   CheckEvaluateUrlTestCases(
219       inclusion_rules,
220       {// Matches the rule.
221        {"https://some.site.test/static", Result::kExclude},
222        // A path under the rule's path prefix is subject to the rule.
223        {"https://some.site.test/static/some/thing", Result::kExclude},
224        // These do not match the rule, so are subject to the basic rules (the
225        // origin).
226        {"https://some.site.test/staticcccccccc", Result::kInclude},
227        {"https://other.site.test/static", Result::kExclude},
228        // The more recently added rule wins out.
229        {"https://some.site.test/static/included", Result::kInclude}});
230 
231   // Note that what matters is when the rule was added, not how specific the URL
232   // path prefix is. Let's add another rule now to show that.
233   EXPECT_TRUE(inclusion_rules.AddUrlRuleIfValid(Result::kExclude,
234                                                 "some.site.test", "/"));
235   EXPECT_EQ(Result::kExclude, inclusion_rules.EvaluateRequestUrl(GURL(
236                                   "https://some.site.test/static/included")));
237 }
238 
TEST(SessionInclusionRulesTest,AddUrlRuleToOriginThatMayIncludeSite)239 TEST(SessionInclusionRulesTest, AddUrlRuleToOriginThatMayIncludeSite) {
240   url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
241 
242   ASSERT_DOMAIN_AND_REGISTRY(root_site_origin, "site.test");
243 
244   SessionInclusionRules inclusion_rules{root_site_origin};
245   EXPECT_TRUE(inclusion_rules.may_include_site_for_testing());
246 
247   // Without any rules yet, the basic rules is just the origin, because
248   // include_site was not set.
249   CheckEvaluateUrlTestCases(inclusion_rules,
250                             {{"https://site.test/static", Result::kInclude},
251                              {"https://other.site.test", Result::kExclude}});
252 
253   // Since the origin's host is the root eTLD+1, it is allowed to set rules that
254   // affect URLs other than the setting origin (but still within the site).
255   CheckAddUrlRuleTestCases(inclusion_rules,
256                            {{Result::kExclude, "excluded.site.test", "/", true},
257                             {Result::kInclude, "included.site.test", "/", true},
258                             {Result::kExclude, "site.test", "/static", true},
259                             // Rules outside of the site are not allowed.
260                             {Result::kExclude, "unrelated.test", "/", false}});
261 
262   EXPECT_EQ(inclusion_rules.num_url_rules_for_testing(), 3u);
263 
264   CheckEvaluateUrlTestCases(inclusion_rules,
265                             {// Path is excluded by rule.
266                              {"https://site.test/static", Result::kExclude},
267                              // Rule excludes URL explicitly.
268                              {"https://excluded.site.test", Result::kExclude},
269                              // Rule includes URL explicitly.
270                              {"https://included.site.test", Result::kInclude},
271                              // Rule does not apply to wrong scheme.
272                              {"http://included.site.test", Result::kExclude},
273                              // No rules applies to these URLs, so the basic
274                              // rules (origin) applies.
275                              {"https://other.site.test", Result::kExclude},
276                              {"https://site.test/stuff", Result::kInclude}});
277 }
278 
TEST(SessionInclusionRulesTest,AddUrlRuleToRulesIncludingSite)279 TEST(SessionInclusionRulesTest, AddUrlRuleToRulesIncludingSite) {
280   url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
281 
282   ASSERT_DOMAIN_AND_REGISTRY(root_site_origin, "site.test");
283 
284   SessionInclusionRules inclusion_rules{root_site_origin};
285   EXPECT_TRUE(inclusion_rules.may_include_site_for_testing());
286 
287   inclusion_rules.SetIncludeSite(true);
288 
289   // Without any rules yet, the basic rules is the site.
290   CheckEvaluateUrlTestCases(inclusion_rules,
291                             {{"https://site.test/static", Result::kInclude},
292                              {"https://other.site.test", Result::kInclude}});
293 
294   // Since the origin's host is the root eTLD+1, it is allowed to set rules that
295   // affect URLs other than the setting origin (but still within the site).
296   CheckAddUrlRuleTestCases(inclusion_rules,
297                            {{Result::kExclude, "excluded.site.test", "/", true},
298                             {Result::kInclude, "included.site.test", "/", true},
299                             {Result::kExclude, "site.test", "/static", true},
300                             // Rules outside of the site are not allowed.
301                             {Result::kExclude, "unrelated.test", "/", false}});
302 
303   EXPECT_EQ(inclusion_rules.num_url_rules_for_testing(), 3u);
304 
305   CheckEvaluateUrlTestCases(
306       inclusion_rules,
307       {// Path is excluded by rule.
308        {"https://site.test/static", Result::kExclude},
309        // Rule excludes URL explicitly.
310        {"https://excluded.site.test", Result::kExclude},
311        // Rule includes URL explicitly.
312        {"https://included.site.test", Result::kInclude},
313        // Rule does not apply to wrong scheme.
314        {"http://included.site.test", Result::kExclude},
315        // No rule applies to these URLs, so the basic rules (site) applies.
316        {"https://other.site.test", Result::kInclude},
317        {"https://site.test/stuff", Result::kInclude}});
318 
319   // Note that the rules are independent of "include_site", so even if that is
320   // "revoked" the rules still work the same way.
321   inclusion_rules.SetIncludeSite(false);
322   CheckEvaluateUrlTestCases(inclusion_rules,
323                             {// Path is excluded by rule.
324                              {"https://site.test/static", Result::kExclude},
325                              // Rule excludes URL explicitly.
326                              {"https://excluded.site.test", Result::kExclude},
327                              // Rule includes URL explicitly.
328                              {"https://included.site.test", Result::kInclude},
329                              // No rules applies to these URLs, so the basic
330                              // rules (which is now the origin) applies.
331                              {"https://other.site.test", Result::kExclude},
332                              {"https://site.test/stuff", Result::kInclude}});
333 }
334 
TEST(SessionInclusionRulesTest,UrlRuleParsing)335 TEST(SessionInclusionRulesTest, UrlRuleParsing) {
336   url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
337 
338   ASSERT_DOMAIN_AND_REGISTRY(root_site_origin, "site.test");
339 
340   // Use the most permissive type of inclusion_rules, to hit the interesting
341   // edge cases.
342   SessionInclusionRules inclusion_rules{root_site_origin};
343   EXPECT_TRUE(inclusion_rules.may_include_site_for_testing());
344 
345   CheckAddUrlRuleTestCases(
346       inclusion_rules,
347       {// Empty host pattern not permitted.
348        {Result::kExclude, "", "/", false},
349        // Host pattern that is only whitespace is not permitted.
350        {Result::kExclude, " ", "/", false},
351        // Forbidden characters in host_pattern.
352        {Result::kExclude, "https://site.test", "/", false},
353        {Result::kExclude, "site.test:8888", "/", false},
354        {Result::kExclude, "site.test,other.test", "/", false},
355        // Non-IPv6-allowable characters within the brackets.
356        {Result::kExclude, "[*.:abcd::3:4:ff]", "/", false},
357        {Result::kExclude, "[1:ab+cd::3:4:ff]", "/", false},
358        {Result::kExclude, "[[1:abcd::3:4:ff]]", "/", false},
359        // Internal wildcard characters are forbidden in the host pattern.
360        {Result::kExclude, "sub.*.site.test", "/", false},
361        // Multiple wildcard characters are forbidden in the host pattern.
362        {Result::kExclude, "*.sub.*.site.test", "/", false},
363        // Wildcard must be followed by a dot.
364        {Result::kExclude, "*site.test", "/", false},
365        // Wildcard must be followed by a non-eTLD.
366        {Result::kExclude, "*.com", "/", false},
367        // Other sites are not allowed.
368        {Result::kExclude, "unrelated.site", "/", false},
369        // Other hosts with no registrable domain are not allowed.
370        {Result::kExclude, "4.31.198.44", "/", false},
371        {Result::kExclude, "[1:abcd::3:4:ff]", "/", false},
372        {Result::kExclude, "co.uk", "/", false},
373        {Result::kExclude, "com", "/", false}});
374 }
375 
TEST(SessionInclusionRulesTest,UrlRuleParsingTopLevelDomain)376 TEST(SessionInclusionRulesTest, UrlRuleParsingTopLevelDomain) {
377   url::Origin tld_origin = url::Origin::Create(GURL("https://com"));
378 
379   ASSERT_DOMAIN_AND_REGISTRY(tld_origin, "");
380 
381   SessionInclusionRules inclusion_rules{tld_origin};
382   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
383 
384   CheckAddUrlRuleTestCases(
385       inclusion_rules,
386       {// Exact host is allowed.
387        {Result::kExclude, "com", "/", true},
388        // Wildcards are not permitted.
389        {Result::kExclude, "*.com", "/", false},
390        // Other hosts with no registrable domain are not allowed.
391        {Result::kExclude, "4.31.198.44", "/", false},
392        {Result::kExclude, "[1:abcd::3:4:ff]", "/", false},
393        {Result::kExclude, "co.uk", "/", false}});
394 }
395 
TEST(SessionInclusionRulesTest,UrlRuleParsingIPv4Address)396 TEST(SessionInclusionRulesTest, UrlRuleParsingIPv4Address) {
397   url::Origin ip_origin = url::Origin::Create(GURL("https://4.31.198.44"));
398 
399   ASSERT_DOMAIN_AND_REGISTRY(ip_origin, "");
400 
401   SessionInclusionRules inclusion_rules{ip_origin};
402   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
403 
404   CheckAddUrlRuleTestCases(
405       inclusion_rules,
406       {// Exact host is allowed.
407        {Result::kExclude, "4.31.198.44", "/", true},
408        // Wildcards are not permitted.
409        {Result::kExclude, "*.31.198.44", "/", false},
410        {Result::kExclude, "*.4.31.198.44", "/", false},
411        // Other hosts with no registrable domain are not allowed.
412        {Result::kExclude, "[1:abcd::3:4:ff]", "/", false},
413        {Result::kExclude, "co.uk", "/", false},
414        {Result::kExclude, "com", "/", false}});
415 }
416 
TEST(SessionInclusionRulesTest,UrlRuleParsingIPv6Address)417 TEST(SessionInclusionRulesTest, UrlRuleParsingIPv6Address) {
418   url::Origin ipv6_origin =
419       url::Origin::Create(GURL("https://[1:abcd::3:4:ff]"));
420 
421   ASSERT_DOMAIN_AND_REGISTRY(ipv6_origin, "");
422 
423   SessionInclusionRules inclusion_rules{ipv6_origin};
424   EXPECT_FALSE(inclusion_rules.may_include_site_for_testing());
425 
426   CheckAddUrlRuleTestCases(
427       inclusion_rules,
428       {// Exact host is allowed.
429        {Result::kExclude, "[1:abcd::3:4:ff]", "/", true},
430        // Wildcards are not permitted.
431        {Result::kExclude, "*.[1:abcd::3:4:ff]", "/", false},
432        // Brackets mismatched.
433        {Result::kExclude, "[1:abcd::3:4:ff", "/", false},
434        {Result::kExclude, "1:abcd::3:4:ff]", "/", false},
435        // Non-IPv6-allowable characters within the brackets.
436        {Result::kExclude, "[*.:abcd::3:4:ff]", "/", false},
437        {Result::kExclude, "[1:ab+cd::3:4:ff]", "/", false},
438        {Result::kExclude, "[[1:abcd::3:4:ff]]", "/", false},
439        // Other hosts with no registrable domain are not allowed.
440        {Result::kExclude, "4.31.198.44", "/", false},
441        {Result::kExclude, "co.uk", "/", false},
442        {Result::kExclude, "com", "/", false}});
443 }
444 
445 // This test is more to document the current behavior than anything else. We may
446 // discover a need for more comprehensive support for port numbers in the
447 // future, in which case:
448 // TODO(chlily): Support port numbers in URL rules.
TEST(SessionInclusionRulesTest,NonstandardPort)449 TEST(SessionInclusionRulesTest, NonstandardPort) {
450   url::Origin nonstandard_port_origin =
451       url::Origin::Create(GURL("https://site.test:8888"));
452 
453   ASSERT_DOMAIN_AND_REGISTRY(nonstandard_port_origin, "site.test");
454 
455   SessionInclusionRules inclusion_rules{nonstandard_port_origin};
456   EXPECT_TRUE(inclusion_rules.may_include_site_for_testing());
457 
458   // Without any URL rules, the default origin rule allows only the same origin.
459   CheckEvaluateUrlTestCases(inclusion_rules,
460                             {{"https://site.test", Result::kExclude},
461                              {"https://site.test:8888", Result::kInclude},
462                              {"https://other.site.test", Result::kExclude}});
463 
464   // If we include_site, then same-site URLs regardless of port number are
465   // included.
466   inclusion_rules.SetIncludeSite(true);
467   CheckEvaluateUrlTestCases(inclusion_rules,
468                             {{"https://site.test", Result::kInclude},
469                              {"https://site.test:8888", Result::kInclude},
470                              {"https://site.test:1234", Result::kInclude},
471                              {"https://other.site.test", Result::kInclude}});
472 
473   // However, adding URL rules to an inclusion_rules based on such an origin may
474   // lead to unintuitive outcomes. It is not possible to specify a rule that
475   // applies to the same origin as the setting origin if the setting origin has
476   // a nonstandard port.
477   CheckAddUrlRuleTestCases(
478       inclusion_rules,
479       {// The pattern is rejected due to the colon, despite being the
480        // same origin.
481        {Result::kExclude, "site.test:8888", "/", false},
482        // A rule with the same host without port specified is accepted.
483        // This rule applies to any URL with the specified host.
484        {Result::kExclude, "site.test", "/", true},
485        // Any explicitly specified port is rejected (due to the colon),
486        // even if it's the standard one.
487        {Result::kExclude, "site.test:443", "/", false}});
488 
489   EXPECT_EQ(inclusion_rules.num_url_rules_for_testing(), 1u);
490 
491   CheckEvaluateUrlTestCases(
492       inclusion_rules,
493       {// This is same-origin but gets caught in the "site.test" rule because
494        // the rule didn't specify a port.
495        {"https://site.test:8888", Result::kExclude},
496        // This is same-site but gets caught in the "site.test" rule because
497        // the rule didn't specify a port.
498        {"https://site.test:1234", Result::kExclude},
499        // Same-site is included by basic rules.
500        {"https://other.site.test", Result::kInclude},
501        // Also excluded explicitly by rule.
502        {"https://site.test", Result::kExclude},
503        {"https://site.test:443", Result::kExclude}});
504 }
505 
TEST(SessionInclusionRulesTest,ToFromProto)506 TEST(SessionInclusionRulesTest, ToFromProto) {
507   // Create a valid SessionInclusionRules object with default inclusion rule and
508   // a couple of additional URL rules.
509   url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
510   ASSERT_DOMAIN_AND_REGISTRY(root_site_origin, "site.test");
511 
512   SessionInclusionRules inclusion_rules{root_site_origin};
513   EXPECT_TRUE(inclusion_rules.may_include_site_for_testing());
514   inclusion_rules.SetIncludeSite(true);
515   EXPECT_TRUE(inclusion_rules.AddUrlRuleIfValid(Result::kExclude,
516                                                 "excluded.site.test", "/"));
517   EXPECT_TRUE(inclusion_rules.AddUrlRuleIfValid(Result::kInclude,
518                                                 "included.site.test", "/"));
519 
520   // Create a corresponding proto object and validate.
521   proto::SessionInclusionRules proto = inclusion_rules.ToProto();
522   EXPECT_EQ(root_site_origin.Serialize(), proto.origin());
523   EXPECT_TRUE(proto.do_include_site());
524   ASSERT_EQ(proto.url_rules().size(), 2);
525   {
526     const auto& rule = proto.url_rules(0);
527     EXPECT_EQ(rule.rule_type(), proto::RuleType::EXCLUDE);
528     EXPECT_EQ(rule.host_matcher_rule(), "excluded.site.test");
529     EXPECT_EQ(rule.path_prefix(), "/");
530   }
531   {
532     const auto& rule = proto.url_rules(1);
533     EXPECT_EQ(rule.rule_type(), proto::RuleType::INCLUDE);
534     EXPECT_EQ(rule.host_matcher_rule(), "included.site.test");
535     EXPECT_EQ(rule.path_prefix(), "/");
536   }
537 
538   // Create a SessionInclusionRules object from the proto and verify
539   // that it is the same as the original.
540   std::unique_ptr<SessionInclusionRules> restored_inclusion_rules =
541       SessionInclusionRules::CreateFromProto(proto);
542   ASSERT_TRUE(restored_inclusion_rules != nullptr);
543   EXPECT_EQ(*restored_inclusion_rules, inclusion_rules);
544 }
545 
TEST(SessionInclusionRulesTest,FailCreateFromInvalidProto)546 TEST(SessionInclusionRulesTest, FailCreateFromInvalidProto) {
547   // Empty proto.
548   {
549     proto::SessionInclusionRules proto;
550     EXPECT_FALSE(SessionInclusionRules::CreateFromProto(proto));
551   }
552   // Opaque origin.
553   {
554     proto::SessionInclusionRules proto;
555     proto.set_origin("about:blank");
556     proto.set_do_include_site(false);
557     EXPECT_FALSE(SessionInclusionRules::CreateFromProto(proto));
558   }
559   // Create a fully populated proto.
560   url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
561   SessionInclusionRules inclusion_rules{root_site_origin};
562   inclusion_rules.SetIncludeSite(true);
563   inclusion_rules.AddUrlRuleIfValid(Result::kExclude, "excluded.site.test",
564                                     "/");
565   inclusion_rules.AddUrlRuleIfValid(Result::kInclude, "included.site.test",
566                                     "/");
567   proto::SessionInclusionRules proto = inclusion_rules.ToProto();
568 
569   // Test for missing proto fields by clearing the fields one at a time.
570   {
571     proto::SessionInclusionRules p(proto);
572     p.clear_origin();
573     EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
574   }
575   {
576     proto::SessionInclusionRules p(proto);
577     p.clear_do_include_site();
578     EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
579   }
580   // URL rules with missing parameters.
581   {
582     proto::SessionInclusionRules p(proto);
583     p.mutable_url_rules(0)->clear_rule_type();
584     EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
585   }
586   {
587     proto::SessionInclusionRules p(proto);
588     p.mutable_url_rules(0)->clear_host_matcher_rule();
589     EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
590   }
591   {
592     proto::SessionInclusionRules p(proto);
593     p.mutable_url_rules(0)->clear_path_prefix();
594     EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
595   }
596 }
597 
598 }  // namespace
599 
600 }  // namespace net::device_bound_sessions
601