SteNicholas commented on code in PR #244: URL: https://github.com/apache/paimon-cpp/pull/244#discussion_r3849088349
########## src/paimon/rest/dlf_auth_test.cpp: ########## @@ -0,0 +1,431 @@ +/* + * 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 "paimon/rest/dlf_auth.h" + +#include <atomic> +#include <chrono> +#include <fstream> +#include <memory> +#include <mutex> +#include <optional> +#include <string> +#include <thread> +#include <utility> +#include <vector> + +#include "gtest/gtest.h" +#include "paimon/catalog_options.h" +#include "paimon/testing/utils/testharness.h" + +namespace paimon::test { + +namespace { + +using StringMap = std::map<std::string, std::string>; + +std::chrono::system_clock::time_point FixedTime() { + return std::chrono::system_clock::from_time_t(1744775086); +} + +class SequenceTokenLoader : public DlfTokenLoader { + public: + explicit SequenceTokenLoader( + std::vector<DlfToken> tokens, + std::chrono::milliseconds load_delay = std::chrono::milliseconds(0)) + : tokens_(std::move(tokens)), load_delay_(load_delay) {} + + Result<DlfToken> LoadToken() override { + std::this_thread::sleep_for(load_delay_); + int32_t index = load_count_.fetch_add(1); + if (index >= static_cast<int32_t>(tokens_.size())) { + return Status::Invalid("test token loader exhausted"); + } + return tokens_[index]; + } + + std::string Description() const override { + return "test sequence"; + } + + int32_t GetLoadCount() const { + return load_count_.load(); + } + + private: + std::vector<DlfToken> tokens_; + std::chrono::milliseconds load_delay_; + std::atomic<int32_t> load_count_{0}; +}; + +class MockEcsHttpClient : public HttpClient { + public: + explicit MockEcsHttpClient(const std::string& metadata_url, bool direct_token = false) + : metadata_url_(metadata_url), direct_token_(direct_token) {} + + Result<HttpResponse> Execute(const HttpRequest& request, + const HttpBodyConsumer& consumer) const override { + last_request_timeout_ms_.store(request.request_timeout_ms); + HttpResponse response; + response.status_code = 200; + std::string body; + if (request.url == metadata_url_) { + if (direct_token_) { + token_requests_.fetch_add(1); + body = R"({"AccessKeyId":"ecs-ak","AccessKeySecret":"ecs-sk",)" + R"("SecurityToken":"ecs-sts","Expiration":"2027-04-16T05:44:46Z"})"; + } else { + role_requests_.fetch_add(1); + body = " test-role\n"; + } + } else if (request.url == metadata_url_ + "test-role") { + token_requests_.fetch_add(1); + body = R"({"AccessKeyId":"ecs-ak","AccessKeySecret":"ecs-sk",)" + R"("SecurityToken":"ecs-sts","Expiration":"2027-04-16T05:44:46Z"})"; + } else { + response.status_code = 404; + } + if (!body.empty()) { + PAIMON_RETURN_NOT_OK(consumer(body.data(), static_cast<int64_t>(body.size()))); + response.body_size = static_cast<int64_t>(body.size()); + } + return response; + } + + int32_t GetRoleRequestCount() const { + return role_requests_.load(); + } + + int32_t GetTokenRequestCount() const { + return token_requests_.load(); + } + + int64_t GetLastRequestTimeoutMillis() const { + return last_request_timeout_ms_.load(); + } + + private: + std::string metadata_url_; + bool direct_token_; + mutable std::atomic<int32_t> role_requests_{0}; + mutable std::atomic<int32_t> token_requests_{0}; + mutable std::atomic<int64_t> last_request_timeout_ms_{-1}; +}; + +} // namespace + +TEST(DlfDefaultSignerTest, SignsJavaCompatibleRequest) { + DlfDefaultSigner signer("cn-beijing"); + const std::string body = R"({"name":"t1"})"; + DlfToken token("YourAccessKeyId", "YourAccessKeySecret", "securityToken"); + RestAuthParameter parameter = + RestAuthParameter::Create("POST", "/v1/wh/databases/db/tables", + {{"warehouse", "my instance"}, {"branch", "main"}}, body); + + ASSERT_OK_AND_ASSIGN(DlfRequestSigner::Headers headers, + signer.SignHeaders(body, FixedTime(), token.GetSecurityToken(), "unused")); + ASSERT_EQ("20250416T034446Z", headers.at("x-dlf-date")); + ASSERT_EQ("Od9T1x3c2+JusJPFMpXe9Q==", headers.at("Content-MD5")); + ASSERT_EQ("application/json", headers.at("Content-Type")); + ASSERT_EQ("UNSIGNED-PAYLOAD", headers.at("x-dlf-content-sha256")); + ASSERT_EQ("v1", headers.at("x-dlf-version")); + ASSERT_EQ("securityToken", headers.at("x-dlf-security-token")); + + ASSERT_OK_AND_ASSIGN(std::string authorization, + signer.Authorization(parameter, token, "unused", headers)); + ASSERT_EQ( + "DLF4-HMAC-SHA256 Credential=YourAccessKeyId/20250416/cn-beijing/" + "DlfNext/aliyun_v4_request,Signature=" + "22594f8bbb8bb0ec296ced6003b7ffdf7022a8ca3815da5b53090daa11a06558", Review Comment: Please add Java's golden vector so cross-language compatibility is actually pinned. The expected strings here are newly minted for this PR. I verified out-of-band that they are consistent with Java's algorithm, but nothing in the suite locks that: a canonicalization regression (signed-header set, the empty-query newline, parameter trimming) would keep these tests green while every real request started coming back 403. Porting `DLFAuthSignatureTest#testGetAuthorization` verbatim would fix that, and it passes against this implementation as-is: - method `POST`, path `/v1/paimon/databases` - parameters `k1=v1`, `k2=v2` - body `{"name":"database","options":{"a":"b"}}` - region `cn-hangzhou`, `x-dlf-date` `20231203T121212Z` - AK `access-key-id` / `access-key-secret`, security token `securityToken` expected: ``` DLF4-HMAC-SHA256 Credential=access-key-id/20231203/cn-hangzhou/DlfNext/aliyun_v4_request,Signature=c72caf1d40b55b1905d891ee3e3de48a2f8bebefa7e39e4f277acc93c269c5e3 ``` A matching golden case for `DlfOpenApiSigner` would be good too, though Java has no published vector for it, so the one here is fine as the reference. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
