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 721e5294 feat: Implement NoopAuthManager and integrate it with 
RestCatalog (#544)
721e5294 is described below

commit 721e5294e7ad6c734e5c91a92ecd6e53570e0431
Author: lishuxu <[email protected]>
AuthorDate: Wed Feb 11 22:48:18 2026 +0800

    feat: Implement NoopAuthManager and integrate it with RestCatalog (#544)
    
    - Add NoopAuthManager for "none" authentication type
    - Register "none" auth type in static registry with case-insensitive 
matching
    - Add KnownAuthTypes() to distinguish known-but-unimplemented types
    (NotImplemented) from unknown types (InvalidArgument)
    - Integrate AuthManager into RestCatalog
---
 src/iceberg/catalog/rest/auth/auth_managers.cc |  51 ++++++++++-
 src/iceberg/catalog/rest/http_client.cc        |  63 +++++++-------
 src/iceberg/catalog/rest/http_client.h         |  13 +--
 src/iceberg/catalog/rest/rest_catalog.cc       | 105 +++++++++++++++--------
 src/iceberg/catalog/rest/rest_catalog.h        |   9 +-
 src/iceberg/test/CMakeLists.txt                |   1 +
 src/iceberg/test/auth_manager_test.cc          | 114 +++++++++++++++++++++++++
 src/iceberg/test/meson.build                   |   7 +-
 src/iceberg/test/rest_catalog_test.cc          |   5 +-
 9 files changed, 291 insertions(+), 77 deletions(-)

diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc 
b/src/iceberg/catalog/rest/auth/auth_managers.cc
index f56cb2e1..c1fe45f8 100644
--- a/src/iceberg/catalog/rest/auth/auth_managers.cc
+++ b/src/iceberg/catalog/rest/auth/auth_managers.cc
@@ -19,7 +19,10 @@
 
 #include "iceberg/catalog/rest/auth/auth_managers.h"
 
+#include <unordered_set>
+
 #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 {
@@ -30,6 +33,16 @@ namespace {
 using AuthManagerRegistry =
     std::unordered_map<std::string, AuthManagerFactory, StringHash, 
StringEqual>;
 
+const std::unordered_set<std::string, StringHash, StringEqual>& 
KnownAuthTypes() {
+  static const std::unordered_set<std::string, StringHash, StringEqual> 
kAuthTypes = {
+      AuthProperties::kAuthTypeNone,
+      AuthProperties::kAuthTypeBasic,
+      AuthProperties::kAuthTypeOAuth2,
+      AuthProperties::kAuthTypeSigV4,
+  };
+  return kAuthTypes;
+}
+
 // Infer the authentication type from properties.
 std::string InferAuthType(
     const std::unordered_map<std::string, std::string>& properties) {
@@ -48,9 +61,39 @@ 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>()},
+  };
+}
+
 // Get the global registry of auth manager factories.
 AuthManagerRegistry& GetRegistry() {
-  static AuthManagerRegistry registry;
+  static AuthManagerRegistry registry = CreateDefaultRegistry();
   return registry;
 }
 
@@ -68,8 +111,10 @@ Result<std::unique_ptr<AuthManager>> AuthManagers::Load(
   auto& registry = GetRegistry();
   auto it = registry.find(auth_type);
   if (it == registry.end()) {
-    // TODO(Li Shuxu): Fallback to default auth manager implementations
-    return NotImplemented("Authentication type '{}' is not supported", 
auth_type);
+    if (KnownAuthTypes().contains(auth_type)) {
+      return NotImplemented("Authentication type '{}' is not yet supported", 
auth_type);
+    }
+    return InvalidArgument("Unknown authentication type: '{}'", auth_type);
   }
 
   return it->second(name, properties);
diff --git a/src/iceberg/catalog/rest/http_client.cc 
b/src/iceberg/catalog/rest/http_client.cc
index 82a118c5..b0824621 100644
--- a/src/iceberg/catalog/rest/http_client.cc
+++ b/src/iceberg/catalog/rest/http_client.cc
@@ -22,6 +22,7 @@
 #include <cpr/cpr.h>
 #include <nlohmann/json.hpp>
 
+#include "iceberg/catalog/rest/auth/auth_session.h"
 #include "iceberg/catalog/rest/constant.h"
 #include "iceberg/catalog/rest/error_handlers.h"
 #include "iceberg/catalog/rest/json_serde_internal.h"
@@ -67,19 +68,17 @@ namespace {
 /// \brief Default error type for unparseable REST responses.
 constexpr std::string_view kRestExceptionType = "RESTException";
 
-/// \brief Merges global default headers with request-specific headers.
-///
-/// Combines the global headers derived from RestCatalogProperties with the 
headers
-/// passed in the specific request. Request-specific headers have higher 
priority
-/// and will override global defaults if the keys conflict (e.g., overriding
-/// the default "Content-Type").
-cpr::Header MergeHeaders(const std::unordered_map<std::string, std::string>& 
defaults,
-                         const std::unordered_map<std::string, std::string>& 
overrides) {
-  cpr::Header combined_headers = {defaults.begin(), defaults.end()};
-  for (const auto& [key, val] : overrides) {
-    combined_headers.insert_or_assign(key, val);
+/// \brief Prepare headers for an HTTP request.
+Result<cpr::Header> BuildHeaders(
+    const std::unordered_map<std::string, std::string>& request_headers,
+    const std::unordered_map<std::string, std::string>& default_headers,
+    auth::AuthSession& session) {
+  std::unordered_map<std::string, std::string> headers(default_headers);
+  for (const auto& [key, val] : request_headers) {
+    headers.emplace(key, val);
   }
-  return combined_headers;
+  ICEBERG_RETURN_UNEXPECTED(session.Authenticate(headers));
+  return cpr::Header(headers.begin(), headers.end());
 }
 
 /// \brief Converts a map of string key-value pairs to cpr::Parameters.
@@ -149,10 +148,11 @@ HttpClient::~HttpClient() = default;
 Result<HttpResponse> HttpClient::Get(
     const std::string& path, const std::unordered_map<std::string, 
std::string>& params,
     const std::unordered_map<std::string, std::string>& headers,
-    const ErrorHandler& error_handler) {
-  auto final_headers = MergeHeaders(default_headers_, headers);
+    const ErrorHandler& error_handler, auth::AuthSession& session) {
+  ICEBERG_ASSIGN_OR_RAISE(auto all_headers,
+                          BuildHeaders(headers, default_headers_, session));
   cpr::Response response =
-      cpr::Get(cpr::Url{path}, GetParameters(params), final_headers, 
*connection_pool_);
+      cpr::Get(cpr::Url{path}, GetParameters(params), all_headers, 
*connection_pool_);
 
   ICEBERG_RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler));
   HttpResponse http_response;
@@ -163,10 +163,11 @@ Result<HttpResponse> HttpClient::Get(
 Result<HttpResponse> HttpClient::Post(
     const std::string& path, const std::string& body,
     const std::unordered_map<std::string, std::string>& headers,
-    const ErrorHandler& error_handler) {
-  auto final_headers = MergeHeaders(default_headers_, headers);
+    const ErrorHandler& error_handler, auth::AuthSession& session) {
+  ICEBERG_ASSIGN_OR_RAISE(auto all_headers,
+                          BuildHeaders(headers, default_headers_, session));
   cpr::Response response =
-      cpr::Post(cpr::Url{path}, cpr::Body{body}, final_headers, 
*connection_pool_);
+      cpr::Post(cpr::Url{path}, cpr::Body{body}, all_headers, 
*connection_pool_);
 
   ICEBERG_RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler));
   HttpResponse http_response;
@@ -178,9 +179,11 @@ Result<HttpResponse> HttpClient::PostForm(
     const std::string& path,
     const std::unordered_map<std::string, std::string>& form_data,
     const std::unordered_map<std::string, std::string>& headers,
-    const ErrorHandler& error_handler) {
-  auto final_headers = MergeHeaders(default_headers_, headers);
-  final_headers.insert_or_assign(kHeaderContentType, kMimeTypeFormUrlEncoded);
+    const ErrorHandler& error_handler, auth::AuthSession& session) {
+  std::unordered_map<std::string, std::string> form_headers(headers);
+  form_headers.insert_or_assign(kHeaderContentType, kMimeTypeFormUrlEncoded);
+  ICEBERG_ASSIGN_OR_RAISE(auto all_headers,
+                          BuildHeaders(form_headers, default_headers_, 
session));
   std::vector<cpr::Pair> pair_list;
   pair_list.reserve(form_data.size());
   for (const auto& [key, val] : form_data) {
@@ -188,7 +191,7 @@ Result<HttpResponse> HttpClient::PostForm(
   }
   cpr::Response response =
       cpr::Post(cpr::Url{path}, cpr::Payload(pair_list.begin(), 
pair_list.end()),
-                final_headers, *connection_pool_);
+                all_headers, *connection_pool_);
 
   ICEBERG_RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler));
   HttpResponse http_response;
@@ -198,9 +201,10 @@ Result<HttpResponse> HttpClient::PostForm(
 
 Result<HttpResponse> HttpClient::Head(
     const std::string& path, const std::unordered_map<std::string, 
std::string>& headers,
-    const ErrorHandler& error_handler) {
-  auto final_headers = MergeHeaders(default_headers_, headers);
-  cpr::Response response = cpr::Head(cpr::Url{path}, final_headers, 
*connection_pool_);
+    const ErrorHandler& error_handler, auth::AuthSession& session) {
+  ICEBERG_ASSIGN_OR_RAISE(auto all_headers,
+                          BuildHeaders(headers, default_headers_, session));
+  cpr::Response response = cpr::Head(cpr::Url{path}, all_headers, 
*connection_pool_);
 
   ICEBERG_RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler));
   HttpResponse http_response;
@@ -211,10 +215,11 @@ Result<HttpResponse> HttpClient::Head(
 Result<HttpResponse> HttpClient::Delete(
     const std::string& path, const std::unordered_map<std::string, 
std::string>& params,
     const std::unordered_map<std::string, std::string>& headers,
-    const ErrorHandler& error_handler) {
-  auto final_headers = MergeHeaders(default_headers_, headers);
-  cpr::Response response = cpr::Delete(cpr::Url{path}, GetParameters(params),
-                                       final_headers, *connection_pool_);
+    const ErrorHandler& error_handler, auth::AuthSession& session) {
+  ICEBERG_ASSIGN_OR_RAISE(auto all_headers,
+                          BuildHeaders(headers, default_headers_, session));
+  cpr::Response response =
+      cpr::Delete(cpr::Url{path}, GetParameters(params), all_headers, 
*connection_pool_);
 
   ICEBERG_RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler));
   HttpResponse http_response;
diff --git a/src/iceberg/catalog/rest/http_client.h 
b/src/iceberg/catalog/rest/http_client.h
index 38f902e4..ea9c10a3 100644
--- a/src/iceberg/catalog/rest/http_client.h
+++ b/src/iceberg/catalog/rest/http_client.h
@@ -82,30 +82,33 @@ class ICEBERG_REST_EXPORT HttpClient {
   Result<HttpResponse> Get(const std::string& path,
                            const std::unordered_map<std::string, std::string>& 
params,
                            const std::unordered_map<std::string, std::string>& 
headers,
-                           const ErrorHandler& error_handler);
+                           const ErrorHandler& error_handler, 
auth::AuthSession& session);
 
   /// \brief Sends a POST request.
   Result<HttpResponse> Post(const std::string& path, const std::string& body,
                             const std::unordered_map<std::string, 
std::string>& headers,
-                            const ErrorHandler& error_handler);
+                            const ErrorHandler& error_handler,
+                            auth::AuthSession& session);
 
   /// \brief Sends a POST request with form data.
   Result<HttpResponse> PostForm(
       const std::string& path,
       const std::unordered_map<std::string, std::string>& form_data,
       const std::unordered_map<std::string, std::string>& headers,
-      const ErrorHandler& error_handler);
+      const ErrorHandler& error_handler, auth::AuthSession& session);
 
   /// \brief Sends a HEAD request.
   Result<HttpResponse> Head(const std::string& path,
                             const std::unordered_map<std::string, 
std::string>& headers,
-                            const ErrorHandler& error_handler);
+                            const ErrorHandler& error_handler,
+                            auth::AuthSession& session);
 
   /// \brief Sends a DELETE request.
   Result<HttpResponse> Delete(const std::string& path,
                               const std::unordered_map<std::string, 
std::string>& params,
                               const std::unordered_map<std::string, 
std::string>& headers,
-                              const ErrorHandler& error_handler);
+                              const ErrorHandler& error_handler,
+                              auth::AuthSession& session);
 
  private:
   std::unordered_map<std::string, std::string> default_headers_;
