From b05b56048b69044e8d1908e5900fb2044e2680bb Mon Sep 17 00:00:00 2001 From: Mykhailo Diachenko Date: Wed, 9 Jul 2025 20:40:21 +0300 Subject: [PATCH 1/4] Custom body to be sent to URL from Credentials Here credentials file specifies the endpoint that should be used to retrieve authentication token. HNAV backend, which implements mTLS authentication also conforms to a bit different body and response schema. Add a possibility to add a custom body for the authentication request and parsing of token from HNAV mTLS server. Relates-To: HERESDK-7942 Signed-off-by: Mykhailo Diachenko --- .../olp/authentication/AuthenticationClient.h | 7 ++ .../src/AuthenticationClientImpl.cpp | 59 ++++++++++++--- .../src/AuthenticationClientImpl.h | 5 +- .../src/SignInResultImpl.cpp | 75 +++++++++++++++---- olp-cpp-sdk-core/include/olp/core/utils/Url.h | 17 +++++ olp-cpp-sdk-core/src/utils/Url.cpp | 16 ++++ 6 files changed, 153 insertions(+), 26 deletions(-) diff --git a/olp-cpp-sdk-authentication/include/olp/authentication/AuthenticationClient.h b/olp-cpp-sdk-authentication/include/olp/authentication/AuthenticationClient.h index c527532e3..eec6a4fcf 100644 --- a/olp-cpp-sdk-authentication/include/olp/authentication/AuthenticationClient.h +++ b/olp-cpp-sdk-authentication/include/olp/authentication/AuthenticationClient.h @@ -83,6 +83,13 @@ class AUTHENTICATION_API AuthenticationClient { * than the default expiration time supported by the access token endpoint. */ std::chrono::seconds expires_in{0}; + + /** + * @brief (Optional) Custom body to be passed, if authentication service + * requires it. Fully overrides default body and resets the request content + * type. + */ + boost::optional custom_body{boost::none}; }; /** diff --git a/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.cpp b/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.cpp index eec62c650..55a0f0ab9 100644 --- a/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.cpp +++ b/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.cpp @@ -45,6 +45,7 @@ #include "olp/core/http/NetworkUtils.h" #include "olp/core/logging/Log.h" #include "olp/core/thread/TaskScheduler.h" +#include "olp/core/utils/Url.h" namespace olp { namespace authentication { @@ -53,7 +54,6 @@ namespace { using RequestBodyData = client::OlpClient::RequestBodyType::element_type; // Tags -constexpr auto kApplicationJson = "application/json"; const std::string kOauthEndpoint = "/oauth2/token"; const std::string kSignoutEndpoint = "/logout"; const std::string kTermsEndpoint = "/terms"; @@ -150,8 +150,16 @@ client::HttpResponse CallApi(const client::OlpClient& client, client::OlpClient::ParametersType headers{ {http::kAuthorizationHeader, auth_header}}; - return client.CallApi(endpoint, "POST", {}, std::move(headers), {}, - std::move(body), kApplicationJson, std::move(context)); + return client.CallApi( + endpoint, "POST", {}, std::move(headers), {}, std::move(body), + AuthenticationClientImpl::kApplicationJson, std::move(context)); +} + +std::string DeduceContentType(const SignInProperties& properties) { + if (properties.custom_body.has_value()) { + return ""; + } + return AuthenticationClientImpl::kApplicationJson; } } // namespace @@ -188,8 +196,16 @@ olp::client::HttpResponse AuthenticationClientImpl::CallAuth( const client::OlpClient& client, const std::string& endpoint, client::CancellationContext context, const AuthenticationCredentials& credentials, - client::OlpClient::RequestBodyType body, std::time_t timestamp) { - const auto url = settings_.token_endpoint_url + endpoint; + client::OlpClient::RequestBodyType body, std::time_t timestamp, + const std::string& content_type) { + // When credentials specify authentication endpoint, it means that + // Authorization header must be created for the corresponding host. + const auto url = [&]() { + if (!credentials.GetEndpointUrl().empty()) { + return credentials.GetEndpointUrl(); + } + return settings_.token_endpoint_url + endpoint; + }(); auto auth_header = GenerateAuthorizationHeader(credentials, url, timestamp, GenerateUid()); @@ -198,7 +214,7 @@ olp::client::HttpResponse AuthenticationClientImpl::CallAuth( {http::kAuthorizationHeader, std::move(auth_header)}}; return client.CallApi(endpoint, "POST", {}, std::move(headers), {}, - std::move(body), kApplicationJson, std::move(context)); + std::move(body), content_type, std::move(context)); } SignInResult AuthenticationClientImpl::ParseAuthResponse( @@ -303,11 +319,31 @@ client::CancellationToken AuthenticationClientImpl::SignInClient( return client::ApiError::Cancelled(); } - auto client = CreateOlpClient(settings_, boost::none, false); + auto olp_client_host = settings_.token_endpoint_url; + auto endpoint = kOauthEndpoint; + // If credentials contain URL for the token endpoint then override default + // endpoint with it. Construction of the `OlpClient` requires the host part + // of URL, while `CallAuth` method - the rest of URL, hence we need to split + // URL passed in the Credentials object. + const auto credentials_endpoint = credentials.GetEndpointUrl(); + const auto maybe_host_and_rest = + olp::utils::Url::ParseHostAndRest(credentials_endpoint); + if (maybe_host_and_rest.has_value()) { + const auto& host_and_rest = maybe_host_and_rest.value(); + olp_client_host = host_and_rest.first; + endpoint = host_and_rest.second; + } + + // To pass correct URL we need to create and modify local copy of shared + // settings object. + auto settings = settings_; + settings.token_endpoint_url = olp_client_host; + auto client = CreateOlpClient(settings, boost::none, false); RequestTimer timer = CreateRequestTimer(client, context); const auto request_body = GenerateClientBody(properties); + const auto content_type = DeduceContentType(properties); SignInClientResponse response; @@ -319,8 +355,8 @@ client::CancellationToken AuthenticationClientImpl::SignInClient( } auto auth_response = - CallAuth(client, kOauthEndpoint, context, credentials, request_body, - timer.GetRequestTime()); + CallAuth(client, endpoint, context, credentials, request_body, + timer.GetRequestTime(), content_type); const auto status = auth_response.GetStatus(); if (status < 0) { @@ -778,6 +814,11 @@ client::CancellationToken AuthenticationClientImpl::GetMyAccount( client::OlpClient::RequestBodyType AuthenticationClientImpl::GenerateClientBody( const SignInProperties& properties) { + if (properties.custom_body.has_value()) { + const auto& content = properties.custom_body.value(); + return std::make_shared(content.data(), + content.data() + content.size()); + }; rapidjson::StringBuffer data; rapidjson::Writer writer(data); writer.StartObject(); diff --git a/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.h b/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.h index 3941263f4..1473f6bfd 100644 --- a/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.h +++ b/olp-cpp-sdk-authentication/src/AuthenticationClientImpl.h @@ -68,6 +68,8 @@ class AuthenticationClientImpl { using SignInUserCacheType = thread::Atomic>; + static constexpr auto kApplicationJson = "application/json"; + explicit AuthenticationClientImpl(AuthenticationSettings settings); virtual ~AuthenticationClientImpl(); @@ -162,7 +164,8 @@ class AuthenticationClientImpl { const client::OlpClient& client, const std::string& endpoint, client::CancellationContext context, const AuthenticationCredentials& credentials, - client::OlpClient::RequestBodyType body, std::time_t timestamp); + client::OlpClient::RequestBodyType body, std::time_t timestamp, + const std::string& content_type = kApplicationJson); SignInResult ParseAuthResponse(int status, std::stringstream& auth_response); diff --git a/olp-cpp-sdk-authentication/src/SignInResultImpl.cpp b/olp-cpp-sdk-authentication/src/SignInResultImpl.cpp index da162ab2c..89920e6ae 100644 --- a/olp-cpp-sdk-authentication/src/SignInResultImpl.cpp +++ b/olp-cpp-sdk-authentication/src/SignInResultImpl.cpp @@ -22,14 +22,61 @@ #include "Constants.h" #include "olp/core/http/HttpStatusCode.h" +namespace olp { +namespace authentication { + namespace { constexpr auto kTokenType = "tokenType"; constexpr auto kUserId = "userId"; constexpr auto kScope = "scope"; -} // namespace +constexpr auto kTokenTypeSnakeCase = "token_type"; +constexpr auto kAccessTokenSnakeCase = "access_token"; +constexpr auto kExpiresInSnakeCase = "expires_in"; -namespace olp { -namespace authentication { +bool HasAccessToken(const rapidjson::Document& document) { + return document.HasMember(Constants::ACCESS_TOKEN) || + document.HasMember(kAccessTokenSnakeCase); +} + +std::string ParseAccessToken(const rapidjson::Document& document) { + if (document.HasMember(Constants::ACCESS_TOKEN)) { + return document[Constants::ACCESS_TOKEN].GetString(); + } + + return document[kAccessTokenSnakeCase].GetString(); +} + +bool HasExpiresIn(const rapidjson::Document& document) { + return document.HasMember(Constants::EXPIRES_IN) || + document.HasMember(kExpiresInSnakeCase); +} + +unsigned ParseExpiresIn(const rapidjson::Document& document) { + if (document.HasMember(Constants::EXPIRES_IN)) { + return document[Constants::EXPIRES_IN].GetUint(); + } + return document[kExpiresInSnakeCase].GetUint(); +} + +bool HasTokenType(const rapidjson::Document& document) { + return document.HasMember(kTokenType) || + document.HasMember(kTokenTypeSnakeCase); +} + +std::string ParseTokenType(const rapidjson::Document& document) { + if (document.HasMember(kTokenType)) { + return document[kTokenType].GetString(); + } + + return document[kTokenTypeSnakeCase].GetString(); +} + +bool IsDocumentValid(const rapidjson::Document& document) { + return HasAccessToken(document) && HasExpiresIn(document) && + HasTokenType(document); +} + +} // namespace SignInResultImpl::SignInResultImpl() noexcept : SignInResultImpl(http::HttpStatusCode::SERVICE_UNAVAILABLE, @@ -41,10 +88,7 @@ SignInResultImpl::SignInResultImpl( : BaseResult(status, std::move(error), json_document), expiry_time_(), expires_in_() { - is_valid_ = this->BaseResult::IsValid() && - json_document->HasMember(Constants::ACCESS_TOKEN) && - json_document->HasMember(kTokenType) && - json_document->HasMember(Constants::EXPIRES_IN); + is_valid_ = this->BaseResult::IsValid() && IsDocumentValid(*json_document); // Extra response data if no errors reported if (!HasError()) { @@ -52,17 +96,16 @@ SignInResultImpl::SignInResultImpl( status_ = http::HttpStatusCode::SERVICE_UNAVAILABLE; error_.message = Constants::ERROR_HTTP_SERVICE_UNAVAILABLE; } else { - if (json_document->HasMember(Constants::ACCESS_TOKEN)) - access_token_ = (*json_document)[Constants::ACCESS_TOKEN].GetString(); - if (json_document->HasMember(kTokenType)) - token_type_ = (*json_document)[kTokenType].GetString(); + if (HasAccessToken(*json_document)) + access_token_ = ParseAccessToken(*json_document); + if (HasTokenType(*json_document)) + token_type_ = ParseTokenType(*json_document); if (json_document->HasMember(Constants::REFRESH_TOKEN)) refresh_token_ = (*json_document)[Constants::REFRESH_TOKEN].GetString(); - if (json_document->HasMember(Constants::EXPIRES_IN)) { - expiry_time_ = std::time(nullptr) + - (*json_document)[Constants::EXPIRES_IN].GetUint(); - expires_in_ = std::chrono::seconds( - (*json_document)[Constants::EXPIRES_IN].GetUint()); + if (HasExpiresIn(*json_document)) { + const auto expires_in = ParseExpiresIn(*json_document); + expiry_time_ = std::time(nullptr) + expires_in; + expires_in_ = std::chrono::seconds(expires_in); } if (json_document->HasMember(kUserId)) user_identifier_ = (*json_document)[kUserId].GetString(); diff --git a/olp-cpp-sdk-core/include/olp/core/utils/Url.h b/olp-cpp-sdk-core/include/olp/core/utils/Url.h index 65df1d1f5..89de556e6 100644 --- a/olp-cpp-sdk-core/include/olp/core/utils/Url.h +++ b/olp-cpp-sdk-core/include/olp/core/utils/Url.h @@ -25,6 +25,7 @@ #include #include +#include namespace olp { namespace utils { @@ -65,6 +66,22 @@ class CORE_API Url { static std::string Construct( const std::string& base, const std::string& path, const std::multimap& query_params); + + /** + * @brief Represents the host part and rest of full URL. + */ + using HostAndRest = std::pair; + + /** + * @brief Separates full URL to the host part the rest. Helps to split URL + * from credentials to parts passed to the OlpClient and NetworkRequest. + * + * @param url Full URL. + * + * @return An optional pair representing host part and the rest of URL. + * Returns boost::none when url cannot be split. + */ + static boost::optional ParseHostAndRest(const std::string& url); }; } // namespace utils diff --git a/olp-cpp-sdk-core/src/utils/Url.cpp b/olp-cpp-sdk-core/src/utils/Url.cpp index a78628b71..698c0f099 100644 --- a/olp-cpp-sdk-core/src/utils/Url.cpp +++ b/olp-cpp-sdk-core/src/utils/Url.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -124,5 +125,20 @@ std::string Url::Construct( return url_ss.str(); } +boost::optional Url::ParseHostAndRest( + const std::string& url) { + const auto host_and_rest_regex = + R"(^(.*:\/\/[A-Za-z0-9\-\.]+(:[0-9]+)?)(.*)$)"; + std::regex regex{host_and_rest_regex}; + std::smatch match; + if (!std::regex_search(url, match, regex) || match.size() != 4) { + return boost::none; + } + + const auto host = std::string{match[1]}; + const auto rest = std::string{match[3]}; + return std::make_pair(host, rest); +} + } // namespace utils } // namespace olp From bfcf939d8fcd2398fd932a53b9ccc74f9662301a Mon Sep 17 00:00:00 2001 From: Mykhailo Diachenko Date: Thu, 10 Jul 2025 07:57:37 +0300 Subject: [PATCH 2/4] MINOR: Fix unit test build Message to make CI happy Relates-To: HERESDK-7942 Signed-off-by: Mykhailo Diachenko --- .../tests/AuthenticationClientTest.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp b/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp index db3a3f43f..f78afbf60 100644 --- a/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp +++ b/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp @@ -51,7 +51,8 @@ class AuthenticationClientImplTestable : public auth::AuthenticationClientImpl { (const client::OlpClient&, const std::string&, client::CancellationContext, const auth::AuthenticationCredentials&, - client::OlpClient::RequestBodyType, std::time_t), + client::OlpClient::RequestBodyType, std::time_t, + const std::string&), (override)); }; @@ -168,7 +169,7 @@ TEST(AuthenticationClientTest, Timestamp) { std::time_t time = 0; - EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate)) + EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _)) .Times(3) .WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time), Wait(request_time), @@ -186,7 +187,7 @@ TEST(AuthenticationClientTest, Timestamp) { std::time_t time = 0; - EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate)) + EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _)) .Times(3) .WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time), Wait(request_time), @@ -204,7 +205,7 @@ TEST(AuthenticationClientTest, Timestamp) { std::time_t time = 0; - EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate)) + EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _)) .Times(3) .WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time), Wait(request_time), @@ -222,7 +223,7 @@ TEST(AuthenticationClientTest, Timestamp) { std::time_t time = 0; - EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate)) + EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _)) .Times(3) .WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time), Wait(request_time), From 9882d2e55fbaca0624b8c7a27362bbb49aaca7bb Mon Sep 17 00:00:00 2001 From: Mykhailo Diachenko Date: Thu, 10 Jul 2025 09:05:13 +0300 Subject: [PATCH 3/4] MINOR: Add unit tests Some files where not covered with dedicated unit tests Relates-To: HERESDK-7942 Signed-off-by: Mykhailo Diachenko --- .../tests/CMakeLists.txt | 1 + .../tests/SignInResultImplTest.cpp | 115 ++++++++---------- olp-cpp-sdk-core/tests/CMakeLists.txt | 1 + olp-cpp-sdk-core/tests/utils/UrlTest.cpp | 58 +++++++++ 4 files changed, 109 insertions(+), 66 deletions(-) create mode 100644 olp-cpp-sdk-core/tests/utils/UrlTest.cpp diff --git a/olp-cpp-sdk-authentication/tests/CMakeLists.txt b/olp-cpp-sdk-authentication/tests/CMakeLists.txt index 62d726b0d..e8d973f40 100644 --- a/olp-cpp-sdk-authentication/tests/CMakeLists.txt +++ b/olp-cpp-sdk-authentication/tests/CMakeLists.txt @@ -20,6 +20,7 @@ set(OLP_AUTHENTICATION_TEST_SOURCES AuthenticationClientTest.cpp DecisionApiClientTest.cpp CryptoTest.cpp + SignInResultImplTest.cpp ) if (ANDROID OR IOS) diff --git a/olp-cpp-sdk-authentication/tests/SignInResultImplTest.cpp b/olp-cpp-sdk-authentication/tests/SignInResultImplTest.cpp index 13d5e4b59..c6881b769 100644 --- a/olp-cpp-sdk-authentication/tests/SignInResultImplTest.cpp +++ b/olp-cpp-sdk-authentication/tests/SignInResultImplTest.cpp @@ -22,10 +22,8 @@ #include "../src/SignInResultImpl.h" namespace { -constexpr auto kClientId = "4GaUh8X9CpOax4CCC3of"; -constexpr auto kTokenResponse = - "{\"accessToken\":" - "\"eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiI0R2FVaDhY" +constexpr auto kToken = + "eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiI0R2FVaDhY" "OUNwT2F4NENDQzNvZiIsImlhdCI6MTU3ODMxMDExNCwiZXhwIjoxNTc4MzEzNzE0LCJraWQiOi" "JqMSJ9." "ZXlKaGJHY2lPaUprYVhJaUxDSmxibU1pT2lKQk1qVTJRMEpETFVoVE5URXlJbjAuLnFMb0Jmd0" @@ -38,11 +36,12 @@ constexpr auto kTokenResponse = "3xIlw0WjEAEfI6hOZnx89FdkKGXuUp7cOPaM-b133mFsE-" "S5uuuQ3vYnztKcfmEwc8SSzgCiyRTuUYUE_ESZdpwQi1jx1rbag7fBFqIIGNSVN_" "d6Scpg2s6ybHftoXajMVydnvmG4Iyls8eOeCcLDYMUPcgy05Uqc5CMvGSsL8WUOJAoLWw3eF_" - "ik1Xyv4iHRz3-67EAzzdwtL6nPbN8c0S16leVshnfdUZA\",\"tokenType\":\"bearer\"," - "\"expiresIn\":3599}"; -constexpr auto kInvalidTokenResponse = + "ik1Xyv4iHRz3-67EAzzdwtL6nPbN8c0S16leVshnfdUZA"; +constexpr auto kTokenType = "bearer"; +constexpr auto kExpiresIn = std::chrono::seconds(3599); +constexpr auto kTokenResponse = "{\"accessToken\":" - "\"eyJhbGciOiJSUzUxbx5sImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiI0R2FVaDhY" + "\"eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiI0R2FVaDhY" "OUNwT2F4NENDQzNvZiIsImlhdCI6MTU3ODMxMDExNCwiZXhwIjoxNTc4MzEzNzE0LCJraWQiOi" "JqMSJ9." "ZXlKaGJHY2lPaUprYVhJaUxDSmxibU1pT2lKQk1qVTJRMEpETFVoVE5URXlJbjAuLnFMb0Jmd0" @@ -57,26 +56,11 @@ constexpr auto kInvalidTokenResponse = "d6Scpg2s6ybHftoXajMVydnvmG4Iyls8eOeCcLDYMUPcgy05Uqc5CMvGSsL8WUOJAoLWw3eF_" "ik1Xyv4iHRz3-67EAzzdwtL6nPbN8c0S16leVshnfdUZA\",\"tokenType\":\"bearer\"," "\"expiresIn\":3599}"; -constexpr auto kNoIdFieldInToken = - "{\"accessToken\":" - "\"eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOjMsImlhdCI6" - "MTU3ODMxMDExNCwiZXhwIjoxNTc4MzEzNzE0LCJraWQiOiJqMSJ9." - "ZXlKaGJHY2lPaUprYVhJaUxDSmxibU1pT2lKQk1qVTJRMEpETFVoVE5URXlJbjAuLnFMb0Jmd0" - "1rSVktRHhuRFA1Ym9kYlEubmdZZkJIUFBsNXVBazZvY0w5djVTa3R4QU4xalE4RENjTzBiYnJO" - "Y2FuUkdKWFRvaU8tRXRuNGFNZE44OTBXaDR5QUlKaWI4LTUzY3lVMWVDZF9iX0ZCRmYxWEFsVU" - "ZkU1lFdUtFWHpoVUVVVnB0ZE1QdlY2VnVEVWktbTJoYWQ3ZEd4YUtXdVNoVVJNVFpOa1NHZy13" - "Lm9wdHJQUzRYQi1CNnBxdTZxa1Y4a09WQlNLbXFLUVhBODRMUV9IS0RZTms." - "UGyfwSjA8g6gDfLY4sCEu4fkrgp8WNm-uq5F2KLJ-zcDJIwoPKNrJjd2FEZdllG8-" - "tbbVWx5fwxn9qV6vLbm5BOMdvX3eMw_FQgsEPQAX3cOyv3he-" - "3xIlw0WjEAEfI6hOZnx89FdkKGXuUp7cOPaM-b133mFsE-" - "S5uuuQ3vYnztKcfmEwc8SSzgCiyRTuUYUE_ESZdpwQi1jx1rbag7fBFqIIGNSVN_" - "d6Scpg2s6ybHftoXajMVydnvmG4Iyls8eOeCcLDYMUPcgy05Uqc5CMvGSsL8WUOJAoLWw3eF_" - "ik1Xyv4iHRz3-67EAzzdwtL6nPbN8c0S16leVshnfdUZA\",\"tokenType\":\"bearer\"," - "\"expiresIn\":3599}"; -constexpr auto kWrongIdFieldInToken = - "{\"accessToken\":" - "\"eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOjMsImlhdCI6" - "MTU3ODMxMDExNCwiZXhwIjoxNTc4MzEzNzE0LCJraWQiOiJqMSJ9." +constexpr auto kTokenResponseSnakeCase = + "{\"access_token\":" + "\"eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiI0R2FVaDhY" + "OUNwT2F4NENDQzNvZiIsImlhdCI6MTU3ODMxMDExNCwiZXhwIjoxNTc4MzEzNzE0LCJraWQiOi" + "JqMSJ9." "ZXlKaGJHY2lPaUprYVhJaUxDSmxibU1pT2lKQk1qVTJRMEpETFVoVE5URXlJbjAuLnFMb0Jmd0" "1rSVktRHhuRFA1Ym9kYlEubmdZZkJIUFBsNXVBazZvY0w5djVTa3R4QU4xalE4RENjTzBiYnJO" "Y2FuUkdKWFRvaU8tRXRuNGFNZE44OTBXaDR5QUlKaWI4LTUzY3lVMWVDZF9iX0ZCRmYxWEFsVU" @@ -87,51 +71,50 @@ constexpr auto kWrongIdFieldInToken = "3xIlw0WjEAEfI6hOZnx89FdkKGXuUp7cOPaM-b133mFsE-" "S5uuuQ3vYnztKcfmEwc8SSzgCiyRTuUYUE_ESZdpwQi1jx1rbag7fBFqIIGNSVN_" "d6Scpg2s6ybHftoXajMVydnvmG4Iyls8eOeCcLDYMUPcgy05Uqc5CMvGSsL8WUOJAoLWw3eF_" - "ik1Xyv4iHRz3-67EAzzdwtL6nPbN8c0S16leVshnfdUZA\",\"tokenType\":\"bearer\"," - "\"expiresIn\":3599}"; -constexpr auto kWrongFormatToken = - "{\"accessToken\":" + "ik1Xyv4iHRz3-67EAzzdwtL6nPbN8c0S16leVshnfdUZA\",\"token_type\":\"bearer\"," + "\"expires_in\":3599}"; +constexpr auto kWrongFormatResponse = + "{\"access_Token\":" "\"eyJhbGciOiJSUzUxMiIsImN0eSI6IkpXVCIsImlzcyI6IkhFUkUiLCJhaWQiOiI0R2FVaDhY" "OUNwT2F4NENDQzNvZiIsImlhdCI6MTU3ODMxMDExNCwiZXhwIjoxNTc4MzEzNzE0LCJraWQiOi" "JqMSJ9\",\"tokenType\":\"bearer\",\"expiresIn\":3599}"; -TEST(SignInResultImplTest, ValidToken) { - auto doc = std::make_shared(); - doc->Parse(kTokenResponse); - olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, - std::string(), doc); - EXPECT_EQ(result.GetClientId(), kClientId); -} +TEST(SignInResultImplTest, Constructor) { + { + SCOPED_TRACE("Valid token"); + auto doc = std::make_shared(); + doc->Parse(kTokenResponse); + olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, + std::string(), doc); -TEST(SignInResultImplTest, WrongIdField) { - auto doc = std::make_shared(); - doc->Parse(kWrongIdFieldInToken); - olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, - std::string(), doc); - EXPECT_TRUE(result.GetClientId().empty()); -} + ASSERT_TRUE(result.IsValid()); + EXPECT_EQ(result.GetAccessToken(), kToken); + EXPECT_EQ(result.GetTokenType(), kTokenType); + EXPECT_EQ(result.GetExpiresIn(), kExpiresIn); + } -TEST(SignInResultImplTest, NoIdField) { - auto doc = std::make_shared(); - doc->Parse(kNoIdFieldInToken); - olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, - std::string(), doc); - EXPECT_TRUE(result.GetClientId().empty()); -} + { + SCOPED_TRACE("Valid token from snake case response"); + auto doc = std::make_shared(); + doc->Parse(kTokenResponseSnakeCase); + olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, + std::string(), doc); -TEST(SignInResultImplTest, NotSupportedFormat) { - auto doc = std::make_shared(); - doc->Parse(kWrongFormatToken); - olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, - std::string(), doc); - EXPECT_TRUE(result.GetClientId().empty()); -} + ASSERT_TRUE(result.IsValid()); + EXPECT_EQ(result.GetAccessToken(), kToken); + EXPECT_EQ(result.GetTokenType(), kTokenType); + EXPECT_EQ(result.GetExpiresIn(), kExpiresIn); + } -TEST(SignInResultImplTest, InvalidToken) { - auto doc = std::make_shared(); - doc->Parse(kInvalidTokenResponse); - olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, - std::string(), doc); - EXPECT_TRUE(result.GetClientId().empty()); + { + SCOPED_TRACE("Bad response"); + auto doc = std::make_shared(); + doc->Parse(kWrongFormatResponse); + olp::authentication::SignInResultImpl result(olp::http::HttpStatusCode::OK, + std::string(), doc); + + ASSERT_FALSE(result.IsValid()); + } } + } // namespace diff --git a/olp-cpp-sdk-core/tests/CMakeLists.txt b/olp-cpp-sdk-core/tests/CMakeLists.txt index 447ab7c38..14a4c60ad 100644 --- a/olp-cpp-sdk-core/tests/CMakeLists.txt +++ b/olp-cpp-sdk-core/tests/CMakeLists.txt @@ -72,6 +72,7 @@ set(OLP_CPP_SDK_CORE_TESTS_SOURCES ./http/NetworkUtils.cpp ./utils/UtilsTest.cpp + ./utils/UrlTest.cpp ) if (ANDROID OR IOS) diff --git a/olp-cpp-sdk-core/tests/utils/UrlTest.cpp b/olp-cpp-sdk-core/tests/utils/UrlTest.cpp new file mode 100644 index 000000000..ba3b86d6c --- /dev/null +++ b/olp-cpp-sdk-core/tests/utils/UrlTest.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2025 HERE Europe B.V. + * + * Licensed 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. + * + * SPDX-License-Identifier: Apache-2.0 + * License-Filename: LICENSE + */ + +#include + +#include + +#include + +namespace { + +using UrlTest = testing::Test; + +TEST(UrlTest, ParseHostAndRest) { + { + SCOPED_TRACE("Bad Url"); + EXPECT_FALSE(olp::utils::Url::ParseHostAndRest("bad url").has_value()); + } + + { + SCOPED_TRACE("Good Url"); + const auto result = olp::utils::Url::ParseHostAndRest( + "https://account.api.here.com/oauth2/token"); + ASSERT_TRUE(result.has_value()); + const auto& host = result.value().first; + const auto& rest = result.value().second; + EXPECT_EQ(host, "https://account.api.here.com"); + EXPECT_EQ(rest, "/oauth2/token"); + } + + { + SCOPED_TRACE("Good Url with port"); + const auto result = olp::utils::Url::ParseHostAndRest( + "https://account.api.here.com:8080/oauth2/token"); + ASSERT_TRUE(result.has_value()); + const auto& host = result.value().first; + const auto& rest = result.value().second; + EXPECT_EQ(host, "https://account.api.here.com:8080"); + EXPECT_EQ(rest, "/oauth2/token"); + } +} +} // namespace From 42e44647df1daaafa7014a8dfebbf69fc146b1fc Mon Sep 17 00:00:00 2001 From: Mykhailo Diachenko Date: Thu, 10 Jul 2025 10:02:43 +0300 Subject: [PATCH 4/4] Add unit test for AuthenticationClient Message to make CI happy Relates-To: HERESDK-7942 Signed-off-by: Mykhailo Diachenko --- .../tests/AuthenticationClientTest.cpp | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp b/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp index f78afbf60..c2e529c90 100644 --- a/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp +++ b/olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp @@ -54,6 +54,17 @@ class AuthenticationClientImplTestable : public auth::AuthenticationClientImpl { client::OlpClient::RequestBodyType, std::time_t, const std::string&), (override)); + + client::HttpResponse RealCallAuth( + const client::OlpClient& client, const std::string& endpoint, + client::CancellationContext context, + const auth::AuthenticationCredentials& credentials, + client::OlpClient::RequestBodyType body, std::time_t time, + const std::string& content_type) { + return auth::AuthenticationClientImpl::CallAuth( + client, endpoint, std::move(context), credentials, std::move(body), + time, content_type); + } }; ACTION_P(Wait, time) { std::this_thread::sleep_for(time); } @@ -264,3 +275,59 @@ TEST(AuthenticationClientTest, GenerateAuthorizationHeader) { "3D\""; EXPECT_EQ(sig, expected_sig); } + +TEST(AuthenticationClientTest, SignInWithCustomUrlAndBody) { + // Making CPPLINT happy + using testing::_; + using testing::Contains; + using testing::DoAll; + using testing::ElementsAreArray; + using testing::Not; + using testing::Pair; + using testing::Return; + using testing::SaveArg; + + using std::placeholders::_1; + using std::placeholders::_2; + using std::placeholders::_3; + using std::placeholders::_4; + using std::placeholders::_5; + using std::placeholders::_6; + using std::placeholders::_7; + + constexpr auto custom_url = "https://example.com/user/login"; + const auto custom_body = std::string("custom_body"); + olp::http::NetworkRequest expected_request{""}; + + const auth::AuthenticationCredentials credentials("", "", custom_url); + auth::SignInProperties properties; + properties.custom_body = custom_body; + + auth::AuthenticationSettings settings; + auto network_mock = std::make_shared(); + settings.network_request_handler = network_mock; + + AuthenticationClientImplTestable auth_impl(settings); + + EXPECT_CALL(*network_mock, Send) + .WillOnce(DoAll( + SaveArg<0>(&expected_request), + Return(olp::http::SendOutcome(olp::http::ErrorCode::UNKNOWN_ERROR)))); + + EXPECT_CALL(auth_impl, CallAuth) + .WillOnce(std::bind(&AuthenticationClientImplTestable::RealCallAuth, + &auth_impl, _1, _2, _3, _4, _5, _6, _7)); + + auth_impl.SignInClient( + credentials, properties, + [=](const auth::AuthenticationClient::SignInClientResponse& response) { + EXPECT_FALSE(response.IsSuccessful()); + EXPECT_EQ(response.GetError().GetErrorCode(), + client::ErrorCode::Unknown); + }); + + EXPECT_EQ(expected_request.GetUrl(), custom_url); + EXPECT_THAT(*expected_request.GetBody(), ElementsAreArray(custom_body)); + EXPECT_THAT(expected_request.GetHeaders(), + Not(Contains(Pair("Content-Type", _)))); +}