You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pulsar.apache.org by gu...@apache.org on 2021/10/13 09:13:49 UTC
[pulsar] branch branch-2.8 updated: [C++] Use URL encoded content
type for OAuth 2.0 authentication (#12341)
This is an automated email from the ASF dual-hosted git repository.
guangning pushed a commit to branch branch-2.8
in repository https://gitbox.apache.org/repos/asf/pulsar.git
The following commit(s) were added to refs/heads/branch-2.8 by this push:
new 6369e46 [C++] Use URL encoded content type for OAuth 2.0 authentication (#12341)
6369e46 is described below
commit 6369e461d862a95bee1f49d340c0a637ed9add5a
Author: Yunze Xu <xy...@163.com>
AuthorDate: Wed Oct 13 17:05:16 2021 +0800
[C++] Use URL encoded content type for OAuth 2.0 authentication (#12341)
Fixes #12334
### Motivation
When C++ client sends a HTTP request for the access token from a OAuth 2.0 server, the content type is JSON, which is incorrect and might not work in some cases.
### Modifications
- Replace `generateJsonBody` with `generateParamMap` to create a map that contains the necessary keys from `CredentialsFlow`.
- Add a `buildClientCredentialsBody` method to convert the map to URL encoded string.
- Change the content type from `json` to `x-www-form-urlencoded`.
### Verifying this change
- [x] Make sure that the change passes the CI checks.
This change is already covered by existing tests, such as `AuthPluginTest.testOauth2RequestBody`. This PR changes the test to verify `generateParamMap` because `generateJsonBody` is removed.
(cherry picked from commit 4ae7f6a1b38a003c9fc26844e52771b776bf64bf)
---
pulsar-client-cpp/lib/auth/AuthOauth2.cc | 72 ++++++++++++++++++++++---------
pulsar-client-cpp/lib/auth/AuthOauth2.h | 2 +-
pulsar-client-cpp/tests/AuthPluginTest.cc | 27 +++++-------
3 files changed, 62 insertions(+), 39 deletions(-)
diff --git a/pulsar-client-cpp/lib/auth/AuthOauth2.cc b/pulsar-client-cpp/lib/auth/AuthOauth2.cc
index a9a1498..ed39ea5 100644
--- a/pulsar-client-cpp/lib/auth/AuthOauth2.cc
+++ b/pulsar-client-cpp/lib/auth/AuthOauth2.cc
@@ -234,41 +234,71 @@ void ClientCredentialFlow::initialize() {
}
void ClientCredentialFlow::close() {}
-std::string ClientCredentialFlow::generateJsonBody() const {
+ParamMap ClientCredentialFlow::generateParamMap() const {
if (!keyFile_.isValid()) {
- return "";
+ return {};
}
- // fill in the request data
- boost::property_tree::ptree pt;
- pt.put("grant_type", "client_credentials");
- pt.put("client_id", keyFile_.getClientId());
- pt.put("client_secret", keyFile_.getClientSecret());
- pt.put("audience", audience_);
+ ParamMap params;
+ params.emplace("grant_type", "client_credentials");
+ params.emplace("client_id", keyFile_.getClientId());
+ params.emplace("client_secret", keyFile_.getClientSecret());
+ params.emplace("audience", audience_);
if (!scope_.empty()) {
- pt.put("scope", scope_);
+ params.emplace("scope", scope_);
+ }
+ return params;
+}
+
+static std::string buildClientCredentialsBody(CURL* curl, const ParamMap& params) {
+ std::ostringstream oss;
+ bool addSeparater = false;
+
+ for (const auto& kv : params) {
+ if (addSeparater) {
+ oss << "&";
+ } else {
+ addSeparater = true;
+ }
+
+ char* encodedKey = curl_easy_escape(curl, kv.first.c_str(), kv.first.length());
+ if (!encodedKey) {
+ LOG_ERROR("curl_easy_escape for " << kv.first << " failed");
+ continue;
+ }
+ char* encodedValue = curl_easy_escape(curl, kv.second.c_str(), kv.second.length());
+ if (!encodedValue) {
+ LOG_ERROR("curl_easy_escape for " << kv.second << " failed");
+ continue;
+ }
+
+ oss << encodedKey << "=" << encodedValue;
+ curl_free(encodedKey);
+ curl_free(encodedValue);
}
- std::ostringstream ss;
- boost::property_tree::json_parser::write_json(ss, pt);
- return ss.str();
+ return oss.str();
}
Oauth2TokenResultPtr ClientCredentialFlow::authenticate() {
Oauth2TokenResultPtr resultPtr = Oauth2TokenResultPtr(new Oauth2TokenResult());
- const auto jsonBody = generateJsonBody();
- if (jsonBody.empty() || tokenEndPoint_.empty()) {
+ if (tokenEndPoint_.empty()) {
return resultPtr;
}
- LOG_DEBUG("Generate JSON body for ClientCredentialFlow: " << jsonBody);
CURL* handle = curl_easy_init();
+ const auto postData = buildClientCredentialsBody(handle, generateParamMap());
+ if (postData.empty()) {
+ curl_easy_cleanup(handle);
+ return resultPtr;
+ }
+ LOG_DEBUG("Generate URL encoded body for ClientCredentialFlow: " << postData);
+
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");
+ list = curl_slist_append(list, "Content-Type: application/x-www-form-urlencoded");
curl_easy_setopt(handle, CURLOPT_HTTPHEADER, list);
curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST");
@@ -287,7 +317,7 @@ Oauth2TokenResultPtr ClientCredentialFlow::authenticate() {
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(handle, CURLOPT_SSL_VERIFYHOST, 0L);
- curl_easy_setopt(handle, CURLOPT_POSTFIELDS, jsonBody.c_str());
+ curl_easy_setopt(handle, CURLOPT_POSTFIELDS, postData.c_str());
char errorBuffer[CURL_ERROR_SIZE];
curl_easy_setopt(handle, CURLOPT_ERRORBUFFER, errorBuffer);
@@ -308,7 +338,7 @@ Oauth2TokenResultPtr ClientCredentialFlow::authenticate() {
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: " << jsonBody);
+ << e.what() << "\nInput Json = " << responseData << " passedin: " << postData);
break;
}
@@ -326,12 +356,12 @@ Oauth2TokenResultPtr ClientCredentialFlow::authenticate() {
}
} else {
LOG_ERROR("Response failed for issuerurl " << issuerUrl_ << ". response Code "
- << response_code << " passedin: " << jsonBody);
+ << response_code << " passedin: " << postData);
}
break;
default:
LOG_ERROR("Response failed for issuerurl " << issuerUrl_ << ". ErrorCode " << res << ": "
- << errorBuffer << " passedin: " << jsonBody);
+ << errorBuffer << " passedin: " << postData);
break;
}
// Free header list
diff --git a/pulsar-client-cpp/lib/auth/AuthOauth2.h b/pulsar-client-cpp/lib/auth/AuthOauth2.h
index 43f3743..b1a5ec6 100644
--- a/pulsar-client-cpp/lib/auth/AuthOauth2.h
+++ b/pulsar-client-cpp/lib/auth/AuthOauth2.h
@@ -55,7 +55,7 @@ class ClientCredentialFlow : public Oauth2Flow {
Oauth2TokenResultPtr authenticate();
void close();
- std::string generateJsonBody() const;
+ ParamMap generateParamMap() const;
private:
std::string tokenEndPoint_;
diff --git a/pulsar-client-cpp/tests/AuthPluginTest.cc b/pulsar-client-cpp/tests/AuthPluginTest.cc
index 5794723..b13505e 100644
--- a/pulsar-client-cpp/tests/AuthPluginTest.cc
+++ b/pulsar-client-cpp/tests/AuthPluginTest.cc
@@ -396,28 +396,21 @@ TEST(AuthPluginTest, testOauth2RequestBody) {
params["client_secret"] = "rT7ps7WY8uhdVuBTKWZkttwLdQotmdEliaM5rLfmgNibvqziZ-g07ZH52N_poGAb";
params["audience"] = "https://dev-kt-aa9ne.us.auth0.com/api/v2/";
- std::string expectedJson = R"({
- "grant_type": "client_credentials",
- "client_id": "Xd23RHsUnvUlP7wchjNYOaIfazgeHd9x",
- "client_secret": "rT7ps7WY8uhdVuBTKWZkttwLdQotmdEliaM5rLfmgNibvqziZ-g07ZH52N_poGAb",
- "audience": "https:\/\/dev-kt-aa9ne.us.auth0.com\/api\/v2\/"
-}
-)";
+ auto createExpectedResult = [&] {
+ auto paramsCopy = params;
+ paramsCopy.emplace("grant_type", "client_credentials");
+ paramsCopy.erase("issuer_url");
+ return paramsCopy;
+ };
+ const auto expectedResult1 = createExpectedResult();
ClientCredentialFlow flow1(params);
- ASSERT_EQ(flow1.generateJsonBody(), expectedJson);
+ ASSERT_EQ(flow1.generateParamMap(), expectedResult1);
params["scope"] = "test-scope";
- expectedJson = R"({
- "grant_type": "client_credentials",
- "client_id": "Xd23RHsUnvUlP7wchjNYOaIfazgeHd9x",
- "client_secret": "rT7ps7WY8uhdVuBTKWZkttwLdQotmdEliaM5rLfmgNibvqziZ-g07ZH52N_poGAb",
- "audience": "https:\/\/dev-kt-aa9ne.us.auth0.com\/api\/v2\/",
- "scope": "test-scope"
-}
-)";
+ const auto expectedResult2 = createExpectedResult();
ClientCredentialFlow flow2(params);
- ASSERT_EQ(flow2.generateJsonBody(), expectedJson);
+ ASSERT_EQ(flow2.generateParamMap(), expectedResult2);
}
TEST(AuthPluginTest, testOauth2Failure) {