Skip to content

Commit

Permalink
Support auth param string for Basic authentication (#15)
Browse files Browse the repository at this point in the history
* Support auth param string for Basic authentication

### Motivation

apache/pulsar#17482 supported basic
authentication for Python client, but the `AuthenticationBasic` class
accepts two positional arguments as the username and password. It's not
good for extension. We should accept an auth param string like
`AuthenticationOauth2` so that no changes are needed if the upstream C++
client's implementation changed, like
apache/pulsar-client-cpp#37.

### Modifications

To be compatible with the existing API, change the first two arguments
to keyword arguments. Then, add the 3rd keyword argument to represent
the auth param string.

### Verifications

`test_basic_auth` and `test_invalid_basic_auth` are extended for this
change.

* Add method argument and related tests

* Add type check for method arg
  • Loading branch information
BewareMyPower authored Oct 13, 2022
1 parent ddb1229 commit 51d3846
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 16 deletions.
37 changes: 29 additions & 8 deletions pulsar/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,18 +347,39 @@ class AuthenticationBasic(Authentication):
"""
Basic Authentication implementation
"""
def __init__(self, username, password):
def __init__(self, username=None, password=None, method='basic', auth_params_string=None):
"""
Create the Basic authentication provider instance.
**Args**
For example, if you want to create a basic authentication instance whose
username is "my-user" and password is "my-pass", there are two ways:
* `username`: Used to authentication as username
* `password`: Used to authentication as password
"""
_check_type(str, username, 'username')
_check_type(str, password, 'password')
self.auth = _pulsar.AuthenticationBasic(username, password)
```
auth = AuthenticationBasic('my-user', 'my-pass')
auth = AuthenticationBasic(auth_params_string='{"username": "my-user", "password": "my-pass"}')
```
**Args**
* username : str, optional
* password : str, optional
* method : str, optional
The authentication method name (default is 'basic')
* auth_params_string : str, optional
The JSON presentation of all fields above (default is None)
If it's not None, the other parameters will be ignored.
Here is an example JSON presentation:
{"username": "my-user", "password": "my-pass", "method": "oms3.0"}
The `username` and `password` fields are required. If the "method" field is not set,
it will be "basic" by default.
"""
if auth_params_string is not None:
_check_type(str, auth_params_string, 'auth_params_string')
self.auth = _pulsar.AuthenticationBasic('', '', '', auth_params_string)
else:
_check_type(str, username, 'username')
_check_type(str, password, 'password')
_check_type(str, method, 'method')
self.auth = _pulsar.AuthenticationBasic(username, password, method, '')

class Client:
"""
Expand Down
12 changes: 9 additions & 3 deletions src/authentication.cc
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,14 @@ struct AuthenticationOauth2Wrapper : public AuthenticationWrapper {
};

struct AuthenticationBasicWrapper : public AuthenticationWrapper {
AuthenticationBasicWrapper(const std::string& username, const std::string& password)
AuthenticationBasicWrapper(const std::string& username, const std::string& password,
const std::string& method, const std::string& authParamsString)
: AuthenticationWrapper() {
this->auth = AuthBasic::create(username, password);
if (authParamsString.empty()) {
this->auth = AuthBasic::create(username, password, method);
} else {
this->auth = AuthBasic::create(authParamsString);
}
}
};

Expand All @@ -115,5 +120,6 @@ void export_authentication() {
init<const std::string&>());

class_<AuthenticationBasicWrapper, bases<AuthenticationWrapper> >(
"AuthenticationBasic", init<const std::string&, const std::string&>());
"AuthenticationBasic",
init<const std::string&, const std::string&, const std::string&, const std::string&>());
}
37 changes: 32 additions & 5 deletions tests/pulsar_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1301,12 +1301,10 @@ def _check_type_error(self, fun):
with self.assertRaises(TypeError):
fun()

def test_basic_auth(self):
username = "admin"
password = "123456"
client = Client(self.adminUrl, authentication=AuthenticationBasic(username, password))
def _test_basic_auth(self, id, auth):
client = Client(self.adminUrl, authentication=auth)

topic = "persistent://private/auth/my-python-topic-basic-auth"
topic = "persistent://private/auth/my-python-topic-basic-auth-" + str(id)
consumer = client.subscribe(topic, "my-sub", consumer_type=ConsumerType.Shared)
producer = client.create_producer(topic)
producer.send(b"hello")
Expand All @@ -1316,13 +1314,42 @@ def test_basic_auth(self):
self.assertEqual(msg.data(), b"hello")
client.close()

def test_basic_auth(self):
username = "admin"
password = "123456"
self._test_basic_auth(0, AuthenticationBasic(username, password))
self._test_basic_auth(1, AuthenticationBasic(
auth_params_string='{{"username": "{}","password": "{}"}}'.format(username, password)
))

def test_basic_auth_method(self):
username = "admin"
password = "123456"
self._test_basic_auth(2, AuthenticationBasic(username, password, 'basic'))
with self.assertRaises(pulsar.AuthorizationError):
self._test_basic_auth(3, AuthenticationBasic(username, password, 'unknown'))
self._test_basic_auth(4, AuthenticationBasic(
auth_params_string='{{"username": "{}","password": "{}", "method": "basic"}}'.format(username, password)
))
with self.assertRaises(pulsar.AuthorizationError):
self._test_basic_auth(5, AuthenticationBasic(
auth_params_string='{{"username": "{}","password": "{}", "method": "unknown"}}'.format(username, password)
))

def test_invalid_basic_auth(self):
username = "invalid"
password = "123456"
client = Client(self.adminUrl, authentication=AuthenticationBasic(username, password))
topic = "persistent://private/auth/my-python-topic-invalid-basic-auth"
with self.assertRaises(pulsar.ConnectError):
client.subscribe(topic, "my-sub", consumer_type=ConsumerType.Shared)
client = Client(self.adminUrl, authentication=AuthenticationBasic(
auth_params_string='{{"username": "{}","password": "{}"}}'.format(username, password)
))
with self.assertRaises(pulsar.ConnectError):
client.subscribe(topic, "my-sub", consumer_type=ConsumerType.Shared)
with self.assertRaises(RuntimeError):
AuthenticationBasic(auth_params_string='invalid auth params')

if __name__ == "__main__":
main()

0 comments on commit 51d3846

Please sign in to comment.