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(