From ba9354e6b4b38b97821ed5d55a8f3f24fab1944c Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Fri, 13 Feb 2026 09:17:42 +0800 Subject: [PATCH 1/2] 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( From 005e4c000baa0daaf8cf49870298b2c241c25c0e Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Thu, 26 Feb 2026 14:47:57 +0800 Subject: [PATCH 2/2] feat: Implement BasicAuthManager and refactor auth manager structure --- src/iceberg/catalog/rest/auth/auth_manager.cc | 45 ++++++++++++++ .../catalog/rest/auth/auth_manager_internal.h | 45 ++++++++++++++ .../catalog/rest/auth/auth_managers.cc | 59 +------------------ src/iceberg/test/auth_manager_test.cc | 4 +- 4 files changed, 95 insertions(+), 58 deletions(-) create mode 100644 src/iceberg/catalog/rest/auth/auth_manager_internal.h diff --git a/src/iceberg/catalog/rest/auth/auth_manager.cc b/src/iceberg/catalog/rest/auth/auth_manager.cc index af02f747a..14946aef6 100644 --- a/src/iceberg/catalog/rest/auth/auth_manager.cc +++ b/src/iceberg/catalog/rest/auth/auth_manager.cc @@ -19,7 +19,11 @@ #include "iceberg/catalog/rest/auth/auth_manager.h" +#include "iceberg/catalog/rest/auth/auth_manager_internal.h" +#include "iceberg/catalog/rest/auth/auth_properties.h" #include "iceberg/catalog/rest/auth/auth_session.h" +#include "iceberg/util/macros.h" +#include "iceberg/util/transform_util.h" namespace iceberg::rest::auth { @@ -45,4 +49,45 @@ Result> AuthManager::TableSession( return parent; } +/// \brief Authentication manager that performs no authentication. +class NoopAuthManager : public AuthManager { + public: + Result> CatalogSession( + [[maybe_unused]] HttpClient& client, + [[maybe_unused]] const std::unordered_map& properties) + override { + return AuthSession::MakeDefault({}); + } +}; + +Result> MakeNoopAuthManager( + [[maybe_unused]] std::string_view name, + [[maybe_unused]] const std::unordered_map& properties) { + return std::make_unique(); +} + +/// \brief Authentication manager that performs basic authentication. +class BasicAuthManager : public AuthManager { + public: + Result> CatalogSession( + [[maybe_unused]] HttpClient& client, + const std::unordered_map& properties) override { + auto username_it = properties.find(AuthProperties::kBasicUsername); + ICEBERG_PRECHECK(username_it != properties.end() && !username_it->second.empty(), + "Missing required property '{}'", AuthProperties::kBasicUsername); + auto password_it = properties.find(AuthProperties::kBasicPassword); + ICEBERG_PRECHECK(password_it != properties.end() && !password_it->second.empty(), + "Missing required property '{}'", AuthProperties::kBasicPassword); + std::string credential = username_it->second + ":" + password_it->second; + return AuthSession::MakeDefault( + {{"Authorization", "Basic " + TransformUtil::Base64Encode(credential)}}); + } +}; + +Result> MakeBasicAuthManager( + [[maybe_unused]] std::string_view name, + [[maybe_unused]] const std::unordered_map& properties) { + return std::make_unique(); +} + } // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_manager_internal.h b/src/iceberg/catalog/rest/auth/auth_manager_internal.h new file mode 100644 index 000000000..96e452390 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_manager_internal.h @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include "iceberg/catalog/rest/auth/auth_manager.h" +#include "iceberg/result.h" + +/// \file iceberg/catalog/rest/auth/auth_manager_internal.h +/// \brief Internal factory functions for built-in AuthManager implementations. + +namespace iceberg::rest::auth { + +/// \brief Create a no-op authentication manager (no authentication). +Result> MakeNoopAuthManager( + std::string_view name, + const std::unordered_map& properties); + +/// \brief Create a basic authentication manager. +Result> MakeBasicAuthManager( + std::string_view name, + const std::unordered_map& properties); + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index 7ca7a0f63..d0bf24844 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -21,10 +21,9 @@ #include +#include "iceberg/catalog/rest/auth/auth_manager_internal.h" #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 { @@ -62,62 +61,10 @@ std::string InferAuthType( return AuthProperties::kAuthTypeNone; } -/// \brief Authentication manager that performs no authentication. -class NoopAuthManager : 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, - [[maybe_unused]] const std::unordered_map& properties) - override { - return AuthSession::MakeDefault({}); - } -}; - -/// \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 - [](std::string_view name, const std::unordered_map& props) - -> Result> { return T::Make(name, props); }; -} - AuthManagerRegistry CreateDefaultRegistry() { return { - {AuthProperties::kAuthTypeNone, MakeAuthFactory()}, - {AuthProperties::kAuthTypeBasic, MakeAuthFactory()}, + {AuthProperties::kAuthTypeNone, MakeNoopAuthManager}, + {AuthProperties::kAuthTypeBasic, MakeBasicAuthManager}, }; } diff --git a/src/iceberg/test/auth_manager_test.cc b/src/iceberg/test/auth_manager_test.cc index f1fcedfff..82db393d0 100644 --- a/src/iceberg/test/auth_manager_test.cc +++ b/src/iceberg/test/auth_manager_test.cc @@ -129,7 +129,7 @@ TEST_F(AuthManagerTest, BasicAuthMissingUsername) { auto session_result = manager_result.value()->CatalogSession(client_, properties); EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument)); - EXPECT_THAT(session_result, HasErrorMessage("Invalid username")); + EXPECT_THAT(session_result, HasErrorMessage("Missing required property")); } // Verifies BasicAuthManager fails when password is missing @@ -142,7 +142,7 @@ TEST_F(AuthManagerTest, BasicAuthMissingPassword) { auto session_result = manager_result.value()->CatalogSession(client_, properties); EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument)); - EXPECT_THAT(session_result, HasErrorMessage("Invalid password")); + EXPECT_THAT(session_result, HasErrorMessage("Missing required property")); } // Verifies BasicAuthManager handles special characters in credentials