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); +}
