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 <memory>
8
9 #include "components/unexportable_keys/unexportable_key_id.h"
10 #include "net/cookies/canonical_cookie.h"
11 #include "net/cookies/cookie_constants.h"
12 #include "net/cookies/cookie_options.h"
13 #include "net/cookies/cookie_store.h"
14 #include "net/cookies/cookie_util.h"
15 #include "net/device_bound_sessions/cookie_craving.h"
16 #include "net/device_bound_sessions/proto/storage.pb.h"
17 #include "net/device_bound_sessions/session_inclusion_rules.h"
18 #include "net/url_request/url_request.h"
19 #include "net/url_request/url_request_context.h"
20
21 namespace net::device_bound_sessions {
22
23 namespace {
24
25 constexpr base::TimeDelta kSessionTtl = base::Days(400);
26
27 }
28
Session(Id id,url::Origin origin,GURL refresh)29 Session::Session(Id id, url::Origin origin, GURL refresh)
30 : id_(id), refresh_url_(refresh), inclusion_rules_(origin) {}
31
Session(Id id,GURL refresh,SessionInclusionRules inclusion_rules,std::vector<CookieCraving> cookie_cravings,bool should_defer_when_expired,base::Time creation_date,base::Time expiry_date)32 Session::Session(Id id,
33 GURL refresh,
34 SessionInclusionRules inclusion_rules,
35 std::vector<CookieCraving> cookie_cravings,
36 bool should_defer_when_expired,
37 base::Time creation_date,
38 base::Time expiry_date)
39 : id_(id),
40 refresh_url_(refresh),
41 inclusion_rules_(std::move(inclusion_rules)),
42 cookie_cravings_(std::move(cookie_cravings)),
43 should_defer_when_expired_(should_defer_when_expired),
44 creation_date_(creation_date),
45 expiry_date_(expiry_date) {}
46
47 Session::~Session() = default;
48
49 // static
CreateIfValid(const SessionParams & params,GURL url)50 std::unique_ptr<Session> Session::CreateIfValid(const SessionParams& params,
51 GURL url) {
52 GURL refresh(params.refresh_url);
53 if (!refresh.is_valid()) {
54 return nullptr;
55 }
56
57 if (params.session_id.empty()) {
58 return nullptr;
59 }
60
61 std::unique_ptr<Session> session(
62 new Session(Id(params.session_id), url::Origin::Create(url), refresh));
63 for (const auto& spec : params.scope.specifications) {
64 if (!spec.domain.empty() && !spec.path.empty()) {
65 const auto inclusion_result =
66 spec.type == SessionParams::Scope::Specification::Type::kExclude
67 ? SessionInclusionRules::InclusionResult::kExclude
68 : SessionInclusionRules::InclusionResult::kInclude;
69 session->inclusion_rules_.AddUrlRuleIfValid(inclusion_result, spec.domain,
70 spec.path);
71 }
72 }
73
74 for (const auto& cred : params.credentials) {
75 if (!cred.name.empty() && !cred.attributes.empty()) {
76 std::optional<CookieCraving> craving = CookieCraving::Create(
77 url, cred.name, cred.attributes, base::Time::Now(), std::nullopt);
78 if (craving) {
79 session->cookie_cravings_.push_back(*craving);
80 }
81 }
82 }
83
84 session->set_creation_date(base::Time::Now());
85 session->set_expiry_date(base::Time::Now() + kSessionTtl);
86
87 return session;
88 }
89
90 // static
CreateFromProto(const proto::Session & proto)91 std::unique_ptr<Session> Session::CreateFromProto(const proto::Session& proto) {
92 if (!proto.has_id() || !proto.has_refresh_url() ||
93 !proto.has_should_defer_when_expired() || !proto.has_expiry_time() ||
94 !proto.has_session_inclusion_rules() || !proto.cookie_cravings_size()) {
95 return nullptr;
96 }
97
98 if (proto.id().empty()) {
99 return nullptr;
100 }
101
102 GURL refresh(proto.refresh_url());
103 if (!refresh.is_valid()) {
104 return nullptr;
105 }
106
107 std::unique_ptr<SessionInclusionRules> inclusion_rules =
108 SessionInclusionRules::CreateFromProto(proto.session_inclusion_rules());
109 if (!inclusion_rules) {
110 return nullptr;
111 }
112
113 std::vector<CookieCraving> cravings;
114 for (const auto& craving_proto : proto.cookie_cravings()) {
115 std::optional<CookieCraving> craving =
116 CookieCraving::CreateFromProto(craving_proto);
117 if (!craving.has_value()) {
118 return nullptr;
119 }
120 cravings.push_back(std::move(*craving));
121 }
122
123 auto creation_date = base::Time::Now();
124 if (proto.has_creation_time()) {
125 creation_date = base::Time::FromDeltaSinceWindowsEpoch(
126 base::Microseconds(proto.creation_time()));
127 }
128
129 auto expiry_date = base::Time::FromDeltaSinceWindowsEpoch(
130 base::Microseconds(proto.expiry_time()));
131 if (base::Time::Now() > expiry_date) {
132 return nullptr;
133 }
134
135 std::unique_ptr<Session> result(new Session(
136 Id(proto.id()), std::move(refresh), std::move(*inclusion_rules),
137 std::move(cravings), proto.should_defer_when_expired(), creation_date,
138 expiry_date));
139
140 return result;
141 }
142
ToProto() const143 proto::Session Session::ToProto() const {
144 proto::Session session_proto;
145 session_proto.set_id(*id_);
146 session_proto.set_refresh_url(refresh_url_.spec());
147 session_proto.set_should_defer_when_expired(should_defer_when_expired_);
148 session_proto.set_creation_time(
149 creation_date_.ToDeltaSinceWindowsEpoch().InMicroseconds());
150 session_proto.set_expiry_time(
151 expiry_date_.ToDeltaSinceWindowsEpoch().InMicroseconds());
152
153 *session_proto.mutable_session_inclusion_rules() = inclusion_rules_.ToProto();
154
155 for (auto& craving : cookie_cravings_) {
156 session_proto.mutable_cookie_cravings()->Add(craving.ToProto());
157 }
158
159 return session_proto;
160 }
161
ShouldDeferRequest(URLRequest * request) const162 bool Session::ShouldDeferRequest(URLRequest* request) const {
163 if (inclusion_rules_.EvaluateRequestUrl(request->url()) ==
164 SessionInclusionRules::kExclude) {
165 // Request is not in scope for this session.
166 return false;
167 }
168
169 // TODO(crbug.com/353766029): Refactor this.
170 // The below is all copied from AddCookieHeaderAndStart. We should refactor
171 // it.
172 CookieStore* cookie_store = request->context()->cookie_store();
173 bool force_ignore_site_for_cookies = request->force_ignore_site_for_cookies();
174 if (cookie_store->cookie_access_delegate() &&
175 cookie_store->cookie_access_delegate()->ShouldIgnoreSameSiteRestrictions(
176 request->url(), request->site_for_cookies())) {
177 force_ignore_site_for_cookies = true;
178 }
179
180 bool is_main_frame_navigation =
181 IsolationInfo::RequestType::kMainFrame ==
182 request->isolation_info().request_type() ||
183 request->force_main_frame_for_same_site_cookies();
184 CookieOptions::SameSiteCookieContext same_site_context =
185 net::cookie_util::ComputeSameSiteContextForRequest(
186 request->method(), request->url_chain(), request->site_for_cookies(),
187 request->initiator(), is_main_frame_navigation,
188 force_ignore_site_for_cookies);
189
190 CookieOptions options;
191 options.set_same_site_cookie_context(same_site_context);
192 options.set_include_httponly();
193 // Not really relevant for CookieCraving, but might as well make it explicit.
194 options.set_do_not_update_access_time();
195
196 CookieAccessParams params{CookieAccessSemantics::NONLEGACY,
197 // DBSC only affects secure URLs
198 false};
199
200 // The main logic. This checks every CookieCraving against every (real)
201 // CanonicalCookie.
202 for (const CookieCraving& cookie_craving : cookie_cravings_) {
203 if (!cookie_craving.IncludeForRequestURL(request->url(), options, params)
204 .status.IsInclude()) {
205 continue;
206 }
207
208 bool satisfied = false;
209 for (const CookieWithAccessResult& request_cookie :
210 request->maybe_sent_cookies()) {
211 // Note that any request_cookie that satisfies the craving is fine, even
212 // if it does not ultimately get included when sending the request. We
213 // only need to ensure the cookie is present in the store.
214 //
215 // Note that in general if a CanonicalCookie isn't included, then the
216 // corresponding CookieCraving typically also isn't included, but there
217 // are exceptions.
218 //
219 // For example, if a CookieCraving is for a secure cookie, and the
220 // request is insecure, then the CookieCraving will be excluded, but the
221 // CanonicalCookie will be included. DBSC only applies to secure context
222 // but there might be similar cases.
223 //
224 // TODO: think about edge cases here...
225 if (cookie_craving.IsSatisfiedBy(request_cookie.cookie)) {
226 satisfied = true;
227 break;
228 }
229 }
230
231 if (!satisfied) {
232 // There's an unsatisfied craving. Defer the request.
233 return true;
234 }
235 }
236
237 // All cookiecravings satisfied.
238 return false;
239 }
240
IsEqualForTesting(const Session & other) const241 bool Session::IsEqualForTesting(const Session& other) const {
242 if (!base::ranges::equal(
243 cookie_cravings_, other.cookie_cravings_,
244 [](const CookieCraving& lhs, const CookieCraving& rhs) {
245 return lhs.IsEqualForTesting(rhs); // IN-TEST
246 })) {
247 return false;
248 }
249
250 return id_ == other.id_ && refresh_url_ == other.refresh_url_ &&
251 inclusion_rules_ == other.inclusion_rules_ &&
252 should_defer_when_expired_ == other.should_defer_when_expired_ &&
253 creation_date_ == other.creation_date_ &&
254 expiry_date_ == other.expiry_date_ &&
255 key_id_or_error_ == other.key_id_or_error_ &&
256 cached_challenge_ == other.cached_challenge_;
257 }
258
RecordAccess()259 void Session::RecordAccess() {
260 expiry_date_ = base::Time::Now() + kSessionTtl;
261 }
262
263 } // namespace net::device_bound_sessions
264