diff --git a/src/iceberg/catalog/rest/rest_catalog.cc 
b/src/iceberg/catalog/rest/rest_catalog.cc
index cc052e24..8cc7be75 100644
--- a/src/iceberg/catalog/rest/rest_catalog.cc
+++ b/src/iceberg/catalog/rest/rest_catalog.cc
@@ -26,6 +26,7 @@
 
 #include <nlohmann/json.hpp>
 
+#include "iceberg/catalog/rest/auth/auth_managers.h"
 #include "iceberg/catalog/rest/catalog_properties.h"
 #include "iceberg/catalog/rest/constant.h"
 #include "iceberg/catalog/rest/endpoint.h"
@@ -33,7 +34,6 @@
 #include "iceberg/catalog/rest/http_client.h"
 #include "iceberg/catalog/rest/json_serde_internal.h"
 #include "iceberg/catalog/rest/resource_paths.h"
-#include "iceberg/catalog/rest/rest_catalog.h"
 #include "iceberg/catalog/rest/rest_util.h"
 #include "iceberg/catalog/rest/types.h"
 #include "iceberg/json_serde_internal.h"
@@ -65,14 +65,15 @@ std::unordered_set<Endpoint> GetDefaultEndpoints() {
   };
 }
 
-/// \brief Fetch server config and merge it with client config
+/// \brief Fetch server configuration from the REST catalog server.
 Result<CatalogConfig> FetchServerConfig(const ResourcePaths& paths,
-                                        const RestCatalogProperties& 
current_config) {
+                                        const RestCatalogProperties& 
current_config,
+                                        auth::AuthSession& session) {
   ICEBERG_ASSIGN_OR_RAISE(auto config_path, paths.Config());
   HttpClient client(current_config.ExtractHeaders());
   ICEBERG_ASSIGN_OR_RAISE(const auto response,
                           client.Get(config_path, /*params=*/{}, 
/*headers=*/{},
-                                     *DefaultErrorHandler::Instance()));
+                                     *DefaultErrorHandler::Instance(), 
session));
   ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
   return CatalogConfigFromJson(json);
 }
