| /* |
| * 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 <string> |
| #include <unordered_map> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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 loading BasicAuthManager with valid credentials |
| TEST_F(AuthManagerTest, LoadBasicAuthManager) { |
| std::unordered_map<std::string, std::string> properties = { |
| {AuthProperties::kAuthType, "basic"}, |
| {AuthProperties::kBasicUsername, "admin"}, |
| {AuthProperties::kBasicPassword, "secret"}}; |
| |
| 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<std::string, std::string> headers; |
| EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); |
| // base64("admin:secret") == "YWRtaW46c2VjcmV0" |
| EXPECT_EQ(headers["Authorization"], "Basic YWRtaW46c2VjcmV0"); |
| } |
| |
| // Verifies BasicAuthManager is case-insensitive for auth type |
| TEST_F(AuthManagerTest, BasicAuthTypeCaseInsensitive) { |
| for (const auto& auth_type : {"BASIC", "Basic", "bAsIc"}) { |
| std::unordered_map<std::string, std::string> properties = { |
| {AuthProperties::kAuthType, auth_type}, |
| {AuthProperties::kBasicUsername, "user"}, |
| {AuthProperties::kBasicPassword, "pass"}}; |
| auto manager_result = AuthManagers::Load("test-catalog", properties); |
| ASSERT_THAT(manager_result, IsOk()) << "Failed for auth type: " << auth_type; |
| |
| auto session_result = manager_result.value()->CatalogSession(client_, properties); |
| ASSERT_THAT(session_result, IsOk()) << "Failed for auth type: " << auth_type; |
| |
| std::unordered_map<std::string, std::string> headers; |
| EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); |
| // base64("user:pass") == "dXNlcjpwYXNz" |
| EXPECT_EQ(headers["Authorization"], "Basic dXNlcjpwYXNz"); |
| } |
| } |
| |
| // Verifies BasicAuthManager fails when username is missing |
| TEST_F(AuthManagerTest, BasicAuthMissingUsername) { |
| std::unordered_map<std::string, std::string> properties = { |
| {AuthProperties::kAuthType, "basic"}, {AuthProperties::kBasicPassword, "secret"}}; |
| |
| auto manager_result = AuthManagers::Load("test-catalog", properties); |
| ASSERT_THAT(manager_result, IsOk()); |
| |
| auto session_result = manager_result.value()->CatalogSession(client_, properties); |
| EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument)); |
| EXPECT_THAT(session_result, HasErrorMessage("Missing required property")); |
| } |
| |
| // Verifies BasicAuthManager fails when password is missing |
| TEST_F(AuthManagerTest, BasicAuthMissingPassword) { |
| std::unordered_map<std::string, std::string> properties = { |
| {AuthProperties::kAuthType, "basic"}, {AuthProperties::kBasicUsername, "admin"}}; |
| |
| auto manager_result = AuthManagers::Load("test-catalog", properties); |
| ASSERT_THAT(manager_result, IsOk()); |
| |
| auto session_result = manager_result.value()->CatalogSession(client_, properties); |
| EXPECT_THAT(session_result, IsError(ErrorKind::kInvalidArgument)); |
| EXPECT_THAT(session_result, HasErrorMessage("Missing required property")); |
| } |
| |
| // Verifies BasicAuthManager handles special characters in credentials |
| TEST_F(AuthManagerTest, BasicAuthSpecialCharacters) { |
| std::unordered_map<std::string, std::string> properties = { |
| {AuthProperties::kAuthType, "basic"}, |
| {AuthProperties::kBasicUsername, "user@domain.com"}, |
| {AuthProperties::kBasicPassword, "p@ss:w0rd!"}}; |
| |
| 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<std::string, std::string> headers; |
| EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); |
| // base64("user@domain.com:p@ss:w0rd!") == "dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE=" |
| EXPECT_EQ(headers["Authorization"], "Basic dXNlckBkb21haW4uY29tOnBAc3M6dzByZCE="); |
| } |
| |
| // Verifies custom auth manager registration |
| TEST_F(AuthManagerTest, RegisterCustomAuthManager) { |
| AuthManagers::Register( |
| "custom", |
| []([[maybe_unused]] std::string_view name, |
| [[maybe_unused]] const std::unordered_map<std::string, std::string>& props) |
| -> Result<std::unique_ptr<AuthManager>> { |
| class CustomAuthManager : public AuthManager { |
| public: |
| Result<std::shared_ptr<AuthSession>> CatalogSession( |
| HttpClient&, const std::unordered_map<std::string, std::string>&) override { |
| return AuthSession::MakeDefault({{"X-Custom-Auth", "custom-value"}}); |
| } |
| }; |
| return std::make_unique<CustomAuthManager>(); |
| }); |
| |
| std::unordered_map<std::string, std::string> 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<std::string, std::string> headers; |
| EXPECT_THAT(session_result.value()->Authenticate(headers), IsOk()); |
| EXPECT_EQ(headers["X-Custom-Auth"], "custom-value"); |
| } |
| |
| } // namespace iceberg::rest::auth |