Skip to content

Commit 8432389

Browse files
author
shuxu.li
committed
feat: Implement OAuth2 authentication for REST catalog
1 parent 0e741b9 commit 8432389

3 files changed

Lines changed: 19 additions & 9 deletions

File tree

src/iceberg/catalog/rest/auth/auth_manager.cc

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,12 @@ class OAuth2AuthManager : public AuthManager {
180180
const auto& credential = credential_it->second;
181181
auto colon_pos = credential.find(':');
182182
if (colon_pos == std::string::npos) {
183-
return InvalidArgument(
184-
"Invalid OAuth2 credential format: expected 'client_id:client_secret'");
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);
185188
}
186-
ctx.client_id = credential.substr(0, colon_pos);
187-
ctx.client_secret = credential.substr(colon_pos + 1);
188189

189190
auto uri_it = properties.find(AuthProperties::kOAuth2ServerUri);
190191
if (uri_it != properties.end() && !uri_it->second.empty()) {

src/iceberg/catalog/rest/auth/oauth2_util.cc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ Result<OAuthTokenResponse> OAuthTokenResponseFromJsonString(const std::string& j
6363
GetJsonValue<std::string>(json, kAccessToken));
6464
ICEBERG_ASSIGN_OR_RAISE(response.token_type,
6565
GetJsonValue<std::string>(json, kTokenType));
66+
// TODO(lishuxu): When implementing auto-refresh, extract exp claim from JWT if
67+
// expires_in is missing.
6668
ICEBERG_ASSIGN_OR_RAISE(response.expires_in,
6769
GetJsonValueOrDefault<int64_t>(json, kExpiresIn, 0));
6870
ICEBERG_ASSIGN_OR_RAISE(response.refresh_token,
@@ -80,9 +82,11 @@ Result<OAuthTokenResponse> FetchToken(HttpClient& client,
8082
const std::string& scope, AuthSession& session) {
8183
std::unordered_map<std::string, std::string> form_data{
8284
{std::string(kGrantType), std::string(kClientCredentials)},
83-
{std::string(kClientId), client_id},
8485
{std::string(kClientSecret), client_secret},
8586
};
87+
if (!client_id.empty()) {
88+
form_data.emplace(std::string(kClientId), client_id);
89+
}
8690
if (!scope.empty()) {
8791
form_data.emplace(std::string(kScope), scope);
8892
}

src/iceberg/test/auth_manager_test.cc

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -251,19 +251,24 @@ TEST_F(AuthManagerTest, OAuth2MissingCredentials) {
251251
}
252252

253253
// Verifies OAuth2 fails with invalid credential format
254-
TEST_F(AuthManagerTest, OAuth2InvalidCredentialFormat) {
254+
// Verifies credential without colon is treated as client_secret only
255+
TEST_F(AuthManagerTest, OAuth2CredentialWithoutColon) {
255256
std::unordered_map<std::string, std::string> properties = {
256257
{AuthProperties::kAuthType, "oauth2"},
257-
{AuthProperties::kOAuth2Credential, "no-colon-separator"},
258+
{AuthProperties::kOAuth2Credential, "secret-only"},
259+
{AuthProperties::kOAuth2Token, "my-static-token"},
258260
{"uri", "http://localhost:8181"},
259261
};
260262

261263
auto manager_result = AuthManagers::Load("test-catalog", properties);
262264
ASSERT_THAT(manager_result, IsOk());
263265

264266
auto session_result = manager_result.value()->CatalogSession(client_, properties);
265-
EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument));
266-
EXPECT_THAT(session_result, HasErrorMessage("client_id:client_secret"));
267+
ASSERT_THAT(session_result, IsOk());
268+
269+
std::unordered_map<std::string, std::string> headers;
270+
ASSERT_THAT(session_result.value()->Authenticate(headers), IsOk());
271+
EXPECT_EQ(headers["Authorization"], "Bearer my-static-token");
267272
}
268273

269274
// Verifies OAuthTokenResponse JSON parsing

0 commit comments

Comments
 (0)