blob: f43404885edca0a108a7afbfa286b15178554cd5 [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 "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());
}