blob: 1ded183b6de77ffbee3588fe1647cb1dcd734e89 [file] [log] [blame]
// 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_service_impl.h"
#include "base/test/test_future.h"
#include "crypto/scoped_mock_unexportable_key_provider.h"
#include "net/device_bound_sessions/mock_session_store.h"
#include "net/device_bound_sessions/session_store.h"
#include "net/device_bound_sessions/unexportable_key_service_factory.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"
#include "url/gurl.h"
using ::testing::ElementsAre;
using ::testing::InSequence;
using ::testing::Invoke;
using ::testing::StrictMock;
using ::testing::UnorderedElementsAre;
namespace net::device_bound_sessions {
namespace {
constexpr net::NetworkTrafficAnnotationTag kDummyAnnotation =
net::DefineNetworkTrafficAnnotation("dbsc_registration", "");
// Constant variables
constexpr char kUrlString[] = "https://example.com";
const GURL kTestUrl(kUrlString);
const std::string kSessionId = "SessionId";
const std::string kOrigin = "example.com";
constexpr char kUrlString2[] = "https://example2.com";
const GURL kTestUrl2(kUrlString2);
const std::string kSessionId2 = "SessionId2";
const std::string kOrigin2 = "example2.com";
const std::string kChallenge = "challenge";
// Matcher for SessionKeys
auto ExpectId(std::string_view id) {
return testing::Field(&SessionKey::id, Session::Id(std::string(id)));
}
std::optional<RegistrationFetcher::RegistrationCompleteParams> TestFetcher(
std::string session_id,
std::string url_string,
std::string origin) {
std::vector<SessionParams::Credential> cookie_credentials;
cookie_credentials.push_back(
SessionParams::Credential{"test_cookie", "secure"});
SessionParams::Scope scope;
scope.include_site = true;
scope.origin = origin;
SessionParams session_params(std::move(session_id), url_string,
std::move(scope), std::move(cookie_credentials));
unexportable_keys::UnexportableKeyId key_id;
return std::make_optional<RegistrationFetcher::RegistrationCompleteParams>(
std::move(session_params), std::move(key_id), GURL(url_string));
}
std::optional<RegistrationFetcher::RegistrationCompleteParams> NullFetcher() {
return std::nullopt;
}
std::optional<RegistrationFetcher::RegistrationCompleteParams>
ContinueFalseFetcher() {
unexportable_keys::UnexportableKeyId key_id;
return std::make_optional<RegistrationFetcher::RegistrationCompleteParams>(
SessionTerminationParams{kSessionId}, std::move(key_id), kTestUrl);
}
RegistrationFetcher::FetcherType TestFetcherFactory(std::string session_id,
std::string url_string,
std::string origin) {
static std::string g_session_id;
static std::string g_url_string;
static std::string g_origin;
g_session_id = std::move(session_id);
g_url_string = std::move(url_string);
g_origin = std::move(origin);
return []() { return TestFetcher(g_session_id, g_url_string, g_origin); };
}
class ScopedTestFetcher {
public:
ScopedTestFetcher(std::string session_id,
std::string url_string,
std::string origin) {
// Reset the testing fetch function.
RegistrationFetcher::SetFetcherForTesting(nullptr);
RegistrationFetcher::SetFetcherForTesting(TestFetcherFactory(
std::move(session_id), std::move(url_string), std::move(origin)));
}
~ScopedTestFetcher() { RegistrationFetcher::SetFetcherForTesting(nullptr); }
};
class ScopedNullFetcher {
public:
ScopedNullFetcher() {
RegistrationFetcher::SetFetcherForTesting(NullFetcher);
}
~ScopedNullFetcher() { RegistrationFetcher::SetFetcherForTesting(nullptr); }
};
class ScopedContinueFalseFetcher {
public:
ScopedContinueFalseFetcher() {
RegistrationFetcher::SetFetcherForTesting(ContinueFalseFetcher);
}
~ScopedContinueFalseFetcher() {
RegistrationFetcher::SetFetcherForTesting(nullptr);
}
};
class TestDeferCompletion {
public:
enum class CallbackType { kRestart, kContinue };
explicit TestDeferCompletion(base::OnceCallback<void(CallbackType)> callback)
: completion_callback_(std::move(callback)) {}
~TestDeferCompletion() = default;
SessionService::RefreshCompleteCallback GetRestartCb() {
return base::BindOnce(&TestDeferCompletion::RestartRequest,
weak_factory_.GetWeakPtr());
}
SessionService::RefreshCompleteCallback GetContinueCb() {
return base::BindOnce(&TestDeferCompletion::ContinueRequest,
weak_factory_.GetWeakPtr());
}
void RestartRequest() {
std::move(completion_callback_).Run(CallbackType::kRestart);
}
void ContinueRequest() {
std::move(completion_callback_).Run(CallbackType::kContinue);
}
private:
base::OnceCallback<void(CallbackType)> completion_callback_;
base::WeakPtrFactory<TestDeferCompletion> weak_factory_{this};
};
class FakeDeviceBoundSessionObserver {
public:
const std::vector<SessionKey>& notifications() const {
return notifications_;
}
void ClearNotifications() { notifications_.clear(); }
void OnSessionAccessed(const SessionKey& session_key) {
notifications_.push_back(session_key);
}
SessionService::OnAccessCallback GetCallback() {
return base::BindRepeating(
&FakeDeviceBoundSessionObserver::OnSessionAccessed,
base::Unretained(this));
}
private:
std::vector<SessionKey> notifications_;
};
class SessionServiceImplTest : public TestWithTaskEnvironment {
public:
SessionServiceImplTest()
: context_(CreateTestURLRequestContextBuilder()->Build()),
service_(*UnexportableKeyServiceFactory::GetInstance()->GetShared(),
context_.get(),
/*store=*/nullptr) {}
URLRequestContext* context() { return context_.get(); }
SessionServiceImpl& service() { return service_; }
// Take list of <session_id, site_url> to add sessions for testing.
void AddSessionsForTesting(
std::vector<std::tuple<std::string, std::string, std::string>>
id_url_origin_list) {
for (const auto& [id, url_str, origin] : id_url_origin_list) {
ScopedTestFetcher scoped_test_fetcher(id, url_str, origin);
auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
GURL(url_str),
{crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
"challenge", /*authorization=*/std::nullopt);
service_.RegisterBoundSession(
base::DoNothing(), std::move(fetch_param),
IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
NetLogWithSource());
}
}
private:
crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
std::unique_ptr<URLRequestContext> context_;
SessionServiceImpl service_;
};
TEST_F(SessionServiceImplTest, RegisterSuccess) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
// The request needs to be samesite for it to be considered
// candidate for deferral.
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
std::optional<Session::Id> maybe_id =
service().GetAnySessionRequiringDeferral(request.get());
ASSERT_TRUE(maybe_id);
EXPECT_EQ(**maybe_id, kSessionId);
}
TEST_F(SessionServiceImplTest, RegisterNoId) {
AddSessionsForTesting({{/*session_id=*/"", kUrlString, kOrigin}});
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
std::optional<Session::Id> maybe_id =
service().GetAnySessionRequiringDeferral(request.get());
// session_id is empty, so should not be valid
EXPECT_FALSE(maybe_id);
}
TEST_F(SessionServiceImplTest, RegisterNullFetcher) {
ScopedNullFetcher scopedNullFetcher;
auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
kTestUrl, {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
kChallenge,
/*authorization=*/std::nullopt);
service().RegisterBoundSession(
base::DoNothing(), std::move(fetch_param),
IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
NetLogWithSource());
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
std::optional<Session::Id> maybe_id =
service().GetAnySessionRequiringDeferral(request.get());
// NullFetcher, so should not be valid
EXPECT_FALSE(maybe_id);
}
TEST_F(SessionServiceImplTest, SetChallengeForBoundSession) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
scoped_refptr<net::HttpResponseHeaders> headers =
HttpResponseHeaders::Builder({1, 1}, "200 OK").Build();
headers->AddHeader(
"Sec-Session-Challenge",
R"("challenge";id="SessionId", "challenge1";id="NonExisted")");
headers->AddHeader("Sec-Session-Challenge", R"("challenge2")");
std::vector<SessionChallengeParam> params =
SessionChallengeParam::CreateIfValid(kTestUrl, headers.get());
EXPECT_EQ(params.size(), 3U);
for (const auto& param : params) {
service().SetChallengeForBoundSession(base::DoNothing(), kTestUrl, param);
}
const Session* session =
service().GetSession(SchemefulSite(kTestUrl), Session::Id(kSessionId));
ASSERT_TRUE(session);
EXPECT_EQ(session->cached_challenge(), "challenge");
session =
service().GetSession(SchemefulSite(kTestUrl), Session::Id("NonExisted"));
ASSERT_FALSE(session);
}
TEST_F(SessionServiceImplTest, ExpiryExtendedOnUser) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
Session* session =
service().GetSession(SchemefulSite(kTestUrl), Session::Id(kSessionId));
ASSERT_TRUE(session);
session->set_expiry_date(base::Time::Now() + base::Days(1));
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
// The request needs to be samesite for it to be considered
// candidate for deferral.
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
service().GetAnySessionRequiringDeferral(request.get());
EXPECT_GT(session->expiry_date(), base::Time::Now() + base::Days(399));
}
TEST_F(SessionServiceImplTest, NullAccessObserver) {
ScopedTestFetcher scoped_test_fetcher(kSessionId, kUrlString, kOrigin);
auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
kTestUrl, {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
"challenge", /*authorization=*/std::nullopt);
service().RegisterBoundSession(
SessionService::OnAccessCallback(), std::move(fetch_param),
IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
NetLogWithSource());
// The access observer was null, so no call is expected
}
TEST_F(SessionServiceImplTest, AccessObserverCalledOnRegistration) {
ScopedTestFetcher scoped_test_fetcher(kSessionId, kUrlString, kOrigin);
auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
kTestUrl, {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
"challenge", /*authorization=*/std::nullopt);
base::test::TestFuture<SessionKey> future;
service().RegisterBoundSession(
future.GetRepeatingCallback<const SessionKey&>(), std::move(fetch_param),
IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
NetLogWithSource());
SessionKey session_key = future.Take();
EXPECT_EQ(session_key.site, SchemefulSite(kTestUrl));
EXPECT_EQ(session_key.id.value(), kSessionId);
}
TEST_F(SessionServiceImplTest, AccessObserverCalledOnDeferral) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
// The request needs to be samesite for it to be considered
// candidate for deferral.
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
base::test::TestFuture<SessionKey> future;
request->SetDeviceBoundSessionAccessCallback(
future.GetRepeatingCallback<const SessionKey&>());
service().GetAnySessionRequiringDeferral(request.get());
SessionKey session_key = future.Take();
EXPECT_EQ(session_key.site, SchemefulSite(kTestUrl));
EXPECT_EQ(session_key.id.value(), kSessionId);
}
TEST_F(SessionServiceImplTest, AccessObserverCalledOnSetChallenge) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
scoped_refptr<net::HttpResponseHeaders> headers =
HttpResponseHeaders::Builder({1, 1}, "200 OK").Build();
headers->AddHeader("Sec-Session-Challenge", "\"challenge\";id=\"SessionId\"");
std::vector<SessionChallengeParam> params =
SessionChallengeParam::CreateIfValid(kTestUrl, headers.get());
ASSERT_EQ(params.size(), 1U);
base::test::TestFuture<SessionKey> future;
service().SetChallengeForBoundSession(
future.GetRepeatingCallback<const SessionKey&>(), kTestUrl, params[0]);
SessionKey session_key = future.Take();
EXPECT_EQ(session_key.site, SchemefulSite(kTestUrl));
EXPECT_EQ(session_key.id.value(), kSessionId);
}
TEST_F(SessionServiceImplTest, GetAllSessions) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin},
{kSessionId2, kUrlString2, kOrigin2}});
base::test::TestFuture<std::vector<SessionKey>> future;
service().GetAllSessionsAsync(
future.GetCallback<const std::vector<SessionKey>&>());
EXPECT_THAT(future.Take(), UnorderedElementsAre(ExpectId(kSessionId),
ExpectId(kSessionId2)));
}
TEST_F(SessionServiceImplTest, DeleteSession) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
auto site = SchemefulSite(kTestUrl);
auto session_id = Session::Id(kSessionId);
ASSERT_TRUE(service().GetSession(site, session_id));
service().DeleteSession(site, session_id);
EXPECT_FALSE(service().GetSession(site, session_id));
}
TEST_F(SessionServiceImplTest, DeleteAllSessionsByCreationTime) {
net::SchemefulSite site(kTestUrl);
AddSessionsForTesting({{"SessionA", kUrlString, kOrigin},
{"SessionB", kUrlString, kOrigin},
{"SessionC", kUrlString, kOrigin}});
service()
.GetSession(site, Session::Id("SessionA"))
->set_creation_date(base::Time::Now() - base::Days(6));
service()
.GetSession(site, Session::Id("SessionB"))
->set_creation_date(base::Time::Now() - base::Days(4));
service()
.GetSession(site, Session::Id("SessionC"))
->set_creation_date(base::Time::Now() - base::Days(2));
base::RunLoop run_loop;
service().DeleteAllSessions(base::Time::Now() - base::Days(5),
base::Time::Now() - base::Days(3),
/*site_matcher=*/
base::NullCallback(), run_loop.QuitClosure());
run_loop.Run();
EXPECT_TRUE(service().GetSession(site, Session::Id("SessionA")));
EXPECT_FALSE(service().GetSession(site, Session::Id("SessionB")));
EXPECT_TRUE(service().GetSession(site, Session::Id("SessionC")));
}
TEST_F(SessionServiceImplTest, DeleteAllSessionsBySite) {
GURL url_a("https://a_example.com");
GURL url_b("https://b_example.com");
AddSessionsForTesting({{kSessionId, url_a.spec(), "a_example.com"},
{kSessionId, url_b.spec(), "b_example.com"}});
SchemefulSite site_a(url_a);
SchemefulSite site_b(url_b);
// `site_matcher` only matches `site_a`
base::RepeatingCallback<bool(const net::SchemefulSite&)> site_matcher =
base::BindRepeating(std::equal_to<net::SchemefulSite>(), site_a);
base::RunLoop run_loop;
service().DeleteAllSessions(
/*created_after_time=*/std::nullopt,
/*created_before_time=*/std::nullopt, site_matcher,
run_loop.QuitClosure());
run_loop.Run();
EXPECT_FALSE(service().GetSession(site_a, Session::Id(kSessionId)));
EXPECT_TRUE(service().GetSession(site_b, Session::Id(kSessionId)));
}
TEST_F(SessionServiceImplTest, TestDeferWithRequestRestart) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
SchemefulSite site(kTestUrl);
ASSERT_TRUE(service().GetSession(site, Session::Id(kSessionId)));
// Create a request to kTestUrl and defer it.
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
// The request needs to be samesite for it to be considered
// candidate for deferral.
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
std::optional<Session::Id> maybe_id =
service().GetAnySessionRequiringDeferral(request.get());
ASSERT_TRUE(maybe_id);
EXPECT_EQ(**maybe_id, kSessionId);
// Defer the request.
// Set AccessCallback for DeferRequestForRefresh().
FakeDeviceBoundSessionObserver observer;
request->SetDeviceBoundSessionAccessCallback(observer.GetCallback());
// Set RestartCallback and ContinueCallback.
base::test::TestFuture<TestDeferCompletion::CallbackType> future;
TestDeferCompletion defer_completion(
future.GetCallback<TestDeferCompletion::CallbackType>());
// Set up the fetcher for a successful refresh.
ScopedTestFetcher scoped_test_fetcher(kSessionId, kUrlString, kOrigin);
service().DeferRequestForRefresh(request.get(), Session::Id(kSessionId),
defer_completion.GetRestartCb(),
defer_completion.GetContinueCb());
// Check access callback triggered by DeferRequestForRefresh.
ASSERT_THAT(observer.notifications(),
ElementsAre(SessionKey(site, Session::Id(kSessionId))));
// Check the restart callback is called for successful fetcher.
EXPECT_EQ(future.Take(), TestDeferCompletion::CallbackType::kRestart);
}
TEST_F(SessionServiceImplTest, TestDeferWithRequestContinue) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
SchemefulSite site_1(kTestUrl);
ASSERT_TRUE(service().GetSession(site_1, Session::Id(kSessionId)));
// Create a request to kTestUrl and defer it.
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
// The request needs to be samesite for it to be considered
// candidate for deferral.
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
std::optional<Session::Id> maybe_id =
service().GetAnySessionRequiringDeferral(request.get());
ASSERT_TRUE(maybe_id);
EXPECT_EQ(**maybe_id, kSessionId);
// Defer the request.
// Set AccessCallback for DeferRequestForRefresh().
base::test::TestFuture<SessionKey> future_2;
request->SetDeviceBoundSessionAccessCallback(
future_2.GetRepeatingCallback<const SessionKey&>());
// Set RestartCallback and ContinueCallback.
base::test::TestFuture<TestDeferCompletion::CallbackType> future_3;
TestDeferCompletion defer_completion(
future_3.GetCallback<TestDeferCompletion::CallbackType>());
// Set up a null fetcher for failure refresh.
ScopedNullFetcher scoped_null_fetcher;
service().DeferRequestForRefresh(request.get(), Session::Id(kSessionId),
defer_completion.GetRestartCb(),
defer_completion.GetContinueCb());
// Check access callback triggered by DeferRequestForRefresh.
SessionKey session_key = future_2.Take();
EXPECT_EQ(session_key.site, site_1);
EXPECT_EQ(session_key.id.value(), kSessionId);
// Check the restart callback is called for successful fetcher.
EXPECT_EQ(future_3.Take(), TestDeferCompletion::CallbackType::kContinue);
}
TEST_F(SessionServiceImplTest, TestDeferRequestArbitrary) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
// No session for site_2.
SchemefulSite site_1(kTestUrl);
SchemefulSite site_2(kTestUrl2);
ASSERT_TRUE(service().GetSession(site_1, Session::Id(kSessionId)));
ASSERT_FALSE(service().GetSession(site_2, Session::Id(kSessionId2)));
// Create a request to kTestUrl and defer it.
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl2, IDLE, &delegate, kDummyAnnotation);
// The request needs to be samesite for it to be considered
// candidate for deferral.
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl2));
std::optional<Session::Id> maybe_id =
service().GetAnySessionRequiringDeferral(request.get());
ASSERT_FALSE(maybe_id);
// Defer the request any way.
// Set AccessCallback for DeferRequestForRefresh().
base::test::TestFuture<SessionKey> future_2;
request->SetDeviceBoundSessionAccessCallback(
future_2.GetRepeatingCallback<const SessionKey&>());
// Set RestartCallback and ContinueCallback.
base::test::TestFuture<TestDeferCompletion::CallbackType> future_3;
TestDeferCompletion defer_completion(
future_3.GetCallback<TestDeferCompletion::CallbackType>());
// Set up a successful fetcher.
ScopedTestFetcher scoped_test_fetcher_2(kSessionId2, kUrlString2, kOrigin2);
service().DeferRequestForRefresh(request.get(), Session::Id(kSessionId2),
defer_completion.GetRestartCb(),
defer_completion.GetContinueCb());
// Check the continue callback is called.
EXPECT_EQ(future_3.Take(), TestDeferCompletion::CallbackType::kContinue);
}
TEST_F(SessionServiceImplTest, RefreshWithNewSessionId) {
// Register a session with kSessionId.
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
auto site = SchemefulSite(kTestUrl);
ASSERT_TRUE(service().GetSession(site, Session::Id(kSessionId)));
// Create a request and defer it.
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
// The request needs to be samesite for it to be considered
// candidate for deferral.
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
std::optional<Session::Id> maybe_id =
service().GetAnySessionRequiringDeferral(request.get());
ASSERT_TRUE(maybe_id);
EXPECT_EQ(**maybe_id, kSessionId);
// Defer the request.
// Set AccessCallback for DeferRequestForRefresh().
FakeDeviceBoundSessionObserver observer;
request->SetDeviceBoundSessionAccessCallback(observer.GetCallback());
// Set RestartCallback and ContinueCallback.
base::test::TestFuture<TestDeferCompletion::CallbackType> future;
TestDeferCompletion defer_completion(
future.GetCallback<TestDeferCompletion::CallbackType>());
// Set up the fetcher for a successful refresh with a new session ID
// which doesn't equal to the refreshing one.
ScopedTestFetcher scoped_test_fetcher(kSessionId2, kUrlString, kOrigin);
service().DeferRequestForRefresh(request.get(), Session::Id(kSessionId),
defer_completion.GetRestartCb(),
defer_completion.GetContinueCb());
// Check access callback triggered by DeferRequestForRefresh.
EXPECT_THAT(observer.notifications(),
ElementsAre(SessionKey(site, Session::Id(kSessionId)),
SessionKey(site, Session::Id(kSessionId2))));
// Check the restart callback is called for successful fetcher.
EXPECT_EQ(future.Take(), TestDeferCompletion::CallbackType::kRestart);
ASSERT_TRUE(service().GetSession(site, Session::Id(kSessionId2)));
ASSERT_FALSE(service().GetSession(site, Session::Id(kSessionId)));
}
TEST_F(SessionServiceImplTest, SessionTerminationFromContinueFalse) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
ASSERT_TRUE(
service().GetSession(SchemefulSite(kTestUrl), Session::Id(kSessionId)));
{
ScopedContinueFalseFetcher scoped_fetcher;
auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
kTestUrl, {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
"challenge", /*authorization=*/std::nullopt);
service().RegisterBoundSession(
base::DoNothing(), std::move(fetch_param),
IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
NetLogWithSource());
}
EXPECT_FALSE(
service().GetSession(SchemefulSite(kTestUrl), Session::Id(kSessionId)));
}
TEST_F(SessionServiceImplTest, NetLogRegistration) {
RecordingNetLogObserver observer;
ScopedTestFetcher scoped_test_fetcher(kSessionId, kUrlString, kOrigin);
auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
kTestUrl, {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
"challenge", /*authorization=*/std::nullopt);
service().RegisterBoundSession(
base::DoNothing(), std::move(fetch_param),
IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
NetLogWithSource::Make(NetLogSourceType::URL_REQUEST));
EXPECT_EQ(
observer.GetEntriesWithType(NetLogEventType::DBSC_REGISTRATION_REQUEST)
.size(),
1u);
}
TEST_F(SessionServiceImplTest, NetLogRefresh) {
AddSessionsForTesting({{kSessionId, kUrlString, kOrigin}});
SchemefulSite site(kTestUrl);
ASSERT_TRUE(service().GetSession(site, Session::Id(kSessionId)));
net::TestDelegate delegate;
std::unique_ptr<URLRequest> request =
context()->CreateRequest(kTestUrl, IDLE, &delegate, kDummyAnnotation);
request->set_site_for_cookies(SiteForCookies::FromUrl(kTestUrl));
base::test::TestFuture<TestDeferCompletion::CallbackType> future;
TestDeferCompletion defer_completion(
future.GetCallback<TestDeferCompletion::CallbackType>());
RecordingNetLogObserver observer;
ScopedTestFetcher scoped_test_fetcher(kSessionId, kUrlString, kOrigin);
service().DeferRequestForRefresh(request.get(), Session::Id(kSessionId),
defer_completion.GetRestartCb(),
defer_completion.GetContinueCb());
EXPECT_EQ(
observer.GetEntriesWithType(NetLogEventType::DBSC_REFRESH_REQUEST).size(),
1u);
}
} // namespace
class SessionServiceImplWithStoreTest : public TestWithTaskEnvironment {
public:
SessionServiceImplWithStoreTest()
: context_(CreateTestURLRequestContextBuilder()->Build()),
store_(std::make_unique<StrictMock<SessionStoreMock>>()),
service_(*UnexportableKeyServiceFactory::GetInstance()->GetShared(),
context_.get(),
store_.get()) {}
SessionServiceImpl& service() { return service_; }
StrictMock<SessionStoreMock>& store() { return *store_; }
void OnSessionsLoaded() {
service().OnLoadSessionsComplete(SessionStore::SessionsMap());
}
void FinishLoadingSessions(SessionStore::SessionsMap loaded_sessions) {
service().OnLoadSessionsComplete(std::move(loaded_sessions));
}
size_t GetSiteSessionsCount(const SchemefulSite& site) {
auto [begin, end] = service().GetSessionsForSite(site);
return std::distance(begin, end);
}
private:
crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_;
std::unique_ptr<URLRequestContext> context_;
std::unique_ptr<StrictMock<SessionStoreMock>> store_;
SessionServiceImpl service_;
};
TEST_F(SessionServiceImplWithStoreTest, UsesSessionStore) {
{
InSequence seq;
EXPECT_CALL(store(), LoadSessions)
.Times(1)
.WillOnce(
Invoke(this, &SessionServiceImplWithStoreTest::OnSessionsLoaded));
EXPECT_CALL(store(), SaveSession).Times(1);
EXPECT_CALL(store(), DeleteSession).Times(1);
}
// Will invoke the store's load session method.
service().LoadSessionsAsync();
ScopedTestFetcher scoped_test_fetcher(kSessionId, kUrlString, kOrigin);
auto fetch_param = RegistrationFetcherParam::CreateInstanceForTesting(
kTestUrl, {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256},
"challenge", /*authorization=*/std::nullopt);
// Will invoke the store's save session method.
service().RegisterBoundSession(
base::DoNothing(), std::move(fetch_param),
IsolationInfo::CreateTransient(/*nonce=*/std::nullopt),
NetLogWithSource());
auto site = SchemefulSite(kTestUrl);
Session* session = service().GetSession(site, Session::Id(kSessionId));
ASSERT_TRUE(session);
EXPECT_EQ(GetSiteSessionsCount(site), 1u);
session->set_expiry_date(base::Time::Now() - base::Days(1));
// Will invoke the store's delete session method.
EXPECT_EQ(GetSiteSessionsCount(site), 0u);
}
TEST_F(SessionServiceImplWithStoreTest, GetAllSessionsWaitsForSessionsToLoad) {
// Start loading
EXPECT_CALL(store(), LoadSessions).Times(1);
service().LoadSessionsAsync();
// Request sessions, which should wait until we finish loading.
base::test::TestFuture<std::vector<SessionKey>> future;
service().GetAllSessionsAsync(
future.GetCallback<const std::vector<SessionKey>&>());
SessionParams::Scope scope;
scope.origin = "example.com";
std::unique_ptr<Session> session = Session::CreateIfValid(
SessionParams("session_id", "https://example.com/refresh",
std::move(scope),
/*creds=*/{}),
kTestUrl);
ASSERT_TRUE(session);
// Complete loading. If we did not defer, we'd miss this session.
SessionStore::SessionsMap session_map;
session_map.insert({SchemefulSite(kTestUrl), std::move(session)});
FinishLoadingSessions(std::move(session_map));
// But we did defer, so we found it.
EXPECT_THAT(future.Take(), UnorderedElementsAre(ExpectId("session_id")));
}
} // namespace net::device_bound_sessions