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