Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> custom_body{boost::none};
};

/**
Expand Down
59 changes: 50 additions & 9 deletions olp-cpp-sdk-authentication/src/AuthenticationClientImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -53,7 +54,6 @@
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";
Expand Down Expand Up @@ -150,8 +150,16 @@
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
Expand Down Expand Up @@ -188,8 +196,16 @@
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());
Expand All @@ -198,7 +214,7 @@
{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(
Expand Down Expand Up @@ -303,11 +319,31 @@
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;

Expand All @@ -319,8 +355,8 @@
}

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) {
Expand Down Expand Up @@ -778,6 +814,11 @@

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<RequestBodyData>(content.data(),

Check warning on line 819 in olp-cpp-sdk-authentication/src/AuthenticationClientImpl.cpp

View check run for this annotation

Codecov / codecov/patch

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

Added line #L819 was not covered by tests
content.data() + content.size());
};
rapidjson::StringBuffer data;
rapidjson::Writer<rapidjson::StringBuffer> writer(data);
writer.StartObject();
Expand Down
5 changes: 4 additions & 1 deletion olp-cpp-sdk-authentication/src/AuthenticationClientImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ class AuthenticationClientImpl {
using SignInUserCacheType =
thread::Atomic<utils::LruCache<std::string, SignInUserResult>>;

static constexpr auto kApplicationJson = "application/json";

explicit AuthenticationClientImpl(AuthenticationSettings settings);
virtual ~AuthenticationClientImpl();

Expand Down Expand Up @@ -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);

Expand Down
75 changes: 59 additions & 16 deletions olp-cpp-sdk-authentication/src/SignInResultImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -41,28 +88,24 @@ 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()) {
if (!IsValid()) {
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();
Expand Down
78 changes: 73 additions & 5 deletions olp-cpp-sdk-authentication/tests/AuthenticationClientTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,20 @@ 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));

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); }
Expand Down Expand Up @@ -168,7 +180,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),
Expand All @@ -186,7 +198,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),
Expand All @@ -204,7 +216,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),
Expand All @@ -222,7 +234,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),
Expand Down Expand Up @@ -263,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<NetworkMock>();
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", _))));
}
1 change: 1 addition & 0 deletions olp-cpp-sdk-authentication/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ set(OLP_AUTHENTICATION_TEST_SOURCES
AuthenticationClientTest.cpp
DecisionApiClientTest.cpp
CryptoTest.cpp
SignInResultImplTest.cpp
)

if (ANDROID OR IOS)
Expand Down
Loading
Loading