| // 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 "chrome/browser/webauthn/challenge_url_fetcher.h" |
| |
| #include <iterator> |
| #include <memory> |
| #include <utility> |
| |
| #include "base/memory/scoped_refptr.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "net/http/http_response_headers.h" |
| #include "services/network/public/cpp/url_loader_completion_status.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/public/mojom/fetch_api.mojom.h" |
| #include "services/network/public/mojom/url_response_head.mojom.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| constexpr char kUrl[] = "https://example.com/challenge_endpoint"; |
| constexpr uint8_t kTestChallenge[] = {0, 1, 2, 3, 4, 5, 6, 7, |
| 8, 9, 10, 11, 12, 13, 14, 15}; |
| constexpr uint8_t kSmallChallenge[] = {0, 1, 2, 3, 4, 5, 6, 7, |
| 8, 9, 10, 11, 12, 13, 14}; |
| |
| constexpr char kChallengeContentType[] = "application/x-webauthn-challenge"; |
| } // namespace |
| |
| class ChallengeUrlFetcherTest : public testing::Test { |
| public: |
| ChallengeUrlFetcherTest() = default; |
| |
| void SetUp() override { |
| fetcher_ = std::make_unique<ChallengeUrlFetcher>( |
| url_loader_factory_.GetSafeWeakWrapper()); |
| } |
| |
| ChallengeUrlFetcher* fetcher() { return fetcher_.get(); } |
| |
| network::TestURLLoaderFactory* url_loader_factory() { |
| return &url_loader_factory_; |
| } |
| |
| base::expected<std::vector<uint8_t>, |
| ChallengeUrlFetcher::ChallengeNotAvailableReason> |
| FetchChallengeAndWait() { |
| base::test::TestFuture<void> future; |
| fetcher()->FetchUrl(GURL(kUrl), future.GetCallback()); |
| EXPECT_TRUE(future.Wait()); |
| return fetcher()->GetChallenge(); |
| } |
| |
| private: |
| base::test::TaskEnvironment task_env_; |
| std::unique_ptr<ChallengeUrlFetcher> fetcher_; |
| network::TestURLLoaderFactory url_loader_factory_; |
| }; |
| |
| TEST_F(ChallengeUrlFetcherTest, ChallengeFetchSuccess) { |
| url_loader_factory()->SetInterceptor(base::BindLambdaForTesting( |
| [this](const network::ResourceRequest& request) { |
| EXPECT_EQ(request.redirect_mode, network::mojom::RedirectMode::kError); |
| EXPECT_EQ(request.credentials_mode, |
| network::mojom::CredentialsMode::kOmit); |
| EXPECT_EQ(fetcher()->GetChallenge().error(), |
| ChallengeUrlFetcher::ChallengeNotAvailableReason:: |
| kWaitingForChallenge); |
| auto head = network::mojom::URLResponseHead::New(); |
| head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(""); |
| head->headers->AddHeader(net::HttpRequestHeaders::kContentType, |
| kChallengeContentType); |
| std::string body(std::begin(kTestChallenge), std::end(kTestChallenge)); |
| url_loader_factory()->AddResponse( |
| GURL(kUrl), std::move(head), body, |
| network::URLLoaderCompletionStatus(net::Error::OK)); |
| })); |
| |
| auto result = FetchChallengeAndWait(); |
| std::vector<uint8_t> expected(std::begin(kTestChallenge), |
| std::end(kTestChallenge)); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_EQ(expected, result.value()); |
| } |
| |
| TEST_F(ChallengeUrlFetcherTest, ChallengeFetchError) { |
| url_loader_factory()->SetInterceptor(base::BindLambdaForTesting( |
| [this](const network::ResourceRequest& request) { |
| EXPECT_EQ(request.redirect_mode, network::mojom::RedirectMode::kError); |
| EXPECT_EQ(request.credentials_mode, |
| network::mojom::CredentialsMode::kOmit); |
| EXPECT_EQ(fetcher()->GetChallenge().error(), |
| ChallengeUrlFetcher::ChallengeNotAvailableReason:: |
| kWaitingForChallenge); |
| auto head = network::mojom::URLResponseHead::New(); |
| head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(""); |
| head->headers->AddHeader(net::HttpRequestHeaders::kContentType, |
| kChallengeContentType); |
| std::string body(std::begin(kTestChallenge), std::end(kTestChallenge)); |
| url_loader_factory()->AddResponse( |
| GURL(kUrl), std::move(head), body, |
| network::URLLoaderCompletionStatus(net::HTTP_NOT_FOUND)); |
| })); |
| |
| auto result = FetchChallengeAndWait(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ( |
| ChallengeUrlFetcher::ChallengeNotAvailableReason::kErrorFetchingChallenge, |
| result.error()); |
| } |
| |
| TEST_F(ChallengeUrlFetcherTest, ChallengeFetchMissingHeader) { |
| url_loader_factory()->SetInterceptor(base::BindLambdaForTesting( |
| [this](const network::ResourceRequest& request) { |
| EXPECT_EQ(request.redirect_mode, network::mojom::RedirectMode::kError); |
| EXPECT_EQ(request.credentials_mode, |
| network::mojom::CredentialsMode::kOmit); |
| EXPECT_EQ(fetcher()->GetChallenge().error(), |
| ChallengeUrlFetcher::ChallengeNotAvailableReason:: |
| kWaitingForChallenge); |
| std::string body(std::begin(kTestChallenge), std::end(kTestChallenge)); |
| url_loader_factory()->AddResponse(kUrl, body); |
| })); |
| |
| auto result = FetchChallengeAndWait(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ( |
| ChallengeUrlFetcher::ChallengeNotAvailableReason::kErrorFetchingChallenge, |
| result.error()); |
| } |
| |
| TEST_F(ChallengeUrlFetcherTest, ChallengeTooSmall) { |
| url_loader_factory()->SetInterceptor(base::BindLambdaForTesting( |
| [this](const network::ResourceRequest& request) { |
| EXPECT_EQ(request.redirect_mode, network::mojom::RedirectMode::kError); |
| EXPECT_EQ(request.credentials_mode, |
| network::mojom::CredentialsMode::kOmit); |
| EXPECT_EQ(fetcher()->GetChallenge().error(), |
| ChallengeUrlFetcher::ChallengeNotAvailableReason:: |
| kWaitingForChallenge); |
| auto head = network::mojom::URLResponseHead::New(); |
| head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(""); |
| head->headers->AddHeader(net::HttpRequestHeaders::kContentType, |
| kChallengeContentType); |
| std::string body(std::begin(kSmallChallenge), |
| std::end(kSmallChallenge)); |
| url_loader_factory()->AddResponse( |
| GURL(kUrl), std::move(head), body, |
| network::URLLoaderCompletionStatus(net::Error::OK)); |
| })); |
| |
| auto result = FetchChallengeAndWait(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ( |
| ChallengeUrlFetcher::ChallengeNotAvailableReason::kErrorFetchingChallenge, |
| result.error()); |
| } |