|
29 | 29 | #include <boost/uuid/uuid.hpp> |
30 | 30 | #include <boost/uuid/uuid_generators.hpp> |
31 | 31 | #include <boost/uuid/uuid_io.hpp> |
32 | | - |
33 | 32 | #include "Constants.h" |
34 | 33 | #include "Crypto.h" |
35 | 34 | #include "SignInResultImpl.h" |
36 | 35 | #include "SignInUserResultImpl.h" |
37 | 36 | #include "SignOutResultImpl.h" |
38 | 37 | #include "SignUpResultImpl.h" |
39 | 38 | #include "olp/authentication/AuthenticationError.h" |
| 39 | +#include "olp/core/client/ApiError.h" |
40 | 40 | #include "olp/core/client/CancellationToken.h" |
41 | 41 | #include "olp/core/client/ErrorCode.h" |
42 | 42 | #include "olp/core/http/HttpStatusCode.h" |
@@ -75,6 +75,7 @@ const std::string kOauthEndpoint = "/oauth2/token"; |
75 | 75 | const std::string kSignoutEndpoint = "/logout"; |
76 | 76 | const std::string kTermsEndpoint = "/terms"; |
77 | 77 | const std::string kUserEndpoint = "/user"; |
| 78 | +const std::string kTimestampEndpoint = "/timestamp"; |
78 | 79 |
|
79 | 80 | // JSON fields |
80 | 81 | constexpr auto kCountryCode = "countryCode"; |
@@ -179,10 +180,17 @@ class AuthenticationClient::Impl final { |
179 | 180 | void SetTaskScheduler(std::shared_ptr<TaskScheduler> task_scheduler); |
180 | 181 |
|
181 | 182 | private: |
| 183 | + using TimeResponse = client::ApiResponse<time_t, client::ApiError>; |
| 184 | + using TimeCallback = std::function<void(TimeResponse)>; |
| 185 | + |
| 186 | + client::CancellationToken GetTimeFromServer(TimeCallback callback); |
| 187 | + static TimeResponse ParseTimeResponse(std::stringstream& payload); |
| 188 | + |
182 | 189 | std::string base64Encode(const std::vector<uint8_t>& vector); |
183 | 190 |
|
184 | 191 | std::string generateHeader(const AuthenticationCredentials& credentials, |
185 | | - const std::string& url); |
| 192 | + const std::string& url, |
| 193 | + const time_t& timestamp = std::time(nullptr)); |
186 | 194 | std::string generateBearerHeader(const std::string& bearer_token); |
187 | 195 |
|
188 | 196 | http::NetworkRequest::RequestBodyType generateClientBody( |
@@ -264,97 +272,173 @@ client::CancellationToken AuthenticationClient::Impl::SignInClient( |
264 | 272 | return client::CancellationToken(); |
265 | 273 | } |
266 | 274 | std::weak_ptr<http::Network> weak_network(network_); |
267 | | - std::string url = server_url_; |
268 | | - url.append(kOauthEndpoint); |
269 | | - http::NetworkRequest request(url); |
270 | | - request.WithVerb(http::NetworkRequest::HttpVerb::POST); |
271 | | - request.WithHeader(http::kAuthorizationHeader, |
272 | | - generateHeader(credentials, url)); |
273 | | - request.WithHeader(http::kContentTypeHeader, kApplicationJson); |
274 | | - request.WithHeader(http::kUserAgentHeader, http::kOlpSdkUserAgent); |
275 | | - request.WithSettings(network_settings_); |
276 | 275 |
|
277 | | - std::shared_ptr<std::stringstream> payload = |
278 | | - std::make_shared<std::stringstream>(); |
279 | | - request.WithBody(generateClientBody(properties)); |
280 | 276 | auto cache = client_token_cache_; |
281 | | - auto send_outcome = network_->Send( |
282 | | - request, payload, |
283 | | - [callback, payload, credentials, |
284 | | - cache](const http::NetworkResponse& network_response) { |
285 | | - auto response_status = network_response.GetStatus(); |
286 | | - auto error_msg = network_response.GetError(); |
287 | | - |
288 | | - // Network not available, use cached token if available |
289 | | - if (response_status < 0) { |
290 | | - // Request cancelled, return |
291 | | - if (response_status == static_cast<int>(olp::http::ErrorCode::CANCELLED_ERROR)) { |
292 | | - callback({{response_status, error_msg}}); |
293 | | - return; |
294 | | - } |
295 | 277 |
|
296 | | - auto cached_response_found = |
297 | | - cache->locked([credentials, callback]( |
298 | | - utils::LruCache<std::string, SignInResult>& c) { |
299 | | - auto it = c.Find(credentials.GetKey()); |
300 | | - if (it != c.end()) { |
301 | | - SignInClientResponse response(it->value()); |
302 | | - callback(response); |
303 | | - return true; |
304 | | - } |
305 | | - return false; |
306 | | - }); |
| 278 | + client::CancellationContext context; |
307 | 279 |
|
308 | | - if (!cached_response_found) { |
309 | | - // Return an error response |
310 | | - SignInClientResponse error_response( |
311 | | - AuthenticationError(response_status, error_msg)); |
312 | | - callback(error_response); |
313 | | - } |
| 280 | + auto time_callback = [=](TimeResponse response) mutable { |
| 281 | + if (!response.IsSuccessful()) { |
| 282 | + callback(AuthenticationError( |
| 283 | + static_cast<int>(response.GetError().GetErrorCode()), |
| 284 | + response.GetError().GetMessage())); |
| 285 | + return; |
| 286 | + } |
314 | 287 |
|
| 288 | + std::string url = server_url_; |
| 289 | + url.append(kOauthEndpoint); |
| 290 | + http::NetworkRequest request(url); |
| 291 | + request.WithVerb(http::NetworkRequest::HttpVerb::POST); |
| 292 | + request.WithHeader(http::kAuthorizationHeader, |
| 293 | + generateHeader(credentials, url, response.GetResult())); |
| 294 | + request.WithHeader(http::kContentTypeHeader, kApplicationJson); |
| 295 | + request.WithHeader(http::kUserAgentHeader, http::kOlpSdkUserAgent); |
| 296 | + request.WithSettings(network_settings_); |
| 297 | + |
| 298 | + std::shared_ptr<std::stringstream> payload = |
| 299 | + std::make_shared<std::stringstream>(); |
| 300 | + request.WithBody(generateClientBody(properties)); |
| 301 | + |
| 302 | + auto network_callback = [callback, payload, credentials, cache]( |
| 303 | + const http::NetworkResponse& network_response) { |
| 304 | + auto response_status = network_response.GetStatus(); |
| 305 | + auto error_msg = network_response.GetError(); |
| 306 | + |
| 307 | + // Network not available, use cached token if available |
| 308 | + if (response_status < 0) { |
| 309 | + // Request cancelled, return |
| 310 | + if (response_status == |
| 311 | + static_cast<int>(olp::http::ErrorCode::CANCELLED_ERROR)) { |
| 312 | + callback({{response_status, error_msg}}); |
315 | 313 | return; |
316 | 314 | } |
317 | 315 |
|
318 | | - auto document = std::make_shared<rapidjson::Document>(); |
319 | | - rapidjson::IStreamWrapper stream(*payload); |
320 | | - document->ParseStream(stream); |
321 | | - |
322 | | - std::shared_ptr<SignInResultImpl> resp_impl = |
323 | | - std::make_shared<SignInResultImpl>(response_status, error_msg, |
324 | | - document); |
325 | | - SignInResult response(resp_impl); |
| 316 | + auto cached_response_found = |
| 317 | + cache->locked([credentials, callback]( |
| 318 | + utils::LruCache<std::string, SignInResult>& c) { |
| 319 | + auto it = c.Find(credentials.GetKey()); |
| 320 | + if (it != c.end()) { |
| 321 | + SignInClientResponse response(it->value()); |
| 322 | + callback(response); |
| 323 | + return true; |
| 324 | + } |
| 325 | + return false; |
| 326 | + }); |
326 | 327 |
|
327 | | - if (!resp_impl->IsValid()) { |
328 | | - callback(response); |
329 | | - return; |
| 328 | + if (!cached_response_found) { |
| 329 | + // Return an error response |
| 330 | + SignInClientResponse error_response( |
| 331 | + AuthenticationError(response_status, error_msg)); |
| 332 | + callback(error_response); |
330 | 333 | } |
331 | 334 |
|
332 | | - switch (response_status) { |
333 | | - case http::HttpStatusCode::OK: { |
334 | | - // Cache the response |
335 | | - cache->locked([credentials, &response]( |
336 | | - utils::LruCache<std::string, SignInResult>& c) { |
337 | | - return c.InsertOrAssign(credentials.GetKey(), response); |
338 | | - }); |
339 | | - // intentially do not break |
| 335 | + return; |
| 336 | + } |
| 337 | + |
| 338 | + auto document = std::make_shared<rapidjson::Document>(); |
| 339 | + rapidjson::IStreamWrapper stream(*payload); |
| 340 | + document->ParseStream(stream); |
| 341 | + |
| 342 | + std::shared_ptr<SignInResultImpl> resp_impl = |
| 343 | + std::make_shared<SignInResultImpl>(response_status, error_msg, |
| 344 | + document); |
| 345 | + SignInResult response(resp_impl); |
| 346 | + |
| 347 | + if (response_status == http::HttpStatusCode::OK) { |
| 348 | + // Cache the response |
| 349 | + cache->locked([credentials, &response]( |
| 350 | + utils::LruCache<std::string, SignInResult>& c) { |
| 351 | + return c.InsertOrAssign(credentials.GetKey(), response); |
| 352 | + }); |
| 353 | + } |
| 354 | + callback(response); |
| 355 | + }; |
| 356 | + |
| 357 | + context.ExecuteOrCancelled( |
| 358 | + [&]() { |
| 359 | + auto send_outcome = |
| 360 | + network_->Send(request, payload, network_callback); |
| 361 | + if (!send_outcome.IsSuccessful()) { |
| 362 | + std::string error_message = |
| 363 | + ErrorCodeToString(send_outcome.GetErrorCode()); |
| 364 | + callback(AuthenticationError( |
| 365 | + static_cast<int>(send_outcome.GetErrorCode()), error_message)); |
| 366 | + return client::CancellationToken(); |
340 | 367 | } |
341 | | - default: |
342 | | - callback(response); |
343 | | - break; |
| 368 | + auto request_id = send_outcome.GetRequestId(); |
| 369 | + return client::CancellationToken([weak_network, request_id]() { |
| 370 | + auto network = weak_network.lock(); |
| 371 | + |
| 372 | + if (network) { |
| 373 | + network->Cancel(request_id); |
| 374 | + } |
| 375 | + }); |
| 376 | + }, |
| 377 | + [&]() { |
| 378 | + callback(AuthenticationError( |
| 379 | + static_cast<int>(http::ErrorCode::CANCELLED_ERROR), "Cancelled")); |
| 380 | + }); |
| 381 | + }; |
| 382 | + |
| 383 | + context.ExecuteOrCancelled( |
| 384 | + [&]() { return GetTimeFromServer(std::move(time_callback)); }, []() {}); |
| 385 | + return client::CancellationToken( |
| 386 | + [context]() mutable { context.CancelOperation(); }); |
| 387 | +} |
| 388 | + |
| 389 | +AuthenticationClient::Impl::TimeResponse |
| 390 | +AuthenticationClient::Impl::ParseTimeResponse(std::stringstream& payload) { |
| 391 | + auto document = std::make_shared<rapidjson::Document>(); |
| 392 | + rapidjson::IStreamWrapper stream(payload); |
| 393 | + document->ParseStream(stream); |
| 394 | + |
| 395 | + if (!document->IsObject()) { |
| 396 | + return client::ApiError(client::ErrorCode::InternalFailure, |
| 397 | + "JSON document root is not an Object type"); |
| 398 | + } |
| 399 | + |
| 400 | + const auto timestamp_it = document->FindMember("timestamp"); |
| 401 | + if (timestamp_it == document->MemberEnd() || !timestamp_it->value.IsUint()) { |
| 402 | + return client::ApiError( |
| 403 | + client::ErrorCode::InternalFailure, |
| 404 | + "JSON document must contain timestamp integer field"); |
| 405 | + } |
| 406 | + |
| 407 | + return timestamp_it->value.GetUint(); |
| 408 | +} |
| 409 | + |
| 410 | +client::CancellationToken AuthenticationClient::Impl::GetTimeFromServer( |
| 411 | + TimeCallback callback) { |
| 412 | + std::weak_ptr<http::Network> weak_network(network_); |
| 413 | + std::string url = server_url_; |
| 414 | + url.append(kTimestampEndpoint); |
| 415 | + http::NetworkRequest request(url); |
| 416 | + request.WithVerb(http::NetworkRequest::HttpVerb::GET); |
| 417 | + request.WithHeader(http::kUserAgentHeader, http::kOlpSdkUserAgent); |
| 418 | + request.WithSettings(network_settings_); |
| 419 | + |
| 420 | + std::shared_ptr<std::stringstream> payload = |
| 421 | + std::make_shared<std::stringstream>(); |
| 422 | + auto cache = user_token_cache_; |
| 423 | + |
| 424 | + auto send_outcome = network_->Send( |
| 425 | + request, payload, |
| 426 | + [callback, payload](const http::NetworkResponse& network_response) { |
| 427 | + if (network_response.GetStatus() != http::HttpStatusCode::OK) { |
| 428 | + callback( |
| 429 | + client::ApiError(network_response.GetStatus())); |
| 430 | + return; |
344 | 431 | } |
| 432 | + |
| 433 | + callback(ParseTimeResponse(*payload)); |
345 | 434 | }); |
346 | 435 |
|
347 | 436 | if (!send_outcome.IsSuccessful()) { |
348 | | - ExecuteOrSchedule(task_scheduler_, [send_outcome, callback] { |
349 | | - std::string error_message = |
350 | | - ErrorCodeToString(send_outcome.GetErrorCode()); |
351 | | - AuthenticationError result({static_cast<int>(send_outcome.GetErrorCode()), |
352 | | - std::move(error_message)}); |
353 | | - callback(result); |
354 | | - }); |
| 437 | + std::string error_message = ErrorCodeToString(send_outcome.GetErrorCode()); |
| 438 | + callback(client::ApiError(static_cast<int>(send_outcome.GetErrorCode()), |
| 439 | + error_message)); |
355 | 440 | return client::CancellationToken(); |
356 | 441 | } |
357 | | - |
358 | 442 | auto request_id = send_outcome.GetRequestId(); |
359 | 443 | return client::CancellationToken([weak_network, request_id]() { |
360 | 444 | auto network = weak_network.lock(); |
@@ -432,7 +516,8 @@ client::CancellationToken AuthenticationClient::Impl::HandleUserRequest( |
432 | 516 | // Network not available, use cached token if available |
433 | 517 | if (response_status < 0) { |
434 | 518 | // Request cancelled, return |
435 | | - if (response_status == static_cast<int>(olp::http::ErrorCode::CANCELLED_ERROR)) { |
| 519 | + if (response_status == |
| 520 | + static_cast<int>(olp::http::ErrorCode::CANCELLED_ERROR)) { |
436 | 521 | callback({{response_status, error_msg}}); |
437 | 522 | return; |
438 | 523 | } |
@@ -660,9 +745,10 @@ std::string AuthenticationClient::Impl::base64Encode( |
660 | 745 | } |
661 | 746 |
|
662 | 747 | std::string AuthenticationClient::Impl::generateHeader( |
663 | | - const AuthenticationCredentials& credentials, const std::string& url) { |
| 748 | + const AuthenticationCredentials& credentials, const std::string& url, |
| 749 | + const time_t& timestamp) { |
664 | 750 | std::string uid = generateUid(); |
665 | | - const std::string currentTime = std::to_string(std::time(nullptr)); |
| 751 | + const std::string currentTime = std::to_string(timestamp); |
666 | 752 | const std::string encodedUri = Url::Encode(url); |
667 | 753 | const std::string encodedQuery = Url::Encode( |
668 | 754 | kOauthConsumerKey + kParamEquals + credentials.GetKey() + kParamAdd + |
|
0 commit comments