@@ -114,10 +115,20 @@ Result<std::shared_ptr<RestCatalog>> RestCatalog::Make(
   if (!file_io) {
     return InvalidArgument("FileIO is required to create RestCatalog");
   }
+
+  std::string catalog_name = config.Get(RestCatalogProperties::kName);
+  ICEBERG_ASSIGN_OR_RAISE(auto auth_manager,
+                          auth::AuthManagers::Load(catalog_name, 
config.configs()));
   ICEBERG_ASSIGN_OR_RAISE(
       auto paths, ResourcePaths::Make(std::string(TrimTrailingSlash(uri)),
                                       
config.Get(RestCatalogProperties::kPrefix)));
-  ICEBERG_ASSIGN_OR_RAISE(auto server_config, FetchServerConfig(*paths, 
config));
+
+  // Create init session for fetching server configuration
+  HttpClient init_client(config.ExtractHeaders());
+  ICEBERG_ASSIGN_OR_RAISE(auto init_session,
+                          auth_manager->InitSession(init_client, 
config.configs()));
+  ICEBERG_ASSIGN_OR_RAISE(auto server_config,
+                          FetchServerConfig(*paths, config, *init_session));
 
   std::unique_ptr<RestCatalogProperties> final_config = 
RestCatalogProperties::FromMap(
       MergeConfigs(server_config.defaults, config.configs(), 
server_config.overrides));
@@ -139,21 +150,32 @@ Result<std::shared_ptr<RestCatalog>> RestCatalog::Make(
       paths, ResourcePaths::Make(std::string(TrimTrailingSlash(final_uri)),
                                  
final_config->Get(RestCatalogProperties::kPrefix)));
 
-  return std::shared_ptr<RestCatalog>(
-      new RestCatalog(std::move(final_config), std::move(file_io), 
std::move(paths),
-                      std::move(endpoints)));
+  auto client = std::make_unique<HttpClient>(final_config->ExtractHeaders());
+  ICEBERG_ASSIGN_OR_RAISE(auto catalog_session,
+                          auth_manager->CatalogSession(*client, 
final_config->configs()));
+
+  return std::shared_ptr<RestCatalog>(new RestCatalog(
+      std::move(final_config), std::move(file_io), std::move(client), 
std::move(paths),
+      std::move(endpoints), std::move(auth_manager), 
std::move(catalog_session)));
 }
 
 RestCatalog::RestCatalog(std::unique_ptr<RestCatalogProperties> config,
                          std::shared_ptr<FileIO> file_io,
+                         std::unique_ptr<HttpClient> client,
                          std::unique_ptr<ResourcePaths> paths,
-                         std::unordered_set<Endpoint> endpoints)
+                         std::unordered_set<Endpoint> endpoints,
+                         std::unique_ptr<auth::AuthManager> auth_manager,
+                         std::shared_ptr<auth::AuthSession> catalog_session)
     : config_(std::move(config)),
       file_io_(std::move(file_io)),
-      client_(std::make_unique<HttpClient>(config_->ExtractHeaders())),
+      client_(std::move(client)),
       paths_(std::move(paths)),
       name_(config_->Get(RestCatalogProperties::kName)),
-      supported_endpoints_(std::move(endpoints)) {}
+      supported_endpoints_(std::move(endpoints)),
+      auth_manager_(std::move(auth_manager)),
+      catalog_session_(std::move(catalog_session)) {
+  ICEBERG_DCHECK(catalog_session_ != nullptr, "catalog_session must not be 
null");
+}
 
 std::string_view RestCatalog::name() const { return name_; }
 
@@ -172,7 +194,8 @@ Result<std::vector<Namespace>> 
RestCatalog::ListNamespaces(const Namespace& ns)
     }
     ICEBERG_ASSIGN_OR_RAISE(
         const auto response,
-        client_->Get(path, params, /*headers=*/{}, 
*NamespaceErrorHandler::Instance()));
+        client_->Get(path, params, /*headers=*/{}, 
*NamespaceErrorHandler::Instance(),
+                     *catalog_session_));
     ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
     ICEBERG_ASSIGN_OR_RAISE(auto list_response, 
ListNamespacesResponseFromJson(json));
     result.insert(result.end(), list_response.namespaces.begin(),
@@ -191,9 +214,10 @@ Status RestCatalog::CreateNamespace(
   ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespaces());
   CreateNamespaceRequest request{.namespace_ = ns, .properties = properties};
   ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
-  ICEBERG_ASSIGN_OR_RAISE(const auto response,
-                          client_->Post(path, json_request, /*headers=*/{},
-                                        *NamespaceErrorHandler::Instance()));
+  ICEBERG_ASSIGN_OR_RAISE(
+      const auto response,
+      client_->Post(path, json_request, /*headers=*/{},
+                    *NamespaceErrorHandler::Instance(), *catalog_session_));
   ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
   ICEBERG_ASSIGN_OR_RAISE(auto create_response, 
CreateNamespaceResponseFromJson(json));
   return {};
@@ -203,9 +227,10 @@ Result<std::unordered_map<std::string, std::string>> 
RestCatalog::GetNamespacePr
     const Namespace& ns) const {
   ICEBERG_ENDPOINT_CHECK(supported_endpoints_, 
Endpoint::GetNamespaceProperties());
   ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespace_(ns));
-  ICEBERG_ASSIGN_OR_RAISE(const auto response,
-                          client_->Get(path, /*params=*/{}, /*headers=*/{},
-                                       *NamespaceErrorHandler::Instance()));
+  ICEBERG_ASSIGN_OR_RAISE(
+      const auto response,
+      client_->Get(path, /*params=*/{}, /*headers=*/{},
+                   *NamespaceErrorHandler::Instance(), *catalog_session_));
   ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
   ICEBERG_ASSIGN_OR_RAISE(auto get_response, 
GetNamespaceResponseFromJson(json));
   return get_response.properties;
@@ -214,9 +239,10 @@ Result<std::unordered_map<std::string, std::string>> 
RestCatalog::GetNamespacePr
 Status RestCatalog::DropNamespace(const Namespace& ns) {
   ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::DropNamespace());
   ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespace_(ns));
-  ICEBERG_ASSIGN_OR_RAISE(const auto response,
-                          client_->Delete(path, /*params=*/{}, /*headers=*/{},
-                                          
*DropNamespaceErrorHandler::Instance()));
+  ICEBERG_ASSIGN_OR_RAISE(
+      const auto response,
+      client_->Delete(path, /*params=*/{}, /*headers=*/{},
+                      *DropNamespaceErrorHandler::Instance(), 
*catalog_session_));
   return {};
 }
 
@@ -227,8 +253,8 @@ Result<bool> RestCatalog::NamespaceExists(const Namespace& 
ns) const {
   }
 
   ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespace_(ns));
