From 572d02ae0a323eb7958f61fb2649d966ccadb4c7 Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Fri, 2 Jan 2026 18:23:42 +0800 Subject: [PATCH 1/6] feat: add restcatalog authentication api --- src/iceberg/catalog/rest/CMakeLists.txt | 8 ++ src/iceberg/catalog/rest/auth/CMakeLists.txt | 23 ++++ src/iceberg/catalog/rest/auth/auth_manager.cc | 39 ++++++ src/iceberg/catalog/rest/auth/auth_manager.h | 122 ++++++++++++++++++ .../catalog/rest/auth/auth_managers.cc | 89 +++++++++++++ src/iceberg/catalog/rest/auth/auth_managers.h | 88 +++++++++++++ .../catalog/rest/auth/auth_properties.h | 84 ++++++++++++ src/iceberg/catalog/rest/auth/auth_session.cc | 65 ++++++++++ src/iceberg/catalog/rest/auth/auth_session.h | 105 +++++++++++++++ src/iceberg/catalog/rest/type_fwd.h | 8 ++ 10 files changed, 631 insertions(+) create mode 100644 src/iceberg/catalog/rest/auth/CMakeLists.txt create mode 100644 src/iceberg/catalog/rest/auth/auth_manager.cc create mode 100644 src/iceberg/catalog/rest/auth/auth_manager.h create mode 100644 src/iceberg/catalog/rest/auth/auth_managers.cc create mode 100644 src/iceberg/catalog/rest/auth/auth_managers.h create mode 100644 src/iceberg/catalog/rest/auth/auth_properties.h create mode 100644 src/iceberg/catalog/rest/auth/auth_session.cc create mode 100644 src/iceberg/catalog/rest/auth/auth_session.h diff --git a/src/iceberg/catalog/rest/CMakeLists.txt b/src/iceberg/catalog/rest/CMakeLists.txt index 12d77e599..b4618b2d2 100644 --- a/src/iceberg/catalog/rest/CMakeLists.txt +++ b/src/iceberg/catalog/rest/CMakeLists.txt @@ -15,6 +15,9 @@ # specific language governing permissions and limitations # under the License. +# Include auth subdirectory +add_subdirectory(auth) + set(ICEBERG_REST_SOURCES catalog_properties.cc endpoint.cc @@ -26,6 +29,11 @@ set(ICEBERG_REST_SOURCES rest_util.cc types.cc) +# Add auth sources with proper path prefix +foreach(AUTH_SOURCE ${ICEBERG_REST_AUTH_SOURCES}) + list(APPEND ICEBERG_REST_SOURCES "auth/${AUTH_SOURCE}") +endforeach() + set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS) set(ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS) set(ICEBERG_REST_STATIC_INSTALL_INTERFACE_LIBS) diff --git a/src/iceberg/catalog/rest/auth/CMakeLists.txt b/src/iceberg/catalog/rest/auth/CMakeLists.txt new file mode 100644 index 000000000..b18b2d06e --- /dev/null +++ b/src/iceberg/catalog/rest/auth/CMakeLists.txt @@ -0,0 +1,23 @@ +# 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. + +set(ICEBERG_REST_AUTH_SOURCES auth_session.cc auth_manager.cc auth_managers.cc) + +# Expose sources to parent scope for inclusion in iceberg_rest library +set(ICEBERG_REST_AUTH_SOURCES + ${ICEBERG_REST_AUTH_SOURCES} + PARENT_SCOPE) diff --git a/src/iceberg/catalog/rest/auth/auth_manager.cc b/src/iceberg/catalog/rest/auth/auth_manager.cc new file mode 100644 index 000000000..24561d333 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_manager.cc @@ -0,0 +1,39 @@ +/* + * 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" + +namespace iceberg::rest::auth { + +std::shared_ptr AuthManager::InitSession( + HttpClient* init_client, + const std::unordered_map& properties) { + // By default, use the catalog session for initialization + return CatalogSession(init_client, properties); +} + +std::shared_ptr AuthManager::TableSession( + [[maybe_unused]] const TableIdentifier& table, + [[maybe_unused]] const std::unordered_map& properties, + std::shared_ptr parent) { + // By default, return the parent session + return parent; +} + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_manager.h b/src/iceberg/catalog/rest/auth/auth_manager.h new file mode 100644 index 000000000..0e860dcbf --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_manager.h @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include + +#include "iceberg/catalog/rest/auth/auth_session.h" +#include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/table_identifier.h" + +/// \file iceberg/catalog/rest/auth/auth_manager.h +/// \brief Authentication manager interface for REST catalog. + +namespace iceberg::rest { +class HttpClient; +} // namespace iceberg::rest + +namespace iceberg::rest::auth { + +/// \brief Manager for authentication sessions. +/// +/// This interface is used to create sessions for the catalog, tables/views, +/// and any other context that requires authentication. +/// +/// Managers are typically stateful and may require initialization and cleanup. +/// The manager is created by the catalog and is closed when the catalog is closed. +/// +/// This interface is modeled after Java Iceberg's AuthManager interface. +class ICEBERG_REST_EXPORT AuthManager { + public: + virtual ~AuthManager() = default; + + /// \brief Return a temporary session for contacting the configuration endpoint. + /// + /// This session is used only during catalog initialization to fetch server + /// configuration. The returned session will be closed after the configuration + /// endpoint is contacted and should not be cached. + /// + /// The provided HTTP client is a short-lived client; it should only be used + /// to fetch initial credentials if required, and must be discarded after that. + /// + /// This method cannot return null. By default, it returns the catalog session. + /// + /// \param init_client A short-lived HTTP client for initialization. + /// \param properties Configuration properties. + /// \return A session for initialization, or the catalog session by default. + virtual std::shared_ptr InitSession( + HttpClient* init_client, + const std::unordered_map& properties); + + /// \brief Return a long-lived session for catalog operations. + /// + /// This session's lifetime is tied to the owning catalog. It serves as the + /// parent session for all other sessions (contextual and table-specific). + /// It is closed when the owning catalog is closed. + /// + /// The provided HTTP client is a long-lived, shared client. Implementors may + /// store it and reuse it for subsequent requests to the authorization server + /// (e.g., for renewing or refreshing credentials). It is not necessary to + /// close it when Close() is called. + /// + /// This method cannot return null. + /// + /// It is not required to cache the returned session internally, as the catalog + /// will keep it alive for the lifetime of the catalog. + /// + /// \param shared_client A long-lived, shared HTTP client. + /// \param properties Configuration properties (merged with server config). + /// \return A session for catalog operations. + virtual std::shared_ptr CatalogSession( + HttpClient* shared_client, + const std::unordered_map& properties) = 0; + + /// \brief Return a session for a specific table or view. + /// + /// If the table or view requires a specific AuthSession (e.g., vended credentials), + /// this method should return a new AuthSession instance. Otherwise, it should + /// return the parent session. + /// + /// This method cannot return null. By default, it returns the parent session. + /// + /// Implementors should cache table sessions internally, as the catalog will not + /// cache them. Also, the owning catalog never closes table sessions; implementations + /// should manage their lifecycle and close them when they are no longer needed. + /// + /// \param table The table identifier. + /// \param properties Properties returned by the table/view endpoint. + /// \param parent The parent session (typically the catalog session). + /// \return A session for the table, or the parent session by default. + virtual std::shared_ptr TableSession( + const TableIdentifier& table, + const std::unordered_map& properties, + std::shared_ptr parent); + + /// \brief Close the manager and release any resources. + /// + /// This method is called when the owning catalog is closed. Implementations + /// should release any resources held by the manager, such as cached sessions + /// or background threads. + virtual void Close() {} +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc new file mode 100644 index 000000000..308e4a5c7 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -0,0 +1,89 @@ +/* + * 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_managers.h" + +#include + +#include "iceberg/catalog/rest/auth/auth_properties.h" + +namespace iceberg::rest::auth { + +namespace { + +/// \brief Convert a string to lowercase for case-insensitive comparison. +std::string ToLower(std::string_view str) { + std::string result(str); + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return std::tolower(c); }); + return result; +} + +/// \brief Infer the authentication type from properties. +/// +/// If no explicit auth type is set, this function tries to infer it from +/// other properties. For example, if "credential" or "token" is present, +/// it implies OAuth2 authentication. +std::string InferAuthType( + const std::unordered_map& properties) { + // Check for explicit auth type + auto it = properties.find(std::string(AuthProperties::kAuthType)); + if (it != properties.end() && !it->second.empty()) { + return ToLower(it->second); + } + + // Infer from OAuth2 properties + bool has_credential = + properties.contains(std::string(AuthProperties::kOAuth2Credential)); + bool has_token = properties.contains(std::string(AuthProperties::kOAuth2Token)); + + if (has_credential || has_token) { + return std::string(AuthProperties::kAuthTypeOAuth2); + } + + // Default to no authentication + return std::string(AuthProperties::kAuthTypeNone); +} + +} // namespace + +std::unordered_map& AuthManagers::GetRegistry() { + static std::unordered_map registry; + return registry; +} + +void AuthManagers::Register(const std::string& auth_type, AuthManagerFactory factory) { + GetRegistry()[ToLower(auth_type)] = std::move(factory); +} + +Result> AuthManagers::Load( + const std::string& name, + const std::unordered_map& properties) { + std::string auth_type = InferAuthType(properties); + + auto& registry = GetRegistry(); + auto it = registry.find(auth_type); + if (it == registry.end()) { + return NotImplemented("Authentication type '{}' is not supported", auth_type); + } + + return it->second(name, properties); +} + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_managers.h b/src/iceberg/catalog/rest/auth/auth_managers.h new file mode 100644 index 000000000..610d2d9b0 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_managers.h @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include +#include + +#include "iceberg/catalog/rest/auth/auth_manager.h" +#include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/result.h" + +/// \file iceberg/catalog/rest/auth/auth_managers.h +/// \brief Factory for creating authentication managers. + +namespace iceberg::rest::auth { + +/// \brief Factory function type for creating AuthManager instances. +/// +/// \param name The name of the manager (used for logging). +/// \param properties Configuration properties. +/// \return A unique pointer to the created AuthManager. +using AuthManagerFactory = std::function( + const std::string& name, + const std::unordered_map& properties)>; + +/// \brief Factory class for loading authentication managers. +/// +/// This class provides a registry-based approach to create AuthManager instances +/// based on the configured authentication type. It supports built-in types +/// (none, basic, oauth2) and allows registration of custom types. +/// +/// This class is modeled after Java Iceberg's AuthManagers class. +class ICEBERG_REST_EXPORT AuthManagers { + public: + /// \brief Load an authentication manager based on configuration. + /// + /// This method reads the "rest.auth.type" property to determine which + /// AuthManager implementation to create. Supported types include: + /// - "none": NoopAuthManager (no authentication) + /// - "basic": BasicAuthManager (HTTP Basic authentication) + /// - "oauth2": OAuth2AuthManager (OAuth2 authentication) + /// - "sigv4": SigV4AuthManager (AWS Signature V4) + /// + /// If no auth type is specified, the method will infer the type based on + /// other properties (e.g., presence of "credential" or "token" implies oauth2). + /// If no auth-related properties are found, it defaults to "none". + /// + /// \param name A name for the manager (used for logging). + /// \param properties Configuration properties. + /// \return A unique pointer to the created AuthManager, or an error. + static Result> Load( + const std::string& name, + const std::unordered_map& properties); + + /// \brief Register a custom authentication manager factory. + /// + /// This allows users to extend the supported authentication types by + /// registering their own AuthManager implementations. + /// + /// \param auth_type The authentication type name (e.g., "custom"). + /// \param factory The factory function to create the AuthManager. + static void Register(const std::string& auth_type, AuthManagerFactory factory); + + private: + /// \brief Get the global registry of auth manager factories. + static std::unordered_map& GetRegistry(); +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_properties.h b/src/iceberg/catalog/rest/auth/auth_properties.h new file mode 100644 index 000000000..c718b8b2b --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_properties.h @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include + +/// \file iceberg/catalog/rest/auth/auth_properties.h +/// \brief Property keys and constants for REST catalog authentication. + +namespace iceberg::rest::auth { + +/// \brief Property keys and constants for authentication configuration. +/// +/// This struct defines all the property keys used to configure authentication +/// for the REST catalog. It follows the same naming conventions as Java Iceberg. +struct AuthProperties { + /// \brief Property key for specifying the authentication type. + static constexpr std::string_view kAuthType = "rest.auth.type"; + + /// \brief Authentication type: no authentication. + static constexpr std::string_view kAuthTypeNone = "none"; + + /// \brief Authentication type: HTTP Basic authentication. + static constexpr std::string_view kAuthTypeBasic = "basic"; + + /// \brief Authentication type: OAuth2 authentication. + static constexpr std::string_view kAuthTypeOAuth2 = "oauth2"; + + /// \brief Authentication type: AWS SigV4 authentication. + static constexpr std::string_view kAuthTypeSigV4 = "sigv4"; + + /// \brief Property key for Basic auth username. + static constexpr std::string_view kBasicUsername = "rest.auth.basic.username"; + + /// \brief Property key for Basic auth password. + static constexpr std::string_view kBasicPassword = "rest.auth.basic.password"; + + /// \brief Property key for OAuth2 token (bearer token). + static constexpr std::string_view kOAuth2Token = "token"; + + /// \brief Property key for OAuth2 credential (client_id:client_secret). + static constexpr std::string_view kOAuth2Credential = "credential"; + + /// \brief Property key for OAuth2 scope. + static constexpr std::string_view kOAuth2Scope = "scope"; + + /// \brief Property key for OAuth2 server URI. + static constexpr std::string_view kOAuth2ServerUri = "oauth2-server-uri"; + + /// \brief Property key for enabling token refresh. + static constexpr std::string_view kOAuth2TokenRefreshEnabled = "token-refresh-enabled"; + + /// \brief Default OAuth2 scope for catalog operations. + static constexpr std::string_view kOAuth2DefaultScope = "catalog"; + + /// \brief Property key for SigV4 region. + static constexpr std::string_view kSigV4Region = "rest.auth.sigv4.region"; + + /// \brief Property key for SigV4 service name. + static constexpr std::string_view kSigV4Service = "rest.auth.sigv4.service"; + + /// \brief Property key for SigV4 delegate auth type. + static constexpr std::string_view kSigV4DelegateAuthType = + "rest.auth.sigv4.delegate-auth-type"; +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_session.cc b/src/iceberg/catalog/rest/auth/auth_session.cc new file mode 100644 index 000000000..d2e5e4c03 --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_session.cc @@ -0,0 +1,65 @@ +/* + * 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_session.h" + +#include + +namespace iceberg::rest::auth { + +namespace { + +/// \brief An empty session implementation that does nothing. +class EmptyAuthSession : public AuthSession { + public: + void Authenticate( + [[maybe_unused]] std::unordered_map& headers) override { + // No-op: empty session does not add any authentication + } + + void Close() override { + // No resources to release + } +}; + +} // namespace + +std::shared_ptr AuthSession::Empty() { + // Use a static local variable for thread-safe singleton initialization + static auto empty_session = std::make_shared(); + return empty_session; +} + +DefaultAuthSession::DefaultAuthSession( + std::unordered_map headers) + : headers_(std::move(headers)) {} + +void DefaultAuthSession::Authenticate( + std::unordered_map& headers) { + for (const auto& [key, value] : headers_) { + headers.try_emplace(key, value); + } +} + +std::shared_ptr DefaultAuthSession::Of( + std::unordered_map headers) { + return std::make_shared(std::move(headers)); +} + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_session.h b/src/iceberg/catalog/rest/auth/auth_session.h new file mode 100644 index 000000000..dad5cc1cf --- /dev/null +++ b/src/iceberg/catalog/rest/auth/auth_session.h @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include + +#include "iceberg/catalog/rest/iceberg_rest_export.h" + +/// \file iceberg/catalog/rest/auth/auth_session.h +/// \brief Authentication session interface for REST catalog. + +namespace iceberg::rest::auth { + +/// \brief An authentication session that can authenticate outgoing HTTP requests. +/// +/// Authentication sessions are typically immutable, but may hold resources that need +/// to be released when the session is no longer needed (e.g., token refresh threads). +/// Implementations should override Close() to release any such resources. +/// +/// This interface is modeled after Java Iceberg's AuthSession interface. +class ICEBERG_REST_EXPORT AuthSession { + public: + virtual ~AuthSession() = default; + + /// \brief Authenticate the given request headers. + /// + /// This method adds authentication information (e.g., Authorization header) + /// to the provided headers map. The implementation should be idempotent. + /// + /// \param headers The headers map to add authentication information to. + virtual void Authenticate(std::unordered_map& headers) = 0; + + /// \brief Close the session and release any resources. + /// + /// This method is called when the session is no longer needed. For stateful + /// sessions (e.g., OAuth2 with token refresh), this should stop any background + /// threads and release resources. + /// + /// Note: Since sessions may be cached, this method may not be called immediately + /// after the session is no longer needed, but rather when the session is evicted + /// from the cache or the cache itself is closed. + virtual void Close() {} + + /// \brief Get a shared pointer to an empty session that does nothing. + /// + /// The empty session is a singleton that simply returns the request unchanged. + /// It is useful as a default or placeholder session. + /// + /// \return A shared pointer to the empty session singleton. + static std::shared_ptr Empty(); +}; + +/// \brief A default authentication session that adds static headers to requests. +/// +/// This implementation authenticates requests by adding a fixed set of headers. +/// It is suitable for authentication methods that use static credentials, +/// such as Basic auth or static bearer tokens. +class ICEBERG_REST_EXPORT DefaultAuthSession : public AuthSession { + public: + /// \brief Construct a DefaultAuthSession with the given headers. + /// + /// \param headers The headers to add to each request for authentication. + explicit DefaultAuthSession(std::unordered_map headers); + + ~DefaultAuthSession() override = default; + + /// \brief Add the configured headers to the request. + /// + /// Headers are added only if they don't already exist in the request + /// (i.e., request headers take precedence). + /// + /// \param headers The headers map to add authentication information to. + void Authenticate(std::unordered_map& headers) override; + + /// \brief Create a DefaultAuthSession with the given headers. + /// + /// \param headers The headers to add to each request. + /// \return A shared pointer to the new session. + static std::shared_ptr Of( + std::unordered_map headers); + + private: + std::unordered_map headers_; +}; + +} // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/type_fwd.h b/src/iceberg/catalog/rest/type_fwd.h index e7fddb91a..daebad6d7 100644 --- a/src/iceberg/catalog/rest/type_fwd.h +++ b/src/iceberg/catalog/rest/type_fwd.h @@ -34,3 +34,11 @@ class RestCatalog; class RestCatalogProperties; } // namespace iceberg::rest + +namespace iceberg::rest::auth { + +class AuthManager; +class AuthSession; +class DefaultAuthSession; + +} // namespace iceberg::rest::auth From 904fdf04f91f9710b5cd42bfff6265514955cd42 Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Fri, 2 Jan 2026 18:31:00 +0800 Subject: [PATCH 2/6] feat: add restcatalog authentication api --- src/iceberg/catalog/rest/meson.build | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/iceberg/catalog/rest/meson.build b/src/iceberg/catalog/rest/meson.build index a914c7e2d..aaff255e4 100644 --- a/src/iceberg/catalog/rest/meson.build +++ b/src/iceberg/catalog/rest/meson.build @@ -16,6 +16,9 @@ # under the License. iceberg_rest_sources = files( + 'auth/auth_manager.cc', + 'auth/auth_managers.cc', + 'auth/auth_session.cc', 'catalog_properties.cc', 'endpoint.cc', 'error_handlers.cc', @@ -72,3 +75,13 @@ install_headers( ], subdir: 'iceberg/catalog/rest', ) + +install_headers( + [ + 'auth/auth_manager.h', + 'auth/auth_managers.h', + 'auth/auth_properties.h', + 'auth/auth_session.h', + ], + subdir: 'iceberg/catalog/rest/auth', +) From 83084adcb07a6db673cd1d3311d2d7635741547d Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Sat, 10 Jan 2026 19:56:17 +0800 Subject: [PATCH 3/6] feat: add restcatalog authentication api --- src/iceberg/catalog/rest/auth/auth_managers.cc | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index 308e4a5c7..522e83c1b 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -20,6 +20,7 @@ #include "iceberg/catalog/rest/auth/auth_managers.h" #include +#include #include "iceberg/catalog/rest/auth/auth_properties.h" @@ -30,29 +31,30 @@ namespace { /// \brief Convert a string to lowercase for case-insensitive comparison. std::string ToLower(std::string_view str) { std::string result(str); - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return std::tolower(c); }); + std::ranges::transform(result, result.begin(), + [](unsigned char c) { return std::tolower(c); }); return result; } /// \brief Infer the authentication type from properties. /// /// If no explicit auth type is set, this function tries to infer it from -/// other properties. For example, if "credential" or "token" is present, -/// it implies OAuth2 authentication. +/// other properties. If "credential" or "token" is present, it implies +/// OAuth2 authentication. Otherwise, defaults to no authentication. +/// +/// This behavior is consistent with Java Iceberg's AuthManagers. std::string InferAuthType( const std::unordered_map& properties) { - // Check for explicit auth type + // Check for explicit auth type first auto it = properties.find(std::string(AuthProperties::kAuthType)); if (it != properties.end() && !it->second.empty()) { return ToLower(it->second); } - // Infer from OAuth2 properties + // Infer from OAuth2 properties (credential or token) bool has_credential = properties.contains(std::string(AuthProperties::kOAuth2Credential)); bool has_token = properties.contains(std::string(AuthProperties::kOAuth2Token)); - if (has_credential || has_token) { return std::string(AuthProperties::kAuthTypeOAuth2); } From 72df76de9a31d767876584add989e7412bc32763 Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Wed, 14 Jan 2026 10:23:09 +0800 Subject: [PATCH 4/6] feat: add restcatalog authentication api --- src/iceberg/catalog/rest/auth/auth_manager.cc | 4 ++-- src/iceberg/catalog/rest/auth/auth_manager.h | 20 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/iceberg/catalog/rest/auth/auth_manager.cc b/src/iceberg/catalog/rest/auth/auth_manager.cc index 24561d333..c5a590eff 100644 --- a/src/iceberg/catalog/rest/auth/auth_manager.cc +++ b/src/iceberg/catalog/rest/auth/auth_manager.cc @@ -21,14 +21,14 @@ namespace iceberg::rest::auth { -std::shared_ptr AuthManager::InitSession( +Result> AuthManager::InitSession( HttpClient* init_client, const std::unordered_map& properties) { // By default, use the catalog session for initialization return CatalogSession(init_client, properties); } -std::shared_ptr AuthManager::TableSession( +Result> AuthManager::TableSession( [[maybe_unused]] const TableIdentifier& table, [[maybe_unused]] const std::unordered_map& properties, std::shared_ptr parent) { diff --git a/src/iceberg/catalog/rest/auth/auth_manager.h b/src/iceberg/catalog/rest/auth/auth_manager.h index 0e860dcbf..0de72ad6c 100644 --- a/src/iceberg/catalog/rest/auth/auth_manager.h +++ b/src/iceberg/catalog/rest/auth/auth_manager.h @@ -25,6 +25,7 @@ #include "iceberg/catalog/rest/auth/auth_session.h" #include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/result.h" #include "iceberg/table_identifier.h" /// \file iceberg/catalog/rest/auth/auth_manager.h @@ -58,12 +59,12 @@ class ICEBERG_REST_EXPORT AuthManager { /// The provided HTTP client is a short-lived client; it should only be used /// to fetch initial credentials if required, and must be discarded after that. /// - /// This method cannot return null. By default, it returns the catalog session. + /// By default, it returns the catalog session. /// /// \param init_client A short-lived HTTP client for initialization. /// \param properties Configuration properties. - /// \return A session for initialization, or the catalog session by default. - virtual std::shared_ptr InitSession( + /// \return A session for initialization, or an error if session creation fails. + virtual Result> InitSession( HttpClient* init_client, const std::unordered_map& properties); @@ -78,15 +79,14 @@ class ICEBERG_REST_EXPORT AuthManager { /// (e.g., for renewing or refreshing credentials). It is not necessary to /// close it when Close() is called. /// - /// This method cannot return null. - /// /// It is not required to cache the returned session internally, as the catalog /// will keep it alive for the lifetime of the catalog. /// /// \param shared_client A long-lived, shared HTTP client. /// \param properties Configuration properties (merged with server config). - /// \return A session for catalog operations. - virtual std::shared_ptr CatalogSession( + /// \return A session for catalog operations, or an error if session creation fails + /// (e.g., missing required credentials, network failure during token fetch). + virtual Result> CatalogSession( HttpClient* shared_client, const std::unordered_map& properties) = 0; @@ -96,7 +96,7 @@ class ICEBERG_REST_EXPORT AuthManager { /// this method should return a new AuthSession instance. Otherwise, it should /// return the parent session. /// - /// This method cannot return null. By default, it returns the parent session. + /// By default, it returns the parent session. /// /// Implementors should cache table sessions internally, as the catalog will not /// cache them. Also, the owning catalog never closes table sessions; implementations @@ -105,8 +105,8 @@ class ICEBERG_REST_EXPORT AuthManager { /// \param table The table identifier. /// \param properties Properties returned by the table/view endpoint. /// \param parent The parent session (typically the catalog session). - /// \return A session for the table, or the parent session by default. - virtual std::shared_ptr TableSession( + /// \return A session for the table, or an error if session creation fails. + virtual Result> TableSession( const TableIdentifier& table, const std::unordered_map& properties, std::shared_ptr parent); From 54b4868451fd0c401a9bbe38a951c6f41e4e4e74 Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Fri, 16 Jan 2026 17:44:44 +0800 Subject: [PATCH 5/6] feat: add restcatalog authentication api --- src/iceberg/catalog/rest/CMakeLists.txt | 10 +- src/iceberg/catalog/rest/auth/CMakeLists.txt | 7 +- src/iceberg/catalog/rest/auth/auth_manager.cc | 14 +-- src/iceberg/catalog/rest/auth/auth_manager.h | 96 ++++++------------- .../catalog/rest/auth/auth_managers.cc | 26 ++--- src/iceberg/catalog/rest/auth/auth_managers.h | 60 ++++-------- .../catalog/rest/auth/auth_properties.h | 15 --- src/iceberg/catalog/rest/auth/auth_session.cc | 30 +----- src/iceberg/catalog/rest/auth/auth_session.h | 37 +++---- src/iceberg/result.h | 4 + 10 files changed, 93 insertions(+), 206 deletions(-) diff --git a/src/iceberg/catalog/rest/CMakeLists.txt b/src/iceberg/catalog/rest/CMakeLists.txt index b4618b2d2..d3d4ed221 100644 --- a/src/iceberg/catalog/rest/CMakeLists.txt +++ b/src/iceberg/catalog/rest/CMakeLists.txt @@ -27,12 +27,10 @@ set(ICEBERG_REST_SOURCES resource_paths.cc rest_catalog.cc rest_util.cc - types.cc) - -# Add auth sources with proper path prefix -foreach(AUTH_SOURCE ${ICEBERG_REST_AUTH_SOURCES}) - list(APPEND ICEBERG_REST_SOURCES "auth/${AUTH_SOURCE}") -endforeach() + types.cc + auth/auth_manager.cc + auth/auth_managers.cc + auth/auth_session.cc) set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS) set(ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS) diff --git a/src/iceberg/catalog/rest/auth/CMakeLists.txt b/src/iceberg/catalog/rest/auth/CMakeLists.txt index b18b2d06e..d44743efb 100644 --- a/src/iceberg/catalog/rest/auth/CMakeLists.txt +++ b/src/iceberg/catalog/rest/auth/CMakeLists.txt @@ -15,9 +15,4 @@ # specific language governing permissions and limitations # under the License. -set(ICEBERG_REST_AUTH_SOURCES auth_session.cc auth_manager.cc auth_managers.cc) - -# Expose sources to parent scope for inclusion in iceberg_rest library -set(ICEBERG_REST_AUTH_SOURCES - ${ICEBERG_REST_AUTH_SOURCES} - PARENT_SCOPE) +iceberg_install_all_headers(iceberg/catalog/rest/auth) diff --git a/src/iceberg/catalog/rest/auth/auth_manager.cc b/src/iceberg/catalog/rest/auth/auth_manager.cc index c5a590eff..df26c3cca 100644 --- a/src/iceberg/catalog/rest/auth/auth_manager.cc +++ b/src/iceberg/catalog/rest/auth/auth_manager.cc @@ -19,21 +19,23 @@ #include "iceberg/catalog/rest/auth/auth_manager.h" +#include "iceberg/catalog/rest/auth/auth_session.h" + namespace iceberg::rest::auth { -Result> AuthManager::InitSession( - HttpClient* init_client, +Result> AuthManager::InitSession( + HttpClient& init_client, const std::unordered_map& properties) { // By default, use the catalog session for initialization return CatalogSession(init_client, properties); } -Result> AuthManager::TableSession( +Result> AuthManager::TableSession( [[maybe_unused]] const TableIdentifier& table, [[maybe_unused]] const std::unordered_map& properties, - std::shared_ptr parent) { - // By default, return the parent session - return parent; + [[maybe_unused]] const AuthSession& parent) { + // By default, return nullptr to indicate the parent session should be reused. + return nullptr; } } // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_manager.h b/src/iceberg/catalog/rest/auth/auth_manager.h index 0de72ad6c..9e76189af 100644 --- a/src/iceberg/catalog/rest/auth/auth_manager.h +++ b/src/iceberg/catalog/rest/auth/auth_manager.h @@ -23,100 +23,58 @@ #include #include -#include "iceberg/catalog/rest/auth/auth_session.h" #include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/catalog/rest/type_fwd.h" #include "iceberg/result.h" #include "iceberg/table_identifier.h" /// \file iceberg/catalog/rest/auth/auth_manager.h /// \brief Authentication manager interface for REST catalog. -namespace iceberg::rest { -class HttpClient; -} // namespace iceberg::rest - namespace iceberg::rest::auth { -/// \brief Manager for authentication sessions. -/// -/// This interface is used to create sessions for the catalog, tables/views, -/// and any other context that requires authentication. -/// -/// Managers are typically stateful and may require initialization and cleanup. -/// The manager is created by the catalog and is closed when the catalog is closed. -/// -/// This interface is modeled after Java Iceberg's AuthManager interface. +/// \brief Produces authentication sessions for catalog and table requests. class ICEBERG_REST_EXPORT AuthManager { public: virtual ~AuthManager() = default; - /// \brief Return a temporary session for contacting the configuration endpoint. - /// - /// This session is used only during catalog initialization to fetch server - /// configuration. The returned session will be closed after the configuration - /// endpoint is contacted and should not be cached. - /// - /// The provided HTTP client is a short-lived client; it should only be used - /// to fetch initial credentials if required, and must be discarded after that. + /// \brief Create a short-lived session used to contact the configuration endpoint. /// - /// By default, it returns the catalog session. - /// - /// \param init_client A short-lived HTTP client for initialization. - /// \param properties Configuration properties. - /// \return A session for initialization, or an error if session creation fails. - virtual Result> InitSession( - HttpClient* init_client, + /// \param init_client HTTP client used for initialization requests. + /// \param properties Client configuration supplied by the catalog. + /// \return Session for initialization or an error if credentials cannot be acquired. + virtual Result> InitSession( + HttpClient& init_client, const std::unordered_map& properties); - /// \brief Return a long-lived session for catalog operations. - /// - /// This session's lifetime is tied to the owning catalog. It serves as the - /// parent session for all other sessions (contextual and table-specific). - /// It is closed when the owning catalog is closed. + /// \brief Create the long-lived catalog session that acts as the parent session. /// - /// The provided HTTP client is a long-lived, shared client. Implementors may - /// store it and reuse it for subsequent requests to the authorization server - /// (e.g., for renewing or refreshing credentials). It is not necessary to - /// close it when Close() is called. - /// - /// It is not required to cache the returned session internally, as the catalog - /// will keep it alive for the lifetime of the catalog. - /// - /// \param shared_client A long-lived, shared HTTP client. - /// \param properties Configuration properties (merged with server config). - /// \return A session for catalog operations, or an error if session creation fails - /// (e.g., missing required credentials, network failure during token fetch). - virtual Result> CatalogSession( - HttpClient* shared_client, + /// \param shared_client HTTP client owned by the catalog and reused for auth calls. + /// \param properties Catalog properties (client config + server defaults). + /// \return Session for catalog operations or an error if authentication cannot be set + /// up. + virtual Result> CatalogSession( + HttpClient& shared_client, const std::unordered_map& properties) = 0; - /// \brief Return a session for a specific table or view. - /// - /// If the table or view requires a specific AuthSession (e.g., vended credentials), - /// this method should return a new AuthSession instance. Otherwise, it should - /// return the parent session. - /// - /// By default, it returns the parent session. + /// \brief Create or reuse a session scoped to a single table/view. /// - /// Implementors should cache table sessions internally, as the catalog will not - /// cache them. Also, the owning catalog never closes table sessions; implementations - /// should manage their lifecycle and close them when they are no longer needed. + /// This method can return a new table-specific session or indicate that the parent + /// catalog session should be reused by returning nullptr. /// - /// \param table The table identifier. - /// \param properties Properties returned by the table/view endpoint. - /// \param parent The parent session (typically the catalog session). - /// \return A session for the table, or an error if session creation fails. - virtual Result> TableSession( + /// \param table Target table identifier. + /// \param properties Table-specific auth properties returned by the server. + /// \param parent Catalog session to read information from. + /// \return A new session for the table, nullptr to reuse parent, or an error. + virtual Result> TableSession( const TableIdentifier& table, const std::unordered_map& properties, - std::shared_ptr parent); + const AuthSession& parent); - /// \brief Close the manager and release any resources. + /// \brief Release resources held by the manager. /// - /// This method is called when the owning catalog is closed. Implementations - /// should release any resources held by the manager, such as cached sessions - /// or background threads. - virtual void Close() {} + /// \return Status of the close operation. + virtual Status Close() { return {}; } }; } // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_managers.cc b/src/iceberg/catalog/rest/auth/auth_managers.cc index 522e83c1b..877a48cb3 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.cc +++ b/src/iceberg/catalog/rest/auth/auth_managers.cc @@ -23,19 +23,12 @@ #include #include "iceberg/catalog/rest/auth/auth_properties.h" +#include "iceberg/util/string_util.h" namespace iceberg::rest::auth { namespace { -/// \brief Convert a string to lowercase for case-insensitive comparison. -std::string ToLower(std::string_view str) { - std::string result(str); - std::ranges::transform(result, result.begin(), - [](unsigned char c) { return std::tolower(c); }); - return result; -} - /// \brief Infer the authentication type from properties. /// /// If no explicit auth type is set, this function tries to infer it from @@ -48,7 +41,7 @@ std::string InferAuthType( // Check for explicit auth type first auto it = properties.find(std::string(AuthProperties::kAuthType)); if (it != properties.end() && !it->second.empty()) { - return ToLower(it->second); + return StringUtils::ToLower(it->second); } // Infer from OAuth2 properties (credential or token) @@ -63,19 +56,20 @@ std::string InferAuthType( return std::string(AuthProperties::kAuthTypeNone); } -} // namespace - -std::unordered_map& AuthManagers::GetRegistry() { - static std::unordered_map registry; +/// \brief Get the global registry of auth manager factories. +AuthManagerRegistry& GetRegistry() { + static AuthManagerRegistry registry; return registry; } -void AuthManagers::Register(const std::string& auth_type, AuthManagerFactory factory) { - GetRegistry()[ToLower(auth_type)] = std::move(factory); +} // namespace + +void AuthManagers::Register(std::string_view auth_type, AuthManagerFactory factory) { + GetRegistry()[StringUtils::ToLower(std::string(auth_type))] = std::move(factory); } Result> AuthManagers::Load( - const std::string& name, + std::string_view name, const std::unordered_map& properties) { std::string auth_type = InferAuthType(properties); diff --git a/src/iceberg/catalog/rest/auth/auth_managers.h b/src/iceberg/catalog/rest/auth/auth_managers.h index 610d2d9b0..ffd0bd61d 100644 --- a/src/iceberg/catalog/rest/auth/auth_managers.h +++ b/src/iceberg/catalog/rest/auth/auth_managers.h @@ -22,67 +22,49 @@ #include #include #include +#include #include #include "iceberg/catalog/rest/auth/auth_manager.h" #include "iceberg/catalog/rest/iceberg_rest_export.h" #include "iceberg/result.h" +#include "iceberg/util/string_util.h" /// \file iceberg/catalog/rest/auth/auth_managers.h /// \brief Factory for creating authentication managers. namespace iceberg::rest::auth { -/// \brief Factory function type for creating AuthManager instances. +/// \brief Function that builds an AuthManager for a given catalog. /// -/// \param name The name of the manager (used for logging). -/// \param properties Configuration properties. -/// \return A unique pointer to the created AuthManager. +/// \param name Catalog name passed to the manager. +/// \param properties Consolidated catalog configuration. +/// \return Newly created manager instance. using AuthManagerFactory = std::function( - const std::string& name, + std::string_view name, const std::unordered_map& properties)>; -/// \brief Factory class for loading authentication managers. -/// -/// This class provides a registry-based approach to create AuthManager instances -/// based on the configured authentication type. It supports built-in types -/// (none, basic, oauth2) and allows registration of custom types. -/// -/// This class is modeled after Java Iceberg's AuthManagers class. +/// \brief Registry type for AuthManager factories with heterogeneous lookup support. +using AuthManagerRegistry = + std::unordered_map; + +/// \brief Registry-backed factory for AuthManager implementations. class ICEBERG_REST_EXPORT AuthManagers { public: - /// \brief Load an authentication manager based on configuration. - /// - /// This method reads the "rest.auth.type" property to determine which - /// AuthManager implementation to create. Supported types include: - /// - "none": NoopAuthManager (no authentication) - /// - "basic": BasicAuthManager (HTTP Basic authentication) - /// - "oauth2": OAuth2AuthManager (OAuth2 authentication) - /// - "sigv4": SigV4AuthManager (AWS Signature V4) + /// \brief Load a manager by consulting the "rest.auth.type" configuration. /// - /// If no auth type is specified, the method will infer the type based on - /// other properties (e.g., presence of "credential" or "token" implies oauth2). - /// If no auth-related properties are found, it defaults to "none". - /// - /// \param name A name for the manager (used for logging). - /// \param properties Configuration properties. - /// \return A unique pointer to the created AuthManager, or an error. + /// \param name Catalog name passed to the manager. + /// \param properties Catalog properties used to determine auth type. + /// \return Manager instance or an error if no factory matches. static Result> Load( - const std::string& name, + std::string_view name, const std::unordered_map& properties); - /// \brief Register a custom authentication manager factory. - /// - /// This allows users to extend the supported authentication types by - /// registering their own AuthManager implementations. + /// \brief Register or override the factory for a given auth type. /// - /// \param auth_type The authentication type name (e.g., "custom"). - /// \param factory The factory function to create the AuthManager. - static void Register(const std::string& auth_type, AuthManagerFactory factory); - - private: - /// \brief Get the global registry of auth manager factories. - static std::unordered_map& GetRegistry(); + /// \param auth_type Case-insensitive type identifier (e.g., "basic"). + /// \param factory Factory function that produces the manager. + static void Register(std::string_view auth_type, AuthManagerFactory factory); }; } // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_properties.h b/src/iceberg/catalog/rest/auth/auth_properties.h index c718b8b2b..64eb46948 100644 --- a/src/iceberg/catalog/rest/auth/auth_properties.h +++ b/src/iceberg/catalog/rest/auth/auth_properties.h @@ -33,49 +33,34 @@ namespace iceberg::rest::auth { struct AuthProperties { /// \brief Property key for specifying the authentication type. static constexpr std::string_view kAuthType = "rest.auth.type"; - /// \brief Authentication type: no authentication. static constexpr std::string_view kAuthTypeNone = "none"; - /// \brief Authentication type: HTTP Basic authentication. static constexpr std::string_view kAuthTypeBasic = "basic"; - /// \brief Authentication type: OAuth2 authentication. static constexpr std::string_view kAuthTypeOAuth2 = "oauth2"; - /// \brief Authentication type: AWS SigV4 authentication. static constexpr std::string_view kAuthTypeSigV4 = "sigv4"; - /// \brief Property key for Basic auth username. static constexpr std::string_view kBasicUsername = "rest.auth.basic.username"; - /// \brief Property key for Basic auth password. static constexpr std::string_view kBasicPassword = "rest.auth.basic.password"; - /// \brief Property key for OAuth2 token (bearer token). static constexpr std::string_view kOAuth2Token = "token"; - /// \brief Property key for OAuth2 credential (client_id:client_secret). static constexpr std::string_view kOAuth2Credential = "credential"; - /// \brief Property key for OAuth2 scope. static constexpr std::string_view kOAuth2Scope = "scope"; - /// \brief Property key for OAuth2 server URI. static constexpr std::string_view kOAuth2ServerUri = "oauth2-server-uri"; - /// \brief Property key for enabling token refresh. static constexpr std::string_view kOAuth2TokenRefreshEnabled = "token-refresh-enabled"; - /// \brief Default OAuth2 scope for catalog operations. static constexpr std::string_view kOAuth2DefaultScope = "catalog"; - /// \brief Property key for SigV4 region. static constexpr std::string_view kSigV4Region = "rest.auth.sigv4.region"; - /// \brief Property key for SigV4 service name. static constexpr std::string_view kSigV4Service = "rest.auth.sigv4.service"; - /// \brief Property key for SigV4 delegate auth type. static constexpr std::string_view kSigV4DelegateAuthType = "rest.auth.sigv4.delegate-auth-type"; diff --git a/src/iceberg/catalog/rest/auth/auth_session.cc b/src/iceberg/catalog/rest/auth/auth_session.cc index d2e5e4c03..6f17ff526 100644 --- a/src/iceberg/catalog/rest/auth/auth_session.cc +++ b/src/iceberg/catalog/rest/auth/auth_session.cc @@ -23,43 +23,21 @@ namespace iceberg::rest::auth { -namespace { - -/// \brief An empty session implementation that does nothing. -class EmptyAuthSession : public AuthSession { - public: - void Authenticate( - [[maybe_unused]] std::unordered_map& headers) override { - // No-op: empty session does not add any authentication - } - - void Close() override { - // No resources to release - } -}; - -} // namespace - -std::shared_ptr AuthSession::Empty() { - // Use a static local variable for thread-safe singleton initialization - static auto empty_session = std::make_shared(); - return empty_session; -} - DefaultAuthSession::DefaultAuthSession( std::unordered_map headers) : headers_(std::move(headers)) {} -void DefaultAuthSession::Authenticate( +Status DefaultAuthSession::Authenticate( std::unordered_map& headers) { for (const auto& [key, value] : headers_) { headers.try_emplace(key, value); } + return {}; } -std::shared_ptr DefaultAuthSession::Of( +std::unique_ptr DefaultAuthSession::Make( std::unordered_map headers) { - return std::make_shared(std::move(headers)); + return std::make_unique(std::move(headers)); } } // namespace iceberg::rest::auth diff --git a/src/iceberg/catalog/rest/auth/auth_session.h b/src/iceberg/catalog/rest/auth/auth_session.h index dad5cc1cf..82397eb63 100644 --- a/src/iceberg/catalog/rest/auth/auth_session.h +++ b/src/iceberg/catalog/rest/auth/auth_session.h @@ -24,6 +24,7 @@ #include #include "iceberg/catalog/rest/iceberg_rest_export.h" +#include "iceberg/result.h" /// \file iceberg/catalog/rest/auth/auth_session.h /// \brief Authentication session interface for REST catalog. @@ -36,7 +37,6 @@ namespace iceberg::rest::auth { /// to be released when the session is no longer needed (e.g., token refresh threads). /// Implementations should override Close() to release any such resources. /// -/// This interface is modeled after Java Iceberg's AuthSession interface. class ICEBERG_REST_EXPORT AuthSession { public: virtual ~AuthSession() = default; @@ -46,8 +46,17 @@ class ICEBERG_REST_EXPORT AuthSession { /// This method adds authentication information (e.g., Authorization header) /// to the provided headers map. The implementation should be idempotent. /// - /// \param headers The headers map to add authentication information to. - virtual void Authenticate(std::unordered_map& headers) = 0; + /// \param[in,out] headers The headers map to add authentication information to. + /// \return Status indicating success or failure of authentication. + /// - Success: Returns Status::OK + /// - Failure: Returns one of the following errors: + /// - AuthenticationFailed: General authentication failure (invalid + /// credentials, etc.) + /// - TokenExpired: Authentication token has expired and needs refresh + /// - NotAuthorized: Not authenticated (401) + /// - IOError: Network or connection errors when reaching auth server + /// - RestError: HTTP errors from authentication service + virtual Status Authenticate(std::unordered_map& headers) = 0; /// \brief Close the session and release any resources. /// @@ -59,14 +68,6 @@ class ICEBERG_REST_EXPORT AuthSession { /// after the session is no longer needed, but rather when the session is evicted /// from the cache or the cache itself is closed. virtual void Close() {} - - /// \brief Get a shared pointer to an empty session that does nothing. - /// - /// The empty session is a singleton that simply returns the request unchanged. - /// It is useful as a default or placeholder session. - /// - /// \return A shared pointer to the empty session singleton. - static std::shared_ptr Empty(); }; /// \brief A default authentication session that adds static headers to requests. @@ -83,19 +84,9 @@ class ICEBERG_REST_EXPORT DefaultAuthSession : public AuthSession { ~DefaultAuthSession() override = default; - /// \brief Add the configured headers to the request. - /// - /// Headers are added only if they don't already exist in the request - /// (i.e., request headers take precedence). - /// - /// \param headers The headers map to add authentication information to. - void Authenticate(std::unordered_map& headers) override; + Status Authenticate(std::unordered_map& headers) override; - /// \brief Create a DefaultAuthSession with the given headers. - /// - /// \param headers The headers to add to each request. - /// \return A shared pointer to the new session. - static std::shared_ptr Of( + static std::unique_ptr Make( std::unordered_map headers); private: diff --git a/src/iceberg/result.h b/src/iceberg/result.h index ddc428a23..223352f61 100644 --- a/src/iceberg/result.h +++ b/src/iceberg/result.h @@ -30,6 +30,7 @@ namespace iceberg { /// \brief Error types for iceberg. enum class ErrorKind { kAlreadyExists, + kAuthenticationFailed, kBadRequest, kCommitFailed, kCommitStateUnknown, @@ -56,6 +57,7 @@ enum class ErrorKind { kNotSupported, kRestError, kServiceUnavailable, + kTokenExpired, kUnknownError, kValidationFailed, }; @@ -91,6 +93,7 @@ using Status = Result; } DEFINE_ERROR_FUNCTION(AlreadyExists) +DEFINE_ERROR_FUNCTION(AuthenticationFailed) DEFINE_ERROR_FUNCTION(BadRequest) DEFINE_ERROR_FUNCTION(CommitFailed) DEFINE_ERROR_FUNCTION(CommitStateUnknown) @@ -117,6 +120,7 @@ DEFINE_ERROR_FUNCTION(NotImplemented) DEFINE_ERROR_FUNCTION(NotSupported) DEFINE_ERROR_FUNCTION(RestError) DEFINE_ERROR_FUNCTION(ServiceUnavailable) +DEFINE_ERROR_FUNCTION(TokenExpired) DEFINE_ERROR_FUNCTION(UnknownError) DEFINE_ERROR_FUNCTION(ValidationFailed) From 28e195a72051dab27fe9432f0d3932973bf07a5c Mon Sep 17 00:00:00 2001 From: "shuxu.li" Date: Fri, 16 Jan 2026 18:54:50 +0800 Subject: [PATCH 6/6] feat: add restcatalog authentication api --- src/iceberg/catalog/rest/CMakeLists.txt | 1 - src/iceberg/catalog/rest/auth/auth_manager.h | 19 +++++++++++++++++-- src/iceberg/catalog/rest/auth/auth_session.h | 6 ++---- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/iceberg/catalog/rest/CMakeLists.txt b/src/iceberg/catalog/rest/CMakeLists.txt index d3d4ed221..3c58d9600 100644 --- a/src/iceberg/catalog/rest/CMakeLists.txt +++ b/src/iceberg/catalog/rest/CMakeLists.txt @@ -15,7 +15,6 @@ # specific language governing permissions and limitations # under the License. -# Include auth subdirectory add_subdirectory(auth) set(ICEBERG_REST_SOURCES diff --git a/src/iceberg/catalog/rest/auth/auth_manager.h b/src/iceberg/catalog/rest/auth/auth_manager.h index 9e76189af..7388c7c54 100644 --- a/src/iceberg/catalog/rest/auth/auth_manager.h +++ b/src/iceberg/catalog/rest/auth/auth_manager.h @@ -34,12 +34,23 @@ namespace iceberg::rest::auth { /// \brief Produces authentication sessions for catalog and table requests. +/// +/// AuthManager is responsible for creating authentication sessions at different scopes: +/// - InitSession: Short-lived session for catalog initialization (optional) +/// - CatalogSession: Long-lived session for catalog-level operations (required) +/// - TableSession: Optional table-specific session or reuse of catalog session +/// +/// Implementations are registered via AuthManagers::Register() and loaded by auth type. class ICEBERG_REST_EXPORT AuthManager { public: virtual ~AuthManager() = default; /// \brief Create a short-lived session used to contact the configuration endpoint. /// + /// This session is used only during catalog initialization to fetch server + /// configuration and perform initial authentication. It is typically discarded after + /// initialization. + /// /// \param init_client HTTP client used for initialization requests. /// \param properties Client configuration supplied by the catalog. /// \return Session for initialization or an error if credentials cannot be acquired. @@ -49,6 +60,10 @@ class ICEBERG_REST_EXPORT AuthManager { /// \brief Create the long-lived catalog session that acts as the parent session. /// + /// This session is used for all catalog-level operations (list namespaces, list tables, + /// etc.) and serves as the parent session for table-specific operations. It is owned + /// by the catalog and reused throughout the catalog's lifetime. + /// /// \param shared_client HTTP client owned by the catalog and reused for auth calls. /// \param properties Catalog properties (client config + server defaults). /// \return Session for catalog operations or an error if authentication cannot be set @@ -64,8 +79,8 @@ class ICEBERG_REST_EXPORT AuthManager { /// /// \param table Target table identifier. /// \param properties Table-specific auth properties returned by the server. - /// \param parent Catalog session to read information from. - /// \return A new session for the table, nullptr to reuse parent, or an error. + /// \param parent Catalog session to inherit from or extract information from. + /// \return A new session for the table, nullptr to reuse parent session, or an error. virtual Result> TableSession( const TableIdentifier& table, const std::unordered_map& properties, diff --git a/src/iceberg/catalog/rest/auth/auth_session.h b/src/iceberg/catalog/rest/auth/auth_session.h index 82397eb63..11bfca02c 100644 --- a/src/iceberg/catalog/rest/auth/auth_session.h +++ b/src/iceberg/catalog/rest/auth/auth_session.h @@ -64,10 +64,8 @@ class ICEBERG_REST_EXPORT AuthSession { /// sessions (e.g., OAuth2 with token refresh), this should stop any background /// threads and release resources. /// - /// Note: Since sessions may be cached, this method may not be called immediately - /// after the session is no longer needed, but rather when the session is evicted - /// from the cache or the cache itself is closed. - virtual void Close() {} + /// \return Status indicating success or failure of closing the session. + virtual Status Close() { return {}; } }; /// \brief A default authentication session that adds static headers to requests.