#include "net/device_bound_sessions/session_inclusion_rules.h"
#include <initializer_list>
#include "base/strings/string_util.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/device_bound_sessions/proto/storage.pb.h"
#include "net/device_bound_sessions/session_error.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace net::device_bound_sessions {
namespace {
using Result = SessionInclusionRules::InclusionResult;
using RuleType = SessionParams::Scope::Specification::Type;
void AssertDomainAndRegistry(const url::Origin& origin,
const std::string& expected_domain_and_registry) {
ASSERT_EQ(
registry_controlled_domains::GetDomainAndRegistry(
origin, registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES),
expected_domain_and_registry)
<< "Unexpected domain and registry.";
}
}
class SessionInclusionRulesTest : public ::testing::Test {
public:
struct EvaluateUrlTestCase {
const char* url;
Result expected_result;
};
struct AddUrlRuleTestCase {
RuleType rule_type;
const char* host_pattern;
const char* path_prefix;
SessionError::ErrorType expected_is_added_result;
};
SessionInclusionRulesTest() = default;
void SetOrigin(const url::Origin& origin) { origin_ = origin; }
void SetIncludeSite(bool include_site) {
params_.include_site = include_site;
}
void CheckMayIncludeSite(bool expected_may_include_site) {
auto inclusion_rules_or_error =
SessionInclusionRules::Create(origin_, params_, GURL());
ASSERT_TRUE(inclusion_rules_or_error.has_value());
SessionInclusionRules& rules = *inclusion_rules_or_error;
EXPECT_EQ(rules.may_include_site_for_testing(), expected_may_include_site);
}
void CheckEvaluateUrlTestCases(
std::initializer_list<EvaluateUrlTestCase> test_cases) {
auto inclusion_rules_or_error =
SessionInclusionRules::Create(origin_, params_, GURL());
ASSERT_TRUE(inclusion_rules_or_error.has_value());
SessionInclusionRules& rules = *inclusion_rules_or_error;
for (const auto& test_case : test_cases) {
SCOPED_TRACE(test_case.url);
EXPECT_EQ(rules.EvaluateRequestUrl(GURL(test_case.url)),
test_case.expected_result);
}
}
void CheckAddUrlRuleTestCases(
std::initializer_list<AddUrlRuleTestCase> test_cases) {
auto inclusion_rules_or_error =
SessionInclusionRules::Create(origin_, params_, GURL());
ASSERT_TRUE(inclusion_rules_or_error.has_value());
for (const auto& test_case : test_cases) {
SCOPED_TRACE(base::JoinString(
{test_case.host_pattern, test_case.path_prefix}, ", "));
params_.specifications.emplace_back(
test_case.rule_type, test_case.host_pattern, test_case.path_prefix);
inclusion_rules_or_error =
SessionInclusionRules::Create(origin_, params_, GURL());
EXPECT_EQ(inclusion_rules_or_error.has_value(),
test_case.expected_is_added_result == SessionError::kSuccess);
if (test_case.expected_is_added_result != SessionError::kSuccess &&
!inclusion_rules_or_error.has_value()) {
EXPECT_EQ(inclusion_rules_or_error.error().type,
test_case.expected_is_added_result);
}
if (!inclusion_rules_or_error.has_value()) {
params_.specifications.pop_back();
}
}
}
const SessionParams::Scope& params() { return params_; }
private:
SessionParams::Scope params_;
url::Origin origin_;
};
TEST_F(SessionInclusionRulesTest, DefaultIncludeOriginMayNotIncludeSite) {
url::Origin subdomain_origin =
url::Origin::Create(GURL("https://some.site.test"));
AssertDomainAndRegistry(subdomain_origin, "site.test");
SetOrigin(subdomain_origin);
CheckMayIncludeSite(false);
CheckEvaluateUrlTestCases(
{
{"", Result::kExclude},
{"https://some.site.test", Result::kInclude},
{"https://some.site.test/path", Result::kInclude},
{"http://some.site.test", Result::kExclude},
{"https://some.other.site.test", Result::kExclude},
{"https://site.test", Result::kExclude},
{"https://unrelated.test", Result::kExclude},
{"https://some.site.test:8888", Result::kExclude}});
}
TEST_F(SessionInclusionRulesTest, DefaultIncludeOriginThoughMayIncludeSite) {
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
AssertDomainAndRegistry(root_site_origin, "site.test");
SetOrigin(root_site_origin);
CheckMayIncludeSite(true);
CheckEvaluateUrlTestCases({
{"", Result::kExclude},
{"https://site.test", Result::kInclude},
{"https://site.test/path", Result::kInclude},
{"http://site.test", Result::kExclude},
{"https://other.site.test", Result::kExclude},
{"https://test", Result::kExclude},
{"https://unrelated.test", Result::kExclude},
{"https://site.test:8888", Result::kExclude}});
}
TEST_F(SessionInclusionRulesTest, IncludeSiteAttemptedButNotAllowed) {
url::Origin subdomain_origin =
url::Origin::Create(GURL("https://some.site.test"));
AssertDomainAndRegistry(subdomain_origin, "site.test");
SessionParams::Scope params;
params.include_site = true;
auto rules_or_error = SessionInclusionRules::Create(
subdomain_origin, params, GURL("https://some.site.test/refresh"));
ASSERT_FALSE(rules_or_error.has_value());
EXPECT_EQ(rules_or_error.error().type,
SessionError::kInvalidScopeIncludeSite);
}
TEST_F(SessionInclusionRulesTest, IncludeSite) {
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
AssertDomainAndRegistry(root_site_origin, "site.test");
SetOrigin(root_site_origin);
CheckMayIncludeSite(true);
SetIncludeSite(true);
CheckEvaluateUrlTestCases(
{
{"", Result::kExclude},
{"https://site.test", Result::kInclude},
{"https://site.test/path", Result::kInclude},
{"http://site.test", Result::kExclude},
{"https://some.site.test", Result::kInclude},
{"https://some.other.site.test", Result::kInclude},
{"https://test", Result::kExclude},
{"https://unrelated.test", Result::kExclude},
{"https://site.test:8888", Result::kInclude}});
}
TEST_F(SessionInclusionRulesTest, AddUrlRuleToOriginOnly) {
url::Origin subdomain_origin =
url::Origin::Create(GURL("https://some.site.test"));
AssertDomainAndRegistry(subdomain_origin, "site.test");
SetOrigin(subdomain_origin);
CheckMayIncludeSite(false);
CheckAddUrlRuleTestCases(
{
{RuleType::kExclude, "some.site.test", "/static",
SessionError::kSuccess},
{RuleType::kInclude, "some.site.test", "/static/included",
SessionError::kSuccess},
{RuleType::kExclude, "some.site.test", "NotAPath",
SessionError::kInvalidScopeRulePath},
{RuleType::kInclude, "*.site.test", "/static/wildcard_match/",
SessionError::kSuccess},
{RuleType::kInclude, "unrelated.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kInclude, "site.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kInclude, "other.site.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kInclude, "https://some.site.test", "/static/https_rule/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kInclude, "some.site.test:8000", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch}});
EXPECT_EQ(params().specifications.size(), 3u);
CheckEvaluateUrlTestCases(
{
{"https://some.site.test/static", Result::kExclude},
{"https://some.site.test/static/some/thing", Result::kExclude},
{"https://some.site.test/staticcccccccc", Result::kInclude},
{"https://other.site.test/static", Result::kExclude},
{"https://some.site.test/static/included", Result::kInclude},
{"https://some.site.test/valid_path", Result::kInclude},
{"https://some.site.test/static/wildcard_match/", Result::kInclude},
{"https://subdomain.site.test/static/wildcard_match/", Result::kExclude},
{"https://unrelated.test/", Result::kExclude},
{"https://site.test/", Result::kExclude},
{"https://other.test/", Result::kExclude},
{"https://some.site.test/static/https_rule/", Result::kExclude},
{"https://some.site.test:8000/", Result::kExclude}});
CheckAddUrlRuleTestCases(
{{RuleType::kExclude, "some.site.test", "/", SessionError::kSuccess}});
CheckEvaluateUrlTestCases(
{{"https://some.site.test/static/included", Result::kExclude}});
}
TEST_F(SessionInclusionRulesTest, AddUrlRuleToOriginThatMayIncludeSite) {
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
AssertDomainAndRegistry(root_site_origin, "site.test");
SetOrigin(root_site_origin);
CheckMayIncludeSite(true);
CheckEvaluateUrlTestCases({{"https://site.test/static", Result::kInclude},
{"https://other.site.test", Result::kExclude}});
CheckAddUrlRuleTestCases(
{{RuleType::kExclude, "excluded.site.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kInclude, "included.site.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "site.test", "/static", SessionError::kSuccess},
{RuleType::kInclude, "unrelated.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch}});
EXPECT_EQ(params().specifications.size(), 1u);
CheckEvaluateUrlTestCases(
{
{"https://site.test/static", Result::kExclude},
{"https://excluded.site.test", Result::kExclude},
{"https://included.site.test", Result::kExclude},
{"http://included.site.test", Result::kExclude},
{"https://other.site.test", Result::kExclude},
{"https://site.test/stuff", Result::kInclude},
{"https://unrelated.test/", Result::kExclude}});
}
TEST_F(SessionInclusionRulesTest, AddUrlRuleToRulesIncludingSite) {
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
AssertDomainAndRegistry(root_site_origin, "site.test");
SetOrigin(root_site_origin);
CheckMayIncludeSite(true);
SetIncludeSite(true);
CheckEvaluateUrlTestCases({{"https://site.test/static", Result::kInclude},
{"https://other.site.test", Result::kInclude}});
CheckAddUrlRuleTestCases(
{{RuleType::kExclude, "excluded.site.test", "/", SessionError::kSuccess},
{RuleType::kInclude, "included.site.test", "/", SessionError::kSuccess},
{RuleType::kExclude, "site.test", "/static", SessionError::kSuccess},
{RuleType::kInclude, "unrelated.test", "/",
SessionError::kScopeRuleSiteScopedHostPatternMismatch}});
EXPECT_EQ(params().specifications.size(), 3u);
CheckEvaluateUrlTestCases(
{
{"https://site.test/static", Result::kExclude},
{"https://excluded.site.test", Result::kExclude},
{"https://included.site.test", Result::kInclude},
{"http://included.site.test", Result::kExclude},
{"https://other.site.test", Result::kInclude},
{"https://site.test/stuff", Result::kInclude},
{"https://unrelated.test/", Result::kExclude}});
}
TEST_F(SessionInclusionRulesTest, AddUrlRuleToRulesIncludingOrigin) {
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
AssertDomainAndRegistry(root_site_origin, "site.test");
SetOrigin(root_site_origin);
CheckMayIncludeSite(true);
SetIncludeSite(false);
CheckAddUrlRuleTestCases(
{{RuleType::kExclude, "excluded.site.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kInclude, "included.site.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "site.test", "/static", SessionError::kSuccess},
{RuleType::kInclude, "unrelated.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch}});
EXPECT_EQ(params().specifications.size(), 1u);
CheckEvaluateUrlTestCases({
{"https://site.test/static", Result::kExclude},
{"https://excluded.site.test", Result::kExclude},
{"https://included.site.test", Result::kExclude},
{"https://other.site.test", Result::kExclude},
{"https://site.test/stuff", Result::kInclude},
{"https://unrelated.test/", Result::kExclude}});
}
TEST_F(SessionInclusionRulesTest, UrlRuleParsing) {
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
AssertDomainAndRegistry(root_site_origin, "site.test");
SetOrigin(root_site_origin);
CheckMayIncludeSite(true);
CheckAddUrlRuleTestCases(
{
{RuleType::kExclude, "", "/",
SessionError::kInvalidScopeRuleHostPattern},
{RuleType::kExclude, " ", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "https://site.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "site.test:8888", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "site.test,other.test", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[*.:abcd::3:4:ff]", "/",
SessionError::kInvalidScopeRuleHostPattern},
{RuleType::kExclude, "[1:ab+cd::3:4:ff]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[[1:abcd::3:4:ff]]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "sub.*.site.test", "/",
SessionError::kInvalidScopeRuleHostPattern},
{RuleType::kExclude, "*.sub.*.site.test", "/",
SessionError::kInvalidScopeRuleHostPattern},
{RuleType::kExclude, "*site.test", "/",
SessionError::kInvalidScopeRuleHostPattern},
{RuleType::kExclude, "*.test", "/", SessionError::kSuccess},
{RuleType::kExclude, "unrelated.site", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "4.31.198.44", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[1:abcd::3:4:ff]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "co.uk", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "com", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch}});
}
TEST_F(SessionInclusionRulesTest, UrlRuleParsingTopLevelDomain) {
url::Origin tld_origin = url::Origin::Create(GURL("https://com"));
AssertDomainAndRegistry(tld_origin, "");
SetOrigin(tld_origin);
CheckMayIncludeSite(false);
CheckAddUrlRuleTestCases(
{
{RuleType::kExclude, "com", "/", SessionError::kSuccess},
{RuleType::kExclude, "*.com", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "4.31.198.44", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[1:abcd::3:4:ff]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "co.uk", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch}});
}
TEST_F(SessionInclusionRulesTest, UrlRuleParsingIPv4Address) {
url::Origin ip_origin = url::Origin::Create(GURL("https://4.31.198.44"));
AssertDomainAndRegistry(ip_origin, "");
SetOrigin(ip_origin);
CheckMayIncludeSite(false);
CheckAddUrlRuleTestCases(
{
{RuleType::kExclude, "4.31.198.44", "/", SessionError::kSuccess},
{RuleType::kExclude, "*.31.198.44", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "*.4.31.198.44", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[1:abcd::3:4:ff]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "co.uk", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "com", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch}});
}
TEST_F(SessionInclusionRulesTest, UrlRuleParsingIPv6Address) {
url::Origin ipv6_origin =
url::Origin::Create(GURL("https://[1:abcd::3:4:ff]"));
AssertDomainAndRegistry(ipv6_origin, "");
SetOrigin(ipv6_origin);
CheckMayIncludeSite(false);
CheckAddUrlRuleTestCases(
{
{RuleType::kExclude, "[1:abcd::3:4:ff]", "/", SessionError::kSuccess},
{RuleType::kExclude, "*.[1:abcd::3:4:ff]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[1:abcd::3:4:ff", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "1:abcd::3:4:ff]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[*.:abcd::3:4:ff]", "/",
SessionError::kInvalidScopeRuleHostPattern},
{RuleType::kExclude, "[1:ab+cd::3:4:ff]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "[[1:abcd::3:4:ff]]", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "4.31.198.44", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "co.uk", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch},
{RuleType::kExclude, "com", "/",
SessionError::kScopeRuleOriginScopedHostPatternMismatch}});
}
TEST_F(SessionInclusionRulesTest, NonstandardPort) {
url::Origin nonstandard_port_origin =
url::Origin::Create(GURL("https://site.test:8888"));
AssertDomainAndRegistry(nonstandard_port_origin, "site.test");
SetOrigin(nonstandard_port_origin);
CheckMayIncludeSite(true);
CheckEvaluateUrlTestCases({{"https://site.test", Result::kExclude},
{"https://site.test:8888", Result::kInclude},
{"https://other.site.test", Result::kExclude}});
SetIncludeSite(true);
CheckEvaluateUrlTestCases({{"https://site.test", Result::kInclude},
{"https://site.test:8888", Result::kInclude},
{"https://site.test:1234", Result::kInclude},
{"https://other.site.test", Result::kInclude}});
CheckAddUrlRuleTestCases(
{
{RuleType::kInclude, "site.test:8888", "/",
SessionError::kScopeRuleSiteScopedHostPatternMismatch},
{RuleType::kExclude, "site.test", "/", SessionError::kSuccess},
{RuleType::kInclude, "site.test:443", "/",
SessionError::kScopeRuleSiteScopedHostPatternMismatch}});
EXPECT_EQ(params().specifications.size(), 1u);
CheckEvaluateUrlTestCases(
{
{"https://site.test:8888", Result::kExclude},
{"https://site.test:1234", Result::kExclude},
{"https://other.site.test", Result::kInclude},
{"https://site.test", Result::kExclude},
{"https://site.test:443", Result::kExclude}});
}
TEST_F(SessionInclusionRulesTest, ToFromProto) {
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
AssertDomainAndRegistry(root_site_origin, "site.test");
SessionParams::Scope params;
params.include_site = true;
params.specifications = {{RuleType::kExclude, "excluded.site.test", "/"},
{RuleType::kInclude, "included.site.test", "/"}};
auto inclusion_rules_or_error = SessionInclusionRules::Create(
root_site_origin, std::move(params), GURL());
ASSERT_TRUE(inclusion_rules_or_error.has_value());
SessionInclusionRules& rules = *inclusion_rules_or_error;
proto::SessionInclusionRules proto = rules.ToProto();
EXPECT_EQ(root_site_origin.Serialize(), proto.origin());
EXPECT_TRUE(proto.do_include_site());
ASSERT_EQ(proto.url_rules().size(), 2);
{
const auto& rule = proto.url_rules(0);
EXPECT_EQ(rule.rule_type(), proto::RuleType::EXCLUDE);
EXPECT_EQ(rule.host_pattern(), "excluded.site.test");
EXPECT_EQ(rule.path_prefix(), "/");
}
{
const auto& rule = proto.url_rules(1);
EXPECT_EQ(rule.rule_type(), proto::RuleType::INCLUDE);
EXPECT_EQ(rule.host_pattern(), "included.site.test");
EXPECT_EQ(rule.path_prefix(), "/");
}
std::optional<SessionInclusionRules> restored_inclusion_rules =
SessionInclusionRules::CreateFromProto(proto);
ASSERT_TRUE(restored_inclusion_rules.has_value());
EXPECT_EQ(*restored_inclusion_rules, rules);
}
TEST_F(SessionInclusionRulesTest, FailCreateFromInvalidProto) {
{
proto::SessionInclusionRules proto;
EXPECT_FALSE(SessionInclusionRules::CreateFromProto(proto));
}
{
proto::SessionInclusionRules proto;
proto.set_origin("about:blank");
proto.set_do_include_site(false);
EXPECT_FALSE(SessionInclusionRules::CreateFromProto(proto));
}
url::Origin root_site_origin = url::Origin::Create(GURL("https://site.test"));
SessionParams::Scope params;
params.include_site = true;
params.specifications = {{RuleType::kExclude, "excluded.site.test", "/"},
{RuleType::kInclude, "included.site.test", "/"}};
auto inclusion_rules_or_error = SessionInclusionRules::Create(
root_site_origin, std::move(params), GURL());
ASSERT_TRUE(inclusion_rules_or_error.has_value());
SessionInclusionRules& rules = *inclusion_rules_or_error;
proto::SessionInclusionRules proto = rules.ToProto();
ASSERT_TRUE(SessionInclusionRules::CreateFromProto(proto).has_value());
{
proto::SessionInclusionRules p(proto);
p.clear_origin();
EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
}
{
proto::SessionInclusionRules p(proto);
p.clear_do_include_site();
EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
}
{
proto::SessionInclusionRules p(proto);
p.mutable_url_rules(0)->clear_rule_type();
EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
}
{
proto::SessionInclusionRules p(proto);
p.mutable_url_rules(0)->clear_host_pattern();
EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
}
{
proto::SessionInclusionRules p(proto);
p.mutable_url_rules(0)->clear_path_prefix();
EXPECT_FALSE(SessionInclusionRules::CreateFromProto(p));
}
}
}