2525#include " iceberg/catalog/rest/auth/auth_properties.h"
2626#include " iceberg/catalog/rest/auth/auth_session.h"
2727#include " iceberg/catalog/rest/auth/oauth2_util.h"
28- #include " iceberg/catalog/rest/catalog_properties.h"
2928#include " iceberg/util/macros.h"
3029#include " iceberg/util/transform_util.h"
3130
@@ -84,7 +83,8 @@ class BasicAuthManager : public AuthManager {
8483 " Missing required property '{}'" , AuthProperties::kBasicPassword );
8584 std::string credential = username_it->second + " :" + password_it->second ;
8685 return AuthSession::MakeDefault (
87- {{" Authorization" , " Basic " + TransformUtil::Base64Encode (credential)}});
86+ {{std::string (kAuthorizationHeader ),
87+ " Basic " + TransformUtil::Base64Encode (credential)}});
8888 }
8989};
9090
@@ -95,29 +95,25 @@ Result<std::unique_ptr<AuthManager>> MakeBasicAuthManager(
9595}
9696
9797// / \brief OAuth2 authentication manager.
98- // /
99- // / Two-phase init: InitSession fetches and caches a token for the config request;
100- // / CatalogSession reuses the cached token and enables refresh.
101- class OAuth2AuthManager : public AuthManager {
98+ class OAuth2Manager : public AuthManager {
10299 public:
103100 Result<std::shared_ptr<AuthSession>> InitSession (
104101 HttpClient& init_client,
105102 const std::unordered_map<std::string, std::string>& properties) override {
103+ ICEBERG_ASSIGN_OR_RAISE (auto config, AuthProperties::FromProperties (properties));
104+ // No token refresh during init (short-lived session).
105+ config.Set (AuthProperties::kKeepRefreshed , false );
106+
106107 // Credential takes priority: fetch a fresh token for the config request.
107- auto credential_it = properties.find (AuthProperties::kOAuth2Credential );
108- if (credential_it != properties.end () && !credential_it->second .empty ()) {
109- ICEBERG_ASSIGN_OR_RAISE (auto ctx, ParseOAuth2Context (properties));
110- auto noop_session = AuthSession::MakeDefault ({});
108+ if (!config.credential ().empty ()) {
109+ auto init_session = AuthSession::MakeDefault (AuthHeaders (config.token ()));
111110 ICEBERG_ASSIGN_OR_RAISE (init_token_response_,
112- FetchToken (init_client, ctx.token_endpoint , ctx.client_id ,
113- ctx.client_secret , ctx.scope , *noop_session));
114- return AuthSession::MakeDefault (
115- {{" Authorization" , " Bearer " + init_token_response_->access_token }});
111+ FetchToken (init_client, *init_session, config));
112+ return AuthSession::MakeDefault (AuthHeaders (init_token_response_->access_token ));
116113 }
117114
118- auto token_it = properties.find (AuthProperties::kOAuth2Token );
119- if (token_it != properties.end () && !token_it->second .empty ()) {
120- return AuthSession::MakeDefault ({{" Authorization" , " Bearer " + token_it->second }});
115+ if (!config.token ().empty ()) {
116+ return AuthSession::MakeDefault (AuthHeaders (config.token ()));
121117 }
122118
123119 return AuthSession::MakeDefault ({});
@@ -126,104 +122,48 @@ class OAuth2AuthManager : public AuthManager {
126122 Result<std::shared_ptr<AuthSession>> CatalogSession (
127123 HttpClient& client,
128124 const std::unordered_map<std::string, std::string>& properties) override {
125+ ICEBERG_ASSIGN_OR_RAISE (auto config, AuthProperties::FromProperties (properties));
126+
127+ // Reuse token from init phase.
129128 if (init_token_response_.has_value ()) {
130129 auto token_response = std::move (*init_token_response_);
131130 init_token_response_.reset ();
132- ICEBERG_ASSIGN_OR_RAISE ( auto ctx, ParseOAuth2Context (properties));
133- return AuthSession::MakeOAuth2 (token_response, ctx. token_endpoint , ctx. client_id ,
134- ctx. client_secret , ctx. scope , client);
131+ return AuthSession::MakeOAuth2 (token_response, config. oauth2_server_uri (),
132+ config. client_id (), config. client_secret () ,
133+ config. scope () , client);
135134 }
136135
137136 // If token is provided, use it directly.
138- auto token_it = properties.find (AuthProperties::kOAuth2Token );
139- if (token_it != properties.end () && !token_it->second .empty ()) {
140- return AuthSession::MakeDefault ({{" Authorization" , " Bearer " + token_it->second }});
137+ if (!config.token ().empty ()) {
138+ return AuthSession::MakeDefault (AuthHeaders (config.token ()));
141139 }
142140
143141 // Fetch a new token using client_credentials grant.
144- auto credential_it = properties.find (AuthProperties::kOAuth2Credential );
145- if (credential_it != properties.end () && !credential_it->second .empty ()) {
146- ICEBERG_ASSIGN_OR_RAISE (auto ctx, ParseOAuth2Context (properties));
147- auto noop_session = AuthSession::MakeDefault ({});
142+ if (!config.credential ().empty ()) {
143+ auto base_session = AuthSession::MakeDefault (AuthHeaders (config.token ()));
148144 OAuthTokenResponse token_response;
149- ICEBERG_ASSIGN_OR_RAISE (token_response,
150- FetchToken (client, ctx. token_endpoint , ctx. client_id ,
151- ctx. client_secret , ctx. scope , *noop_session));
152- return AuthSession::MakeOAuth2 (token_response, ctx. token_endpoint , ctx. client_id ,
153- ctx. client_secret , ctx. scope , client);
145+ ICEBERG_ASSIGN_OR_RAISE (token_response, FetchToken (client, *base_session, config));
146+ // TODO(lishuxu): should we directly pass config to the MakeOAuth2 call?
147+ return AuthSession::MakeOAuth2 (token_response, config. oauth2_server_uri (),
148+ config. client_id (), config. client_secret () ,
149+ config. scope () , client);
154150 }
155151
156152 return AuthSession::MakeDefault ({});
157153 }
158154
159- // TODO(lishuxu): Override TableSession() to support token exchange (RFC 8693).
160- // TODO(lishuxu): Override ContextualSession() to support per-context token exchange.
155+ // TODO(lishuxu): Override TableSession() for token exchange (RFC 8693).
156+ // TODO(lishuxu): Override ContextualSession() for per-context exchange.
161157
162158 private:
163- struct OAuth2Context {
164- std::string client_id;
165- std::string client_secret;
166- std::string token_endpoint;
167- std::string scope;
168- };
169-
170- // / \brief Parse credential, token endpoint, and scope from properties.
171- static Result<OAuth2Context> ParseOAuth2Context (
172- const std::unordered_map<std::string, std::string>& properties) {
173- OAuth2Context ctx;
174-
175- auto credential_it = properties.find (AuthProperties::kOAuth2Credential );
176- if (credential_it == properties.end () || credential_it->second .empty ()) {
177- return InvalidArgument (" OAuth2 authentication requires '{}' property" ,
178- AuthProperties::kOAuth2Credential );
179- }
180- const auto & credential = credential_it->second ;
181- auto colon_pos = credential.find (' :' );
182- if (colon_pos == std::string::npos) {
183- // No colon: treat entire string as client_secret with empty client_id.
184- ctx.client_secret = credential;
185- } else {
186- ctx.client_id = credential.substr (0 , colon_pos);
187- ctx.client_secret = credential.substr (colon_pos + 1 );
188- }
189-
190- auto uri_it = properties.find (AuthProperties::kOAuth2ServerUri );
191- if (uri_it != properties.end () && !uri_it->second .empty ()) {
192- ctx.token_endpoint = uri_it->second ;
193- } else {
194- // {uri}/v1/oauth/tokens.
195- auto catalog_uri_it = properties.find (RestCatalogProperties::kUri .key ());
196- if (catalog_uri_it == properties.end () || catalog_uri_it->second .empty ()) {
197- return InvalidArgument (
198- " OAuth2 authentication requires '{}' or '{}' property to determine "
199- " token endpoint" ,
200- AuthProperties::kOAuth2ServerUri , RestCatalogProperties::kUri .key ());
201- }
202- std::string_view base = catalog_uri_it->second ;
203- while (!base.empty () && base.back () == ' /' ) {
204- base.remove_suffix (1 );
205- }
206- ctx.token_endpoint =
207- std::string (base) + " /" + std::string (AuthProperties::kOAuth2DefaultTokenPath );
208- }
209-
210- ctx.scope = AuthProperties::kOAuth2DefaultScope ;
211- auto scope_it = properties.find (AuthProperties::kOAuth2Scope );
212- if (scope_it != properties.end () && !scope_it->second .empty ()) {
213- ctx.scope = scope_it->second ;
214- }
215-
216- return ctx;
217- }
218-
219159 // / Cached token from InitSession
220160 std::optional<OAuthTokenResponse> init_token_response_;
221161};
222162
223- Result<std::unique_ptr<AuthManager>> MakeOAuth2AuthManager (
163+ Result<std::unique_ptr<AuthManager>> MakeOAuth2Manager (
224164 [[maybe_unused]] std::string_view name,
225165 [[maybe_unused]] const std::unordered_map<std::string, std::string>& properties) {
226- return std::make_unique<OAuth2AuthManager >();
166+ return std::make_unique<OAuth2Manager >();
227167}
228168
229169} // namespace iceberg::rest::auth
0 commit comments