| // 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/registration_fetcher.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/run_loop.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "components/unexportable_keys/mock_unexportable_key_service.h" |
| #include "components/unexportable_keys/unexportable_key_service.h" |
| #include "components/unexportable_keys/unexportable_key_service_impl.h" |
| #include "components/unexportable_keys/unexportable_key_task_manager.h" |
| #include "crypto/scoped_mock_unexportable_key_provider.h" |
| #include "net/base/features.h" |
| #include "net/base/network_anonymization_key.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/cookies/cookie_access_result.h" |
| #include "net/cookies/cookie_store.h" |
| #include "net/cookies/cookie_store_test_callbacks.h" |
| #include "net/device_bound_sessions/registration_request_param.h" |
| #include "net/device_bound_sessions/test_support.h" |
| #include "net/http/http_request_headers.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_status_code.h" |
| #include "net/log/test_net_log.h" |
| #include "net/socket/socket_test_util.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/test/test_with_task_environment.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_context_builder.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| |
| namespace net::device_bound_sessions { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::ElementsAre; |
| using ::testing::Invoke; |
| using ::testing::Return; |
| using ::testing::WithArg; |
| |
| constexpr char kBasicValidJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "scope": { |
| "include_site": true, |
| "scope_specification" : [ |
| { |
| "type": "include", |
| "domain": "trusted.example.com", |
| "path": "/only_trusted_path" |
| } |
| ] |
| }, |
| "credentials": [{ |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| |
| constexpr char kSessionIdentifier[] = "session_id"; |
| constexpr char kRedirectPath[] = "/redirect"; |
| constexpr char kChallenge[] = "test_challenge"; |
| const GURL kRegistrationUrl = GURL("https://www.example.test/startsession"); |
| constexpr unexportable_keys::BackgroundTaskPriority kTaskPriority = |
| unexportable_keys::BackgroundTaskPriority::kBestEffort; |
| std::vector<crypto::SignatureVerifier::SignatureAlgorithm> CreateAlgArray() { |
| return {crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256, |
| crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256}; |
| } |
| |
| struct InvokeCallbackArgumentAction {}; |
| |
| class RegistrationTest : public TestWithTaskEnvironment { |
| protected: |
| RegistrationTest() |
| : server_(test_server::EmbeddedTestServer::TYPE_HTTPS), |
| context_(CreateTestURLRequestContextBuilder()->Build()), |
| unexportable_key_service_(task_manager_) {} |
| |
| unexportable_keys::UnexportableKeyService& unexportable_key_service() { |
| return unexportable_key_service_; |
| } |
| |
| RegistrationFetcherParam GetBasicParam( |
| std::optional<GURL> url = std::nullopt) { |
| if (!url) { |
| url = server_.GetURL("/"); |
| } |
| |
| return RegistrationFetcherParam::CreateInstanceForTesting( |
| *url, CreateAlgArray(), std::string(kChallenge), |
| /*authorization=*/std::nullopt); |
| } |
| |
| void CreateKeyAndRunCallback( |
| base::OnceCallback<void(unexportable_keys::ServiceErrorOr< |
| unexportable_keys::UnexportableKeyId>)> |
| callback) { |
| unexportable_key_service_.GenerateSigningKeySlowlyAsync( |
| CreateAlgArray(), kTaskPriority, std::move(callback)); |
| } |
| |
| test_server::EmbeddedTestServer server_; |
| std::unique_ptr<URLRequestContext> context_; |
| |
| const url::Origin kOrigin = url::Origin::Create(GURL("https://origin/")); |
| unexportable_keys::UnexportableKeyTaskManager task_manager_{ |
| crypto::UnexportableKeyProvider::Config()}; |
| unexportable_keys::UnexportableKeyServiceImpl unexportable_key_service_; |
| }; |
| |
| class TestRegistrationCallback { |
| public: |
| TestRegistrationCallback() = default; |
| |
| RegistrationFetcher::RegistrationCompleteCallback callback() { |
| return base::BindOnce(&TestRegistrationCallback::OnRegistrationComplete, |
| base::Unretained(this)); |
| } |
| |
| void WaitForCall() { |
| if (called_) { |
| return; |
| } |
| |
| base::RunLoop run_loop; |
| |
| waiting_ = true; |
| closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| } |
| |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> outcome() { |
| EXPECT_TRUE(called_); |
| return std::move(outcome_); |
| } |
| |
| private: |
| void OnRegistrationComplete( |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> params) { |
| EXPECT_FALSE(called_); |
| |
| called_ = true; |
| outcome_ = std::move(params); |
| |
| if (waiting_) { |
| waiting_ = false; |
| std::move(closure_).Run(); |
| } |
| } |
| |
| bool called_ = false; |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> outcome_ = |
| std::nullopt; |
| |
| bool waiting_ = false; |
| base::OnceClosure closure_; |
| }; |
| |
| std::unique_ptr<test_server::HttpResponse> ReturnResponse( |
| HttpStatusCode code, |
| std::string_view response_text, |
| const test_server::HttpRequest& request) { |
| auto response = std::make_unique<test_server::BasicHttpResponse>(); |
| response->set_code(code); |
| response->set_content_type("application/json"); |
| response->set_content(response_text); |
| return response; |
| } |
| |
| std::unique_ptr<test_server::HttpResponse> ReturnUnauthorized( |
| const test_server::HttpRequest& request) { |
| auto response = std::make_unique<test_server::BasicHttpResponse>(); |
| response->set_code(HTTP_UNAUTHORIZED); |
| response->AddCustomHeader("Sec-Session-Challenge", R"("challenge")"); |
| return response; |
| } |
| |
| std::unique_ptr<test_server::HttpResponse> ReturnTextResponse( |
| const test_server::HttpRequest& request) { |
| auto response = std::make_unique<test_server::BasicHttpResponse>(); |
| response->set_code(HTTP_OK); |
| response->set_content_type("text/plain"); |
| response->set_content("some content"); |
| return response; |
| } |
| |
| std::unique_ptr<test_server::HttpResponse> ReturnInvalidResponse( |
| const test_server::HttpRequest& request) { |
| return std::make_unique<test_server::RawHttpResponse>( |
| "", "Not a valid HTTP response."); |
| } |
| |
| class UnauthorizedThenSuccessResponseContainer { |
| public: |
| UnauthorizedThenSuccessResponseContainer(int unauthorize_response_times) |
| : run_times(0), error_respose_times(unauthorize_response_times) {} |
| |
| std::unique_ptr<test_server::HttpResponse> Return( |
| const test_server::HttpRequest& request) { |
| if (run_times++ < error_respose_times) { |
| return ReturnUnauthorized(request); |
| } |
| return ReturnResponse(HTTP_OK, kBasicValidJson, request); |
| } |
| |
| private: |
| int run_times; |
| int error_respose_times; |
| }; |
| |
| TEST_F(RegistrationTest, BasicSuccess) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating([](const test_server::HttpRequest& request) { |
| auto resp_iter = request.headers.find("Sec-Session-Response"); |
| EXPECT_TRUE(resp_iter != request.headers.end()); |
| if (resp_iter != request.headers.end()) { |
| EXPECT_TRUE(VerifyEs256Jwt(resp_iter->second)); |
| } |
| return ReturnResponse(HTTP_OK, kBasicValidJson, request); |
| })); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| EXPECT_THAT(session_params->scope.specifications, |
| ElementsAre(SessionParams::Scope::Specification( |
| SessionParams::Scope::Specification::Type::kInclude, |
| "trusted.example.com", "/only_trusted_path"))); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, NoScopeJson) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "credentials": [{ |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_FALSE(session_params->scope.include_site); |
| EXPECT_TRUE(session_params->scope.specifications.empty()); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, NoSessionIdJson) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "credentials": [{ |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_FALSE(out_params); |
| } |
| |
| TEST_F(RegistrationTest, SpecificationNotDictJson) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "scope": { |
| "include_site": true, |
| "scope_specification" : [ |
| "type", "domain", "path" |
| ] |
| }, |
| "credentials": [{ |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| EXPECT_TRUE(session_params->scope.specifications.empty()); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, OneMissingPath) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "scope": { |
| "include_site": true, |
| "scope_specification" : [ |
| { |
| "type": "include", |
| "domain": "trusted.example.com" |
| }, |
| { |
| "type": "exclude", |
| "domain": "new.example.com", |
| "path": "/only_trusted_path" |
| } |
| ] |
| }, |
| "credentials": [{ |
| "type": "cookie", |
| "name": "other_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| |
| EXPECT_THAT(session_params->scope.specifications, |
| ElementsAre(SessionParams::Scope::Specification( |
| SessionParams::Scope::Specification::Type::kExclude, |
| "new.example.com", "/only_trusted_path"))); |
| |
| EXPECT_THAT(session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "other_cookie", |
| "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, OneSpecTypeInvalid) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "scope": { |
| "include_site": true, |
| "scope_specification" : [ |
| { |
| "type": "invalid", |
| "domain": "trusted.example.com", |
| "path": "/only_trusted_path" |
| }, |
| { |
| "type": "exclude", |
| "domain": "new.example.com", |
| "path": "/only_trusted_path" |
| } |
| ] |
| }, |
| "credentials": [{ |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| |
| EXPECT_THAT(session_params->scope.specifications, |
| ElementsAre(SessionParams::Scope::Specification( |
| SessionParams::Scope::Specification::Type::kExclude, |
| "new.example.com", "/only_trusted_path"))); |
| |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, InvalidTypeSpecList) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "scope": { |
| "include_site": true, |
| "scope_specification" : "missing" |
| }, |
| "credentials": [{ |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| EXPECT_TRUE(session_params->scope.specifications.empty()); |
| } |
| |
| TEST_F(RegistrationTest, TypeIsNotCookie) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "credentials": [{ |
| "type": "sync auth", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, TwoTypesCookie_NotCookie) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "credentials": [ |
| { |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }, |
| { |
| "type": "sync auth", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| } |
| ] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, TwoTypesNotCookie_Cookie) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "credentials": [ |
| { |
| "type": "sync auth", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }, |
| { |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| } |
| ] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, CredEntryWithoutDict) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "credentials": [{ |
| "type": "cookie", |
| "name": "auth_cookie", |
| "attributes": "Domain=example.com; Path=/; Secure; SameSite=None" |
| }, |
| "test"] |
| })"; |
| |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, ReturnTextFile) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler(base::BindRepeating(&ReturnTextResponse)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, ReturnInvalidJson) { |
| std::string invalid_json = "*{}"; |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, invalid_json)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, ReturnEmptyJson) { |
| std::string empty_json = "{}"; |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, empty_json)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, NetworkErrorServerShutdown) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| ASSERT_TRUE(server_.Start()); |
| GURL url = server_.GetURL("/"); |
| ASSERT_TRUE(server_.ShutdownAndWaitUntilComplete()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(url); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, NetworkErrorInvalidResponse) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler(base::BindRepeating(&ReturnInvalidResponse)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, ServerError500) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler(base::BindRepeating( |
| &ReturnResponse, HTTP_INTERNAL_SERVER_ERROR, kBasicValidJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, ServerErrorReturnOne401ThenSuccess) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| |
| auto* container = new UnauthorizedThenSuccessResponseContainer(1); |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&UnauthorizedThenSuccessResponseContainer::Return, |
| base::Owned(container))); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| EXPECT_THAT(session_params->scope.specifications, |
| ElementsAre(SessionParams::Scope::Specification( |
| SessionParams::Scope::Specification::Type::kInclude, |
| "trusted.example.com", "/only_trusted_path"))); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| std::unique_ptr<test_server::HttpResponse> ReturnRedirect( |
| const std::string& location, |
| const test_server::HttpRequest& request) { |
| if (request.relative_url != "/") { |
| return nullptr; |
| } |
| |
| auto response = std::make_unique<test_server::BasicHttpResponse>(); |
| response->set_code(HTTP_FOUND); |
| response->AddCustomHeader("Location", location); |
| response->set_content("Redirected"); |
| response->set_content_type("text/plain"); |
| return std::move(response); |
| } |
| |
| std::unique_ptr<test_server::HttpResponse> CheckRedirect( |
| bool* redirect_followed_out, |
| const test_server::HttpRequest& request) { |
| if (request.relative_url != kRedirectPath) { |
| return nullptr; |
| } |
| |
| *redirect_followed_out = true; |
| return ReturnResponse(HTTP_OK, kBasicValidJson, request); |
| } |
| |
| TEST_F(RegistrationTest, FollowHttpsRedirect) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| bool followed = false; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnRedirect, kRedirectPath)); |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&CheckRedirect, &followed)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| |
| EXPECT_TRUE(followed); |
| EXPECT_NE(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, DontFollowHttpRedirect) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| bool followed = false; |
| test_server::EmbeddedTestServer http_server_; |
| ASSERT_TRUE(http_server_.Start()); |
| const GURL target = http_server_.GetURL(kRedirectPath); |
| |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnRedirect, target.spec())); |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&CheckRedirect, &followed)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| |
| EXPECT_FALSE(followed); |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| TEST_F(RegistrationTest, FailOnSslErrorExpired) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson)); |
| server_.SetSSLConfig(net::EmbeddedTestServer::CERT_EXPIRED); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcherParam params = GetBasicParam(); |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| std::move(params), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| |
| callback.WaitForCall(); |
| EXPECT_EQ(callback.outcome(), std::nullopt); |
| } |
| |
| std::unique_ptr<test_server::HttpResponse> ReturnResponseForRefreshRequest( |
| const test_server::HttpRequest& request) { |
| auto response = std::make_unique<test_server::BasicHttpResponse>(); |
| |
| auto resp_iter = request.headers.find("Sec-Session-Response"); |
| std::string session_response = |
| resp_iter != request.headers.end() ? resp_iter->second : ""; |
| if (session_response.empty()) { |
| const auto session_iter = request.headers.find("Sec-Session-Id"); |
| EXPECT_TRUE(session_iter != request.headers.end() && |
| !session_iter->second.empty()); |
| |
| response->set_code(HTTP_UNAUTHORIZED); |
| response->AddCustomHeader("Sec-Session-Challenge", |
| R"("test_challenge";id="session_id")"); |
| return response; |
| } |
| |
| response->set_code(HTTP_OK); |
| response->set_content_type("application/json"); |
| response->set_content(kBasicValidJson); |
| return response; |
| } |
| |
| TEST_F(RegistrationTest, BasicSuccessForExistingKey) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| auto isolation_info = IsolationInfo::CreateTransient(/*nonce=*/std::nullopt); |
| auto request_param = RegistrationRequestParam::CreateForTesting( |
| server_.base_url(), kSessionIdentifier, kChallenge); |
| CreateKeyAndRunCallback(base::BindOnce( |
| &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param), |
| std::ref(unexportable_key_service()), context_.get(), |
| std::ref(isolation_info), /*net_log_source=*/std::nullopt, |
| callback.callback())); |
| |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| EXPECT_THAT(session_params->scope.specifications, |
| ElementsAre(SessionParams::Scope::Specification( |
| SessionParams::Scope::Specification::Type::kInclude, |
| "trusted.example.com", "/only_trusted_path"))); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, FetchRegistrationWithCachedChallenge) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponseForRefreshRequest)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| auto request_param = RegistrationRequestParam::CreateForTesting( |
| server_.base_url(), kSessionIdentifier, kChallenge); |
| auto isolation_info = IsolationInfo::CreateTransient(/*nonce=*/std::nullopt); |
| CreateKeyAndRunCallback(base::BindOnce( |
| &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param), |
| std::ref(unexportable_key_service()), context_.get(), |
| std::ref(isolation_info), /*net_log_source=*/std::nullopt, |
| callback.callback())); |
| |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| EXPECT_THAT(session_params->scope.specifications, |
| ElementsAre(SessionParams::Scope::Specification( |
| SessionParams::Scope::Specification::Type::kInclude, |
| "trusted.example.com", "/only_trusted_path"))); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, FetchRegistrationAndChallengeRequired) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponseForRefreshRequest)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| auto request_param = RegistrationRequestParam::CreateForTesting( |
| server_.base_url(), kSessionIdentifier, std::nullopt); |
| auto isolation_info = IsolationInfo::CreateTransient(/*nonce=*/std::nullopt); |
| CreateKeyAndRunCallback(base::BindOnce( |
| &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param), |
| std::ref(unexportable_key_service()), context_.get(), |
| std::ref(isolation_info), /*net_log_source=*/std::nullopt, |
| callback.callback())); |
| |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_TRUE(session_params->scope.include_site); |
| EXPECT_THAT(session_params->scope.specifications, |
| ElementsAre(SessionParams::Scope::Specification( |
| SessionParams::Scope::Specification::Type::kInclude, |
| "trusted.example.com", "/only_trusted_path"))); |
| EXPECT_THAT( |
| session_params->credentials, |
| ElementsAre(SessionParams::Credential( |
| "auth_cookie", "Domain=example.com; Path=/; Secure; SameSite=None"))); |
| } |
| |
| TEST_F(RegistrationTest, ContinueFalse) { |
| constexpr char kTestingJson[] = |
| R"({ |
| "session_identifier": "session_id", |
| "continue": false |
| })"; |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kTestingJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionTerminationParams* session_params = |
| std::get_if<SessionTerminationParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_EQ(session_params->session_id, "session_id"); |
| } |
| |
| TEST_F(RegistrationTest, RetriesOnKeyFailure) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| unexportable_keys::MockUnexportableKeyService mock_service; |
| |
| // We only want to mock the first call to SignSlowlyAsync, so proxy |
| // other required calls to `unexportable_key_service()`. |
| EXPECT_CALL(mock_service, GetAlgorithm(_)) |
| .WillRepeatedly( |
| Invoke(&unexportable_key_service(), |
| &unexportable_keys::UnexportableKeyService::GetAlgorithm)); |
| EXPECT_CALL(mock_service, GetSubjectPublicKeyInfo(_)) |
| .WillRepeatedly(Invoke( |
| &unexportable_key_service(), |
| &unexportable_keys::UnexportableKeyService::GetSubjectPublicKeyInfo)); |
| EXPECT_CALL(mock_service, SignSlowlyAsync(_, _, _, _)) |
| .WillOnce(base::test::RunOnceCallback<3>( |
| base::unexpected(unexportable_keys::ServiceError::kCryptoApiFailed))) |
| .WillOnce( |
| Invoke(&unexportable_key_service(), |
| &unexportable_keys::UnexportableKeyService::SignSlowlyAsync)); |
| |
| TestRegistrationCallback callback; |
| auto isolation_info = IsolationInfo::CreateTransient(/*nonce=*/std::nullopt); |
| auto request_param = RegistrationRequestParam::CreateForTesting( |
| server_.base_url(), kSessionIdentifier, kChallenge); |
| CreateKeyAndRunCallback(base::BindOnce( |
| &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param), |
| std::ref(mock_service), context_.get(), std::ref(isolation_info), |
| /*net_log_source=*/std::nullopt, callback.callback())); |
| callback.WaitForCall(); |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionParams* session_params = |
| std::get_if<SessionParams>(&out_params->params); |
| EXPECT_TRUE(session_params); |
| } |
| |
| TEST_F(RegistrationTest, TerminateSessionOnRepeatedFailure) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| unexportable_keys::MockUnexportableKeyService mock_service; |
| |
| EXPECT_CALL(mock_service, GetAlgorithm(_)) |
| .WillRepeatedly( |
| Invoke(&unexportable_key_service(), |
| &unexportable_keys::UnexportableKeyService::GetAlgorithm)); |
| EXPECT_CALL(mock_service, GetSubjectPublicKeyInfo(_)) |
| .WillRepeatedly(Invoke( |
| &unexportable_key_service(), |
| &unexportable_keys::UnexportableKeyService::GetSubjectPublicKeyInfo)); |
| EXPECT_CALL(mock_service, SignSlowlyAsync(_, _, _, _)) |
| .WillRepeatedly(base::test::RunOnceCallbackRepeatedly<3>( |
| base::unexpected(unexportable_keys::ServiceError::kCryptoApiFailed))); |
| |
| TestRegistrationCallback callback; |
| auto request_param = RegistrationRequestParam::CreateForTesting( |
| server_.base_url(), kSessionIdentifier, kChallenge); |
| CreateKeyAndRunCallback(base::BindOnce( |
| &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param), |
| std::ref(mock_service), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback())); |
| callback.WaitForCall(); |
| |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionTerminationParams* session_params = |
| std::get_if<SessionTerminationParams>(&out_params->params); |
| EXPECT_TRUE(session_params); |
| } |
| |
| TEST_F(RegistrationTest, NetLogResultLogged) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&ReturnResponse, HTTP_OK, kBasicValidJson)); |
| ASSERT_TRUE(server_.Start()); |
| |
| RecordingNetLogObserver net_log_observer; |
| TestRegistrationCallback callback; |
| RegistrationFetcher::StartCreateTokenAndFetch( |
| GetBasicParam(), unexportable_key_service(), context_.get(), |
| IsolationInfo::CreateTransient(/*nonce=*/std::nullopt), |
| /*net_log_source=*/std::nullopt, callback.callback()); |
| callback.WaitForCall(); |
| |
| EXPECT_EQ( |
| net_log_observer.GetEntriesWithType(NetLogEventType::DBSC_REFRESH_RESULT) |
| .size(), |
| 1u); |
| } |
| |
| TEST_F(RegistrationTest, TerminateSessionOnRepeatedChallenge) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| |
| auto* container = new UnauthorizedThenSuccessResponseContainer(100); |
| server_.RegisterRequestHandler( |
| base::BindRepeating(&UnauthorizedThenSuccessResponseContainer::Return, |
| base::Owned(container))); |
| ASSERT_TRUE(server_.Start()); |
| |
| TestRegistrationCallback callback; |
| auto isolation_info = IsolationInfo::CreateTransient(/*nonce=*/std::nullopt); |
| auto request_param = RegistrationRequestParam::CreateForTesting( |
| server_.base_url(), kSessionIdentifier, kChallenge); |
| CreateKeyAndRunCallback(base::BindOnce( |
| &RegistrationFetcher::StartFetchWithExistingKey, std::move(request_param), |
| std::ref(unexportable_key_service()), context_.get(), |
| std::ref(isolation_info), /*net_log_source=*/std::nullopt, |
| callback.callback())); |
| callback.WaitForCall(); |
| |
| std::optional<RegistrationFetcher::RegistrationCompleteParams> out_params = |
| callback.outcome(); |
| ASSERT_TRUE(out_params); |
| const SessionTerminationParams* session_params = |
| std::get_if<SessionTerminationParams>(&out_params->params); |
| ASSERT_TRUE(session_params); |
| EXPECT_EQ(session_params->session_id, kSessionIdentifier); |
| } |
| |
| class RegistrationTokenHelperTest : public testing::Test { |
| public: |
| RegistrationTokenHelperTest() : unexportable_key_service_(task_manager_) {} |
| |
| unexportable_keys::UnexportableKeyService& unexportable_key_service() { |
| return unexportable_key_service_; |
| } |
| |
| void RunBackgroundTasks() { task_environment_.RunUntilIdle(); } |
| |
| private: |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::ThreadPoolExecutionMode:: |
| QUEUED}; // QUEUED - tasks don't run until `RunUntilIdle()` is |
| // called. |
| unexportable_keys::UnexportableKeyTaskManager task_manager_{ |
| crypto::UnexportableKeyProvider::Config()}; |
| unexportable_keys::UnexportableKeyServiceImpl unexportable_key_service_; |
| }; |
| |
| TEST_F(RegistrationTokenHelperTest, CreateSuccess) { |
| crypto::ScopedMockUnexportableKeyProvider scoped_mock_key_provider_; |
| base::test::TestFuture< |
| std::optional<RegistrationFetcher::RegistrationTokenResult>> |
| future; |
| RegistrationFetcher::CreateTokenAsyncForTesting( |
| unexportable_key_service(), "test_challenge", |
| GURL("https://accounts.example.test.com/Register"), |
| /*authorization=*/std::nullopt, future.GetCallback()); |
| RunBackgroundTasks(); |
| ASSERT_TRUE(future.Get().has_value()); |
| } |
| |
| TEST_F(RegistrationTokenHelperTest, CreateFail) { |
| crypto::ScopedNullUnexportableKeyProvider scoped_null_key_provider_; |
| base::test::TestFuture< |
| std::optional<RegistrationFetcher::RegistrationTokenResult>> |
| future; |
| RegistrationFetcher::CreateTokenAsyncForTesting( |
| unexportable_key_service(), "test_challenge", |
| GURL("https://https://accounts.example.test/Register"), |
| /*authorization=*/std::nullopt, future.GetCallback()); |
| RunBackgroundTasks(); |
| EXPECT_FALSE(future.Get().has_value()); |
| } |
| |
| } // namespace |
| |
| } // namespace net::device_bound_sessions |