Skip to content

Commit 87a3506

Browse files
Add docs and tests for AuthenticationOauth2 (#120)
### Modifications Add tests to verify the changes of apache/pulsar-client-cpp#249 work for the Python client. Add docs to describe valid JSON fields used to create an `AuthenticationOauth2` instance.
1 parent cf4a9c0 commit 87a3506

File tree

4 files changed

+176
-3
lines changed

4 files changed

+176
-3
lines changed

.github/workflows/ci-pr-validation.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ jobs:
7070
WHEEL=$(find dist -name '*.whl')
7171
pip3 install ${WHEEL}[avro]
7272
73+
- name: Run Oauth2 tests
74+
run: |
75+
docker compose -f ./build-support/docker-compose-pulsar-oauth2.yml up -d
76+
# Wait until the namespace is created, currently there is no good way to check it via CLI
77+
sleep 10
78+
python3 tests/oauth2_test.py
79+
docker compose -f ./build-support/docker-compose-pulsar-oauth2.yml down
80+
7381
- name: Start Pulsar service
7482
run: ./build-support/pulsar-test-service-start.sh
7583

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#
2+
# Licensed to the Apache Software Foundation (ASF) under one
3+
# or more contributor license agreements. See the NOTICE file
4+
# distributed with this work for additional information
5+
# regarding copyright ownership. The ASF licenses this file
6+
# to you under the Apache License, Version 2.0 (the
7+
# "License"); you may not use this file except in compliance
8+
# with the License. You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing,
13+
# software distributed under the License is distributed on an
14+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
# KIND, either express or implied. See the License for the
16+
# specific language governing permissions and limitations
17+
# under the License.
18+
#
19+
20+
version: '3'
21+
networks:
22+
pulsar:
23+
driver: bridge
24+
services:
25+
standalone:
26+
image: apachepulsar/pulsar:latest
27+
container_name: standalone
28+
hostname: local
29+
restart: "no"
30+
networks:
31+
- pulsar
32+
environment:
33+
- metadataStoreUrl=zk:localhost:2181
34+
- clusterName=standalone-oauth2
35+
- advertisedAddress=localhost
36+
- advertisedListeners=external:pulsar://localhost:6650
37+
- PULSAR_MEM=-Xms512m -Xmx512m -XX:MaxDirectMemorySize=256m
38+
- PULSAR_PREFIX_authenticationEnabled=true
39+
- PULSAR_PREFIX_authenticationProviders=org.apache.pulsar.broker.authentication.AuthenticationProviderToken
40+
- PULSAR_PREFIX_tokenPublicKey=data:;base64,MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2tZd/4gJda3U2Pc3tpgRAN7JPGWx/Gn17v/0IiZlNNRbP/Mmf0Vc6G1qsnaRaWNWOR+t6/a6ekFHJMikQ1N2X6yfz4UjMc8/G2FDPRmWjA+GURzARjVhxc/BBEYGoD0Kwvbq/u9CZm2QjlKrYaLfg3AeB09j0btNrDJ8rBsNzU6AuzChRvXj9IdcE/A/4N/UQ+S9cJ4UXP6NJbToLwajQ5km+CnxdGE6nfB7LWHvOFHjn9C2Rb9e37CFlmeKmIVFkagFM0gbmGOb6bnGI8Bp/VNGV0APef4YaBvBTqwoZ1Z4aDHy5eRxXfAMdtBkBupmBXqL6bpd15XRYUbu/7ck9QIDAQAB
41+
- PULSAR_PREFIX_brokerClientAuthenticationPlugin=org.apache.pulsar.client.impl.auth.oauth2.AuthenticationOAuth2
42+
- PULSAR_PREFIX_brokerClientAuthenticationParameters={"issuerUrl":"https://dev-kt-aa9ne.us.auth0.com","audience":"https://dev-kt-aa9ne.us.auth0.com/api/v2/","privateKey":"data:application/json;base64,ewogICAgICAgICAgICAiY2xpZW50X2lkIjoiWGQyM1JIc1VudlVsUDd3Y2hqTllPYUlmYXpnZUhkOXgiLAogICAgICAgICAgICAiY2xpZW50X3NlY3JldCI6InJUN3BzN1dZOHVoZFZ1QlRLV1prdHR3TGRRb3RtZEVsaWFNNXJMZm1nTmlidnF6aVotZzA3Wkg1Mk5fcG9HQWIiCiAgICAgICAgfQ=="}
43+
ports:
44+
- "6650:6650"
45+
- "8080:8080"
46+
command: bash -c "bin/apply-config-from-env.py conf/standalone.conf && exec bin/pulsar standalone -nss -nfw"

pulsar/__init__.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,15 +289,44 @@ class AuthenticationOauth2(Authentication):
289289
"""
290290
Oauth2 Authentication implementation
291291
"""
292-
def __init__(self, auth_params_string):
292+
def __init__(self, auth_params_string: str):
293293
"""
294294
Create the Oauth2 authentication provider instance.
295295
296+
You can create the instance by setting the necessary fields in the JSON string.
297+
298+
.. code-block:: python
299+
300+
auth = AuthenticationOauth2('{"issuer_url": "xxx", "private_key": "yyy"}')
301+
302+
The valid JSON fields are:
303+
304+
* issuer_url (required)
305+
The URL of the authentication provider which allows the Pulsar client to obtain an
306+
access token.
307+
* private_key (required)
308+
The URL to the JSON credentials file. It supports the following pattern formats:
309+
310+
* ``/path/to/file``
311+
* ``file:///path/to/file``
312+
* ``file:/path/to/file``
313+
* ``data:application/json;base64,<base64-encoded-value>``
314+
315+
The file content or the based64 encoded value is the encoded JSON string that contains
316+
the following fields:
317+
318+
* ``client_id``
319+
* ``client_secret``
320+
* audience
321+
The OAuth 2.0 "resource server" identifier for a Pulsar cluster.
322+
* scope
323+
The scope of an access request.
324+
296325
Parameters
297326
----------
298-
299-
auth_params_string: str
327+
auth_params_string : str
300328
JSON encoded configuration for Oauth2 client
329+
301330
"""
302331
_check_type(str, auth_params_string, 'auth_params_string')
303332
self.auth = _pulsar.AuthenticationOauth2.create(auth_params_string)

tests/oauth2_test.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Licensed to the Apache Software Foundation (ASF) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The ASF licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
#
20+
21+
from unittest import TestCase, main
22+
from pulsar import AuthenticationOauth2, AuthenticationError, Client
23+
import base64
24+
import os
25+
26+
# This test should run against the standalone that is set up with
27+
# build-support/docker-compose-pulsar-oauth2.yml
28+
class Oauth2Test(TestCase):
29+
30+
service_url = 'pulsar://localhost:6650'
31+
32+
def test_invalid_private_key(self):
33+
def test_create_client(auth_params_string):
34+
client = Client(self.service_url, authentication=AuthenticationOauth2(auth_params_string))
35+
with self.assertRaises(AuthenticationError):
36+
client.create_producer('oauth2-test-base64')
37+
client.close()
38+
39+
test_create_client('{"private_key":"xxx:yyy"}')
40+
test_create_client('{"private_key":"data:"}')
41+
test_create_client('{"private_key":"data:application/x-pem"}')
42+
test_create_client('{"private_key":"data:application/json;xxx"}')
43+
44+
def test_key_file(self):
45+
path = (os.path.dirname(os.path.abspath(__file__))
46+
+ '/test-conf/cpp_credentials_file.json')
47+
auth = AuthenticationOauth2(f'''{{
48+
"issuer_url": "https://dev-kt-aa9ne.us.auth0.com",
49+
"private_key": "{path}",
50+
"audience": "https://dev-kt-aa9ne.us.auth0.com/api/v2/"
51+
}}''')
52+
client = Client(self.service_url, authentication=auth)
53+
producer = client.create_producer('oauth2-test-base64')
54+
producer.close()
55+
client.close()
56+
57+
def test_base64(self):
58+
credentials = '''{
59+
"client_id":"Xd23RHsUnvUlP7wchjNYOaIfazgeHd9x",
60+
"client_secret":"rT7ps7WY8uhdVuBTKWZkttwLdQotmdEliaM5rLfmgNibvqziZ-g07ZH52N_poGAb"
61+
}'''
62+
base64_credentials = base64.b64encode(credentials.encode()).decode()
63+
auth = AuthenticationOauth2(f'''{{
64+
"issuer_url": "https://dev-kt-aa9ne.us.auth0.com",
65+
"private_key": "data:application/json;base64,{base64_credentials}",
66+
"audience": "https://dev-kt-aa9ne.us.auth0.com/api/v2/"
67+
}}''')
68+
client = Client(self.service_url, authentication=auth)
69+
producer = client.create_producer('oauth2-test-base64')
70+
producer.close()
71+
client.close()
72+
73+
def test_wrong_secret(self):
74+
credentials = '''{
75+
"client_id": "my-id",
76+
"client_secret":"my-secret"
77+
}'''
78+
base64_credentials = base64.b64encode(credentials.encode()).decode()
79+
auth = AuthenticationOauth2(f'''{{
80+
"issuer_url": "https://dev-kt-aa9ne.us.auth0.com",
81+
"private_key": "data:application/json;base64,{base64_credentials}",
82+
"audience": "https://dev-kt-aa9ne.us.auth0.com/api/v2/"
83+
}}''')
84+
client = Client(self.service_url, authentication=auth)
85+
with self.assertRaises(AuthenticationError):
86+
client.create_producer('oauth2-test-base64')
87+
client.close()
88+
89+
if __name__ == '__main__':
90+
main()

0 commit comments

Comments
 (0)