Skip to content

Commit f955a55

Browse files
authored
feat: Implement BasicAuthManager to support basic authentication (#564)
1 parent 37bc389 commit f955a55

File tree

4 files changed

+177
-26
lines changed

4 files changed

+177
-26
lines changed

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@
1919

2020
#include "iceberg/catalog/rest/auth/auth_manager.h"
2121

22+
#include "iceberg/catalog/rest/auth/auth_manager_internal.h"
23+
#include "iceberg/catalog/rest/auth/auth_properties.h"
2224
#include "iceberg/catalog/rest/auth/auth_session.h"
25+
#include "iceberg/util/macros.h"
26+
#include "iceberg/util/transform_util.h"
2327

2428
namespace iceberg::rest::auth {
2529

@@ -45,4 +49,45 @@ Result<std::shared_ptr<AuthSession>> AuthManager::TableSession(
4549
return parent;
4650
}
4751

52+
/// \brief Authentication manager that performs no authentication.
53+
class NoopAuthManager : public AuthManager {
54+
public:
55+
Result<std::shared_ptr<AuthSession>> CatalogSession(
56+
[[maybe_unused]] HttpClient& client,
57+
[[maybe_unused]] const std::unordered_map<std::string, std::string>& properties)
58+
override {
59+
return AuthSession::MakeDefault({});
60+
}
61+
};
62+
63+
Result<std::unique_ptr<AuthManager>> MakeNoopAuthManager(
64+
[[maybe_unused]] std::string_view name,
65+
[[maybe_unused]] const std::unordered_map<std::string, std::string>& properties) {
66+
return std::make_unique<NoopAuthManager>();
67+
}
68+
69+
/// \brief Authentication manager that performs basic authentication.
70+
class BasicAuthManager : public AuthManager {
71+
public:
72+
Result<std::shared_ptr<AuthSession>> CatalogSession(
73+
[[maybe_unused]] HttpClient& client,
74+
const std::unordered_map<std::string, std::string>& properties) override {
75+
auto username_it = properties.find(AuthProperties::kBasicUsername);
76+
ICEBERG_PRECHECK(username_it != properties.end() && !username_it->second.empty(),
77+
"Missing required property '{}'", AuthProperties::kBasicUsername);
78+
auto password_it = properties.find(AuthProperties::kBasicPassword);
79+
ICEBERG_PRECHECK(password_it != properties.end() && !password_it->second.empty(),
80+
"Missing required property '{}'", AuthProperties::kBasicPassword);
81+
std::string credential = username_it->second + ":" + password_it->second;
82+
return AuthSession::MakeDefault(
83+
{{"Authorization", "Basic " + TransformUtil::Base64Encode(credential)}});
84+
}
85+
};
86+
87+
Result<std::unique_ptr<AuthManager>> MakeBasicAuthManager(
88+
[[maybe_unused]] std::string_view name,
89+
[[maybe_unused]] const std::unordered_map<std::string, std::string>& properties) {
90+
return std::make_unique<BasicAuthManager>();
91+
}
92+
4893
} // namespace iceberg::rest::auth
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
#include <memory>
23+
#include <string>
24+
#include <string_view>
25+
#include <unordered_map>
26+
27+
#include "iceberg/catalog/rest/auth/auth_manager.h"
28+
#include "iceberg/result.h"
29+
30+
/// \file iceberg/catalog/rest/auth/auth_manager_internal.h
31+
/// \brief Internal factory functions for built-in AuthManager implementations.
32+
33+
namespace iceberg::rest::auth {
34+
35+
/// \brief Create a no-op authentication manager (no authentication).
36+
Result<std::unique_ptr<AuthManager>> MakeNoopAuthManager(
37+
std::string_view name,
38+
const std::unordered_map<std::string, std::string>& properties);
39+
40+
/// \brief Create a basic authentication manager.
41+
Result<std::unique_ptr<AuthManager>> MakeBasicAuthManager(
42+
std::string_view name,
43+
const std::unordered_map<std::string, std::string>& properties);
44+
45+
} // namespace iceberg::rest::auth

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

Lines changed: 3 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@
2121

2222
#include <unordered_set>
2323

24+
#include "iceberg/catalog/rest/auth/auth_manager_internal.h"
2425
#include "iceberg/catalog/rest/auth/auth_properties.h"
25-
#include "iceberg/catalog/rest/auth/auth_session.h"
2626
#include "iceberg/util/string_util.h"
2727

2828
namespace iceberg::rest::auth {
@@ -61,33 +61,10 @@ std::string InferAuthType(
6161
return AuthProperties::kAuthTypeNone;
6262
}
6363

64-
/// \brief Authentication manager that performs no authentication.
65-
class NoopAuthManager : public AuthManager {
66-
public:
67-
static Result<std::unique_ptr<AuthManager>> Make(
68-
[[maybe_unused]] std::string_view name,
69-
[[maybe_unused]] const std::unordered_map<std::string, std::string>& properties) {
70-
return std::make_unique<NoopAuthManager>();
71-
}
72-
73-
Result<std::shared_ptr<AuthSession>> CatalogSession(
74-
[[maybe_unused]] HttpClient& client,
75-
[[maybe_unused]] const std::unordered_map<std::string, std::string>& properties)
76-
override {
77-
return AuthSession::MakeDefault({});
78-
}
79-
};
80-
81-
template <typename T>
82-
AuthManagerFactory MakeAuthFactory() {
83-
return
84-
[](std::string_view name, const std::unordered_map<std::string, std::string>& props)
85-
-> Result<std::unique_ptr<AuthManager>> { return T::Make(name, props); };
86-
}
87-
8864
AuthManagerRegistry CreateDefaultRegistry() {
8965
return {
90-
{AuthProperties::kAuthTypeNone, MakeAuthFactory<NoopAuthManager>()},
66+
{AuthProperties::kAuthTypeNone, MakeNoopAuthManager},
67+
{AuthProperties::kAuthTypeBasic, MakeBasicAuthManager},
9168
};
9269
}
9370

src/iceberg/test/auth_manager_test.cc

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,90 @@ TEST_F(AuthManagerTest, UnknownAuthTypeReturnsInvalidArgument) {
8080
EXPECT_THAT(result, HasErrorMessage("Unknown authentication type"));
8181
}
8282

83+
// Verifies loading BasicAuthManager with valid credentials
84+
TEST_F(AuthManagerTest, LoadBasicAuthManager) {
85+
std::unordered_map<std::string, std::string> properties = {
86+
{AuthProperties::kAuthType, "basic"},
87+
{AuthProperties::kBasicUsername, "admin"},
88+
{AuthProperties::kBasicPassword, "secret"}};
89+
90+
auto manager_result = AuthManagers::Load("test-catalog", properties);
91+
ASSERT_THAT(manager_result, IsOk());
92+
93+
auto session_result = manager_result.value()->CatalogSession(client_, properties);
94+
ASSERT_THAT(session_result, IsOk());
95+
96+
std::unordered_map<std::string, std::string> headers;
97+
EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk());
98+
// base64("admin:secret") == "YWRtaW46c2VjcmV0"
99+
EXPECT_EQ(headers["Authorization"], "Basic YWRtaW46c2VjcmV0");
100+
}
101+
102+
// Verifies BasicAuthManager is case-insensitive for auth type
103+
TEST_F(AuthManagerTest, BasicAuthTypeCaseInsensitive) {
104+
for (const auto& auth_type : {"BASIC", "Basic", "bAsIc"}) {
105+
std::unordered_map<std::string, std::string> properties = {
106+
{AuthProperties::kAuthType, auth_type},
107+
{AuthProperties::kBasicUsername, "user"},
108+
{AuthProperties::kBasicPassword, "pass"}};
109+
auto manager_result = AuthManagers::Load("test-catalog", properties);
110+
ASSERT_THAT(manager_result, IsOk()) << "Failed for auth type: " << auth_type;
111+
112+
auto session_result = manager_result.value()->CatalogSession(client_, properties);
113+
ASSERT_THAT(session_result, IsOk()) << "Failed for auth type: " << auth_type;
114+
115+
std::unordered_map<std::string, std::string> headers;
116+
EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk());
117+
// base64("user:pass") == "dXNlcjpwYXNz"
118+
EXPECT_EQ(headers["Authorization"], "Basic dXNlcjpwYXNz");
119+
}
120+
}
121+
122+
// Verifies BasicAuthManager fails when username is missing
123+
TEST_F(AuthManagerTest, BasicAuthMissingUsername) {
124+
std::unordered_map<std::string, std::string> properties = {
125+
{AuthProperties::kAuthType, "basic"}, {AuthProperties::kBasicPassword, "secret"}};
126+
127+
auto manager_result = AuthManagers::Load("test-catalog", properties);
128+
ASSERT_THAT(manager_result, IsOk());
129+
130+
auto session_result = manager_result.value()->CatalogSession(client_, properties);
131+
EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument));
132+
EXPECT_THAT(session_result, HasErrorMessage("Missing required property"));
133+
}
134+
135+
// Verifies BasicAuthManager fails when password is missing
136+
TEST_F(AuthManagerTest, BasicAuthMissingPassword) {
137+
std::unordered_map<std::string, std::string> properties = {
138+
{AuthProperties::kAuthType, "basic"}, {AuthProperties::kBasicUsername, "admin"}};
139+
140+
auto manager_result = AuthManagers::Load("test-catalog", properties);
141+
ASSERT_THAT(manager_result, IsOk());
142+
143+
auto session_result = manager_result.value()->CatalogSession(client_, properties);
144+
EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument));
145+
EXPECT_THAT(session_result, HasErrorMessage("Missing required property"));
146+
}
147+
148+
// Verifies BasicAuthManager handles special characters in credentials
149+
TEST_F(AuthManagerTest, BasicAuthSpecialCharacters) {
150+
std::unordered_map<std::string, std::string> properties = {
151+
{AuthProperties::kAuthType, "basic"},
152+
{AuthProperties::kBasicUsername, "user@domain.com"},
153+
{AuthProperties::kBasicPassword, "p@ss:w0rd!"}};
154+
155+
auto manager_result = AuthManagers::Load("test-catalog", properties);
156+
ASSERT_THAT(manager_result, IsOk());
157+
158+
auto session_result = manager_result.value()->CatalogSession(client_, properties);
159+
ASSERT_THAT(session_result, IsOk());
160+
161+
std::unordered_map<std::string, std::string> headers;
162+
EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk());
163+
// base64("user@domain.com:p@ss:w0rd!") == "dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE="
164+
EXPECT_EQ(headers["Authorization"], "Basic dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE=");
165+
}
166+
83167
// Verifies custom auth manager registration
84168
TEST_F(AuthManagerTest, RegisterCustomAuthManager) {
85169
AuthManagers::Register(

0 commit comments

Comments
 (0)