// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "net/device_bound_sessions/session.h" #include "base/test/bind.h" #include "net/cookies/cookie_constants.h" #include "net/cookies/cookie_inclusion_status.h" #include "net/cookies/cookie_util.h" #include "net/device_bound_sessions/proto/storage.pb.h" #include "net/test/test_with_task_environment.h" #include "net/url_request/url_request_context_builder.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace net::device_bound_sessions { namespace { class SessionTest : public TestWithTaskEnvironment { protected: SessionTest() : context_(CreateTestURLRequestContextBuilder()->Build()) {} std::unique_ptr context_; }; class FakeDelegate : public URLRequest::Delegate { void OnReadCompleted(URLRequest* request, int bytes_read) override {} }; constexpr net::NetworkTrafficAnnotationTag kDummyAnnotation = net::DefineNetworkTrafficAnnotation("dbsc_registration", ""); constexpr char kSessionId[] = "SessionId"; constexpr char kUrlString[] = "https://example.test/index.html"; const GURL kTestUrl(kUrlString); SessionParams CreateValidParams() { SessionParams::Scope scope; std::vector cookie_credentials( {SessionParams::Credential{"test_cookie", "Secure; Domain=example.test"}}); SessionParams params{kSessionId, kUrlString, std::move(scope), std::move(cookie_credentials)}; return params; } TEST_F(SessionTest, ValidService) { auto session = Session::CreateIfValid(CreateValidParams(), kTestUrl); EXPECT_TRUE(session); } TEST_F(SessionTest, DefaultExpiry) { auto session = Session::CreateIfValid(CreateValidParams(), kTestUrl); ASSERT_TRUE(session); EXPECT_LT(base::Time::Now() + base::Days(399), session->expiry_date()); } TEST_F(SessionTest, InvalidServiceRefreshUrl) { auto params = CreateValidParams(); params.refresh_url = ""; EXPECT_FALSE(Session::CreateIfValid(params, kTestUrl)); } TEST_F(SessionTest, ToFromProto) { std::unique_ptr session = Session::CreateIfValid(CreateValidParams(), kTestUrl); ASSERT_TRUE(session); // Convert to proto and validate contents. proto::Session sproto = session->ToProto(); EXPECT_EQ(Session::Id(sproto.id()), session->id()); EXPECT_EQ(sproto.refresh_url(), session->refresh_url().spec()); EXPECT_EQ(sproto.should_defer_when_expired(), session->should_defer_when_expired()); // Restore session from proto and validate contents. std::unique_ptr restored = Session::CreateFromProto(sproto); ASSERT_TRUE(restored); EXPECT_TRUE(restored->IsEqualForTesting(*session)); } TEST_F(SessionTest, FailCreateFromInvalidProto) { // Empty proto. { proto::Session sproto; EXPECT_FALSE(Session::CreateFromProto(sproto)); } // Create a fully populated proto. std::unique_ptr session = Session::CreateIfValid(CreateValidParams(), kTestUrl); ASSERT_TRUE(session); proto::Session sproto = session->ToProto(); // Missing fields. { proto::Session s(sproto); s.clear_id(); EXPECT_FALSE(Session::CreateFromProto(s)); } { proto::Session s(sproto); s.clear_refresh_url(); EXPECT_FALSE(Session::CreateFromProto(s)); } { proto::Session s(sproto); s.clear_should_defer_when_expired(); EXPECT_FALSE(Session::CreateFromProto(s)); } { proto::Session s(sproto); s.clear_expiry_time(); EXPECT_FALSE(Session::CreateFromProto(s)); } { proto::Session s(sproto); s.clear_session_inclusion_rules(); EXPECT_FALSE(Session::CreateFromProto(s)); } // Empty id. { proto::Session s(sproto); s.set_id(""); EXPECT_FALSE(Session::CreateFromProto(s)); } // Invalid refresh URL. { proto::Session s(sproto); s.set_refresh_url("blank"); EXPECT_FALSE(Session::CreateFromProto(s)); } // Expired { proto::Session s(sproto); base::Time expiry_date = base::Time::Now() - base::Days(1); s.set_expiry_time(expiry_date.ToDeltaSinceWindowsEpoch().InMicroseconds()); EXPECT_FALSE(Session::CreateFromProto(s)); } } TEST_F(SessionTest, DeferredSession) { auto params = CreateValidParams(); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl)); bool is_deferred = session->ShouldDeferRequest(request.get()); EXPECT_TRUE(is_deferred); } TEST_F(SessionTest, NotDeferredAsExcluded) { auto params = CreateValidParams(); SessionParams::Scope::Specification spec; spec.type = SessionParams::Scope::Specification::Type::kExclude; spec.domain = "example.test"; spec.path = "/index.html"; params.scope.specifications.push_back(spec); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl)); bool is_deferred = session->ShouldDeferRequest(request.get()); EXPECT_FALSE(is_deferred); } TEST_F(SessionTest, NotDeferredSubdomain) { const char subdomain[] = "https://test.example.test/index.html"; const GURL url_subdomain(subdomain); auto params = CreateValidParams(); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(url_subdomain, IDLE, &delegate, kDummyAnnotation); request->set_site_for_cookies(SiteForCookies::FromUrl(url_subdomain)); bool is_deferred = session->ShouldDeferRequest(request.get()); EXPECT_FALSE(is_deferred); } TEST_F(SessionTest, DeferredIncludedSubdomain) { // Unless include site is specified, only same origin will be // matched even if the spec adds an include for a different // origin. const char subdomain[] = "https://test.example.test/index.html"; const GURL url_subdomain(subdomain); auto params = CreateValidParams(); SessionParams::Scope::Specification spec; spec.type = SessionParams::Scope::Specification::Type::kInclude; spec.domain = "test.example.test"; spec.path = "/index.html"; params.scope.specifications.push_back(spec); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(url_subdomain, IDLE, &delegate, kDummyAnnotation); request->set_site_for_cookies(SiteForCookies::FromUrl(url_subdomain)); ASSERT_TRUE(session->ShouldDeferRequest(request.get())); } TEST_F(SessionTest, NotDeferredWithCookieSession) { auto params = CreateValidParams(); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl)); bool is_deferred = session->ShouldDeferRequest(request.get()); EXPECT_TRUE(is_deferred); CookieInclusionStatus status; auto source = CookieSourceType::kHTTP; auto cookie = CanonicalCookie::Create( kTestUrl, "test_cookie=v;Secure; Domain=example.test", base::Time::Now(), std::nullopt, std::nullopt, source, &status); ASSERT_TRUE(cookie); CookieAccessResult access_result; request->set_maybe_sent_cookies({{*cookie.get(), access_result}}); EXPECT_FALSE(session->ShouldDeferRequest(request.get())); } TEST_F(SessionTest, NotDeferredInsecure) { const char insecure_url[] = "http://example.test/index.html"; const GURL test_insecure_url(insecure_url); auto params = CreateValidParams(); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest( test_insecure_url, IDLE, &delegate, kDummyAnnotation); request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl)); bool is_deferred = session->ShouldDeferRequest(request.get()); EXPECT_FALSE(is_deferred); } class InsecureDelegate : public CookieAccessDelegate { public: bool ShouldTreatUrlAsTrustworthy(const GURL& url) const override { return true; } CookieAccessSemantics GetAccessSemantics( const CanonicalCookie& cookie) const override { return CookieAccessSemantics::UNKNOWN; } CookieLegacyScope GetAccessForLegacyCookieScope( const CanonicalCookie& cookie) const override { return CookieLegacyScope::UNKNOWN; } // Returns whether a cookie should be attached regardless of its SameSite // value vs the request context. bool ShouldIgnoreSameSiteRestrictions( const GURL& url, const SiteForCookies& site_for_cookies) const override { return true; } [[nodiscard]] std::optional< std::pair> ComputeFirstPartySetMetadataMaybeAsync( const net::SchemefulSite& site, const net::SchemefulSite* top_frame_site, base::OnceCallback callback) const override { return std::nullopt; } [[nodiscard]] std::optional< base::flat_map> FindFirstPartySetEntries( const base::flat_set& sites, base::OnceCallback< void(base::flat_map)> callback) const override { return std::nullopt; } }; TEST_F(SessionTest, NotDeferredNotSameSite) { auto params = CreateValidParams(); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); bool is_deferred = session->ShouldDeferRequest(request.get()); EXPECT_FALSE(is_deferred); } TEST_F(SessionTest, DeferredNotSameSiteDelegate) { context_->cookie_store()->SetCookieAccessDelegate( std::make_unique()); auto params = CreateValidParams(); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); bool is_deferred = session->ShouldDeferRequest(request.get()); EXPECT_TRUE(is_deferred); } TEST_F(SessionTest, NotDeferredIncludedSubdomainHostCraving) { // Unless include site is specified, only same origin will be // matched even if the spec adds an include for a different // origin. const char subdomain[] = "https://test.example.test/index.html"; const GURL url_subdomain(subdomain); auto params = CreateValidParams(); SessionParams::Scope::Specification spec; spec.type = SessionParams::Scope::Specification::Type::kInclude; spec.domain = "test.example.test"; spec.path = "/index.html"; params.scope.specifications.push_back(spec); std::vector cookie_credentials( {SessionParams::Credential{"test_cookie", "Secure;"}}); params.credentials = std::move(cookie_credentials); auto session = Session::CreateIfValid(params, kTestUrl); ASSERT_TRUE(session); net::TestDelegate delegate; std::unique_ptr request = context_->CreateRequest(url_subdomain, IDLE, &delegate, kDummyAnnotation); request->set_site_for_cookies(SiteForCookies::FromUrl(url_subdomain)); ASSERT_FALSE(session->ShouldDeferRequest(request.get())); } TEST_F(SessionTest, CreationDate) { auto session = Session::CreateIfValid(CreateValidParams(), kTestUrl); ASSERT_TRUE(session); // Make sure it's set to a plausible value. EXPECT_LT(base::Time::Now() - base::Days(1), session->creation_date()); } } // namespace } // namespace net::device_bound_sessions