Skip to content

Commit cd53cc3

Browse files
Custom body to be sent to URL from Credentials (#1620)
* 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 <ext-mykhailo.z.diachenko@here.com> * MINOR: Fix unit test build Message to make CI happy Relates-To: HERESDK-7942 Signed-off-by: Mykhailo Diachenko <ext-mykhailo.z.diachenko@here.com> * MINOR: Add unit tests Some files where not covered with dedicated unit tests Relates-To: HERESDK-7942 Signed-off-by: Mykhailo Diachenko <ext-mykhailo.z.diachenko@here.com> * Add unit test for AuthenticationClient Message to make CI happy Relates-To: HERESDK-7942 Signed-off-by: Mykhailo Diachenko <ext-mykhailo.z.diachenko@here.com> --------- Signed-off-by: Mykhailo Diachenko <ext-mykhailo.z.diachenko@here.com>
1 parent 66cb092 commit cd53cc3

File tree

11 files changed

+335
-97
lines changed

11 files changed

+335
-97
lines changed

olp-cpp-sdk-authentication/include/olp/authentication/AuthenticationClient.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,13 @@ class AUTHENTICATION_API AuthenticationClient {
8383
* than the default expiration time supported by the access token endpoint.
8484
*/
8585
std::chrono::seconds expires_in{0};
86+
87+
/**
88+
* @brief (Optional) Custom body to be passed, if authentication service
89+
* requires it. Fully overrides default body and resets the request content
90+
* type.
91+
*/
92+
boost::optional<std::string> custom_body{boost::none};
8693
};
8794

8895
/**

olp-cpp-sdk-authentication/src/AuthenticationClientImpl.cpp

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "olp/core/http/NetworkUtils.h"
4646
#include "olp/core/logging/Log.h"
4747
#include "olp/core/thread/TaskScheduler.h"
48+
#include "olp/core/utils/Url.h"
4849

4950
namespace olp {
5051
namespace authentication {
@@ -53,7 +54,6 @@ namespace {
5354
using RequestBodyData = client::OlpClient::RequestBodyType::element_type;
5455

5556
// Tags
56-
constexpr auto kApplicationJson = "application/json";
5757
const std::string kOauthEndpoint = "/oauth2/token";
5858
const std::string kSignoutEndpoint = "/logout";
5959
const std::string kTermsEndpoint = "/terms";
@@ -150,8 +150,16 @@ client::HttpResponse CallApi(const client::OlpClient& client,
150150
client::OlpClient::ParametersType headers{
151151
{http::kAuthorizationHeader, auth_header}};
152152

153-
return client.CallApi(endpoint, "POST", {}, std::move(headers), {},
154-
std::move(body), kApplicationJson, std::move(context));
153+
return client.CallApi(
154+
endpoint, "POST", {}, std::move(headers), {}, std::move(body),
155+
AuthenticationClientImpl::kApplicationJson, std::move(context));
156+
}
157+
158+
std::string DeduceContentType(const SignInProperties& properties) {
159+
if (properties.custom_body.has_value()) {
160+
return "";
161+
}
162+
return AuthenticationClientImpl::kApplicationJson;
155163
}
156164

157165
} // namespace
@@ -188,8 +196,16 @@ olp::client::HttpResponse AuthenticationClientImpl::CallAuth(
188196
const client::OlpClient& client, const std::string& endpoint,
189197
client::CancellationContext context,
190198
const AuthenticationCredentials& credentials,
191-
client::OlpClient::RequestBodyType body, std::time_t timestamp) {
192-
const auto url = settings_.token_endpoint_url + endpoint;
199+
client::OlpClient::RequestBodyType body, std::time_t timestamp,
200+
const std::string& content_type) {
201+
// When credentials specify authentication endpoint, it means that
202+
// Authorization header must be created for the corresponding host.
203+
const auto url = [&]() {
204+
if (!credentials.GetEndpointUrl().empty()) {
205+
return credentials.GetEndpointUrl();
206+
}
207+
return settings_.token_endpoint_url + endpoint;
208+
}();
193209

194210
auto auth_header =
195211
GenerateAuthorizationHeader(credentials, url, timestamp, GenerateUid());
@@ -198,7 +214,7 @@ olp::client::HttpResponse AuthenticationClientImpl::CallAuth(
198214
{http::kAuthorizationHeader, std::move(auth_header)}};
199215

200216
return client.CallApi(endpoint, "POST", {}, std::move(headers), {},
201-
std::move(body), kApplicationJson, std::move(context));
217+
std::move(body), content_type, std::move(context));
202218
}
203219

204220
SignInResult AuthenticationClientImpl::ParseAuthResponse(
@@ -303,11 +319,31 @@ client::CancellationToken AuthenticationClientImpl::SignInClient(
303319
return client::ApiError::Cancelled();
304320
}
305321

306-
auto client = CreateOlpClient(settings_, boost::none, false);
322+
auto olp_client_host = settings_.token_endpoint_url;
323+
auto endpoint = kOauthEndpoint;
324+
// If credentials contain URL for the token endpoint then override default
325+
// endpoint with it. Construction of the `OlpClient` requires the host part
326+
// of URL, while `CallAuth` method - the rest of URL, hence we need to split
327+
// URL passed in the Credentials object.
328+
const auto credentials_endpoint = credentials.GetEndpointUrl();
329+
const auto maybe_host_and_rest =
330+
olp::utils::Url::ParseHostAndRest(credentials_endpoint);
331+
if (maybe_host_and_rest.has_value()) {
332+
const auto& host_and_rest = maybe_host_and_rest.value();
333+
olp_client_host = host_and_rest.first;
334+
endpoint = host_and_rest.second;
335+
}
336+
337+
// To pass correct URL we need to create and modify local copy of shared
338+
// settings object.
339+
auto settings = settings_;
340+
settings.token_endpoint_url = olp_client_host;
341+
auto client = CreateOlpClient(settings, boost::none, false);
307342

308343
RequestTimer timer = CreateRequestTimer(client, context);
309344

310345
const auto request_body = GenerateClientBody(properties);
346+
const auto content_type = DeduceContentType(properties);
311347

312348
SignInClientResponse response;
313349

@@ -319,8 +355,8 @@ client::CancellationToken AuthenticationClientImpl::SignInClient(
319355
}
320356

321357
auto auth_response =
322-
CallAuth(client, kOauthEndpoint, context, credentials, request_body,
323-
timer.GetRequestTime());
358+
CallAuth(client, endpoint, context, credentials, request_body,
359+
timer.GetRequestTime(), content_type);
324360

325361
const auto status = auth_response.GetStatus();
326362
if (status < 0) {
@@ -778,6 +814,11 @@ client::CancellationToken AuthenticationClientImpl::GetMyAccount(
778814

779815
client::OlpClient::RequestBodyType AuthenticationClientImpl::GenerateClientBody(
780816
const SignInProperties& properties) {
817+
if (properties.custom_body.has_value()) {
818+
const auto& content = properties.custom_body.value();
819+
return std::make_shared<RequestBodyData>(content.data(),
820+
content.data() + content.size());
821+
};
781822
rapidjson::StringBuffer data;
782823
rapidjson::Writer<rapidjson::StringBuffer> writer(data);
783824
writer.StartObject();

olp-cpp-sdk-authentication/src/AuthenticationClientImpl.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ class AuthenticationClientImpl {
6868
using SignInUserCacheType =
6969
thread::Atomic<utils::LruCache<std::string, SignInUserResult>>;
7070

71+
static constexpr auto kApplicationJson = "application/json";
72+
7173
explicit AuthenticationClientImpl(AuthenticationSettings settings);
7274
virtual ~AuthenticationClientImpl();
7375

@@ -162,7 +164,8 @@ class AuthenticationClientImpl {
162164
const client::OlpClient& client, const std::string& endpoint,
163165
client::CancellationContext context,
164166
const AuthenticationCredentials& credentials,
165-
client::OlpClient::RequestBodyType body, std::time_t timestamp);
167+
client::OlpClient::RequestBodyType body, std::time_t timestamp,
168+
const std::string& content_type = kApplicationJson);
166169

167170
SignInResult ParseAuthResponse(int status, std::stringstream& auth_response);
168171

olp-cpp-sdk-authentication/src/SignInResultImpl.cpp

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,61 @@
2222
#include "Constants.h"
2323
#include "olp/core/http/HttpStatusCode.h"
2424

25+
namespace olp {
26+
namespace authentication {
27+
2528
namespace {
2629
constexpr auto kTokenType = "tokenType";
2730
constexpr auto kUserId = "userId";
2831
constexpr auto kScope = "scope";
29-
} // namespace
32+
constexpr auto kTokenTypeSnakeCase = "token_type";
33+
constexpr auto kAccessTokenSnakeCase = "access_token";
34+
constexpr auto kExpiresInSnakeCase = "expires_in";
3035

31-
namespace olp {
32-
namespace authentication {
36+
bool HasAccessToken(const rapidjson::Document& document) {
37+
return document.HasMember(Constants::ACCESS_TOKEN) ||
38+
document.HasMember(kAccessTokenSnakeCase);
39+
}
40+
41+
std::string ParseAccessToken(const rapidjson::Document& document) {
42+
if (document.HasMember(Constants::ACCESS_TOKEN)) {
43+
return document[Constants::ACCESS_TOKEN].GetString();
44+
}
45+
46+
return document[kAccessTokenSnakeCase].GetString();
47+
}
48+
49+
bool HasExpiresIn(const rapidjson::Document& document) {
50+
return document.HasMember(Constants::EXPIRES_IN) ||
51+
document.HasMember(kExpiresInSnakeCase);
52+
}
53+
54+
unsigned ParseExpiresIn(const rapidjson::Document& document) {
55+
if (document.HasMember(Constants::EXPIRES_IN)) {
56+
return document[Constants::EXPIRES_IN].GetUint();
57+
}
58+
return document[kExpiresInSnakeCase].GetUint();
59+
}
60+
61+
bool HasTokenType(const rapidjson::Document& document) {
62+
return document.HasMember(kTokenType) ||
63+
document.HasMember(kTokenTypeSnakeCase);
64+
}
65+
66+
std::string ParseTokenType(const rapidjson::Document& document) {
67+
if (document.HasMember(kTokenType)) {
68+
return document[kTokenType].GetString();
69+
}
70+
71+
return document[kTokenTypeSnakeCase].GetString();
72+
}
73+
74+
bool IsDocumentValid(const rapidjson::Document& document) {
75+
return HasAccessToken(document) && HasExpiresIn(document) &&
76+
HasTokenType(document);
77+
}
78+
79+
} // namespace
3380

3481
SignInResultImpl::SignInResultImpl() noexcept
3582
: SignInResultImpl(http::HttpStatusCode::SERVICE_UNAVAILABLE,
@@ -41,28 +88,24 @@ SignInResultImpl::SignInResultImpl(
4188
: BaseResult(status, std::move(error), json_document),
4289
expiry_time_(),
4390
expires_in_() {
44-
is_valid_ = this->BaseResult::IsValid() &&
45-
json_document->HasMember(Constants::ACCESS_TOKEN) &&
46-
json_document->HasMember(kTokenType) &&
47-
json_document->HasMember(Constants::EXPIRES_IN);
91+
is_valid_ = this->BaseResult::IsValid() && IsDocumentValid(*json_document);
4892

4993
// Extra response data if no errors reported
5094
if (!HasError()) {
5195
if (!IsValid()) {
5296
status_ = http::HttpStatusCode::SERVICE_UNAVAILABLE;
5397
error_.message = Constants::ERROR_HTTP_SERVICE_UNAVAILABLE;
5498
} else {
55-
if (json_document->HasMember(Constants::ACCESS_TOKEN))
56-
access_token_ = (*json_document)[Constants::ACCESS_TOKEN].GetString();
57-
if (json_document->HasMember(kTokenType))
58-
token_type_ = (*json_document)[kTokenType].GetString();
99+
if (HasAccessToken(*json_document))
100+
access_token_ = ParseAccessToken(*json_document);
101+
if (HasTokenType(*json_document))
102+
token_type_ = ParseTokenType(*json_document);
59103
if (json_document->HasMember(Constants::REFRESH_TOKEN))
60104
refresh_token_ = (*json_document)[Constants::REFRESH_TOKEN].GetString();
61-
if (json_document->HasMember(Constants::EXPIRES_IN)) {
62-
expiry_time_ = std::time(nullptr) +
63-
(*json_document)[Constants::EXPIRES_IN].GetUint();
64-
expires_in_ = std::chrono::seconds(
65-
(*json_document)[Constants::EXPIRES_IN].GetUint());
105+
if (HasExpiresIn(*json_document)) {
106+
const auto expires_in = ParseExpiresIn(*json_document);
107+
expiry_time_ = std::time(nullptr) + expires_in;
108+
expires_in_ = std::chrono::seconds(expires_in);
66109
}
67110
if (json_document->HasMember(kUserId))
68111
user_identifier_ = (*json_document)[kUserId].GetString();

olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp

Lines changed: 73 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,20 @@ class AuthenticationClientImplTestable : public auth::AuthenticationClientImpl {
5151
(const client::OlpClient&, const std::string&,
5252
client::CancellationContext,
5353
const auth::AuthenticationCredentials&,
54-
client::OlpClient::RequestBodyType, std::time_t),
54+
client::OlpClient::RequestBodyType, std::time_t,
55+
const std::string&),
5556
(override));
57+
58+
client::HttpResponse RealCallAuth(
59+
const client::OlpClient& client, const std::string& endpoint,
60+
client::CancellationContext context,
61+
const auth::AuthenticationCredentials& credentials,
62+
client::OlpClient::RequestBodyType body, std::time_t time,
63+
const std::string& content_type) {
64+
return auth::AuthenticationClientImpl::CallAuth(
65+
client, endpoint, std::move(context), credentials, std::move(body),
66+
time, content_type);
67+
}
5668
};
5769

5870
ACTION_P(Wait, time) { std::this_thread::sleep_for(time); }
@@ -168,7 +180,7 @@ TEST(AuthenticationClientTest, Timestamp) {
168180

169181
std::time_t time = 0;
170182

171-
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate))
183+
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _))
172184
.Times(3)
173185
.WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time),
174186
Wait(request_time),
@@ -186,7 +198,7 @@ TEST(AuthenticationClientTest, Timestamp) {
186198

187199
std::time_t time = 0;
188200

189-
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate))
201+
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _))
190202
.Times(3)
191203
.WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time),
192204
Wait(request_time),
@@ -204,7 +216,7 @@ TEST(AuthenticationClientTest, Timestamp) {
204216

205217
std::time_t time = 0;
206218

207-
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate))
219+
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _))
208220
.Times(3)
209221
.WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time),
210222
Wait(request_time),
@@ -222,7 +234,7 @@ TEST(AuthenticationClientTest, Timestamp) {
222234

223235
std::time_t time = 0;
224236

225-
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate))
237+
EXPECT_CALL(auth_impl, CallAuth(_, _, _, _, _, timestamp_predicate, _))
226238
.Times(3)
227239
.WillRepeatedly(testing::DoAll(testing::SaveArg<5>(&time),
228240
Wait(request_time),
@@ -263,3 +275,59 @@ TEST(AuthenticationClientTest, GenerateAuthorizationHeader) {
263275
"3D\"";
264276
EXPECT_EQ(sig, expected_sig);
265277
}
278+
279+
TEST(AuthenticationClientTest, SignInWithCustomUrlAndBody) {
280+
// Making CPPLINT happy
281+
using testing::_;
282+
using testing::Contains;
283+
using testing::DoAll;
284+
using testing::ElementsAreArray;
285+
using testing::Not;
286+
using testing::Pair;
287+
using testing::Return;
288+
using testing::SaveArg;
289+
290+
using std::placeholders::_1;
291+
using std::placeholders::_2;
292+
using std::placeholders::_3;
293+
using std::placeholders::_4;
294+
using std::placeholders::_5;
295+
using std::placeholders::_6;
296+
using std::placeholders::_7;
297+
298+
constexpr auto custom_url = "https://example.com/user/login";
299+
const auto custom_body = std::string("custom_body");
300+
olp::http::NetworkRequest expected_request{""};
301+
302+
const auth::AuthenticationCredentials credentials("", "", custom_url);
303+
auth::SignInProperties properties;
304+
properties.custom_body = custom_body;
305+
306+
auth::AuthenticationSettings settings;
307+
auto network_mock = std::make_shared<NetworkMock>();
308+
settings.network_request_handler = network_mock;
309+
310+
AuthenticationClientImplTestable auth_impl(settings);
311+
312+
EXPECT_CALL(*network_mock, Send)
313+
.WillOnce(DoAll(
314+
SaveArg<0>(&expected_request),
315+
Return(olp::http::SendOutcome(olp::http::ErrorCode::UNKNOWN_ERROR))));
316+
317+
EXPECT_CALL(auth_impl, CallAuth)
318+
.WillOnce(std::bind(&AuthenticationClientImplTestable::RealCallAuth,
319+
&auth_impl, _1, _2, _3, _4, _5, _6, _7));
320+
321+
auth_impl.SignInClient(
322+
credentials, properties,
323+
[=](const auth::AuthenticationClient::SignInClientResponse& response) {
324+
EXPECT_FALSE(response.IsSuccessful());
325+
EXPECT_EQ(response.GetError().GetErrorCode(),
326+
client::ErrorCode::Unknown);
327+
});
328+
329+
EXPECT_EQ(expected_request.GetUrl(), custom_url);
330+
EXPECT_THAT(*expected_request.GetBody(), ElementsAreArray(custom_body));
331+
EXPECT_THAT(expected_request.GetHeaders(),
332+
Not(Contains(Pair("Content-Type", _))));
333+
}

olp-cpp-sdk-authentication/tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ set(OLP_AUTHENTICATION_TEST_SOURCES
2020
AuthenticationClientTest.cpp
2121
DecisionApiClientTest.cpp
2222
CryptoTest.cpp
23+
SignInResultImplTest.cpp
2324
)
2425

2526
if (ANDROID OR IOS)

0 commit comments

Comments
 (0)