Skip to content

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

Merged
merged 3 commits into from
Mar 15, 2021
Merged

Add callback for oauth #960

merged 3 commits into from
Mar 15, 2021

Conversation

stevenylai
Copy link
Contributor

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

@ghost
Copy link

ghost commented Sep 25, 2020

It looks like @stevenylai hasn't signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement here.

Once you've signed reply with [clabot:check] to prove it.

Appreciation of efforts,

clabot

@stevenylai
Copy link
Contributor Author

[clabot:check]

@ghost
Copy link

ghost commented Sep 25, 2020

It looks like @stevenylai hasn't signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement here.

Once you've signed reply with [clabot:check] to prove it.

Appreciation of efforts,

clabot

@stevenylai
Copy link
Contributor Author

It looks like @stevenylai hasn't signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement here.

Once you've signed reply with [clabot:check] to prove it.

Appreciation of efforts,

clabot

[clabot:check]

@ghost
Copy link

ghost commented Sep 29, 2020

It looks like @stevenylai hasn't signed our Contributor License Agreement, yet.

The purpose of a CLA is to ensure that the guardian of a project's outputs has the necessary ownership or grants of rights over all contributions to allow them to distribute under the chosen licence.
Wikipedia

You can read and sign our full Contributor License Agreement here.

Once you've signed reply with [clabot:check] to prove it.

Appreciation of efforts,

clabot

@stevenylai
Copy link
Contributor Author

[clabot:check]

@ghost
Copy link

ghost commented Sep 29, 2020

@confluentinc It looks like @stevenylai just signed our Contributor License Agreement. 👍

Always at your service,

clabot

@robooo
Copy link

robooo commented Jan 27, 2021

any updates @stevenylai ? in which release this could be merged?

@stevenylai
Copy link
Contributor Author

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.

Copy link
Contributor

@edenhill edenhill left a 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");
Copy link
Contributor

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 {
Copy link
Contributor

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;
Copy link
Contributor

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;
Copy link
Contributor

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);
Copy link
Contributor

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;
Copy link
Contributor

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")) {
Copy link
Contributor

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;
Copy link
Contributor

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 "
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

@edenhill edenhill left a 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.
Copy link
Contributor

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.
Copy link
Contributor

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.

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']
Copy link
Contributor

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?

Copy link
Contributor Author

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.

'value.serializer': StringSerializer('utf_8'),
'security.protocol': 'sasl_plaintext',
'sasl.mechanisms': 'OAUTHBEARER',
'sasl.oauthbearer.config': "not-important",
Copy link
Contributor

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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), "",
Copy link
Contributor

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.

@edenhill
Copy link
Contributor

I think this part of from the rdkafka.h documentation needs to go into the python docs as well:

 * Note that before any SASL/OAUTHBEARER broker connection can succeed the
 * application must call rd_kafka_oauthbearer_set_token() once -- either
 * directly or, more typically, by invoking either rd_kafka_poll() or
 * rd_kafka_queue_poll() -- in order to cause retrieval of an initial token to
 * occur.

(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
'value.serializer': StringSerializer('utf_8'),
'security.protocol': 'sasl_plaintext',
'sasl.mechanisms': 'OAUTHBEARER',
'sasl.oauthbearer.config': 'not-important',
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@edenhill edenhill merged commit b7f8dce into confluentinc:master Mar 15, 2021
@edenhill
Copy link
Contributor

Thanks for your contribution!

@stevenylai stevenylai deleted the oauth branch March 16, 2021 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Expose OAUTH config options
3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy