From 23ea12099704a8d6b9e5eb5af8b02352fbcc0651 Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Thu, 29 Jan 2026 17:40:30 +0800 Subject: [PATCH 01/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- .../catalog/rest/auth/auth_managers.cc | 43 +++++- src/iceberg/catalog/rest/rest_catalog.cc | 107 ++++++++++---- src/iceberg/catalog/rest/rest_catalog.h | 11 +- src/iceberg/test/CMakeLists.txt | 1 + src/iceberg/test/auth_manager_test.cc | 137 ++++++++++++++++++ 5 files changed, 269 insertions(+), 30 deletions(-) create mode 100644 src/iceberg/test/auth_manager_test.cc diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index f56cb2e18..e9dde4a7d 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 + #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,17 @@ namespace { using AuthManagerRegistry = std::unordered_map; +/// \brief Known authentication types that are defined in the Iceberg spec. +const std::unordered_set& KnownAuthTypes() { + static const std::unordered_set types = { + AuthProperties::kAuthTypeNone, + AuthProperties::kAuthTypeBasic, + AuthProperties::kAuthTypeOAuth2, + AuthProperties::kAuthTypeSigV4, + }; + return types; +} + // Infer the authentication type from properties. std::string InferAuthType( const std::unordered_map& properties) { @@ -48,9 +62,30 @@ std::string InferAuthType( return AuthProperties::kAuthTypeNone; } +/// \brief Authentication manager that performs no authentication. +class NoopAuthManager : public AuthManager { + public: + Result> CatalogSession( + [[maybe_unused]] HttpClient& shared_client, + [[maybe_unused]] const std::unordered_map& properties) + override { + return AuthSession::MakeDefault({}); + } +}; + // Get the global registry of auth manager factories. AuthManagerRegistry& GetRegistry() { - static AuthManagerRegistry registry; + static AuthManagerRegistry registry = [] { + AuthManagerRegistry r; + // Register built-in "none" auth manager + r[AuthProperties::kAuthTypeNone] = + []([[maybe_unused]] std::string_view name, + [[maybe_unused]] const std::unordered_map& props) + -> Result> { + return std::make_unique(); + }; + return r; + }(); return registry; } @@ -68,8 +103,10 @@ Result> 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/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index cc052e241..d645a74b6 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -26,6 +26,9 @@ #include +#include "iceberg/catalog/rest/auth/auth_manager.h" +#include "iceberg/catalog/rest/auth/auth_managers.h" +#include "iceberg/catalog/rest/auth/auth_session.h" #include "iceberg/catalog/rest/catalog_properties.h" #include "iceberg/catalog/rest/constant.h" #include "iceberg/catalog/rest/endpoint.h" @@ -66,12 +69,18 @@ std::unordered_set GetDefaultEndpoints() { } /// \brief Fetch server config and merge it with client config -Result FetchServerConfig(const ResourcePaths& paths, - const RestCatalogProperties& current_config) { +Result FetchServerConfig( + const ResourcePaths& paths, const RestCatalogProperties& current_config, + const std::shared_ptr& session) { ICEBERG_ASSIGN_OR_RAISE(auto config_path, paths.Config()); HttpClient client(current_config.ExtractHeaders()); + + // Get authentication headers + std::unordered_map auth_headers; + ICEBERG_RETURN_UNEXPECTED(session->Authenticate(auth_headers)); + ICEBERG_ASSIGN_OR_RAISE(const auto response, - client.Get(config_path, /*params=*/{}, /*headers=*/{}, + client.Get(config_path, /*params=*/{}, auth_headers, *DefaultErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); return CatalogConfigFromJson(json); @@ -114,11 +123,23 @@ Result> 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)); + + // Merge client config with server defaults and overrides std::unique_ptr final_config = RestCatalogProperties::FromMap( MergeConfigs(server_config.defaults, config.configs(), server_config.overrides)); @@ -139,27 +160,43 @@ Result> RestCatalog::Make( paths, ResourcePaths::Make(std::string(TrimTrailingSlash(final_uri)), final_config->Get(RestCatalogProperties::kPrefix))); - return std::shared_ptr( - new RestCatalog(std::move(final_config), std::move(file_io), std::move(paths), - std::move(endpoints))); + auto client = std::make_unique(final_config->ExtractHeaders()); + ICEBERG_ASSIGN_OR_RAISE(auto catalog_session, + auth_manager->CatalogSession(*client, final_config->configs())); + return std::shared_ptr(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 config, std::shared_ptr file_io, + std::unique_ptr client, std::unique_ptr paths, - std::unordered_set endpoints) + std::unordered_set endpoints, + std::unique_ptr auth_manager, + std::shared_ptr catalog_session) : config_(std::move(config)), file_io_(std::move(file_io)), - client_(std::make_unique(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)) {} std::string_view RestCatalog::name() const { return name_; } +Result> RestCatalog::AuthHeaders() const { + std::unordered_map headers; + ICEBERG_RETURN_UNEXPECTED(catalog_session_->Authenticate(headers)); + return headers; +} + Result> RestCatalog::ListNamespaces(const Namespace& ns) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::ListNamespaces()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespaces()); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + std::vector result; std::string next_token; while (true) { @@ -172,7 +209,7 @@ Result> RestCatalog::ListNamespaces(const Namespace& ns) } ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, params, /*headers=*/{}, *NamespaceErrorHandler::Instance())); + client_->Get(path, params, auth_headers, *NamespaceErrorHandler::Instance())); 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(), @@ -189,10 +226,12 @@ Status RestCatalog::CreateNamespace( const Namespace& ns, const std::unordered_map& properties) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::CreateNamespace()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespaces()); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + 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=*/{}, + client_->Post(path, json_request, auth_headers, *NamespaceErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto create_response, CreateNamespaceResponseFromJson(json)); @@ -203,8 +242,10 @@ Result> 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(auto auth_headers, AuthHeaders()); + ICEBERG_ASSIGN_OR_RAISE(const auto response, - client_->Get(path, /*params=*/{}, /*headers=*/{}, + client_->Get(path, /*params=*/{}, auth_headers, *NamespaceErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto get_response, GetNamespaceResponseFromJson(json)); @@ -214,8 +255,10 @@ Result> 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(auto auth_headers, AuthHeaders()); + ICEBERG_ASSIGN_OR_RAISE(const auto response, - client_->Delete(path, /*params=*/{}, /*headers=*/{}, + client_->Delete(path, /*params=*/{}, auth_headers, *DropNamespaceErrorHandler::Instance())); return {}; } @@ -227,8 +270,10 @@ Result RestCatalog::NamespaceExists(const Namespace& ns) const { } ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespace_(ns)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + return CaptureNoSuchNamespace( - client_->Head(path, /*headers=*/{}, *NamespaceErrorHandler::Instance())); + client_->Head(path, auth_headers, *NamespaceErrorHandler::Instance())); } Status RestCatalog::UpdateNamespaceProperties( @@ -236,12 +281,14 @@ Status RestCatalog::UpdateNamespaceProperties( const std::unordered_set& removals) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::UpdateNamespace()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->NamespaceProperties(ns)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + UpdateNamespacePropertiesRequest request{ .removals = std::vector(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=*/{}, + client_->Post(path, json_request, auth_headers, *NamespaceErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto update_response, @@ -251,8 +298,9 @@ Status RestCatalog::UpdateNamespaceProperties( Result> RestCatalog::ListTables(const Namespace& ns) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::ListTables()); - ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Tables(ns)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + std::vector result; std::string next_token; while (true) { @@ -262,7 +310,7 @@ Result> RestCatalog::ListTables(const Namespace& ns } ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, params, /*headers=*/{}, *TableErrorHandler::Instance())); + client_->Get(path, params, auth_headers, *TableErrorHandler::Instance())); 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(), @@ -282,6 +330,7 @@ Result RestCatalog::CreateTableInternal( const std::unordered_map& properties, bool stage_create) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::CreateTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Tables(identifier.ns)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); CreateTableRequest request{ .name = identifier.name, @@ -296,7 +345,7 @@ Result 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, auth_headers, *TableErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); return LoadTableResultFromJson(json); @@ -320,6 +369,7 @@ Result> RestCatalog::UpdateTable( const std::vector>& updates) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::UpdateTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); CommitTableRequest request{.identifier = identifier}; request.requirements.reserve(requirements.size()); @@ -334,7 +384,7 @@ Result> 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, auth_headers, *TableErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto commit_response, CommitTableResponseFromJson(json)); @@ -363,6 +413,7 @@ Result> RestCatalog::StageCreateTable( Status RestCatalog::DropTable(const TableIdentifier& identifier, bool purge) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::DeleteTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); std::unordered_map params; if (purge) { @@ -370,7 +421,7 @@ 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, auth_headers, *TableErrorHandler::Instance())); return {}; } @@ -381,19 +432,22 @@ Result RestCatalog::TableExists(const TableIdentifier& identifier) const { } ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + return CaptureNoSuchTable( - client_->Head(path, /*headers=*/{}, *TableErrorHandler::Instance())); + client_->Head(path, auth_headers, *TableErrorHandler::Instance())); } Status RestCatalog::RenameTable(const TableIdentifier& from, const TableIdentifier& to) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::RenameTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Rename()); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); RenameTableRequest request{.source = from, .destination = to}; 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, auth_headers, *TableErrorHandler::Instance())); return {}; } @@ -402,9 +456,11 @@ Result RestCatalog::LoadTableInternal( const TableIdentifier& identifier) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::LoadTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, /*params=*/{}, /*headers=*/{}, *TableErrorHandler::Instance())); + client_->Get(path, /*params=*/{}, auth_headers, *TableErrorHandler::Instance())); return response.body(); } @@ -422,6 +478,7 @@ Result> RestCatalog::RegisterTable( const TableIdentifier& identifier, const std::string& metadata_file_location) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::RegisterTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Register(identifier.ns)); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); RegisterTableRequest request{ .name = identifier.name, @@ -431,7 +488,7 @@ Result> 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, auth_headers, *TableErrorHandler::Instance())); 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 721df29d8..85d7fa52f 100644 --- a/src/iceberg/catalog/rest/rest_catalog.h +++ b/src/iceberg/catalog/rest/rest_catalog.h @@ -105,8 +105,13 @@ class ICEBERG_REST_EXPORT RestCatalog : public Catalog, private: RestCatalog(std::unique_ptr config, - std::shared_ptr file_io, std::unique_ptr paths, - std::unordered_set endpoints); + std::shared_ptr file_io, std::unique_ptr client, + std::unique_ptr paths, + std::unordered_set endpoints, + std::unique_ptr auth_manager, + std::shared_ptr catalog_session); + + Result> AuthHeaders() const; Result LoadTableInternal(const TableIdentifier& identifier) const; @@ -122,6 +127,8 @@ class ICEBERG_REST_EXPORT RestCatalog : public Catalog, std::unique_ptr paths_; std::string name_; std::unordered_set supported_endpoints_; + std::unique_ptr auth_manager_; + std::shared_ptr catalog_session_; }; } // namespace iceberg::rest diff --git a/src/iceberg/test/CMakeLists.txt b/src/iceberg/test/CMakeLists.txt index 215d883b0..28e7cb19a 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 000000000..9d1520436 --- /dev/null +++ b/src/iceberg/test/auth_manager_test.cc @@ -0,0 +1,137 @@ +/* + * 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 +#include + +#include +#include + +#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 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 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 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 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 that known but unimplemented auth type returns NotImplemented +TEST_F(AuthManagerTest, KnownButUnimplementedAuthType) { + std::unordered_map properties = { + {AuthProperties::kAuthType, "basic"}}; + + auto result = AuthManagers::Load("test-catalog", properties); + EXPECT_THAT(result, IsError(ErrorKind::kNotImplemented)); + EXPECT_THAT(result, HasErrorMessage("not yet supported")); +} + +// Verifies that OAuth2 auth type is inferred from credential or token properties +TEST_F(AuthManagerTest, InferOAuth2FromProperties) { + // From credential + auto result1 = AuthManagers::Load( + "test", {{AuthProperties::kOAuth2Credential, "client_id:client_secret"}}); + EXPECT_THAT(result1, IsError(ErrorKind::kNotImplemented)); + + // From token + auto result2 = + AuthManagers::Load("test", {{AuthProperties::kOAuth2Token, "bearer-token"}}); + EXPECT_THAT(result2, IsError(ErrorKind::kNotImplemented)); +} + +// Verifies custom auth manager registration +TEST_F(AuthManagerTest, RegisterCustomAuthManager) { + AuthManagers::Register( + "custom", + []([[maybe_unused]] std::string_view name, + [[maybe_unused]] const std::unordered_map& props) + -> Result> { + class CustomAuthManager : public AuthManager { + public: + Result> CatalogSession( + HttpClient&, const std::unordered_map&) override { + return AuthSession::MakeDefault({{"X-Custom-Auth", "custom-value"}}); + } + }; + return std::make_unique(); + }); + + std::unordered_map 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 headers; + EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); + EXPECT_EQ(headers["X-Custom-Auth"], "custom-value"); +} + +} // namespace iceberg::rest::auth From 6cb385344809eb748393297c8f19e5c72c1a79ca Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Thu, 29 Jan 2026 18:14:02 +0800 Subject: [PATCH 02/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- .../catalog/rest/auth/auth_managers.cc | 1 - src/iceberg/catalog/rest/rest_catalog.cc | 1 - src/iceberg/test/auth_manager_test.cc | 23 ------------------- 3 files changed, 25 deletions(-) diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index e9dde4a7d..320fbcd46 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -77,7 +77,6 @@ class NoopAuthManager : public AuthManager { AuthManagerRegistry& GetRegistry() { static AuthManagerRegistry registry = [] { AuthManagerRegistry r; - // Register built-in "none" auth manager r[AuthProperties::kAuthTypeNone] = []([[maybe_unused]] std::string_view name, [[maybe_unused]] const std::unordered_map& props) diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index d645a74b6..d6bd760e1 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -139,7 +139,6 @@ Result> RestCatalog::Make( ICEBERG_ASSIGN_OR_RAISE(auto server_config, FetchServerConfig(*paths, config, init_session)); - // Merge client config with server defaults and overrides std::unique_ptr final_config = RestCatalogProperties::FromMap( MergeConfigs(server_config.defaults, config.configs(), server_config.overrides)); diff --git a/src/iceberg/test/auth_manager_test.cc b/src/iceberg/test/auth_manager_test.cc index 9d1520436..c6e9f1231 100644 --- a/src/iceberg/test/auth_manager_test.cc +++ b/src/iceberg/test/auth_manager_test.cc @@ -80,29 +80,6 @@ TEST_F(AuthManagerTest, UnknownAuthTypeReturnsInvalidArgument) { EXPECT_THAT(result, HasErrorMessage("Unknown authentication type")); } -// Verifies that known but unimplemented auth type returns NotImplemented -TEST_F(AuthManagerTest, KnownButUnimplementedAuthType) { - std::unordered_map properties = { - {AuthProperties::kAuthType, "basic"}}; - - auto result = AuthManagers::Load("test-catalog", properties); - EXPECT_THAT(result, IsError(ErrorKind::kNotImplemented)); - EXPECT_THAT(result, HasErrorMessage("not yet supported")); -} - -// Verifies that OAuth2 auth type is inferred from credential or token properties -TEST_F(AuthManagerTest, InferOAuth2FromProperties) { - // From credential - auto result1 = AuthManagers::Load( - "test", {{AuthProperties::kOAuth2Credential, "client_id:client_secret"}}); - EXPECT_THAT(result1, IsError(ErrorKind::kNotImplemented)); - - // From token - auto result2 = - AuthManagers::Load("test", {{AuthProperties::kOAuth2Token, "bearer-token"}}); - EXPECT_THAT(result2, IsError(ErrorKind::kNotImplemented)); -} - // Verifies custom auth manager registration TEST_F(AuthManagerTest, RegisterCustomAuthManager) { AuthManagers::Register( From 563b28bf9b9eb8d19c8a16a2b34df0d4a40006f7 Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Thu, 29 Jan 2026 18:23:55 +0800 Subject: [PATCH 03/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- src/iceberg/catalog/rest/rest_catalog.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index d6bd760e1..16d063e85 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -68,7 +68,7 @@ std::unordered_set GetDefaultEndpoints() { }; } -/// \brief Fetch server config and merge it with client config +/// \brief Fetch server configuration from the REST catalog server. Result FetchServerConfig( const ResourcePaths& paths, const RestCatalogProperties& current_config, const std::shared_ptr& session) { From d7268be3853828351c30d6b02298a23e6ce98d5a Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Tue, 3 Feb 2026 13:08:09 +0800 Subject: [PATCH 04/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- src/iceberg/catalog/rest/auth/auth_managers.cc | 6 +++--- src/iceberg/catalog/rest/rest_catalog.cc | 11 +++++------ src/iceberg/test/meson.build | 7 ++++++- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index 320fbcd46..2e3b35f58 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -35,13 +35,13 @@ using AuthManagerRegistry = /// \brief Known authentication types that are defined in the Iceberg spec. const std::unordered_set& KnownAuthTypes() { - static const std::unordered_set types = { + static const std::unordered_set kAuthTypes = { AuthProperties::kAuthTypeNone, AuthProperties::kAuthTypeBasic, AuthProperties::kAuthTypeOAuth2, AuthProperties::kAuthTypeSigV4, }; - return types; + return kAuthTypes; } // Infer the authentication type from properties. @@ -66,7 +66,7 @@ std::string InferAuthType( class NoopAuthManager : public AuthManager { public: Result> CatalogSession( - [[maybe_unused]] HttpClient& shared_client, + [[maybe_unused]] HttpClient& client, [[maybe_unused]] const std::unordered_map& properties) override { return AuthSession::MakeDefault({}); diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index 16d063e85..391f02e5e 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -69,15 +69,14 @@ std::unordered_set GetDefaultEndpoints() { } /// \brief Fetch server configuration from the REST catalog server. -Result FetchServerConfig( - const ResourcePaths& paths, const RestCatalogProperties& current_config, - const std::shared_ptr& session) { +Result FetchServerConfig(const ResourcePaths& paths, + const RestCatalogProperties& current_config, + auth::AuthSession& session) { ICEBERG_ASSIGN_OR_RAISE(auto config_path, paths.Config()); HttpClient client(current_config.ExtractHeaders()); - // Get authentication headers std::unordered_map auth_headers; - ICEBERG_RETURN_UNEXPECTED(session->Authenticate(auth_headers)); + ICEBERG_RETURN_UNEXPECTED(session.Authenticate(auth_headers)); ICEBERG_ASSIGN_OR_RAISE(const auto response, client.Get(config_path, /*params=*/{}, auth_headers, @@ -137,7 +136,7 @@ Result> RestCatalog::Make( 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)); + FetchServerConfig(*paths, config, *init_session)); std::unique_ptr final_config = RestCatalogProperties::FromMap( MergeConfigs(server_config.defaults, config.configs(), server_config.overrides)); diff --git a/src/iceberg/test/meson.build b/src/iceberg/test/meson.build index 8b657f4e7..71ab6942e 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], }, } From 557489311dc838b202093f4f6e7581be86fd71ee Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Tue, 10 Feb 2026 12:02:00 +0800 Subject: [PATCH 05/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- .../catalog/rest/auth/auth_managers.cc | 31 ++++--- src/iceberg/catalog/rest/http_client.cc | 40 +++++--- src/iceberg/catalog/rest/http_client.h | 15 +-- src/iceberg/catalog/rest/rest_catalog.cc | 92 +++++++------------ src/iceberg/catalog/rest/rest_catalog.h | 2 - src/iceberg/test/rest_catalog_test.cc | 2 +- 6 files changed, 86 insertions(+), 96 deletions(-) diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index 2e3b35f58..c3dcb7168 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -33,7 +33,6 @@ namespace { using AuthManagerRegistry = std::unordered_map; -/// \brief Known authentication types that are defined in the Iceberg spec. const std::unordered_set& KnownAuthTypes() { static const std::unordered_set kAuthTypes = { AuthProperties::kAuthTypeNone, @@ -65,6 +64,12 @@ std::string InferAuthType( /// \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) @@ -73,18 +78,22 @@ class NoopAuthManager : public AuthManager { } }; +template +AuthManagerFactory MakeAuthFactory() { + return []([[maybe_unused]] std::string_view name, + [[maybe_unused]] const std::unordered_map& props) + -> Result> { return T::Make(name, props); }; +} + +AuthManagerRegistry CreateDefaultRegistry() { + return { + {AuthProperties::kAuthTypeNone, MakeAuthFactory()}, + }; +} + // Get the global registry of auth manager factories. AuthManagerRegistry& GetRegistry() { - static AuthManagerRegistry registry = [] { - AuthManagerRegistry r; - r[AuthProperties::kAuthTypeNone] = - []([[maybe_unused]] std::string_view name, - [[maybe_unused]] const std::unordered_map& props) - -> Result> { - return std::make_unique(); - }; - return r; - }(); + static AuthManagerRegistry registry = CreateDefaultRegistry(); return registry; } diff --git a/src/iceberg/catalog/rest/http_client.cc b/src/iceberg/catalog/rest/http_client.cc index 82a118c59..735bedc7a 100644 --- a/src/iceberg/catalog/rest/http_client.cc +++ b/src/iceberg/catalog/rest/http_client.cc @@ -22,6 +22,7 @@ #include #include +#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" @@ -146,11 +147,21 @@ HttpClient::HttpClient(std::unordered_map default_head HttpClient::~HttpClient() = default; +void HttpClient::SetAuthSession(auth::AuthSession* session) { session_ = session; } + +Result> HttpClient::AuthHeaders() { + std::unordered_map headers; + if (session_) { + ICEBERG_RETURN_UNEXPECTED(session_->Authenticate(headers)); + } + return headers; +} + Result HttpClient::Get( const std::string& path, const std::unordered_map& params, - const std::unordered_map& headers, const ErrorHandler& error_handler) { - auto final_headers = MergeHeaders(default_headers_, headers); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Get(cpr::Url{path}, GetParameters(params), final_headers, *connection_pool_); @@ -160,11 +171,10 @@ Result HttpClient::Get( return http_response; } -Result HttpClient::Post( - const std::string& path, const std::string& body, - const std::unordered_map& headers, - const ErrorHandler& error_handler) { - auto final_headers = MergeHeaders(default_headers_, headers); +Result HttpClient::Post(const std::string& path, const std::string& body, + const ErrorHandler& error_handler) { + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Post(cpr::Url{path}, cpr::Body{body}, final_headers, *connection_pool_); @@ -177,9 +187,9 @@ Result HttpClient::Post( Result HttpClient::PostForm( const std::string& path, const std::unordered_map& form_data, - const std::unordered_map& headers, const ErrorHandler& error_handler) { - auto final_headers = MergeHeaders(default_headers_, headers); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + auto final_headers = MergeHeaders(default_headers_, auth_headers); final_headers.insert_or_assign(kHeaderContentType, kMimeTypeFormUrlEncoded); std::vector pair_list; pair_list.reserve(form_data.size()); @@ -196,10 +206,10 @@ Result HttpClient::PostForm( return http_response; } -Result HttpClient::Head( - const std::string& path, const std::unordered_map& headers, - const ErrorHandler& error_handler) { - auto final_headers = MergeHeaders(default_headers_, headers); +Result HttpClient::Head(const std::string& path, + const ErrorHandler& error_handler) { + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Head(cpr::Url{path}, final_headers, *connection_pool_); ICEBERG_RETURN_UNEXPECTED(HandleFailureResponse(response, error_handler)); @@ -210,9 +220,9 @@ Result HttpClient::Head( Result HttpClient::Delete( const std::string& path, const std::unordered_map& params, - const std::unordered_map& headers, const ErrorHandler& error_handler) { - auto final_headers = MergeHeaders(default_headers_, headers); + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Delete(cpr::Url{path}, GetParameters(params), final_headers, *connection_pool_); diff --git a/src/iceberg/catalog/rest/http_client.h b/src/iceberg/catalog/rest/http_client.h index 38f902e4d..128b930bd 100644 --- a/src/iceberg/catalog/rest/http_client.h +++ b/src/iceberg/catalog/rest/http_client.h @@ -78,38 +78,39 @@ class ICEBERG_REST_EXPORT HttpClient { HttpClient(HttpClient&&) = delete; HttpClient& operator=(HttpClient&&) = delete; + /// \brief Set the authentication session + void SetAuthSession(auth::AuthSession* session); + /// \brief Sends a GET request. Result Get(const std::string& path, const std::unordered_map& params, - const std::unordered_map& headers, const ErrorHandler& error_handler); /// \brief Sends a POST request. Result Post(const std::string& path, const std::string& body, - const std::unordered_map& headers, const ErrorHandler& error_handler); /// \brief Sends a POST request with form data. Result PostForm( const std::string& path, const std::unordered_map& form_data, - const std::unordered_map& headers, const ErrorHandler& error_handler); /// \brief Sends a HEAD request. - Result Head(const std::string& path, - const std::unordered_map& headers, - const ErrorHandler& error_handler); + Result Head(const std::string& path, const ErrorHandler& error_handler); /// \brief Sends a DELETE request. Result Delete(const std::string& path, const std::unordered_map& params, - const std::unordered_map& headers, const ErrorHandler& error_handler); private: + /// \brief Get authentication headers. + Result> AuthHeaders(); + std::unordered_map default_headers_; std::unique_ptr connection_pool_; + auth::AuthSession* session_ = nullptr; }; } // namespace iceberg::rest diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index 391f02e5e..145495d0b 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -74,13 +74,11 @@ Result FetchServerConfig(const ResourcePaths& paths, auth::AuthSession& session) { ICEBERG_ASSIGN_OR_RAISE(auto config_path, paths.Config()); HttpClient client(current_config.ExtractHeaders()); + client.SetAuthSession(&session); - std::unordered_map auth_headers; - ICEBERG_RETURN_UNEXPECTED(session.Authenticate(auth_headers)); - - ICEBERG_ASSIGN_OR_RAISE(const auto response, - client.Get(config_path, /*params=*/{}, auth_headers, - *DefaultErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE( + const auto response, + client.Get(config_path, /*params=*/{}, *DefaultErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); return CatalogConfigFromJson(json); } @@ -143,12 +141,9 @@ Result> RestCatalog::Make( std::unordered_set endpoints; if (!server_config.endpoints.empty()) { - // Endpoints are already parsed during JSON deserialization, just convert to set endpoints = std::unordered_set(server_config.endpoints.begin(), server_config.endpoints.end()); } else { - // If a server does not send the endpoints field, use default set of endpoints - // for backwards compatibility with legacy servers endpoints = GetDefaultEndpoints(); } @@ -161,6 +156,7 @@ Result> RestCatalog::Make( auto client = std::make_unique(final_config->ExtractHeaders()); ICEBERG_ASSIGN_OR_RAISE(auto catalog_session, auth_manager->CatalogSession(*client, final_config->configs())); + return std::shared_ptr(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))); @@ -180,20 +176,15 @@ RestCatalog::RestCatalog(std::unique_ptr config, name_(config_->Get(RestCatalogProperties::kName)), supported_endpoints_(std::move(endpoints)), auth_manager_(std::move(auth_manager)), - catalog_session_(std::move(catalog_session)) {} + catalog_session_(std::move(catalog_session)) { + client_->SetAuthSession(catalog_session_.get()); +} std::string_view RestCatalog::name() const { return name_; } -Result> RestCatalog::AuthHeaders() const { - std::unordered_map headers; - ICEBERG_RETURN_UNEXPECTED(catalog_session_->Authenticate(headers)); - return headers; -} - Result> RestCatalog::ListNamespaces(const Namespace& ns) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::ListNamespaces()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespaces()); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); std::vector result; std::string next_token; @@ -207,7 +198,7 @@ Result> RestCatalog::ListNamespaces(const Namespace& ns) } ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, params, auth_headers, *NamespaceErrorHandler::Instance())); + client_->Get(path, params, *NamespaceErrorHandler::Instance())); 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(), @@ -224,13 +215,12 @@ Status RestCatalog::CreateNamespace( const Namespace& ns, const std::unordered_map& properties) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::CreateNamespace()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespaces()); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); 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, auth_headers, - *NamespaceErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE( + const auto response, + client_->Post(path, json_request, *NamespaceErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto create_response, CreateNamespaceResponseFromJson(json)); return {}; @@ -240,11 +230,10 @@ Result> 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(auto auth_headers, AuthHeaders()); - ICEBERG_ASSIGN_OR_RAISE(const auto response, - client_->Get(path, /*params=*/{}, auth_headers, - *NamespaceErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE( + const auto response, + client_->Get(path, /*params=*/{}, *NamespaceErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto get_response, GetNamespaceResponseFromJson(json)); return get_response.properties; @@ -253,25 +242,21 @@ Result> 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(auto auth_headers, AuthHeaders()); - ICEBERG_ASSIGN_OR_RAISE(const auto response, - client_->Delete(path, /*params=*/{}, auth_headers, - *DropNamespaceErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE( + const auto response, + client_->Delete(path, /*params=*/{}, *DropNamespaceErrorHandler::Instance())); return {}; } Result RestCatalog::NamespaceExists(const Namespace& ns) const { if (!supported_endpoints_.contains(Endpoint::NamespaceExists())) { - // Fall back to GetNamespaceProperties return CaptureNoSuchNamespace(GetNamespaceProperties(ns)); } ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespace_(ns)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); - return CaptureNoSuchNamespace( - client_->Head(path, auth_headers, *NamespaceErrorHandler::Instance())); + return CaptureNoSuchNamespace(client_->Head(path, *NamespaceErrorHandler::Instance())); } Status RestCatalog::UpdateNamespaceProperties( @@ -279,15 +264,14 @@ Status RestCatalog::UpdateNamespaceProperties( const std::unordered_set& removals) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::UpdateNamespace()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->NamespaceProperties(ns)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); UpdateNamespacePropertiesRequest request{ .removals = std::vector(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, auth_headers, - *NamespaceErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE( + const auto response, + client_->Post(path, json_request, *NamespaceErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto update_response, UpdateNamespacePropertiesResponseFromJson(json)); @@ -297,7 +281,6 @@ Status RestCatalog::UpdateNamespaceProperties( Result> RestCatalog::ListTables(const Namespace& ns) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::ListTables()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Tables(ns)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); std::vector result; std::string next_token; @@ -306,9 +289,8 @@ Result> RestCatalog::ListTables(const Namespace& ns if (!next_token.empty()) { params[kQueryParamPageToken] = next_token; } - ICEBERG_ASSIGN_OR_RAISE( - const auto response, - client_->Get(path, params, auth_headers, *TableErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE(const auto response, + client_->Get(path, params, *TableErrorHandler::Instance())); 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(), @@ -328,7 +310,6 @@ Result RestCatalog::CreateTableInternal( const std::unordered_map& properties, bool stage_create) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::CreateTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Tables(identifier.ns)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); CreateTableRequest request{ .name = identifier.name, @@ -343,7 +324,7 @@ Result RestCatalog::CreateTableInternal( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, auth_headers, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *TableErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); return LoadTableResultFromJson(json); @@ -367,7 +348,6 @@ Result> RestCatalog::UpdateTable( const std::vector>& updates) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::UpdateTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); CommitTableRequest request{.identifier = identifier}; request.requirements.reserve(requirements.size()); @@ -382,7 +362,7 @@ Result> RestCatalog::UpdateTable( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, auth_headers, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *TableErrorHandler::Instance())); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto commit_response, CommitTableResponseFromJson(json)); @@ -411,41 +391,35 @@ Result> RestCatalog::StageCreateTable( Status RestCatalog::DropTable(const TableIdentifier& identifier, bool purge) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::DeleteTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); std::unordered_map params; if (purge) { params["purgeRequested"] = "true"; } - ICEBERG_ASSIGN_OR_RAISE( - const auto response, - client_->Delete(path, params, auth_headers, *TableErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE(const auto response, + client_->Delete(path, params, *TableErrorHandler::Instance())); return {}; } Result RestCatalog::TableExists(const TableIdentifier& identifier) const { if (!supported_endpoints_.contains(Endpoint::TableExists())) { - // Fall back to call LoadTable return CaptureNoSuchTable(LoadTableInternal(identifier)); } ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); - return CaptureNoSuchTable( - client_->Head(path, auth_headers, *TableErrorHandler::Instance())); + return CaptureNoSuchTable(client_->Head(path, *TableErrorHandler::Instance())); } Status RestCatalog::RenameTable(const TableIdentifier& from, const TableIdentifier& to) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::RenameTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Rename()); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); RenameTableRequest request{.source = from, .destination = to}; ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, auth_headers, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *TableErrorHandler::Instance())); return {}; } @@ -454,11 +428,10 @@ Result RestCatalog::LoadTableInternal( const TableIdentifier& identifier) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::LoadTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, /*params=*/{}, auth_headers, *TableErrorHandler::Instance())); + client_->Get(path, /*params=*/{}, *TableErrorHandler::Instance())); return response.body(); } @@ -476,7 +449,6 @@ Result> RestCatalog::RegisterTable( const TableIdentifier& identifier, const std::string& metadata_file_location) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::RegisterTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Register(identifier.ns)); - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); RegisterTableRequest request{ .name = identifier.name, @@ -486,7 +458,7 @@ Result> RestCatalog::RegisterTable( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, auth_headers, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *TableErrorHandler::Instance())); 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 85d7fa52f..d498d9c71 100644 --- a/src/iceberg/catalog/rest/rest_catalog.h +++ b/src/iceberg/catalog/rest/rest_catalog.h @@ -111,8 +111,6 @@ class ICEBERG_REST_EXPORT RestCatalog : public Catalog, std::unique_ptr auth_manager, std::shared_ptr catalog_session); - Result> AuthHeaders() const; - Result LoadTableInternal(const TableIdentifier& identifier) const; Result CreateTableInternal( diff --git a/src/iceberg/test/rest_catalog_test.cc b/src/iceberg/test/rest_catalog_test.cc index 20560979b..f685fa9d0 100644 --- a/src/iceberg/test/rest_catalog_test.cc +++ b/src/iceberg/test/rest_catalog_test.cc @@ -167,7 +167,7 @@ TEST_F(RestCatalogIntegrationTest, FetchServerConfigDirect) { 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, {}, *DefaultErrorHandler::Instance()); ASSERT_THAT(response_result, IsOk()); auto json_result = FromJsonString(response_result->body()); ASSERT_THAT(json_result, IsOk()); From 68e80811b7450b9c5455df02e1babc1d31811dd8 Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Tue, 10 Feb 2026 16:37:09 +0800 Subject: [PATCH 06/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- .../catalog/rest/auth/auth_managers.cc | 6 +- src/iceberg/catalog/rest/http_client.cc | 31 +++++----- src/iceberg/catalog/rest/http_client.h | 22 +++---- src/iceberg/catalog/rest/rest_catalog.cc | 61 +++++++++++-------- src/iceberg/test/rest_catalog_test.cc | 5 +- 5 files changed, 70 insertions(+), 55 deletions(-) diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index c3dcb7168..c1fe45f87 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -80,9 +80,9 @@ class NoopAuthManager : public AuthManager { template AuthManagerFactory MakeAuthFactory() { - return []([[maybe_unused]] std::string_view name, - [[maybe_unused]] const std::unordered_map& props) - -> Result> { return T::Make(name, props); }; + return + [](std::string_view name, const std::unordered_map& props) + -> Result> { return T::Make(name, props); }; } AuthManagerRegistry CreateDefaultRegistry() { diff --git a/src/iceberg/catalog/rest/http_client.cc b/src/iceberg/catalog/rest/http_client.cc index 735bedc7a..402608033 100644 --- a/src/iceberg/catalog/rest/http_client.cc +++ b/src/iceberg/catalog/rest/http_client.cc @@ -147,20 +147,17 @@ HttpClient::HttpClient(std::unordered_map default_head HttpClient::~HttpClient() = default; -void HttpClient::SetAuthSession(auth::AuthSession* session) { session_ = session; } - -Result> HttpClient::AuthHeaders() { +Result> HttpClient::AuthHeaders( + auth::AuthSession& session) { std::unordered_map headers; - if (session_) { - ICEBERG_RETURN_UNEXPECTED(session_->Authenticate(headers)); - } + ICEBERG_RETURN_UNEXPECTED(session.Authenticate(headers)); return headers; } Result HttpClient::Get( const std::string& path, const std::unordered_map& params, - const ErrorHandler& error_handler) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + const ErrorHandler& error_handler, auth::AuthSession& session) { + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Get(cpr::Url{path}, GetParameters(params), final_headers, *connection_pool_); @@ -172,8 +169,9 @@ Result HttpClient::Get( } Result HttpClient::Post(const std::string& path, const std::string& body, - const ErrorHandler& error_handler) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + const ErrorHandler& error_handler, + auth::AuthSession& session) { + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Post(cpr::Url{path}, cpr::Body{body}, final_headers, *connection_pool_); @@ -187,8 +185,8 @@ Result HttpClient::Post(const std::string& path, const std::string Result HttpClient::PostForm( const std::string& path, const std::unordered_map& form_data, - const ErrorHandler& error_handler) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + const ErrorHandler& error_handler, auth::AuthSession& session) { + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); auto final_headers = MergeHeaders(default_headers_, auth_headers); final_headers.insert_or_assign(kHeaderContentType, kMimeTypeFormUrlEncoded); std::vector pair_list; @@ -207,8 +205,9 @@ Result HttpClient::PostForm( } Result HttpClient::Head(const std::string& path, - const ErrorHandler& error_handler) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + const ErrorHandler& error_handler, + auth::AuthSession& session) { + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Head(cpr::Url{path}, final_headers, *connection_pool_); @@ -220,8 +219,8 @@ Result HttpClient::Head(const std::string& path, Result HttpClient::Delete( const std::string& path, const std::unordered_map& params, - const ErrorHandler& error_handler) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders()); + const ErrorHandler& error_handler, auth::AuthSession& session) { + ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); auto final_headers = MergeHeaders(default_headers_, auth_headers); cpr::Response response = cpr::Delete(cpr::Url{path}, GetParameters(params), final_headers, *connection_pool_); diff --git a/src/iceberg/catalog/rest/http_client.h b/src/iceberg/catalog/rest/http_client.h index 128b930bd..8f38caae8 100644 --- a/src/iceberg/catalog/rest/http_client.h +++ b/src/iceberg/catalog/rest/http_client.h @@ -78,39 +78,39 @@ class ICEBERG_REST_EXPORT HttpClient { HttpClient(HttpClient&&) = delete; HttpClient& operator=(HttpClient&&) = delete; - /// \brief Set the authentication session - void SetAuthSession(auth::AuthSession* session); - /// \brief Sends a GET request. Result Get(const std::string& path, const std::unordered_map& params, - const ErrorHandler& error_handler); + const ErrorHandler& error_handler, auth::AuthSession& session); /// \brief Sends a POST request. Result Post(const std::string& path, const std::string& body, - const ErrorHandler& error_handler); + const ErrorHandler& error_handler, + auth::AuthSession& session); /// \brief Sends a POST request with form data. Result PostForm( const std::string& path, const std::unordered_map& form_data, - const ErrorHandler& error_handler); + const ErrorHandler& error_handler, auth::AuthSession& session); /// \brief Sends a HEAD request. - Result Head(const std::string& path, const ErrorHandler& error_handler); + Result Head(const std::string& path, const ErrorHandler& error_handler, + auth::AuthSession& session); /// \brief Sends a DELETE request. Result Delete(const std::string& path, const std::unordered_map& params, - const ErrorHandler& error_handler); + const ErrorHandler& error_handler, + auth::AuthSession& session); private: - /// \brief Get authentication headers. - Result> AuthHeaders(); + /// \brief Get authentication headers from a session. + static Result> AuthHeaders( + auth::AuthSession& session); std::unordered_map default_headers_; std::unique_ptr connection_pool_; - auth::AuthSession* session_ = nullptr; }; } // namespace iceberg::rest diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index 145495d0b..ec1365751 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -74,11 +74,10 @@ Result FetchServerConfig(const ResourcePaths& paths, auth::AuthSession& session) { ICEBERG_ASSIGN_OR_RAISE(auto config_path, paths.Config()); HttpClient client(current_config.ExtractHeaders()); - client.SetAuthSession(&session); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client.Get(config_path, /*params=*/{}, *DefaultErrorHandler::Instance())); + client.Get(config_path, /*params=*/{}, *DefaultErrorHandler::Instance(), session)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); return CatalogConfigFromJson(json); } @@ -141,9 +140,12 @@ Result> RestCatalog::Make( std::unordered_set endpoints; if (!server_config.endpoints.empty()) { + // Endpoints are already parsed during JSON deserialization, just convert to set endpoints = std::unordered_set(server_config.endpoints.begin(), server_config.endpoints.end()); } else { + // If a server does not send the endpoints field, use default set of endpoints + // for backwards compatibility with legacy servers endpoints = GetDefaultEndpoints(); } @@ -176,16 +178,13 @@ RestCatalog::RestCatalog(std::unique_ptr config, name_(config_->Get(RestCatalogProperties::kName)), supported_endpoints_(std::move(endpoints)), auth_manager_(std::move(auth_manager)), - catalog_session_(std::move(catalog_session)) { - client_->SetAuthSession(catalog_session_.get()); -} + catalog_session_(std::move(catalog_session)) {} std::string_view RestCatalog::name() const { return name_; } Result> RestCatalog::ListNamespaces(const Namespace& ns) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::ListNamespaces()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespaces()); - std::vector result; std::string next_token; while (true) { @@ -196,9 +195,9 @@ Result> RestCatalog::ListNamespaces(const Namespace& ns) if (!next_token.empty()) { params[kQueryParamPageToken] = next_token; } - ICEBERG_ASSIGN_OR_RAISE( - const auto response, - client_->Get(path, params, *NamespaceErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE(const auto response, + client_->Get(path, params, *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(), @@ -220,7 +219,8 @@ Status RestCatalog::CreateNamespace( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *NamespaceErrorHandler::Instance())); + client_->Post(path, json_request, *NamespaceErrorHandler::Instance(), + *catalog_session_)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto create_response, CreateNamespaceResponseFromJson(json)); return {}; @@ -233,7 +233,8 @@ Result> RestCatalog::GetNamespacePr ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, /*params=*/{}, *NamespaceErrorHandler::Instance())); + client_->Get(path, /*params=*/{}, *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; @@ -245,18 +246,21 @@ Status RestCatalog::DropNamespace(const Namespace& ns) { ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Delete(path, /*params=*/{}, *DropNamespaceErrorHandler::Instance())); + client_->Delete(path, /*params=*/{}, *DropNamespaceErrorHandler::Instance(), + *catalog_session_)); return {}; } Result RestCatalog::NamespaceExists(const Namespace& ns) const { if (!supported_endpoints_.contains(Endpoint::NamespaceExists())) { + // Fall back to GetNamespaceProperties return CaptureNoSuchNamespace(GetNamespaceProperties(ns)); } ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespace_(ns)); - return CaptureNoSuchNamespace(client_->Head(path, *NamespaceErrorHandler::Instance())); + return CaptureNoSuchNamespace( + client_->Head(path, *NamespaceErrorHandler::Instance(), *catalog_session_)); } Status RestCatalog::UpdateNamespaceProperties( @@ -271,7 +275,8 @@ Status RestCatalog::UpdateNamespaceProperties( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *NamespaceErrorHandler::Instance())); + client_->Post(path, json_request, *NamespaceErrorHandler::Instance(), + *catalog_session_)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto update_response, UpdateNamespacePropertiesResponseFromJson(json)); @@ -289,8 +294,9 @@ Result> RestCatalog::ListTables(const Namespace& ns if (!next_token.empty()) { params[kQueryParamPageToken] = next_token; } - ICEBERG_ASSIGN_OR_RAISE(const auto response, - client_->Get(path, params, *TableErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE( + const auto response, + client_->Get(path, params, *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(), @@ -324,7 +330,8 @@ Result RestCatalog::CreateTableInternal( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *TableErrorHandler::Instance(), + *catalog_session_)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); return LoadTableResultFromJson(json); @@ -362,7 +369,8 @@ Result> RestCatalog::UpdateTable( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *TableErrorHandler::Instance(), + *catalog_session_)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); ICEBERG_ASSIGN_OR_RAISE(auto commit_response, CommitTableResponseFromJson(json)); @@ -396,8 +404,9 @@ Status RestCatalog::DropTable(const TableIdentifier& identifier, bool purge) { if (purge) { params["purgeRequested"] = "true"; } - ICEBERG_ASSIGN_OR_RAISE(const auto response, - client_->Delete(path, params, *TableErrorHandler::Instance())); + ICEBERG_ASSIGN_OR_RAISE( + const auto response, + client_->Delete(path, params, *TableErrorHandler::Instance(), *catalog_session_)); return {}; } @@ -408,7 +417,8 @@ Result RestCatalog::TableExists(const TableIdentifier& identifier) const { ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); - return CaptureNoSuchTable(client_->Head(path, *TableErrorHandler::Instance())); + return CaptureNoSuchTable( + client_->Head(path, *TableErrorHandler::Instance(), *catalog_session_)); } Status RestCatalog::RenameTable(const TableIdentifier& from, const TableIdentifier& to) { @@ -419,7 +429,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, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *TableErrorHandler::Instance(), + *catalog_session_)); return {}; } @@ -431,7 +442,8 @@ Result RestCatalog::LoadTableInternal( ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, /*params=*/{}, *TableErrorHandler::Instance())); + client_->Get(path, /*params=*/{}, *TableErrorHandler::Instance(), + *catalog_session_)); return response.body(); } @@ -458,7 +470,8 @@ Result> RestCatalog::RegisterTable( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *TableErrorHandler::Instance())); + client_->Post(path, json_request, *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/test/rest_catalog_test.cc b/src/iceberg/test/rest_catalog_test.cc index f685fa9d0..21b5bc508 100644 --- a/src/iceberg/test/rest_catalog_test.cc +++ b/src/iceberg/test/rest_catalog_test.cc @@ -36,6 +36,7 @@ #include #include +#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, {}, *DefaultErrorHandler::Instance(), *noop_session); ASSERT_THAT(response_result, IsOk()); auto json_result = FromJsonString(response_result->body()); ASSERT_THAT(json_result, IsOk()); From 0d7aa8d5f9350f532eac4f666af39bc66ccfcd9c Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Tue, 10 Feb 2026 16:48:44 +0800 Subject: [PATCH 07/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- src/iceberg/catalog/rest/rest_catalog.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index ec1365751..f38250c6e 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -178,7 +178,9 @@ RestCatalog::RestCatalog(std::unique_ptr config, name_(config_->Get(RestCatalogProperties::kName)), supported_endpoints_(std::move(endpoints)), auth_manager_(std::move(auth_manager)), - catalog_session_(std::move(catalog_session)) {} + 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_; } From e94a1debe63b9759948c199161c63143b45b85ac Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Wed, 11 Feb 2026 18:04:26 +0800 Subject: [PATCH 08/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- src/iceberg/catalog/rest/http_client.cc | 81 ++++++++++++------------ src/iceberg/catalog/rest/http_client.h | 12 ++-- src/iceberg/catalog/rest/rest_catalog.cc | 64 +++++++++---------- src/iceberg/test/rest_catalog_test.cc | 4 +- 4 files changed, 77 insertions(+), 84 deletions(-) diff --git a/src/iceberg/catalog/rest/http_client.cc b/src/iceberg/catalog/rest/http_client.cc index 402608033..63dfa9963 100644 --- a/src/iceberg/catalog/rest/http_client.cc +++ b/src/iceberg/catalog/rest/http_client.cc @@ -68,19 +68,18 @@ 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& defaults, - const std::unordered_map& overrides) { - cpr::Header combined_headers = {defaults.begin(), defaults.end()}; - for (const auto& [key, val] : overrides) { - combined_headers.insert_or_assign(key, val); +/// \brief Build final request headers by merging request headers, default headers, and +/// applying authentication. +Result BuildHeaders( + const std::unordered_map& request_headers, + const std::unordered_map& default_headers, + auth::AuthSession& session) { + std::unordered_map headers(request_headers); + for (const auto& [key, val] : default_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. @@ -147,20 +146,14 @@ HttpClient::HttpClient(std::unordered_map default_head HttpClient::~HttpClient() = default; -Result> HttpClient::AuthHeaders( - auth::AuthSession& session) { - std::unordered_map headers; - ICEBERG_RETURN_UNEXPECTED(session.Authenticate(headers)); - return headers; -} - Result HttpClient::Get( const std::string& path, const std::unordered_map& params, + const std::unordered_map& headers, const ErrorHandler& error_handler, auth::AuthSession& session) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); - auto final_headers = MergeHeaders(default_headers_, auth_headers); + 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; @@ -168,13 +161,14 @@ Result HttpClient::Get( return http_response; } -Result HttpClient::Post(const std::string& path, const std::string& body, - const ErrorHandler& error_handler, - auth::AuthSession& session) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); - auto final_headers = MergeHeaders(default_headers_, auth_headers); +Result HttpClient::Post( + const std::string& path, const std::string& body, + const std::unordered_map& 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; @@ -185,10 +179,12 @@ Result HttpClient::Post(const std::string& path, const std::string Result HttpClient::PostForm( const std::string& path, const std::unordered_map& form_data, + const std::unordered_map& headers, const ErrorHandler& error_handler, auth::AuthSession& session) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); - auto final_headers = MergeHeaders(default_headers_, auth_headers); - final_headers.insert_or_assign(kHeaderContentType, kMimeTypeFormUrlEncoded); + std::unordered_map 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 pair_list; pair_list.reserve(form_data.size()); for (const auto& [key, val] : form_data) { @@ -196,7 +192,7 @@ Result 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; @@ -204,12 +200,12 @@ Result HttpClient::PostForm( return http_response; } -Result HttpClient::Head(const std::string& path, - const ErrorHandler& error_handler, - auth::AuthSession& session) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); - auto final_headers = MergeHeaders(default_headers_, auth_headers); - cpr::Response response = cpr::Head(cpr::Url{path}, final_headers, *connection_pool_); +Result HttpClient::Head( + const std::string& path, const std::unordered_map& headers, + 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; @@ -219,11 +215,12 @@ Result HttpClient::Head(const std::string& path, Result HttpClient::Delete( const std::string& path, const std::unordered_map& params, + const std::unordered_map& headers, const ErrorHandler& error_handler, auth::AuthSession& session) { - ICEBERG_ASSIGN_OR_RAISE(auto auth_headers, AuthHeaders(session)); - auto final_headers = MergeHeaders(default_headers_, auth_headers); - cpr::Response response = cpr::Delete(cpr::Url{path}, GetParameters(params), - final_headers, *connection_pool_); + 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 8f38caae8..ea9c10a39 100644 --- a/src/iceberg/catalog/rest/http_client.h +++ b/src/iceberg/catalog/rest/http_client.h @@ -81,10 +81,12 @@ class ICEBERG_REST_EXPORT HttpClient { /// \brief Sends a GET request. Result Get(const std::string& path, const std::unordered_map& params, + const std::unordered_map& headers, const ErrorHandler& error_handler, auth::AuthSession& session); /// \brief Sends a POST request. Result Post(const std::string& path, const std::string& body, + const std::unordered_map& headers, const ErrorHandler& error_handler, auth::AuthSession& session); @@ -92,23 +94,23 @@ class ICEBERG_REST_EXPORT HttpClient { Result PostForm( const std::string& path, const std::unordered_map& form_data, + const std::unordered_map& headers, const ErrorHandler& error_handler, auth::AuthSession& session); /// \brief Sends a HEAD request. - Result Head(const std::string& path, const ErrorHandler& error_handler, + Result Head(const std::string& path, + const std::unordered_map& headers, + const ErrorHandler& error_handler, auth::AuthSession& session); /// \brief Sends a DELETE request. Result Delete(const std::string& path, const std::unordered_map& params, + const std::unordered_map& headers, const ErrorHandler& error_handler, auth::AuthSession& session); private: - /// \brief Get authentication headers from a session. - static Result> AuthHeaders( - auth::AuthSession& session); - std::unordered_map default_headers_; std::unique_ptr connection_pool_; }; diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index f38250c6e..5588d620d 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -74,10 +74,9 @@ Result FetchServerConfig(const ResourcePaths& paths, 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=*/{}, *DefaultErrorHandler::Instance(), session)); + ICEBERG_ASSIGN_OR_RAISE(const auto response, + client.Get(config_path, /*params=*/{}, /*headers=*/{}, + *DefaultErrorHandler::Instance(), session)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); return CatalogConfigFromJson(json); } @@ -123,7 +122,6 @@ Result> RestCatalog::Make( 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))); @@ -197,9 +195,10 @@ Result> RestCatalog::ListNamespaces(const Namespace& ns) if (!next_token.empty()) { params[kQueryParamPageToken] = next_token; } - ICEBERG_ASSIGN_OR_RAISE(const auto response, - client_->Get(path, params, *NamespaceErrorHandler::Instance(), - *catalog_session_)); + 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 list_response, ListNamespacesResponseFromJson(json)); result.insert(result.end(), list_response.namespaces.begin(), @@ -216,13 +215,12 @@ Status RestCatalog::CreateNamespace( const Namespace& ns, const std::unordered_map& properties) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::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, *NamespaceErrorHandler::Instance(), - *catalog_session_)); + 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 {}; @@ -232,11 +230,10 @@ Result> 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=*/{}, *NamespaceErrorHandler::Instance(), - *catalog_session_)); + 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; @@ -245,11 +242,10 @@ Result> 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=*/{}, *DropNamespaceErrorHandler::Instance(), - *catalog_session_)); + client_->Delete(path, /*params=*/{}, /*headers=*/{}, + *DropNamespaceErrorHandler::Instance(), *catalog_session_)); return {}; } @@ -260,9 +256,8 @@ Result RestCatalog::NamespaceExists(const Namespace& ns) const { } ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Namespace_(ns)); - - return CaptureNoSuchNamespace( - client_->Head(path, *NamespaceErrorHandler::Instance(), *catalog_session_)); + return CaptureNoSuchNamespace(client_->Head( + path, /*headers=*/{}, *NamespaceErrorHandler::Instance(), *catalog_session_)); } Status RestCatalog::UpdateNamespaceProperties( @@ -270,15 +265,14 @@ Status RestCatalog::UpdateNamespaceProperties( const std::unordered_set& removals) { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::UpdateNamespace()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->NamespaceProperties(ns)); - UpdateNamespacePropertiesRequest request{ .removals = std::vector(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, *NamespaceErrorHandler::Instance(), - *catalog_session_)); + 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)); @@ -288,7 +282,6 @@ Status RestCatalog::UpdateNamespaceProperties( Result> RestCatalog::ListTables(const Namespace& ns) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::ListTables()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Tables(ns)); - std::vector result; std::string next_token; while (true) { @@ -298,7 +291,8 @@ Result> RestCatalog::ListTables(const Namespace& ns } ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, params, *TableErrorHandler::Instance(), *catalog_session_)); + 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(), @@ -332,7 +326,7 @@ Result RestCatalog::CreateTableInternal( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *TableErrorHandler::Instance(), + client_->Post(path, json_request, /*headers=*/{}, *TableErrorHandler::Instance(), *catalog_session_)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); @@ -371,7 +365,7 @@ Result> RestCatalog::UpdateTable( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *TableErrorHandler::Instance(), + client_->Post(path, json_request, /*headers=*/{}, *TableErrorHandler::Instance(), *catalog_session_)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); @@ -408,19 +402,20 @@ Status RestCatalog::DropTable(const TableIdentifier& identifier, bool purge) { } ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Delete(path, params, *TableErrorHandler::Instance(), *catalog_session_)); + client_->Delete(path, params, /*headers=*/{}, *TableErrorHandler::Instance(), + *catalog_session_)); return {}; } Result RestCatalog::TableExists(const TableIdentifier& identifier) const { if (!supported_endpoints_.contains(Endpoint::TableExists())) { + // Fall back to call LoadTable return CaptureNoSuchTable(LoadTableInternal(identifier)); } ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); - - return CaptureNoSuchTable( - client_->Head(path, *TableErrorHandler::Instance(), *catalog_session_)); + return CaptureNoSuchTable(client_->Head( + path, /*headers=*/{}, *TableErrorHandler::Instance(), *catalog_session_)); } Status RestCatalog::RenameTable(const TableIdentifier& from, const TableIdentifier& to) { @@ -431,7 +426,7 @@ 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, *TableErrorHandler::Instance(), + client_->Post(path, json_request, /*headers=*/{}, *TableErrorHandler::Instance(), *catalog_session_)); return {}; @@ -441,10 +436,9 @@ Result RestCatalog::LoadTableInternal( const TableIdentifier& identifier) const { ICEBERG_ENDPOINT_CHECK(supported_endpoints_, Endpoint::LoadTable()); ICEBERG_ASSIGN_OR_RAISE(auto path, paths_->Table(identifier)); - ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Get(path, /*params=*/{}, *TableErrorHandler::Instance(), + client_->Get(path, /*params=*/{}, /*headers=*/{}, *TableErrorHandler::Instance(), *catalog_session_)); return response.body(); } @@ -472,7 +466,7 @@ Result> RestCatalog::RegisterTable( ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request))); ICEBERG_ASSIGN_OR_RAISE( const auto response, - client_->Post(path, json_request, *TableErrorHandler::Instance(), + client_->Post(path, json_request, /*headers=*/{}, *TableErrorHandler::Instance(), *catalog_session_)); ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body())); diff --git a/src/iceberg/test/rest_catalog_test.cc b/src/iceberg/test/rest_catalog_test.cc index 21b5bc508..e52c87eea 100644 --- a/src/iceberg/test/rest_catalog_test.cc +++ b/src/iceberg/test/rest_catalog_test.cc @@ -169,8 +169,8 @@ TEST_F(RestCatalogIntegrationTest, FetchServerConfigDirect) { std::string config_url = std::format("{}:{}/v1/config", kLocalhostUri, kRestCatalogPort); - auto response_result = - client.Get(config_url, {}, *DefaultErrorHandler::Instance(), *noop_session); + 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()); From 3d1a92c397109191cdf98181f119feb351220b7a Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Wed, 11 Feb 2026 18:14:56 +0800 Subject: [PATCH 09/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog --- src/iceberg/catalog/rest/http_client.cc | 3 +-- src/iceberg/catalog/rest/rest_catalog.cc | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/iceberg/catalog/rest/http_client.cc b/src/iceberg/catalog/rest/http_client.cc index 63dfa9963..5f2eb7732 100644 --- a/src/iceberg/catalog/rest/http_client.cc +++ b/src/iceberg/catalog/rest/http_client.cc @@ -68,8 +68,7 @@ namespace { /// \brief Default error type for unparseable REST responses. constexpr std::string_view kRestExceptionType = "RESTException"; -/// \brief Build final request headers by merging request headers, default headers, and -/// applying authentication. +/// \brief Prepare headers for an HTTP request. Result BuildHeaders( const std::unordered_map& request_headers, const std::unordered_map& default_headers, diff --git a/src/iceberg/catalog/rest/rest_catalog.cc b/src/iceberg/catalog/rest/rest_catalog.cc index 5588d620d..8cc7be753 100644 --- a/src/iceberg/catalog/rest/rest_catalog.cc +++ b/src/iceberg/catalog/rest/rest_catalog.cc @@ -26,9 +26,7 @@ #include -#include "iceberg/catalog/rest/auth/auth_manager.h" #include "iceberg/catalog/rest/auth/auth_managers.h" -#include "iceberg/catalog/rest/auth/auth_session.h" #include "iceberg/catalog/rest/catalog_properties.h" #include "iceberg/catalog/rest/constant.h" #include "iceberg/catalog/rest/endpoint.h" @@ -36,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" From 5831f7da4d728e152a7101a0b3da5b80fc7c246b Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Wed, 11 Feb 2026 18:32:39 +0800 Subject: [PATCH 10/11] feat: Implement NoopAuthManager and integrate AuthManager into RestCatalog From cfc763e20e32afc63f9eb9a7be1e1cf3926b8ab0 Mon Sep 17 00:00:00 2001 From: Gang Wu Date: Wed, 11 Feb 2026 22:18:13 +0800 Subject: [PATCH 11/11] Update src/iceberg/catalog/rest/http_client.cc --- src/iceberg/catalog/rest/http_client.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iceberg/catalog/rest/http_client.cc b/src/iceberg/catalog/rest/http_client.cc index 5f2eb7732..b0824621b 100644 --- a/src/iceberg/catalog/rest/http_client.cc +++ b/src/iceberg/catalog/rest/http_client.cc @@ -73,8 +73,8 @@ Result BuildHeaders( const std::unordered_map& request_headers, const std::unordered_map& default_headers, auth::AuthSession& session) { - std::unordered_map headers(request_headers); - for (const auto& [key, val] : default_headers) { + std::unordered_map headers(default_headers); + for (const auto& [key, val] : request_headers) { headers.emplace(key, val); } ICEBERG_RETURN_UNEXPECTED(session.Authenticate(headers));