This is an automated email from the ASF dual-hosted git repository.

gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new f955a552 feat: Implement BasicAuthManager to support basic 
authentication (#564)
f955a552 is described below

commit f955a5529f018fddb284459502ae61870f3cbba4
Author: lishuxu <[email protected]>
AuthorDate: Thu Feb 26 16:25:46 2026 +0800

    feat: Implement BasicAuthManager to support basic authentication (#564)
---
 src/iceberg/catalog/rest/auth/auth_manager.cc      | 45 ++++++++++++
 .../{auth_manager.cc => auth_manager_internal.h}   | 41 +++++------
 src/iceberg/catalog/rest/auth/auth_managers.cc     | 29 +-------
 src/iceberg/test/auth_manager_test.cc              | 84 ++++++++++++++++++++++
 4 files changed, 151 insertions(+), 48 deletions(-)

diff --git a/src/iceberg/catalog/rest/auth/auth_manager.cc 
b/src/iceberg/catalog/rest/auth/auth_manager.cc
index af02f747..14946aef 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<std::shared_ptr<AuthSession>> 
AuthManager::TableSession(
   return parent;
 }
 
+/// \brief Authentication manager that performs no authentication.
+class NoopAuthManager : public AuthManager {
+ public:
+  Result<std::shared_ptr<AuthSession>> CatalogSession(
+      [[maybe_unused]] HttpClient& client,
+      [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
properties)
+      override {
+    return AuthSession::MakeDefault({});
+  }
+};
+
+Result<std::unique_ptr<AuthManager>> MakeNoopAuthManager(
+    [[maybe_unused]] std::string_view name,
+    [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
properties) {
+  return std::make_unique<NoopAuthManager>();
+}
+
+/// \brief Authentication manager that performs basic authentication.
+class BasicAuthManager : public AuthManager {
+ public:
+  Result<std::shared_ptr<AuthSession>> CatalogSession(
+      [[maybe_unused]] HttpClient& client,
+      const std::unordered_map<std::string, std::string>& 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<std::unique_ptr<AuthManager>> MakeBasicAuthManager(
+    [[maybe_unused]] std::string_view name,
+    [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
properties) {
+  return std::make_unique<BasicAuthManager>();
+}
+
 }  // namespace iceberg::rest::auth
diff --git a/src/iceberg/catalog/rest/auth/auth_manager.cc 
b/src/iceberg/catalog/rest/auth/auth_manager_internal.h
similarity index 54%
copy from src/iceberg/catalog/rest/auth/auth_manager.cc
copy to src/iceberg/catalog/rest/auth/auth_manager_internal.h
index af02f747..96e45239 100644
--- a/src/iceberg/catalog/rest/auth/auth_manager.cc
+++ b/src/iceberg/catalog/rest/auth/auth_manager_internal.h
@@ -17,32 +17,29 @@
  * under the License.
  */
 
+#pragma once
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+
 #include "iceberg/catalog/rest/auth/auth_manager.h"
+#include "iceberg/result.h"
 
-#include "iceberg/catalog/rest/auth/auth_session.h"
+/// \file iceberg/catalog/rest/auth/auth_manager_internal.h
+/// \brief Internal factory functions for built-in AuthManager implementations.
 
 namespace iceberg::rest::auth {
 
-Result<std::shared_ptr<AuthSession>> AuthManager::InitSession(
-    HttpClient& init_client,
-    const std::unordered_map<std::string, std::string>& properties) {
-  // By default, use the catalog session for initialization
-  return CatalogSession(init_client, properties);
-}
-
-Result<std::shared_ptr<AuthSession>> AuthManager::ContextualSession(
-    [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
context,
-    std::shared_ptr<AuthSession> parent) {
-  // By default, return the parent session as-is
-  return parent;
-}
-
-Result<std::shared_ptr<AuthSession>> AuthManager::TableSession(
-    [[maybe_unused]] const TableIdentifier& table,
-    [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
properties,
-    std::shared_ptr<AuthSession> parent) {
-  // By default, return the parent session as-is
-  return parent;
-}
+/// \brief Create a no-op authentication manager (no authentication).
+Result<std::unique_ptr<AuthManager>> MakeNoopAuthManager(
+    std::string_view name,
+    const std::unordered_map<std::string, std::string>& properties);
+
+/// \brief Create a basic authentication manager.
+Result<std::unique_ptr<AuthManager>> MakeBasicAuthManager(
+    std::string_view name,
+    const std::unordered_map<std::string, std::string>& 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 c1fe45f8..d0bf2484 100644
--- a/src/iceberg/catalog/rest/auth/auth_managers.cc
+++ b/src/iceberg/catalog/rest/auth/auth_managers.cc
@@ -21,8 +21,8 @@
 
 #include <unordered_set>
 
+#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"
 
 namespace iceberg::rest::auth {
@@ -61,33 +61,10 @@ std::string InferAuthType(
   return AuthProperties::kAuthTypeNone;
 }
 
-/// \brief Authentication manager that performs no authentication.
-class NoopAuthManager : public AuthManager {
- public:
-  static Result<std::unique_ptr<AuthManager>> Make(
-      [[maybe_unused]] std::string_view name,
-      [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
properties) {
-    return std::make_unique<NoopAuthManager>();
-  }
-
-  Result<std::shared_ptr<AuthSession>> CatalogSession(
-      [[maybe_unused]] HttpClient& client,
-      [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
properties)
-      override {
-    return AuthSession::MakeDefault({});
-  }
-};
-
-template <typename T>
-AuthManagerFactory MakeAuthFactory() {
-  return
-      [](std::string_view name, const std::unordered_map<std::string, 
std::string>& props)
-          -> Result<std::unique_ptr<AuthManager>> { return T::Make(name, 
props); };
-}
-
 AuthManagerRegistry CreateDefaultRegistry() {
   return {
-      {AuthProperties::kAuthTypeNone, MakeAuthFactory<NoopAuthManager>()},
+      {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 c6e9f123..82db393d 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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("Missing required property"));
+}
+
+// Verifies BasicAuthManager fails when password is missing
+TEST_F(AuthManagerTest, BasicAuthMissingPassword) {
+  std::unordered_map<std::string, std::string> 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("Missing required property"));
+}
+
+// Verifies BasicAuthManager handles special characters in credentials
+TEST_F(AuthManagerTest, BasicAuthSpecialCharacters) {
+  std::unordered_map<std::string, std::string> properties = {
+      {AuthProperties::kAuthType, "basic"},
+      {AuthProperties::kBasicUsername, "[email protected]"},
+      {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<std::string, std::string> headers;
+  EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk());
+  // base64("[email protected]:p@ss:w0rd!") == 
"dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE="
+  EXPECT_EQ(headers["Authorization"], "Basic 
dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE=");
+}
+
 // Verifies custom auth manager registration
 TEST_F(AuthManagerTest, RegisterCustomAuthManager) {
   AuthManagers::Register(

Reply via email to