blob: 33ae450abfaa0a58602b653632eac9cb6868b0c3 [file] [log] [blame]
/**
* 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 <lib/auth/AuthOauth2.h>
#include <curl/curl.h>
#include <sstream>
#include <stdexcept>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <lib/LogUtils.h>
DECLARE_LOG_OBJECT()
namespace pulsar {
// AuthDataOauth2
AuthDataOauth2::AuthDataOauth2(const std::string& accessToken) { accessToken_ = accessToken; }
AuthDataOauth2::~AuthDataOauth2() {}
bool AuthDataOauth2::hasDataForHttp() { return true; }
std::string AuthDataOauth2::getHttpHeaders() { return "Authorization: Bearer " + accessToken_; }
bool AuthDataOauth2::hasDataFromCommand() { return true; }
std::string AuthDataOauth2::getCommandData() { return accessToken_; }
// Oauth2TokenResult
Oauth2TokenResult::Oauth2TokenResult() { expiresIn_ = undefined_expiration; }
Oauth2TokenResult::~Oauth2TokenResult() {}
Oauth2TokenResult& Oauth2TokenResult::setAccessToken(const std::string& accessToken) {
accessToken_ = accessToken;
return *this;
}
Oauth2TokenResult& Oauth2TokenResult::setIdToken(const std::string& idToken) {
idToken_ = idToken;
return *this;
}
Oauth2TokenResult& Oauth2TokenResult::setRefreshToken(const std::string& refreshToken) {
refreshToken_ = refreshToken;
return *this;
}
Oauth2TokenResult& Oauth2TokenResult::setExpiresIn(const int64_t expiresIn) {
expiresIn_ = expiresIn;
return *this;
}
const std::string& Oauth2TokenResult::getAccessToken() const { return accessToken_; }
const std::string& Oauth2TokenResult::getIdToken() const { return idToken_; }
const std::string& Oauth2TokenResult::getRefreshToken() const { return refreshToken_; }
int64_t Oauth2TokenResult::getExpiresIn() const { return expiresIn_; }
// CachedToken
CachedToken::CachedToken() {}
CachedToken::~CachedToken() {}
// Oauth2CachedToken
static int64_t currentTimeMillis() {
using namespace boost::posix_time;
using boost::posix_time::milliseconds;
using boost::posix_time::seconds;
static ptime time_t_epoch(boost::gregorian::date(1970, 1, 1));
time_duration diff = microsec_clock::universal_time() - time_t_epoch;
return diff.total_milliseconds();
}
Oauth2CachedToken::Oauth2CachedToken(Oauth2TokenResultPtr token) {
latest_ = token;
int64_t expiredIn = token->getExpiresIn();
if (expiredIn > 0) {
expiresAt_ = expiredIn + currentTimeMillis();
} else {
throw std::runtime_error("ExpiresIn in Oauth2TokenResult invalid value: " + expiredIn);
}
authData_ = AuthenticationDataPtr(new AuthDataOauth2(token->getAccessToken()));
}
AuthenticationDataPtr Oauth2CachedToken::getAuthData() { return authData_; }
Oauth2CachedToken::~Oauth2CachedToken() {}
bool Oauth2CachedToken::isExpired() { return expiresAt_ < currentTimeMillis(); }
// OauthFlow
Oauth2Flow::Oauth2Flow() {}
Oauth2Flow::~Oauth2Flow() {}
// ClientCredentialFlow
static std::string readFromFile(const std::string& credentialsFilePath) {
std::ifstream input(credentialsFilePath);
std::stringstream buffer;
buffer << input.rdbuf();
return buffer.str();
}
ClientCredentialFlow::ClientCredentialFlow(const std::string& issuerUrl, const std::string& clientId,
const std::string& clientSecret, const std::string& audience) {
issuerUrl_ = issuerUrl;
clientId_ = clientId;
clientSecret_ = clientSecret;
audience_ = audience;
this->initialize();
}
// read clientId/clientSecret from passed in `credentialsFilePath`
ClientCredentialFlow::ClientCredentialFlow(const std::string& issuerUrl,
const std::string& credentialsFilePath,
const std::string& audience) {
issuerUrl_ = issuerUrl;
audience_ = audience;
boost::property_tree::ptree loadPtreeRoot;
try {
boost::property_tree::read_json(credentialsFilePath, loadPtreeRoot);
} catch (boost::property_tree::json_parser_error& e) {
LOG_ERROR("Failed to parse json input file for credentialsFilePath: " << credentialsFilePath
<< "with error:" << e.what());
return;
}
const std::string defaultNotFoundString = "Client Id / Secret Not Found";
clientId_ = loadPtreeRoot.get<std::string>("client_id", defaultNotFoundString);
clientSecret_ = loadPtreeRoot.get<std::string>("client_secret", defaultNotFoundString);
if (clientId_ == defaultNotFoundString || clientSecret_ == defaultNotFoundString) {
LOG_ERROR("Not get valid clientId / clientSecret: " << clientId_ << "/" << clientSecret_);
return;
}
this->initialize();
}
static size_t curlWriteCallback(void* contents, size_t size, size_t nmemb, void* responseDataPtr) {
((std::string*)responseDataPtr)->append((char*)contents, size * nmemb);
return size * nmemb;
}
void ClientCredentialFlow::initialize() {
CURL* handle = curl_easy_init();
CURLcode res;
std::string responseData;
// set header: json, request type: post
struct curl_slist* list = NULL;
list = curl_slist_append(list, "Accept: application/json");
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "GET");
// set URL: well-know endpoint
issuerUrl_.append("/.well-known/openid-configuration");
curl_easy_setopt(handle, CURLOPT_URL, issuerUrl_.c_str());
// Write callback
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curlWriteCallback);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &responseData);
// New connection is made for each call
curl_easy_setopt(handle, CURLOPT_FRESH_CONNECT, 1L);
curl_easy_setopt(handle, CURLOPT_FORBID_REUSE, 1L);
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
// Make get call to server
res = curl_easy_perform(handle);
switch (res) {
case CURLE_OK:
long response_code;
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code);
LOG_DEBUG("Received well-known configuration data " << issuerUrl_ << " code " << response_code);
if (response_code == 200) {
boost::property_tree::ptree root;
std::stringstream stream;
stream << responseData;
try {
boost::property_tree::read_json(stream, root);
} catch (boost::property_tree::json_parser_error& e) {
LOG_ERROR("Failed to parse well-known configuration data response: "
<< e.what() << "\nInput Json = " << responseData);
break;
}
this->tokenEndPoint_ = root.get<std::string>("token_endpoint");
LOG_DEBUG("Get token endpoint: " << this->tokenEndPoint_);
} else {
LOG_ERROR("Response failed for getting the well-known configuration "
<< issuerUrl_ << ". response Code " << response_code);
}
break;
default:
LOG_ERROR("Response failed for getting the well-known configuration " << issuerUrl_
<< ". Error Code " << res);
break;
}
// Free header list
curl_slist_free_all(list);
curl_easy_cleanup(handle);
}
void ClientCredentialFlow::close() {}
Oauth2TokenResultPtr ClientCredentialFlow::authenticate() {
Oauth2TokenResultPtr resultPtr = Oauth2TokenResultPtr(new Oauth2TokenResult());
CURL* handle = curl_easy_init();
CURLcode res;
std::string responseData;
// set header: json, request type: post
struct curl_slist* list = NULL;
list = curl_slist_append(list, "Content-Type: application/json");
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST");
// set URL: issuerUrl
curl_easy_setopt(handle, CURLOPT_URL, tokenEndPoint_.c_str());
// Write callback
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curlWriteCallback);
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &responseData);
// New connection is made for each call
curl_easy_setopt(handle, CURLOPT_FRESH_CONNECT, 1L);
curl_easy_setopt(handle, CURLOPT_FORBID_REUSE, 1L);
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
// fill in the request data
boost::property_tree::ptree pt;
pt.put("grant_type", "client_credentials");
pt.put("client_id", clientId_);
pt.put("client_secret", clientSecret_);
pt.put("audience", audience_);
std::stringstream ss;
boost::property_tree::json_parser::write_json(ss, pt);
std::string ssString = ss.str();
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, ssString.c_str());
// Make get call to server
res = curl_easy_perform(handle);
LOG_DEBUG("issuerUrl_ " << issuerUrl_ << " clientid: " << clientId_ << " client_secret " << clientSecret_
<< " audience " << audience_ << " ssstring " << ssString);
switch (res) {
case CURLE_OK:
long response_code;
curl_easy_getinfo(handle, CURLINFO_RESPONSE_CODE, &response_code);
LOG_DEBUG("Response received for issuerurl " << issuerUrl_ << " code " << response_code);
if (response_code == 200) {
boost::property_tree::ptree root;
std::stringstream stream;
stream << responseData;
try {
boost::property_tree::read_json(stream, root);
} catch (boost::property_tree::json_parser_error& e) {
LOG_ERROR("Failed to parse json of Oauth2 response: "
<< e.what() << "\nInput Json = " << responseData << " passedin: " << ssString);
break;
}
resultPtr->setAccessToken(root.get<std::string>("access_token"));
resultPtr->setExpiresIn(root.get<uint32_t>("expires_in"));
LOG_DEBUG("access_token: " << resultPtr->getAccessToken()
<< " expires_in: " << resultPtr->getExpiresIn());
} else {
LOG_ERROR("Response failed for issuerurl " << issuerUrl_ << ". response Code "
<< response_code << " passedin: " << ssString);
}
break;
default:
LOG_ERROR("Response failed for issuerurl " << issuerUrl_ << ". Error Code " << res
<< " passedin: " << ssString);
break;
}
// Free header list
curl_slist_free_all(list);
curl_easy_cleanup(handle);
return resultPtr;
}
// AuthOauth2
AuthOauth2::AuthOauth2(ParamMap& params) {
std::map<std::string, std::string>::iterator it;
it = params.find("private_key");
if (it != params.end()) {
flowPtr_ = FlowPtr(
new ClientCredentialFlow(params["issuer_url"], params["private_key"], params["audience"]));
} else {
flowPtr_ = FlowPtr(new ClientCredentialFlow(params["issuer_url"], params["client_id"],
params["client_secret"], params["audience"]));
}
}
AuthOauth2::~AuthOauth2() {}
ParamMap parseJsonAuthParamsString(const std::string& authParamsString) {
ParamMap params;
if (!authParamsString.empty()) {
boost::property_tree::ptree root;
std::stringstream stream;
stream << authParamsString;
try {
boost::property_tree::read_json(stream, root);
for (const auto& item : root) {
params[item.first] = item.second.get_value<std::string>();
}
} catch (boost::property_tree::json_parser_error& e) {
LOG_ERROR("Invalid String Error: " << e.what());
}
}
return params;
}
AuthenticationPtr AuthOauth2::create(const std::string& authParamsString) {
ParamMap params = parseJsonAuthParamsString(authParamsString);
return create(params);
}
AuthenticationPtr AuthOauth2::create(ParamMap& params) { return AuthenticationPtr(new AuthOauth2(params)); }
const std::string AuthOauth2::getAuthMethodName() const { return "token"; }
Result AuthOauth2::getAuthData(AuthenticationDataPtr& authDataContent) {
if (cachedTokenPtr_ == nullptr || cachedTokenPtr_->isExpired()) {
cachedTokenPtr_ = CachedTokenPtr(new Oauth2CachedToken(flowPtr_->authenticate()));
}
authDataContent = cachedTokenPtr_->getAuthData();
return ResultOk;
}
} // namespace pulsar