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) {