| // 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/log/test_net_log.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<URLRequestContext> 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"; |
| constexpr char kUrlStringForWrongETLD[] = "https://example.co.uk/index.html"; |
| const GURL kTestUrl(kUrlString); |
| const GURL kTestUrlForWrongETLD(kUrlStringForWrongETLD); |
| |
| SessionParams CreateValidParams() { |
| SessionParams::Scope scope; |
| scope.origin = "example.test"; |
| std::vector<SessionParams::Credential> 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, InvalidTestUrl) { |
| auto params = CreateValidParams(); |
| EXPECT_FALSE(Session::CreateIfValid(params, kTestUrlForWrongETLD)); |
| } |
| |
| TEST_F(SessionTest, ToFromProto) { |
| std::unique_ptr<Session> 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<Session> 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 = |
| 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<URLRequest> 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<URLRequest> 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<URLRequest> 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<URLRequest> 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<URLRequest> 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<URLRequest> 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; |
| } |
| |
| CookieScopeSemantics GetScopeSemantics( |
| const CanonicalCookie& cookie) const override { |
| return CookieScopeSemantics::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<FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo>> |
| ComputeFirstPartySetMetadataMaybeAsync( |
| const net::SchemefulSite& site, |
| const net::SchemefulSite* top_frame_site, |
| base::OnceCallback<void(FirstPartySetMetadata, |
| FirstPartySetsCacheFilter::MatchInfo)> callback) |
| const override { |
| return std::nullopt; |
| } |
| [[nodiscard]] std::optional< |
| base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>> |
| FindFirstPartySetEntries( |
| const base::flat_set<net::SchemefulSite>& sites, |
| base::OnceCallback< |
| void(base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>)> |
| 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<URLRequest> 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<InsecureDelegate>()); |
| auto params = CreateValidParams(); |
| auto session = Session::CreateIfValid(params, kTestUrl); |
| ASSERT_TRUE(session); |
| net::TestDelegate delegate; |
| std::unique_ptr<URLRequest> 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<SessionParams::Credential> 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<URLRequest> 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()); |
| } |
| |
| TEST_F(SessionTest, NetLogSessionInfo) { |
| auto params = CreateValidParams(); |
| auto session = Session::CreateIfValid(params, kTestUrl); |
| ASSERT_TRUE(session); |
| net::TestDelegate delegate; |
| std::unique_ptr<URLRequest> request = |
| context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); |
| request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl)); |
| |
| RecordingNetLogObserver net_log_observer; |
| session->ShouldDeferRequest(request.get()); |
| EXPECT_EQ( |
| net_log_observer.GetEntriesWithType(NetLogEventType::DBSC_REQUEST).size(), |
| 1u); |
| } |
| |
| TEST_F(SessionTest, NetLogMissingCookie) { |
| auto params = CreateValidParams(); |
| auto session = Session::CreateIfValid(params, kTestUrl); |
| ASSERT_TRUE(session); |
| net::TestDelegate delegate; |
| std::unique_ptr<URLRequest> request = |
| context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); |
| request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl)); |
| |
| RecordingNetLogObserver net_log_observer; |
| session->ShouldDeferRequest(request.get()); |
| EXPECT_EQ( |
| net_log_observer |
| .GetEntriesWithType(NetLogEventType::CHECK_DBSC_REFRESH_REQUIRED) |
| .size(), |
| 1u); |
| } |
| |
| TEST_F(SessionTest, NetLogNoRefresh) { |
| auto params = CreateValidParams(); |
| auto session = Session::CreateIfValid(params, kTestUrl); |
| ASSERT_TRUE(session); |
| net::TestDelegate delegate; |
| std::unique_ptr<URLRequest> request = |
| context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation); |
| request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl)); |
| |
| 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}}); |
| |
| RecordingNetLogObserver net_log_observer; |
| session->ShouldDeferRequest(request.get()); |
| EXPECT_EQ( |
| net_log_observer |
| .GetEntriesWithType(NetLogEventType::CHECK_DBSC_REFRESH_REQUIRED) |
| .size(), |
| 1u); |
| } |
| |
| } // namespace |
| |
| } // namespace net::device_bound_sessions |