-
Notifications
You must be signed in to change notification settings - Fork 920
Add callback for oauth #960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
It looks like @stevenylai hasn't signed our Contributor License Agreement, yet.
You can read and sign our full Contributor License Agreement here. Once you've signed reply with Appreciation of efforts, clabot |
[clabot:check] |
It looks like @stevenylai hasn't signed our Contributor License Agreement, yet.
You can read and sign our full Contributor License Agreement here. Once you've signed reply with Appreciation of efforts, clabot |
[clabot:check] |
It looks like @stevenylai hasn't signed our Contributor License Agreement, yet.
You can read and sign our full Contributor License Agreement here. Once you've signed reply with Appreciation of efforts, clabot |
[clabot:check] |
@confluentinc It looks like @stevenylai just signed our Contributor License Agreement. 👍 Always at your service, clabot |
any updates @stevenylai ? in which release this could be merged? |
I have updated my branch. Let's see if the developers from confluentinc has time to review my change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great stuff!
Py_DECREF(result); | ||
PyErr_Format(PyExc_TypeError, | ||
"expect returned value from oauth " | ||
"to be (token_str, expiry_int_ms) tuple"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Durations (in various forms) are usually expressed as floating point seconds in Python, we might want to do the same here for expiry_int_ms
|
||
if (!result) { | ||
goto err; | ||
} else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This else block is not needed since the if above has a goto. Reduce indent.
PyObject *eo = NULL, *result = NULL; | ||
CallState *cs = NULL; | ||
const char *token = NULL; | ||
long long expiry = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use int64_t
@@ -1521,6 +1521,48 @@ static void log_cb (const rd_kafka_t *rk, int level, | |||
CallState_resume(cs); | |||
} | |||
|
|||
static void oauth_cb (rd_kafka_t *rk, const char *oauthbearer_config, void *opaque) { | |||
Handle *h = opaque; | |||
PyObject *eo = NULL, *result = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for initialization of eo, result, cs, token and expirty, err_code, they're always set below before being used.
Py_DECREF(result); | ||
if (err_code) { | ||
PyErr_Format(PyExc_ValueError, | ||
"invalid token: %s", err_msg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should remove "invalid token: " and just keep the "%s" as the error message from librdkafka should be complete as-is.
Py_DECREF(eo); | ||
|
||
if (!result) { | ||
goto err; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use 8 whitespace indent in the C code (throughout)
@@ -1949,6 +1991,19 @@ rd_kafka_conf_t *common_conf_setup (rd_kafka_type_t ktype, | |||
Py_XDECREF(ks8); | |||
Py_DECREF(ks); | |||
continue; | |||
} else if (!strcmp(k, "oauth")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think this should be called oauth_cb to be consistent with other callback names
@@ -236,6 +236,7 @@ typedef struct { | |||
rd_kafka_type_t type; /* Producer or consumer */ | |||
|
|||
PyObject *logger; | |||
PyObject *oauth; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oauth_cb
if (!PyArg_ParseTuple(result, "sl", &token, &expiry)) { | ||
Py_DECREF(result); | ||
PyErr_Format(PyExc_TypeError, | ||
"expect returned value from oauth " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oauth_cb
@@ -1949,6 +1990,19 @@ rd_kafka_conf_t *common_conf_setup (rd_kafka_type_t ktype, | |||
Py_XDECREF(ks8); | |||
Py_DECREF(ks); | |||
continue; | |||
} else if (!strcmp(k, "oauth_cb")) { | |||
if (h->oauth_cb) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a PyCallable_Check here, see stats_cb above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I have also updated my branch to the current master
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good!
Please add a dry-run test-case to test_misc.py that simply instantiates a client (with no bootstraps) with a proper callable oauth_cb, just so we have verification that part works.
docs/index.rst
Outdated
@@ -459,6 +459,9 @@ The Python bindings also provide some additional configuration properties: | |||
This callback is served upon calling ``client.poll()`` or ``producer.flush()``. See | |||
https://github.com/edenhill/librdkafka/wiki/Statistics" for more information. | |||
|
|||
* ``oauth_cb(oauthbearer_config_str)``: Callback for retrieving Oauth token. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a line saying that oauthbearer_config_str is the value of the sasl.oauthbearer.config
configuration property.
docs/index.rst
Outdated
@@ -459,6 +459,9 @@ The Python bindings also provide some additional configuration properties: | |||
This callback is served upon calling ``client.poll()`` or ``producer.flush()``. See | |||
https://github.com/edenhill/librdkafka/wiki/Statistics" for more information. | |||
|
|||
* ``oauth_cb(oauthbearer_config_str)``: Callback for retrieving Oauth token. | |||
Return value of this callback is expected to be ``(token_str, expiry_time)`` tuple. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to indicate that expiry_time is float seconds absolute (or is it relative?) time.
examples/oauth_producer.py
Outdated
resp = requests.post(args.token_url, | ||
auth=(args.client_id, args.client_secret), data=payload) | ||
token = resp.json() | ||
return token['access_token'], time.time() + token['expires_in'] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps cast token[exp..] to float?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I think time.time() + float(token['expires_in'])
is a bit redundant but I see there are already codes which does float()
conversion so I adapted mine anyways to be consistent.
examples/oauth_producer.py
Outdated
'value.serializer': StringSerializer('utf_8'), | ||
'security.protocol': 'sasl_plaintext', | ||
'sasl.mechanisms': 'OAUTHBEARER', | ||
'sasl.oauthbearer.config': "not-important", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
micro-nit: mix of ' and "
const char *token; | ||
double expiry; | ||
char err_msg[2048]; | ||
rd_kafka_resp_err_t err_code = RD_KAFKA_RESP_ERR_NO_ERROR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: the initialization is not neededd, it is never used before assignment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, I missed this one from my previous change.
"to be (token_str, expiry_time) tuple"); | ||
goto err; | ||
} | ||
err_code = rd_kafka_oauthbearer_set_token(h->rk, token, (int64_t)(expiry * 1000), "", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try to keep below 80 columns width.
I think this part of from the rdkafka.h documentation needs to go into the python docs as well:
(rewritten to make sense for python) |
update docstr Addressing review comments No need for 1k multiplication now that we are in secs Add doc for oauth_cb Check callable
examples/oauth_producer.py
Outdated
'value.serializer': StringSerializer('utf_8'), | ||
'security.protocol': 'sasl_plaintext', | ||
'sasl.mechanisms': 'OAUTHBEARER', | ||
'sasl.oauthbearer.config': 'not-important', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: this make it seem like sasl.oauthbearer.config is not generally important, but required.
Perhaps better to make it more explicit by commenting out the line with a comment saying that no extra configuration is required for this example, but that the property is used to pass configuration to the oauth_cb.
@@ -55,7 +55,10 @@ def producer_config(args): | |||
'value.serializer': StringSerializer('utf_8'), | |||
'security.protocol': 'sasl_plaintext', | |||
'sasl.mechanisms': 'OAUTHBEARER', | |||
'sasl.oauthbearer.config': 'not-important', | |||
# sasl.oauthbearer.config can be used to pass argument to your oauth_cb |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
Thanks for your contribution! |
We have requirements to make use of the OAuth server in our company for Kafka. Our Kafka server-side implementation is based on: https://github.com/kafka-security/oauth
This change expose the necessary callback for Python client
This closes #839