-  return CaptureNoSuchNamespace(
-      client_->Head(path, /*headers=*/{}, *NamespaceErrorHandler::Instance()));
+  return CaptureNoSuchNamespace(client_->Head(
+      path, /*headers=*/{}, *NamespaceErrorHandler::Instance(), 
*catalog_session_));
 }
 
 Status RestCatalog::UpdateNamespaceProperties(
@@ -240,9 +266,10 @@ Status RestCatalog::UpdateNamespaceProperties(
       .removals = std::vector<std::string>(removals.begin(), removals.end()),
       .updates = updates};
   ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
-  ICEBERG_ASSIGN_OR_RAISE(const auto response,
-                          client_->Post(path, json_request, /*headers=*/{},
-                                        *NamespaceErrorHandler::Instance()));
+  ICEBERG_ASSIGN_OR_RAISE(
+      const auto response,
+      client_->Post(path, json_request, /*headers=*/{},
+                    *NamespaceErrorHandler::Instance(), *catalog_session_));
   ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
   ICEBERG_ASSIGN_OR_RAISE(auto update_response,
                           UpdateNamespacePropertiesResponseFromJson(json));
@@ -251,7 +278,6 @@ Status RestCatalog::UpdateNamespaceProperties(
 
 Result<std::vector<TableIdentifier>> RestCatalog::ListTables(const Namespace& 
ns) const {
   ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::ListTables());
-
   ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Tables(ns));
   std::vector<TableIdentifier> result;
   std::string next_token;
@@ -262,7 +288,8 @@ Result<std::vector<TableIdentifier>> 
RestCatalog::ListTables(const Namespace& ns
     }
     ICEBERG_ASSIGN_OR_RAISE(
         const auto response,
-        client_->Get(path, params, /*headers=*/{}, 
*TableErrorHandler::Instance()));
+        client_->Get(path, params, /*headers=*/{}, 
*TableErrorHandler::Instance(),
+                     *catalog_session_));
     ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
     ICEBERG_ASSIGN_OR_RAISE(auto list_response, 
ListTablesResponseFromJson(json));
     result.insert(result.end(), list_response.identifiers.begin(),
@@ -296,7 +323,8 @@ Result<LoadTableResult> RestCatalog::CreateTableInternal(
   ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
   ICEBERG_ASSIGN_OR_RAISE(
       const auto response,
-      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance()));
+      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance(),
+                    *catalog_session_));
 
   ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
   return LoadTableResultFromJson(json);
@@ -334,7 +362,8 @@ Result<std::shared_ptr<Table>> RestCatalog::UpdateTable(
   ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
   ICEBERG_ASSIGN_OR_RAISE(
       const auto response,
-      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance()));
+      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance(),
+                    *catalog_session_));
 
   ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
   ICEBERG_ASSIGN_OR_RAISE(auto commit_response, 
CommitTableResponseFromJson(json));
@@ -370,7 +399,8 @@ Status RestCatalog::DropTable(const TableIdentifier& 
identifier, bool purge) {
   }
   ICEBERG_ASSIGN_OR_RAISE(
       const auto response,
-      client_->Delete(path, params, /*headers=*/{}, 
*TableErrorHandler::Instance()));
+      client_->Delete(path, params, /*headers=*/{}, 
*TableErrorHandler::Instance(),
+                      *catalog_session_));
   return {};
 }
 
@@ -381,8 +411,8 @@ Result<bool> RestCatalog::TableExists(const 
TableIdentifier& identifier) const {
   }
 
   ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier));
-  return CaptureNoSuchTable(
-      client_->Head(path, /*headers=*/{}, *TableErrorHandler::Instance()));
+  return CaptureNoSuchTable(client_->Head(
+      path, /*headers=*/{}, *TableErrorHandler::Instance(), 
*catalog_session_));
 }
 
 Status RestCatalog::RenameTable(const TableIdentifier& from, const 
TableIdentifier& to) {
@@ -393,7 +423,8 @@ Status RestCatalog::RenameTable(const TableIdentifier& 
from, const TableIdentifi
   ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
   ICEBERG_ASSIGN_OR_RAISE(
       const auto response,
-      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance()));
+      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance(),
+                    *catalog_session_));
 
   return {};
 }
