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.h"
6
7 #include "base/test/bind.h"
8 #include "net/cookies/cookie_constants.h"
9 #include "net/cookies/cookie_inclusion_status.h"
10 #include "net/cookies/cookie_util.h"
11 #include "net/device_bound_sessions/proto/storage.pb.h"
12 #include "net/test/test_with_task_environment.h"
13 #include "net/url_request/url_request_context_builder.h"
14 #include "net/url_request/url_request_test_util.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace net::device_bound_sessions {
18
19 namespace {
20
21 class SessionTest : public TestWithTaskEnvironment {
22 protected:
SessionTest()23 SessionTest() : context_(CreateTestURLRequestContextBuilder()->Build()) {}
24
25 std::unique_ptr<URLRequestContext> context_;
26 };
27
28 class FakeDelegate : public URLRequest::Delegate {
OnReadCompleted(URLRequest * request,int bytes_read)29 void OnReadCompleted(URLRequest* request, int bytes_read) override {}
30 };
31
32 constexpr net::NetworkTrafficAnnotationTag kDummyAnnotation =
33 net::DefineNetworkTrafficAnnotation("dbsc_registration", "");
34 constexpr char kSessionId[] = "SessionId";
35 constexpr char kUrlString[] = "https://example.test/index.html";
36 const GURL kTestUrl(kUrlString);
37
CreateValidParams()38 SessionParams CreateValidParams() {
39 SessionParams::Scope scope;
40 std::vector<SessionParams::Credential> cookie_credentials(
41 {SessionParams::Credential{"test_cookie",
42 "Secure; Domain=example.test"}});
43 SessionParams params{kSessionId, kUrlString, std::move(scope),
44 std::move(cookie_credentials)};
45 return params;
46 }
47
TEST_F(SessionTest,ValidService)48 TEST_F(SessionTest, ValidService) {
49 auto session = Session::CreateIfValid(CreateValidParams(), kTestUrl);
50 EXPECT_TRUE(session);
51 }
52
TEST_F(SessionTest,DefaultExpiry)53 TEST_F(SessionTest, DefaultExpiry) {
54 auto session = Session::CreateIfValid(CreateValidParams(), kTestUrl);
55 ASSERT_TRUE(session);
56 EXPECT_LT(base::Time::Now() + base::Days(399), session->expiry_date());
57 }
58
TEST_F(SessionTest,InvalidServiceRefreshUrl)59 TEST_F(SessionTest, InvalidServiceRefreshUrl) {
60 auto params = CreateValidParams();
61 params.refresh_url = "";
62 EXPECT_FALSE(Session::CreateIfValid(params, kTestUrl));
63 }
64
TEST_F(SessionTest,ToFromProto)65 TEST_F(SessionTest, ToFromProto) {
66 std::unique_ptr<Session> session =
67 Session::CreateIfValid(CreateValidParams(), kTestUrl);
68 ASSERT_TRUE(session);
69
70 // Convert to proto and validate contents.
71 proto::Session sproto = session->ToProto();
72 EXPECT_EQ(Session::Id(sproto.id()), session->id());
73 EXPECT_EQ(sproto.refresh_url(), session->refresh_url().spec());
74 EXPECT_EQ(sproto.should_defer_when_expired(),
75 session->should_defer_when_expired());
76
77 // Restore session from proto and validate contents.
78 std::unique_ptr<Session> restored = Session::CreateFromProto(sproto);
79 ASSERT_TRUE(restored);
80 EXPECT_TRUE(restored->IsEqualForTesting(*session));
81 }
82
TEST_F(SessionTest,FailCreateFromInvalidProto)83 TEST_F(SessionTest, FailCreateFromInvalidProto) {
84 // Empty proto.
85 {
86 proto::Session sproto;
87 EXPECT_FALSE(Session::CreateFromProto(sproto));
88 }
89
90 // Create a fully populated proto.
91 std::unique_ptr<Session> session =
92 Session::CreateIfValid(CreateValidParams(), kTestUrl);
93 ASSERT_TRUE(session);
94 proto::Session sproto = session->ToProto();
95
96 // Missing fields.
97 {
98 proto::Session s(sproto);
99 s.clear_id();
100 EXPECT_FALSE(Session::CreateFromProto(s));
101 }
102 {
103 proto::Session s(sproto);
104 s.clear_refresh_url();
105 EXPECT_FALSE(Session::CreateFromProto(s));
106 }
107 {
108 proto::Session s(sproto);
109 s.clear_should_defer_when_expired();
110 EXPECT_FALSE(Session::CreateFromProto(s));
111 }
112 {
113 proto::Session s(sproto);
114 s.clear_expiry_time();
115 EXPECT_FALSE(Session::CreateFromProto(s));
116 }
117 {
118 proto::Session s(sproto);
119 s.clear_session_inclusion_rules();
120 EXPECT_FALSE(Session::CreateFromProto(s));
121 }
122
123 // Empty id.
124 {
125 proto::Session s(sproto);
126 s.set_id("");
127 EXPECT_FALSE(Session::CreateFromProto(s));
128 }
129 // Invalid refresh URL.
130 {
131 proto::Session s(sproto);
132 s.set_refresh_url("blank");
133 EXPECT_FALSE(Session::CreateFromProto(s));
134 }
135
136 // Expired
137 {
138 proto::Session s(sproto);
139 base::Time expiry_date = base::Time::Now() - base::Days(1);
140 s.set_expiry_time(expiry_date.ToDeltaSinceWindowsEpoch().InMicroseconds());
141 EXPECT_FALSE(Session::CreateFromProto(s));
142 }
143 }
144
TEST_F(SessionTest,DeferredSession)145 TEST_F(SessionTest, DeferredSession) {
146 auto params = CreateValidParams();
147 auto session = Session::CreateIfValid(params, kTestUrl);
148 ASSERT_TRUE(session);
149 net::TestDelegate delegate;
150 std::unique_ptr<URLRequest> request =
151 context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
152 request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
153
154 bool is_deferred = session->ShouldDeferRequest(request.get());
155 EXPECT_TRUE(is_deferred);
156 }
157
TEST_F(SessionTest,NotDeferredAsExcluded)158 TEST_F(SessionTest, NotDeferredAsExcluded) {
159 auto params = CreateValidParams();
160 SessionParams::Scope::Specification spec;
161 spec.type = SessionParams::Scope::Specification::Type::kExclude;
162 spec.domain = "example.test";
163 spec.path = "/index.html";
164 params.scope.specifications.push_back(spec);
165 auto session = Session::CreateIfValid(params, kTestUrl);
166 ASSERT_TRUE(session);
167 net::TestDelegate delegate;
168 std::unique_ptr<URLRequest> request =
169 context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
170 request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
171
172 bool is_deferred = session->ShouldDeferRequest(request.get());
173 EXPECT_FALSE(is_deferred);
174 }
175
TEST_F(SessionTest,NotDeferredSubdomain)176 TEST_F(SessionTest, NotDeferredSubdomain) {
177 const char subdomain[] = "https://test.example.test/index.html";
178 const GURL url_subdomain(subdomain);
179 auto params = CreateValidParams();
180 auto session = Session::CreateIfValid(params, kTestUrl);
181 ASSERT_TRUE(session);
182 net::TestDelegate delegate;
183 std::unique_ptr<URLRequest> request =
184 context_->CreateRequest(url_subdomain, IDLE, &delegate, kDummyAnnotation);
185 request->set_site_for_cookies(SiteForCookies::FromUrl(url_subdomain));
186
187 bool is_deferred = session->ShouldDeferRequest(request.get());
188 EXPECT_FALSE(is_deferred);
189 }
190
TEST_F(SessionTest,DeferredIncludedSubdomain)191 TEST_F(SessionTest, DeferredIncludedSubdomain) {
192 // Unless include site is specified, only same origin will be
193 // matched even if the spec adds an include for a different
194 // origin.
195 const char subdomain[] = "https://test.example.test/index.html";
196 const GURL url_subdomain(subdomain);
197 auto params = CreateValidParams();
198 SessionParams::Scope::Specification spec;
199 spec.type = SessionParams::Scope::Specification::Type::kInclude;
200 spec.domain = "test.example.test";
201 spec.path = "/index.html";
202 params.scope.specifications.push_back(spec);
203 auto session = Session::CreateIfValid(params, kTestUrl);
204 ASSERT_TRUE(session);
205 net::TestDelegate delegate;
206 std::unique_ptr<URLRequest> request =
207 context_->CreateRequest(url_subdomain, IDLE, &delegate, kDummyAnnotation);
208 request->set_site_for_cookies(SiteForCookies::FromUrl(url_subdomain));
209 ASSERT_TRUE(session->ShouldDeferRequest(request.get()));
210 }
211
TEST_F(SessionTest,NotDeferredWithCookieSession)212 TEST_F(SessionTest, NotDeferredWithCookieSession) {
213 auto params = CreateValidParams();
214 auto session = Session::CreateIfValid(params, kTestUrl);
215 ASSERT_TRUE(session);
216 net::TestDelegate delegate;
217 std::unique_ptr<URLRequest> request =
218 context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
219 request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
220 bool is_deferred = session->ShouldDeferRequest(request.get());
221 EXPECT_TRUE(is_deferred);
222
223 CookieInclusionStatus status;
224 auto source = CookieSourceType::kHTTP;
225 auto cookie = CanonicalCookie::Create(
226 kTestUrl, "test_cookie=v;Secure; Domain=example.test", base::Time::Now(),
227 std::nullopt, std::nullopt, source, &status);
228 ASSERT_TRUE(cookie);
229 CookieAccessResult access_result;
230 request->set_maybe_sent_cookies({{*cookie.get(), access_result}});
231 EXPECT_FALSE(session->ShouldDeferRequest(request.get()));
232 }
233
TEST_F(SessionTest,NotDeferredInsecure)234 TEST_F(SessionTest, NotDeferredInsecure) {
235 const char insecure_url[] = "http://example.test/index.html";
236 const GURL test_insecure_url(insecure_url);
237 auto params = CreateValidParams();
238 auto session = Session::CreateIfValid(params, kTestUrl);
239 ASSERT_TRUE(session);
240 net::TestDelegate delegate;
241 std::unique_ptr<URLRequest> request = context_->CreateRequest(
242 test_insecure_url, IDLE, &delegate, kDummyAnnotation);
243 request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
244
245 bool is_deferred = session->ShouldDeferRequest(request.get());
246 EXPECT_FALSE(is_deferred);
247 }
248
249 class InsecureDelegate : public CookieAccessDelegate {
250 public:
ShouldTreatUrlAsTrustworthy(const GURL & url) const251 bool ShouldTreatUrlAsTrustworthy(const GURL& url) const override {
252 return true;
253 }
GetAccessSemantics(const CanonicalCookie & cookie) const254 CookieAccessSemantics GetAccessSemantics(
255 const CanonicalCookie& cookie) const override {
256 return CookieAccessSemantics::UNKNOWN;
257 }
258
GetAccessForLegacyCookieScope(const CanonicalCookie & cookie) const259 CookieLegacyScope GetAccessForLegacyCookieScope(
260 const CanonicalCookie& cookie) const override {
261 return CookieLegacyScope::UNKNOWN;
262 }
263 // Returns whether a cookie should be attached regardless of its SameSite
264 // value vs the request context.
ShouldIgnoreSameSiteRestrictions(const GURL & url,const SiteForCookies & site_for_cookies) const265 bool ShouldIgnoreSameSiteRestrictions(
266 const GURL& url,
267 const SiteForCookies& site_for_cookies) const override {
268 return true;
269 }
270 [[nodiscard]] std::optional<
271 std::pair<FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo>>
ComputeFirstPartySetMetadataMaybeAsync(const net::SchemefulSite & site,const net::SchemefulSite * top_frame_site,base::OnceCallback<void (FirstPartySetMetadata,FirstPartySetsCacheFilter::MatchInfo)> callback) const272 ComputeFirstPartySetMetadataMaybeAsync(
273 const net::SchemefulSite& site,
274 const net::SchemefulSite* top_frame_site,
275 base::OnceCallback<void(FirstPartySetMetadata,
276 FirstPartySetsCacheFilter::MatchInfo)> callback)
277 const override {
278 return std::nullopt;
279 }
280 [[nodiscard]] std::optional<
281 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) const282 FindFirstPartySetEntries(
283 const base::flat_set<net::SchemefulSite>& sites,
284 base::OnceCallback<
285 void(base::flat_map<net::SchemefulSite, net::FirstPartySetEntry>)>
286 callback) const override {
287 return std::nullopt;
288 }
289 };
290
TEST_F(SessionTest,NotDeferredNotSameSite)291 TEST_F(SessionTest, NotDeferredNotSameSite) {
292 auto params = CreateValidParams();
293 auto session = Session::CreateIfValid(params, kTestUrl);
294 ASSERT_TRUE(session);
295 net::TestDelegate delegate;
296 std::unique_ptr<URLRequest> request =
297 context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
298
299 bool is_deferred = session->ShouldDeferRequest(request.get());
300 EXPECT_FALSE(is_deferred);
301 }
302
TEST_F(SessionTest,DeferredNotSameSiteDelegate)303 TEST_F(SessionTest, DeferredNotSameSiteDelegate) {
304 context_->cookie_store()->SetCookieAccessDelegate(
305 std::make_unique<InsecureDelegate>());
306 auto params = CreateValidParams();
307 auto session = Session::CreateIfValid(params, kTestUrl);
308 ASSERT_TRUE(session);
309 net::TestDelegate delegate;
310 std::unique_ptr<URLRequest> request =
311 context_->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
312
313 bool is_deferred = session->ShouldDeferRequest(request.get());
314 EXPECT_TRUE(is_deferred);
315 }
316
TEST_F(SessionTest,NotDeferredIncludedSubdomainHostCraving)317 TEST_F(SessionTest, NotDeferredIncludedSubdomainHostCraving) {
318 // Unless include site is specified, only same origin will be
319 // matched even if the spec adds an include for a different
320 // origin.
321 const char subdomain[] = "https://test.example.test/index.html";
322 const GURL url_subdomain(subdomain);
323 auto params = CreateValidParams();
324 SessionParams::Scope::Specification spec;
325 spec.type = SessionParams::Scope::Specification::Type::kInclude;
326 spec.domain = "test.example.test";
327 spec.path = "/index.html";
328 params.scope.specifications.push_back(spec);
329 std::vector<SessionParams::Credential> cookie_credentials(
330 {SessionParams::Credential{"test_cookie", "Secure;"}});
331 params.credentials = std::move(cookie_credentials);
332 auto session = Session::CreateIfValid(params, kTestUrl);
333 ASSERT_TRUE(session);
334 net::TestDelegate delegate;
335 std::unique_ptr<URLRequest> request =
336 context_->CreateRequest(url_subdomain, IDLE, &delegate, kDummyAnnotation);
337 request->set_site_for_cookies(SiteForCookies::FromUrl(url_subdomain));
338 ASSERT_FALSE(session->ShouldDeferRequest(request.get()));
339 }
340
TEST_F(SessionTest,CreationDate)341 TEST_F(SessionTest, CreationDate) {
342 auto session = Session::CreateIfValid(CreateValidParams(), kTestUrl);
343 ASSERT_TRUE(session);
344 // Make sure it's set to a plausible value.
345 EXPECT_LT(base::Time::Now() - base::Days(1), session->creation_date());
346 }
347
348 } // namespace
349
350 } // namespace net::device_bound_sessions
351