From ba9354e6b4b38b97821ed5d55a8f3f24fab1944c Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Fri, 13 Feb 2026 09:17:42 +0800 Subject: [PATCH] feat: Implement BasicAuthManager to suppoert basic authentication --- .../catalog/rest/auth/auth_managers.cc | 30 +++++++ src/iceberg/test/auth_manager_test.cc | 84 +++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index c1fe45f87..7ca7a0f63 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -24,6 +24,7 @@ #include "iceberg/catalog/rest/auth/auth_properties.h" #include "iceberg/catalog/rest/auth/auth_session.h" #include "iceberg/util/string_util.h" +#include "iceberg/util/transform_util.h" namespace iceberg::rest::auth { @@ -78,6 +79,34 @@ class NoopAuthManager : public AuthManager { } }; +/// \brief Authentication manager that performs basic authentication. +class BasicAuthManager : public AuthManager { + public: + static Result> Make( + [[maybe_unused]] std::string_view name, + [[maybe_unused]] const std::unordered_map& properties) { + return std::make_unique(); + } + + Result> CatalogSession( + [[maybe_unused]] HttpClient& client, + const std::unordered_map& properties) override { + auto username_it = properties.find(AuthProperties::kBasicUsername); + if (username_it == properties.end()) { + return InvalidArgument("Invalid username: missing required property '{}'", + AuthProperties::kBasicUsername); + } + auto password_it = properties.find(AuthProperties::kBasicPassword); + if (password_it == properties.end()) { + return InvalidArgument("Invalid password: missing required property '{}'", + AuthProperties::kBasicPassword); + } + std::string credential = username_it->second + ":" + password_it->second; + return AuthSession::MakeDefault( + {{"Authorization", "Basic " + TransformUtil::Base64Encode(credential)}}); + } +}; + template AuthManagerFactory MakeAuthFactory() { return @@ -88,6 +117,7 @@ AuthManagerFactory MakeAuthFactory() { AuthManagerRegistry CreateDefaultRegistry() { return { {AuthProperties::kAuthTypeNone, MakeAuthFactory()}, + {AuthProperties::kAuthTypeBasic, MakeAuthFactory()}, }; } diff --git a/src/iceberg/test/auth_manager_test.cc b/src/iceberg/test/auth_manager_test.cc index c6e9f1231..f1fcedfff 100644 --- a/src/iceberg/test/auth_manager_test.cc +++ b/src/iceberg/test/auth_manager_test.cc @@ -80,6 +80,90 @@ TEST_F(AuthManagerTest, UnknownAuthTypeReturnsInvalidArgument) { EXPECT_THAT(result, HasErrorMessage("Unknown authentication type")); } +// Verifies loading BasicAuthManager with valid credentials +TEST_F(AuthManagerTest, LoadBasicAuthManager) { + std::unordered_map properties = { + {AuthProperties::kAuthType, "basic"}, + {AuthProperties::kBasicUsername, "admin"}, + {AuthProperties::kBasicPassword, "secret"}}; + + auto manager_result = AuthManagers::Load("test-catalog", properties); + ASSERT_THAT(manager_result, IsOk()); + + auto session_result = manager_result.value()->CatalogSession(client_, properties); + ASSERT_THAT(session_result, IsOk()); + + std::unordered_map headers; + EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); + // base64("admin:secret") == "YWRtaW46c2VjcmV0" + EXPECT_EQ(headers["Authorization"], "Basic YWRtaW46c2VjcmV0"); +} + +// Verifies BasicAuthManager is case-insensitive for auth type +TEST_F(AuthManagerTest, BasicAuthTypeCaseInsensitive) { + for (const auto& auth_type : {"BASIC", "Basic", "bAsIc"}) { + std::unordered_map properties = { + {AuthProperties::kAuthType, auth_type}, + {AuthProperties::kBasicUsername, "user"}, + {AuthProperties::kBasicPassword, "pass"}}; + auto manager_result = AuthManagers::Load("test-catalog", properties); + ASSERT_THAT(manager_result, IsOk()) << "Failed for auth type: " << auth_type; + + auto session_result = manager_result.value()->CatalogSession(client_, properties); + ASSERT_THAT(session_result, IsOk()) << "Failed for auth type: " << auth_type; + + std::unordered_map headers; + EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); + // base64("user:pass") == "dXNlcjpwYXNz" + EXPECT_EQ(headers["Authorization"], "Basic dXNlcjpwYXNz"); + } +} + +// Verifies BasicAuthManager fails when username is missing +TEST_F(AuthManagerTest, BasicAuthMissingUsername) { + std::unordered_map properties = { + {AuthProperties::kAuthType, "basic"}, {AuthProperties::kBasicPassword, "secret"}}; + + auto manager_result = AuthManagers::Load("test-catalog", properties); + ASSERT_THAT(manager_result, IsOk()); + + auto session_result = manager_result.value()->CatalogSession(client_, properties); + EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument)); + EXPECT_THAT(session_result, HasErrorMessage("Invalid username")); +} + +// Verifies BasicAuthManager fails when password is missing +TEST_F(AuthManagerTest, BasicAuthMissingPassword) { + std::unordered_map properties = { + {AuthProperties::kAuthType, "basic"}, {AuthProperties::kBasicUsername, "admin"}}; + + auto manager_result = AuthManagers::Load("test-catalog", properties); + ASSERT_THAT(manager_result, IsOk()); + + auto session_result = manager_result.value()->CatalogSession(client_, properties); + EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument)); + EXPECT_THAT(session_result, HasErrorMessage("Invalid password")); +} + +// Verifies BasicAuthManager handles special characters in credentials +TEST_F(AuthManagerTest, BasicAuthSpecialCharacters) { + std::unordered_map properties = { + {AuthProperties::kAuthType, "basic"}, + {AuthProperties::kBasicUsername, "user@domain.com"}, + {AuthProperties::kBasicPassword, "p@ss:w0rd!"}}; + + auto manager_result = AuthManagers::Load("test-catalog", properties); + ASSERT_THAT(manager_result, IsOk()); + + auto session_result = manager_result.value()->CatalogSession(client_, properties); + ASSERT_THAT(session_result, IsOk()); + + std::unordered_map headers; + EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); + // base64("user@domain.com:p@ss:w0rd!") == "dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE=" + EXPECT_EQ(headers["Authorization"], "Basic dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE="); +} + // Verifies custom auth manager registration TEST_F(AuthManagerTest, RegisterCustomAuthManager) { AuthManagers::Register(