This is an automated email from the ASF dual-hosted git repository.

penghui pushed a commit to branch branch-2.11
in repository https://gitbox.apache.org/repos/asf/pulsar.git

commit d82d629f326074427da5c680b144c6e011616a1c
Author: Zixuan Liu <[email protected]>
AuthorDate: Fri Aug 5 12:08:42 2022 +0800

    [improve][cpp-client] Add basic authentication (#15822)
    
    * [improve][cpp-client] Add basic authentication
    
    Signed-off-by: Zixuan Liu <[email protected]>
    
    * Fix test
    
    Signed-off-by: Zixuan Liu <[email protected]>
    (cherry picked from commit 4840a98e247a1c75b27dd8ac12162c974bfbd777)
---
 pulsar-client-cpp/include/pulsar/Authentication.h  |  46 +++++++
 .../include/pulsar/c/authentication.h              |   3 +
 pulsar-client-cpp/lib/Authentication.cc            |   7 ++
 pulsar-client-cpp/lib/auth/AuthBasic.cc            | 110 ++++++++++++++++
 pulsar-client-cpp/lib/auth/AuthBasic.h             |  46 +++++++
 pulsar-client-cpp/pulsar-test-service-start.sh     |   4 +
 pulsar-client-cpp/test-conf/.htpasswd              |   1 +
 pulsar-client-cpp/test-conf/standalone-ssl.conf    |   2 +-
 pulsar-client-cpp/tests/AuthBasicTest.cc           | 140 +++++++++++++++++++++
 9 files changed, 358 insertions(+), 1 deletion(-)

diff --git a/pulsar-client-cpp/include/pulsar/Authentication.h 
b/pulsar-client-cpp/include/pulsar/Authentication.h
index 185ac335c80..7ab1e65a621 100644
--- a/pulsar-client-cpp/include/pulsar/Authentication.h
+++ b/pulsar-client-cpp/include/pulsar/Authentication.h
@@ -293,6 +293,52 @@ class PULSAR_PUBLIC AuthToken : public Authentication {
     AuthenticationDataPtr authDataToken_;
 };
 
+/**
+ * Basic based implementation of Pulsar client authentication
+ */
+class PULSAR_PUBLIC AuthBasic : public Authentication {
+   public:
+    explicit AuthBasic(AuthenticationDataPtr&);
+    ~AuthBasic() override;
+
+    /**
+     * Create an AuthBasic with a ParamMap
+     *
+     * It is equal to create(params[“username”], params[“password”])
+     * @see create(const std::string&, const std::string&)
+     */
+    static AuthenticationPtr create(ParamMap& params);
+
+    /**
+     * Create an AuthBasic with an authentication parameter string
+     *
+     * @param authParamsString the JSON format string: {"username": "admin", 
"password": "123456"}
+     */
+    static AuthenticationPtr create(const std::string& authParamsString);
+
+    /**
+     * Create an AuthBasic with the required parameters
+     */
+    static AuthenticationPtr create(const std::string& username, const 
std::string& password);
+
+    /**
+     * @return “basic”
+     */
+    const std::string getAuthMethodName() const override;
+
+    /**
+     * Get AuthenticationData from the current instance
+     *
+     * @param[out] authDataBasic the shared pointer of AuthenticationData. The 
content of AuthenticationData
+     * is changed to the internal data of the current instance.
+     * @return ResultOk
+     */
+    Result getAuthData(AuthenticationDataPtr& authDataBasic) override;
+
+   private:
+    AuthenticationDataPtr authDataBasic_;
+};
+
 /**
  * Athenz implementation of Pulsar client authentication
  */
diff --git a/pulsar-client-cpp/include/pulsar/c/authentication.h 
b/pulsar-client-cpp/include/pulsar/c/authentication.h
index 20247263031..9712e7158a6 100644
--- a/pulsar-client-cpp/include/pulsar/c/authentication.h
+++ b/pulsar-client-cpp/include/pulsar/c/authentication.h
@@ -39,6 +39,9 @@ PULSAR_PUBLIC pulsar_authentication_t 
*pulsar_authentication_token_create(const
 PULSAR_PUBLIC pulsar_authentication_t 
*pulsar_authentication_token_create_with_supplier(
     token_supplier tokenSupplier, void *ctx);
 
+PULSAR_PUBLIC pulsar_authentication_t 
*pulsar_authentication_basic_create(const char *username,
+                                                                          
const char *password);
+
 PULSAR_PUBLIC pulsar_authentication_t 
*pulsar_authentication_athenz_create(const char *authParamsString);
 
 PULSAR_PUBLIC pulsar_authentication_t 
*pulsar_authentication_oauth2_create(const char *authParamsString);
diff --git a/pulsar-client-cpp/lib/Authentication.cc 
b/pulsar-client-cpp/lib/Authentication.cc
index 105d1c37d2b..8fc007dba0b 100644
--- a/pulsar-client-cpp/lib/Authentication.cc
+++ b/pulsar-client-cpp/lib/Authentication.cc
@@ -23,6 +23,7 @@
 #include "auth/AuthAthenz.h"
 #include "auth/AuthToken.h"
 #include "auth/AuthOauth2.h"
+#include "auth/AuthBasic.h"
 #include <lib/LogUtils.h>
 
 #include <string>
@@ -129,6 +130,9 @@ AuthenticationPtr tryCreateBuiltinAuth(const std::string& 
pluginName, ParamMap&
     } else if (boost::iequals(pluginName, OAUTH2_TOKEN_PLUGIN_NAME) ||
                boost::iequals(pluginName, OAUTH2_TOKEN_JAVA_PLUGIN_NAME)) {
         return AuthOauth2::create(paramMap);
+    } else if (boost::iequals(pluginName, BASIC_PLUGIN_NAME) ||
+               boost::iequals(pluginName, BASIC_JAVA_PLUGIN_NAME)) {
+        return AuthBasic::create(paramMap);
     } else {
         return AuthenticationPtr();
     }
@@ -146,6 +150,9 @@ AuthenticationPtr tryCreateBuiltinAuth(const std::string& 
pluginName, const std:
     } else if (boost::iequals(pluginName, OAUTH2_TOKEN_PLUGIN_NAME) ||
                boost::iequals(pluginName, OAUTH2_TOKEN_JAVA_PLUGIN_NAME)) {
         return AuthOauth2::create(authParamsString);
+    } else if (boost::iequals(pluginName, BASIC_PLUGIN_NAME) ||
+               boost::iequals(pluginName, BASIC_JAVA_PLUGIN_NAME)) {
+        return AuthBasic::create(authParamsString);
     } else {
         return AuthenticationPtr();
     }
diff --git a/pulsar-client-cpp/lib/auth/AuthBasic.cc 
b/pulsar-client-cpp/lib/auth/AuthBasic.cc
new file mode 100644
index 00000000000..463e1474ce9
--- /dev/null
+++ b/pulsar-client-cpp/lib/auth/AuthBasic.cc
@@ -0,0 +1,110 @@
+/**
+ * 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 "AuthBasic.h"
+
+#include <stdexcept>
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+#include <boost/property_tree/json_parser.hpp>
+#include <boost/property_tree/ptree.hpp>
+namespace ptree = boost::property_tree;
+
+#include <sstream>
+#include <functional>
+
+namespace pulsar {
+
+std::string base64_encode(const std::string& s) {
+    using namespace boost::archive::iterators;
+    using It = base64_from_binary<transform_width<std::string::const_iterator, 
6, 8>>;
+    auto data = std::string(It(std::begin(s)), It(std::end(s)));
+    return data.append((3 - s.size() % 3) % 3, '=');
+}
+
+AuthDataBasic::AuthDataBasic(const std::string& username, const std::string& 
password) {
+    commandAuthToken_ = username + ":" + password;
+    httpAuthToken_ = base64_encode(commandAuthToken_);
+}
+
+AuthDataBasic::~AuthDataBasic() {}
+
+bool AuthDataBasic::hasDataForHttp() { return true; }
+
+std::string AuthDataBasic::getHttpHeaders() { return "Authorization: Basic " + 
httpAuthToken_; }
+
+bool AuthDataBasic::hasDataFromCommand() { return true; }
+
+std::string AuthDataBasic::getCommandData() { return commandAuthToken_; }
+
+// AuthBasic
+
+AuthBasic::AuthBasic(AuthenticationDataPtr& authDataBasic) { authDataBasic_ = 
authDataBasic; }
+
+AuthBasic::~AuthBasic() = default;
+
+AuthenticationPtr AuthBasic::create(const std::string& username, const 
std::string& password) {
+    AuthenticationDataPtr authDataBasic = AuthenticationDataPtr(new 
AuthDataBasic(username, password));
+    return AuthenticationPtr(new AuthBasic(authDataBasic));
+}
+
+ParamMap parseBasicAuthParamsString(const std::string& authParamsString) {
+    ParamMap params;
+    if (!authParamsString.empty()) {
+        ptree::ptree root;
+        std::stringstream stream;
+        stream << authParamsString;
+        try {
+            ptree::read_json(stream, root);
+            for (const auto& item : root) {
+                params[item.first] = item.second.get_value<std::string>();
+            }
+        } catch (ptree::json_parser_error& e) {
+            throw std::runtime_error(e.message());
+        }
+    }
+    return params;
+}
+
+AuthenticationPtr AuthBasic::create(const std::string& authParamsString) {
+    ParamMap paramMap = parseBasicAuthParamsString(authParamsString);
+    return create(paramMap);
+}
+
+AuthenticationPtr AuthBasic::create(ParamMap& params) {
+    auto usernameIt = params.find("username");
+    if (usernameIt == params.end()) {
+        throw std::runtime_error("No username provided for basic provider");
+    }
+    auto passwordIt = params.find("password");
+    if (passwordIt == params.end()) {
+        throw std::runtime_error("No password provided for basic provider");
+    }
+
+    return create(usernameIt->second, passwordIt->second);
+}
+
+const std::string AuthBasic::getAuthMethodName() const { return "basic"; }
+
+Result AuthBasic::getAuthData(AuthenticationDataPtr& authDataBasic) {
+    authDataBasic = authDataBasic_;
+    return ResultOk;
+}
+
+}  // namespace pulsar
diff --git a/pulsar-client-cpp/lib/auth/AuthBasic.h 
b/pulsar-client-cpp/lib/auth/AuthBasic.h
new file mode 100644
index 00000000000..89b995afa81
--- /dev/null
+++ b/pulsar-client-cpp/lib/auth/AuthBasic.h
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ */
+
+#pragma once
+
+#include <pulsar/Authentication.h>
+#include <string>
+#include <boost/function.hpp>
+
+namespace pulsar {
+
+const std::string BASIC_PLUGIN_NAME = "basic";
+const std::string BASIC_JAVA_PLUGIN_NAME = 
"org.apache.pulsar.client.impl.auth.AuthenticationBasic";
+
+class AuthDataBasic : public AuthenticationDataProvider {
+   public:
+    AuthDataBasic(const std::string& username, const std::string& password);
+    ~AuthDataBasic();
+
+    bool hasDataForHttp();
+    std::string getHttpHeaders();
+    bool hasDataFromCommand();
+    std::string getCommandData();
+
+   private:
+    std::string commandAuthToken_;
+    std::string httpAuthToken_;
+};
+
+}  // namespace pulsar
diff --git a/pulsar-client-cpp/pulsar-test-service-start.sh 
b/pulsar-client-cpp/pulsar-test-service-start.sh
index 48edd6ed9ae..63915cfc311 100755
--- a/pulsar-client-cpp/pulsar-test-service-start.sh
+++ b/pulsar-client-cpp/pulsar-test-service-start.sh
@@ -40,6 +40,10 @@ DATA_DIR=/tmp/pulsar-test-data
 rm -rf $DATA_DIR
 mkdir -p $DATA_DIR
 
+# Set up basic authentication
+cp $SRC_DIR/pulsar-client-cpp/test-conf/.htpasswd $DATA_DIR/.htpasswd
+export PULSAR_EXTRA_OPTS=-Dpulsar.auth.basic.conf=$DATA_DIR/.htpasswd
+
 # Copy TLS test certificates
 mkdir -p $DATA_DIR/certs
 cp $SRC_DIR/pulsar-broker/src/test/resources/authentication/tls/*.pem 
$DATA_DIR/certs
diff --git a/pulsar-client-cpp/test-conf/.htpasswd 
b/pulsar-client-cpp/test-conf/.htpasswd
new file mode 100644
index 00000000000..2aa3a4772ab
--- /dev/null
+++ b/pulsar-client-cpp/test-conf/.htpasswd
@@ -0,0 +1 @@
+admin:$apr1$FG4AO6aX$KGYPuMoLUou3i6vUkPUUf.
diff --git a/pulsar-client-cpp/test-conf/standalone-ssl.conf 
b/pulsar-client-cpp/test-conf/standalone-ssl.conf
index 7c7eeb4a74c..9d0da557493 100644
--- a/pulsar-client-cpp/test-conf/standalone-ssl.conf
+++ b/pulsar-client-cpp/test-conf/standalone-ssl.conf
@@ -97,7 +97,7 @@ anonymousUserRole=anonymous
 authenticationEnabled=true
 
 # Authentication provider name list, which is comma separated list of class 
names
-authenticationProviders=org.apache.pulsar.broker.authentication.AuthenticationProviderTls,org.apache.pulsar.broker.authentication.AuthenticationProviderToken
+authenticationProviders=org.apache.pulsar.broker.authentication.AuthenticationProviderTls,org.apache.pulsar.broker.authentication.AuthenticationProviderToken,org.apache.pulsar.broker.authentication.AuthenticationProviderBasic
 
 # Enforce authorization
 authorizationEnabled=true
diff --git a/pulsar-client-cpp/tests/AuthBasicTest.cc 
b/pulsar-client-cpp/tests/AuthBasicTest.cc
new file mode 100644
index 00000000000..296eff3cd57
--- /dev/null
+++ b/pulsar-client-cpp/tests/AuthBasicTest.cc
@@ -0,0 +1,140 @@
+/**
+ * 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 <pulsar/Authentication.h>
+
+#include <gtest/gtest.h>
+#include <pulsar/Client.h>
+
+#include <string>
+
+using namespace pulsar;
+
+static const std::string serviceUrl = "pulsar://localhost:6650";
+static const std::string serviceUrlHttp = "http://localhost:8080";;
+
+TEST(AuthPluginBasic, testBasic) {
+    ClientConfiguration config = ClientConfiguration();
+    AuthenticationPtr auth = pulsar::AuthBasic::create("admin", "123456");
+
+    ASSERT_TRUE(auth != NULL);
+    ASSERT_EQ(auth->getAuthMethodName(), "basic");
+
+    pulsar::AuthenticationDataPtr data;
+    ASSERT_EQ(auth->getAuthData(data), pulsar::ResultOk);
+    ASSERT_EQ(data->hasDataFromCommand(), true);
+    ASSERT_EQ(data->getCommandData(), "admin:123456");
+    ASSERT_EQ(data->hasDataForTls(), false);
+    ASSERT_EQ(data->hasDataForHttp(), true);
+    ASSERT_EQ(auth.use_count(), 1);
+
+    config.setAuth(auth);
+    Client client(serviceUrl, config);
+
+    std::string topicName = "persistent://private/auth/test-basic";
+    std::string subName = "subscription-name";
+
+    Producer producer;
+    Result result = client.createProducer(topicName, producer);
+    ASSERT_EQ(ResultOk, result);
+    producer.close();
+}
+
+TEST(AuthPluginBasic, testBasicWithHttp) {
+    ClientConfiguration config = ClientConfiguration();
+    AuthenticationPtr auth = pulsar::AuthBasic::create("admin", "123456");
+
+    ASSERT_TRUE(auth != NULL);
+    ASSERT_EQ(auth->getAuthMethodName(), "basic");
+
+    pulsar::AuthenticationDataPtr data;
+    ASSERT_EQ(auth->getAuthData(data), pulsar::ResultOk);
+    ASSERT_EQ(data->hasDataFromCommand(), true);
+    ASSERT_EQ(data->getCommandData(), "admin:123456");
+    ASSERT_EQ(data->hasDataForTls(), false);
+    ASSERT_EQ(data->hasDataForHttp(), true);
+
+    config.setAuth(auth);
+    Client client(serviceUrlHttp, config);
+
+    std::string topicName = "persistent://private/auth/test-basic";
+    std::string subName = "subscription-name";
+
+    Producer producer;
+    Result result = client.createProducer(topicName, producer);
+    ASSERT_EQ(ResultOk, result);
+    producer.close();
+}
+
+TEST(AuthPluginBasic, testNoAuth) {
+    ClientConfiguration config = ClientConfiguration();
+    Client client(serviceUrl, config);
+
+    std::string topicName = "persistent://private/auth/test-basic";
+    std::string subName = "subscription-name";
+
+    Producer producer;
+    Result result = client.createProducer(topicName, producer);
+    ASSERT_EQ(ResultAuthorizationError, result);
+}
+
+TEST(AuthPluginBasic, testNoAuthWithHttp) {
+    ClientConfiguration config = ClientConfiguration();
+    Client client(serviceUrlHttp, config);
+
+    std::string topicName = "persistent://private/auth/test-basic";
+    std::string subName = "subscription-name";
+
+    Producer producer;
+    Result result = client.createProducer(topicName, producer);
+    ASSERT_EQ(ResultConnectError, result);
+}
+
+TEST(AuthPluginBasic, testLoadAuth) {
+    AuthenticationPtr auth = pulsar::AuthBasic::create("admin", "123456");
+    ASSERT_TRUE(auth != NULL);
+    ASSERT_EQ(auth->getAuthMethodName(), "basic");
+    pulsar::AuthenticationDataPtr data;
+    ASSERT_EQ(auth->getAuthData(data), pulsar::ResultOk);
+    ASSERT_EQ(data->hasDataFromCommand(), true);
+    ASSERT_EQ(data->getCommandData(), "admin:123456");
+    ASSERT_EQ(data->hasDataForTls(), false);
+    ASSERT_EQ(data->hasDataForHttp(), true);
+
+    auth = 
pulsar::AuthBasic::create("{\"username\":\"super-user\",\"password\":\"123789\"}");
+    ASSERT_TRUE(auth != NULL);
+    ASSERT_EQ(auth->getAuthMethodName(), "basic");
+    ASSERT_EQ(auth->getAuthData(data), pulsar::ResultOk);
+    ASSERT_EQ(data->hasDataFromCommand(), true);
+    ASSERT_EQ(data->getCommandData(), "super-user:123789");
+    ASSERT_EQ(data->hasDataForTls(), false);
+    ASSERT_EQ(data->hasDataForHttp(), true);
+
+    ParamMap p = ParamMap();
+    p["username"] = "super-user-2";
+    p["password"] = "456789";
+    auth = pulsar::AuthBasic::create(p);
+    ASSERT_TRUE(auth != NULL);
+    ASSERT_EQ(auth->getAuthMethodName(), "basic");
+    ASSERT_EQ(auth->getAuthData(data), pulsar::ResultOk);
+    ASSERT_EQ(data->hasDataFromCommand(), true);
+    ASSERT_EQ(data->getCommandData(), "super-user-2:456789");
+    ASSERT_EQ(data->hasDataForTls(), false);
+    ASSERT_EQ(data->hasDataForHttp(), true);
+}

Reply via email to