@@ -404,7 +435,8 @@ Result<std::string> RestCatalog::LoadTableInternal(
   ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier));
   ICEBERG_ASSIGN_OR_RAISE(
       const auto response,
-      client_->Get(path, /*params=*/{}, /*headers=*/{}, 
*TableErrorHandler::Instance()));
+      client_->Get(path, /*params=*/{}, /*headers=*/{}, 
*TableErrorHandler::Instance(),
+                   *catalog_session_));
   return response.body();
 }
 
@@ -431,7 +463,8 @@ Result<std::shared_ptr<Table>> RestCatalog::RegisterTable(
   ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
   ICEBERG_ASSIGN_OR_RAISE(
       const auto response,
-      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance()));
+      client_->Post(path, json_request, /*headers=*/{}, 
*TableErrorHandler::Instance(),
+                    *catalog_session_));
 
   ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
   ICEBERG_ASSIGN_OR_RAISE(auto load_result, LoadTableResultFromJson(json));
diff --git a/src/iceberg/catalog/rest/rest_catalog.h 
b/src/iceberg/catalog/rest/rest_catalog.h
index 721df29d..d498d9c7 100644
--- a/src/iceberg/catalog/rest/rest_catalog.h
+++ b/src/iceberg/catalog/rest/rest_catalog.h
@@ -105,8 +105,11 @@ class ICEBERG_REST_EXPORT RestCatalog : public Catalog,
 
  private:
   RestCatalog(std::unique_ptr<RestCatalogProperties> config,
-              std::shared_ptr<FileIO> file_io, std::unique_ptr<ResourcePaths> 
paths,
-              std::unordered_set<Endpoint> endpoints);
+              std::shared_ptr<FileIO> file_io, std::unique_ptr<HttpClient> 
client,
+              std::unique_ptr<ResourcePaths> paths,
+              std::unordered_set<Endpoint> endpoints,
+              std::unique_ptr<auth::AuthManager> auth_manager,
+              std::shared_ptr<auth::AuthSession> catalog_session);
 
   Result<std::string> LoadTableInternal(const TableIdentifier& identifier) 
const;
 
@@ -122,6 +125,8 @@ class ICEBERG_REST_EXPORT RestCatalog : public Catalog,
   std::unique_ptr<ResourcePaths> paths_;
   std::string name_;
   std::unordered_set<Endpoint> supported_endpoints_;
+  std::unique_ptr<auth::AuthManager> auth_manager_;
+  std::shared_ptr<auth::AuthSession> catalog_session_;
 };
 
 }  // namespace iceberg::rest
diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt
index 215d883b..28e7cb19 100644
--- a/src/iceberg/test/CMakeLists.txt
+++ b/src/iceberg/test/CMakeLists.txt
@@ -216,6 +216,7 @@ if(ICEBERG_BUILD_REST)
 
   add_rest_iceberg_test(rest_catalog_test
                         SOURCES
+                        auth_manager_test.cc
                         endpoint_test.cc
                         rest_json_serde_test.cc
                         rest_util_test.cc)
