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

mmerli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pulsar-client-go.git


The following commit(s) were added to refs/heads/master by this push:
     new 2ae909e  feat: add basic authentication (#778)
2ae909e is described below

commit 2ae909ecb2d01dffd7517d6fd5aaf1f664c6932f
Author: Zixuan Liu <[email protected]>
AuthorDate: Fri Jun 24 03:45:52 2022 +0800

    feat: add basic authentication (#778)
---
 Dockerfile                         |  2 +
 integration-tests/.htpasswd        |  1 +
 integration-tests/license_test.go  |  1 +
 integration-tests/standalone.conf  |  2 +-
 pulsar/client.go                   |  5 +++
 pulsar/client_impl_test.go         | 47 +++++++++++++++++++++
 pulsar/internal/auth/basic.go      | 84 ++++++++++++++++++++++++++++++++++++++
 pulsar/internal/auth/basic_test.go | 70 +++++++++++++++++++++++++++++++
 pulsar/internal/auth/provider.go   |  3 ++
 9 files changed, 214 insertions(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index e12cc80..6dd5817 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -30,6 +30,8 @@ COPY integration-tests/certs /pulsar/certs
 COPY integration-tests/tokens /pulsar/tokens
 COPY integration-tests/standalone.conf /pulsar/conf
 COPY integration-tests/client.conf /pulsar/conf
+COPY integration-tests/.htpasswd /pulsar/conf
+ENV PULSAR_EXTRA_OPTS="-Dpulsar.auth.basic.conf=/pulsar/conf/.htpasswd"
 COPY pulsar-test-service-start.sh /pulsar/bin
 COPY pulsar-test-service-stop.sh /pulsar/bin
 COPY run-ci.sh /pulsar/bin
diff --git a/integration-tests/.htpasswd b/integration-tests/.htpasswd
new file mode 100644
index 0000000..2aa3a47
--- /dev/null
+++ b/integration-tests/.htpasswd
@@ -0,0 +1 @@
+admin:$apr1$FG4AO6aX$KGYPuMoLUou3i6vUkPUUf.
diff --git a/integration-tests/license_test.go 
b/integration-tests/license_test.go
index e829560..cd6d9d5 100644
--- a/integration-tests/license_test.go
+++ b/integration-tests/license_test.go
@@ -68,6 +68,7 @@ var skip = map[string]bool{
        "../pulsar/internal/pulsar_proto/PulsarApi.pb.go": true,
        "../.github/workflows/bot.yaml":                   true,
        "../integration-tests/pb/hello.pb.go":             true,
+       "../integration-tests/.htpasswd":                  true,
 }
 
 func TestLicense(t *testing.T) {
diff --git a/integration-tests/standalone.conf 
b/integration-tests/standalone.conf
index a298a61..8cd2828 100644
--- a/integration-tests/standalone.conf
+++ b/integration-tests/standalone.conf
@@ -98,7 +98,7 @@ anonymousUserRole=anonymous
 authenticationEnabled=true
 
 # Autentication 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.go b/pulsar/client.go
index f4642c6..bc05b25 100644
--- a/pulsar/client.go
+++ b/pulsar/client.go
@@ -78,6 +78,11 @@ func NewAuthenticationOAuth2(authParams map[string]string) 
Authentication {
        return oauth
 }
 
+// NewAuthenticationBasic Creates Basic Authentication provider
+func NewAuthenticationBasic(username, password string) (Authentication, error) 
{
+       return auth.NewAuthenticationBasic(username, password)
+}
+
 // ClientOptions is used to construct a Pulsar Client instance.
 type ClientOptions struct {
        // Configure the service URL for the Pulsar service.
diff --git a/pulsar/client_impl_test.go b/pulsar/client_impl_test.go
index 2d83c99..ba8f6eb 100644
--- a/pulsar/client_impl_test.go
+++ b/pulsar/client_impl_test.go
@@ -28,6 +28,8 @@ import (
        "testing"
        "time"
 
+       "github.com/stretchr/testify/require"
+
        "github.com/apache/pulsar-client-go/pulsar/internal"
 
        "github.com/apache/pulsar-client-go/pulsar/internal/auth"
@@ -1019,3 +1021,48 @@ func TestHTTPOAuth2AuthFailed(t *testing.T) {
 
        client.Close()
 }
+
+func TestHTTPBasicAuth(t *testing.T) {
+       basicAuth, err := NewAuthenticationBasic("admin", "123456")
+       require.NoError(t, err)
+       require.NotNil(t, basicAuth)
+
+       client, err := NewClient(ClientOptions{
+               URL:            webServiceURL,
+               Authentication: basicAuth,
+       })
+       require.NoError(t, err)
+       require.NotNil(t, client)
+
+       producer, err := client.CreateProducer(ProducerOptions{
+               Topic: newAuthTopicName(),
+       })
+
+       require.NoError(t, err)
+       require.NotNil(t, producer)
+
+       client.Close()
+}
+
+func TestHTTPSBasicAuth(t *testing.T) {
+       basicAuth, err := NewAuthenticationBasic("admin", "123456")
+       require.NoError(t, err)
+       require.NotNil(t, basicAuth)
+
+       client, err := NewClient(ClientOptions{
+               URL:                   webServiceURLTLS,
+               TLSTrustCertsFilePath: caCertsPath,
+               TLSValidateHostname:   true,
+               Authentication:        basicAuth,
+       })
+       require.NoError(t, err)
+       require.NotNil(t, client)
+
+       producer, err := client.CreateProducer(ProducerOptions{
+               Topic: newAuthTopicName(),
+       })
+       require.NoError(t, err)
+       require.NotNil(t, producer)
+
+       client.Close()
+}
diff --git a/pulsar/internal/auth/basic.go b/pulsar/internal/auth/basic.go
new file mode 100644
index 0000000..58a87f5
--- /dev/null
+++ b/pulsar/internal/auth/basic.go
@@ -0,0 +1,84 @@
+// 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.
+
+package auth
+
+import (
+       "crypto/tls"
+       "encoding/base64"
+       "errors"
+       "net/http"
+)
+
+type basicAuthProvider struct {
+       rt               http.RoundTripper
+       commandAuthToken []byte
+       httpAuthToken    string
+}
+
+func NewAuthenticationBasic(username, password string) (Provider, error) {
+       if username == "" {
+               return nil, errors.New("username cannot be empty")
+       }
+       if password == "" {
+               return nil, errors.New("password cannot be empty")
+       }
+
+       commandAuthToken := []byte(username + ":" + password)
+       return &basicAuthProvider{
+               commandAuthToken: commandAuthToken,
+               httpAuthToken:    "Basic " + 
base64.StdEncoding.EncodeToString(commandAuthToken),
+       }, nil
+}
+
+func NewAuthenticationBasicWithParams(params map[string]string) (Provider, 
error) {
+       return NewAuthenticationBasic(params["username"], params["password"])
+}
+
+func (b *basicAuthProvider) Init() error {
+       return nil
+}
+
+func (b *basicAuthProvider) Name() string {
+       return "basic"
+}
+
+func (b *basicAuthProvider) GetTLSCertificate() (*tls.Certificate, error) {
+       return nil, nil
+}
+
+func (b *basicAuthProvider) GetData() ([]byte, error) {
+       return b.commandAuthToken, nil
+}
+
+func (b *basicAuthProvider) Close() error {
+       return nil
+}
+
+func (b *basicAuthProvider) RoundTrip(req *http.Request) (*http.Response, 
error) {
+       req.Header.Add("Authorization", b.httpAuthToken)
+       return b.rt.RoundTrip(req)
+}
+
+func (b *basicAuthProvider) Transport() http.RoundTripper {
+       return b.rt
+}
+
+func (b *basicAuthProvider) WithTransport(tr http.RoundTripper) error {
+       b.rt = tr
+       return nil
+}
diff --git a/pulsar/internal/auth/basic_test.go 
b/pulsar/internal/auth/basic_test.go
new file mode 100644
index 0000000..b212d0b
--- /dev/null
+++ b/pulsar/internal/auth/basic_test.go
@@ -0,0 +1,70 @@
+// 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.
+
+package auth
+
+import (
+       "errors"
+       "io/ioutil"
+       "net/http"
+       "net/http/httptest"
+       "testing"
+
+       "github.com/stretchr/testify/require"
+)
+
+func TestNewAuthenticationBasicWithParams(t *testing.T) {
+       username := "admin"
+       password := "123456"
+
+       provider, err := NewAuthenticationBasic(username, password)
+       require.NoError(t, err)
+       require.NotNil(t, provider)
+
+       data, err := provider.GetData()
+       require.NoError(t, err)
+       require.Equal(t, []byte(username+":"+password), data)
+
+       s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r 
*http.Request) {
+               _, _ = w.Write([]byte(r.Header.Get("Authorization")))
+       }))
+
+       client := s.Client()
+       err = provider.WithTransport(client.Transport)
+       require.NoError(t, err)
+       client.Transport = provider
+
+       resp, err := client.Get(s.URL)
+       require.NoError(t, err)
+
+       body, err := ioutil.ReadAll(resp.Body)
+       _ = resp.Body.Close()
+       require.NoError(t, err)
+       require.Equal(t, []byte("Basic YWRtaW46MTIzNDU2"), body)
+}
+
+func TestNewAuthenticationBasicWithInvalidParams(t *testing.T) {
+       username := "admin"
+       password := "123456"
+       provider, err := NewAuthenticationBasic("", password)
+       require.Equal(t, errors.New("username cannot be empty"), err)
+       require.Nil(t, provider)
+
+       provider, err = NewAuthenticationBasic(username, "")
+       require.Equal(t, errors.New("password cannot be empty"), err)
+       require.Nil(t, provider)
+}
diff --git a/pulsar/internal/auth/provider.go b/pulsar/internal/auth/provider.go
index 1731490..031ea8d 100644
--- a/pulsar/internal/auth/provider.go
+++ b/pulsar/internal/auth/provider.go
@@ -80,6 +80,9 @@ func NewProvider(name string, params string) (Provider, 
error) {
        case "oauth2", 
"org.apache.pulsar.client.impl.auth.oauth2.AuthenticationOAuth2":
                return NewAuthenticationOAuth2WithParams(m)
 
+       case "basic", "org.apache.pulsar.client.impl.auth.AuthenticationBasic":
+               return NewAuthenticationBasicWithParams(m)
+
        default:
                return nil, fmt.Errorf("invalid auth provider '%s'", name)
        }

Reply via email to