• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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