diff --git a/src/iceberg/test/auth_manager_test.cc 
b/src/iceberg/test/auth_manager_test.cc
new file mode 100644
index 00000000..c6e9f123
--- /dev/null
+++ b/src/iceberg/test/auth_manager_test.cc
@@ -0,0 +1,114 @@
+/*
+ * 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.
+ */
+
+#include "iceberg/catalog/rest/auth/auth_manager.h"
+
+#include <string>
+#include <unordered_map>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "iceberg/catalog/rest/auth/auth_managers.h"
+#include "iceberg/catalog/rest/auth/auth_properties.h"
+#include "iceberg/catalog/rest/auth/auth_session.h"
+#include "iceberg/catalog/rest/http_client.h"
+#include "iceberg/test/matchers.h"
+
+namespace iceberg::rest::auth {
+
+class AuthManagerTest : public ::testing::Test {
+ protected:
+  HttpClient client_{{}};
+};
+
+// Verifies loading NoopAuthManager with explicit "none" auth type
+TEST_F(AuthManagerTest, LoadNoopAuthManagerExplicit) {
+  std::unordered_map<std::string, std::string> properties = {
+      {AuthProperties::kAuthType, "none"}};
+
+  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());
+  EXPECT_TRUE(headers.empty());
+}
+
+// Verifies that NoopAuthManager is inferred when no auth properties are set
+TEST_F(AuthManagerTest, LoadNoopAuthManagerInferred) {
+  auto manager_result = AuthManagers::Load("test-catalog", {});
+  ASSERT_THAT(manager_result, IsOk());
+}
+
+// Verifies that auth type is case-insensitive
+TEST_F(AuthManagerTest, AuthTypeCaseInsensitive) {
+  for (const auto& auth_type : {"NONE", "None", "NoNe"}) {
+    std::unordered_map<std::string, std::string> properties = {
+        {AuthProperties::kAuthType, auth_type}};
+    EXPECT_THAT(AuthManagers::Load("test-catalog", properties), IsOk())
+        << "Failed for auth type: " << auth_type;
+  }
+}
+
+// Verifies that unknown auth type returns InvalidArgument
+TEST_F(AuthManagerTest, UnknownAuthTypeReturnsInvalidArgument) {
+  std::unordered_map<std::string, std::string> properties = {
+      {AuthProperties::kAuthType, "unknown-auth-type"}};
+
+  auto result = AuthManagers::Load("test-catalog", properties);
+  EXPECT_THAT(result, IsError(ErrorKind::kInvalidArgument));
+  EXPECT_THAT(result, HasErrorMessage("Unknown authentication type"));
+}
+
+// Verifies custom auth manager registration
+TEST_F(AuthManagerTest, RegisterCustomAuthManager) {
+  AuthManagers::Register(
+      "custom",
+      []([[maybe_unused]] std::string_view name,
+         [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
props)
+          -> Result<std::unique_ptr<AuthManager>> {
+        class CustomAuthManager : public AuthManager {
+         public:
+          Result<std::shared_ptr<AuthSession>> CatalogSession(
+              HttpClient&, const std::unordered_map<std::string, 
std::string>&) override {
+            return AuthSession::MakeDefault({{"X-Custom-Auth", 
"custom-value"}});
+          }
+        };
+        return std::make_unique<CustomAuthManager>();
+      });
+
+  std::unordered_map<std::string, std::string> properties = {
+      {AuthProperties::kAuthType, "custom"}};
+
+  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());
+  EXPECT_EQ(headers["X-Custom-Auth"], "custom-value");
+}
+
+}  // namespace iceberg::rest::auth
diff --git a/src/iceberg/test/meson.build b/src/iceberg/test/meson.build
index 8b657f4e..71ab6942 100644
--- a/src/iceberg/test/meson.build
+++ b/src/iceberg/test/meson.build
@@ -104,7 +104,12 @@ iceberg_tests = {
 if get_option('rest').enabled()
     iceberg_tests += {
         'rest_catalog_test': {
-            'sources': files('rest_json_serde_test.cc', 'rest_util_test.cc'),
+            'sources': files(
+                'auth_manager_test.cc',
+                'endpoint_test.cc',
+                'rest_json_serde_test.cc',
+                'rest_util_test.cc',
+            ),
             'dependencies': [iceberg_rest_dep],
         },
     }
diff --git a/src/iceberg/test/rest_catalog_test.cc 
b/src/iceberg/test/rest_catalog_test.cc
index 20560979..e52c87ee 100644
--- a/src/iceberg/test/rest_catalog_test.cc
+++ b/src/iceberg/test/rest_catalog_test.cc
@@ -36,6 +36,7 @@
 #include <nlohmann/json.hpp>
 #include <sys/socket.h>
 
+#include "iceberg/catalog/rest/auth/auth_session.h"
 #include "iceberg/catalog/rest/catalog_properties.h"
 #include "iceberg/catalog/rest/error_handlers.h"
 #include "iceberg/catalog/rest/http_client.h"
@@ -164,10 +165,12 @@ TEST_F(RestCatalogIntegrationTest, MakeCatalogSuccess) {
 TEST_F(RestCatalogIntegrationTest, FetchServerConfigDirect) {
   // Create HTTP client and fetch config directly
   HttpClient client({});
+  auto noop_session = auth::AuthSession::MakeDefault({});
   std::string config_url =
       std::format("{}:{}/v1/config", kLocalhostUri, kRestCatalogPort);
 
-  auto response_result = client.Get(config_url, {}, {}, 
*DefaultErrorHandler::Instance());
+  auto response_result = client.Get(config_url, {}, /*headers=*/{},
+                                    *DefaultErrorHandler::Instance(), 
*noop_session);
   ASSERT_THAT(response_result, IsOk());
   auto json_result = FromJsonString(response_result->body());
   ASSERT_THAT(json_result, IsOk());


Reply via email to