From: Sergey Kitov Date: Mon, 11 Feb 2019 14:37:53 +0000 (+0200) Subject: lib-oauth2: Refactor oauth request code. X-Git-Tag: 2.3.6~29 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=aa6100ca42d99473f7201e01e799a17baf8cbd5f;p=thirdparty%2Fdovecot%2Fcore.git lib-oauth2: Refactor oauth request code. Use one parameterized function instead of three copy-paste functions. --- diff --git a/src/auth/db-oauth2.c b/src/auth/db-oauth2.c index 16d0bc35a7..b0e9d8989e 100644 --- a/src/auth/db-oauth2.c +++ b/src/auth/db-oauth2.c @@ -553,7 +553,7 @@ static void db_oauth2_process_fields(struct db_oauth2_request *req, } static void -db_oauth2_introspect_continue(struct oauth2_introspection_result *result, +db_oauth2_introspect_continue(struct oauth2_request_result *result, struct db_oauth2_request *req) { enum passdb_result passdb_result; @@ -600,7 +600,7 @@ static void db_oauth2_lookup_introspect(struct db_oauth2_request *req) } static void -db_oauth2_lookup_passwd_grant(struct oauth2_passwd_grant_result *result, +db_oauth2_lookup_passwd_grant(struct oauth2_request_result *result, struct db_oauth2_request *req) { enum passdb_result passdb_result; @@ -650,7 +650,7 @@ db_oauth2_lookup_passwd_grant(struct oauth2_passwd_grant_result *result, } static void -db_oauth2_lookup_continue(struct oauth2_token_validation_result *result, +db_oauth2_lookup_continue(struct oauth2_request_result *result, struct db_oauth2_request *req) { enum passdb_result passdb_result; diff --git a/src/lib-oauth2/Makefile.am b/src/lib-oauth2/Makefile.am index af09349404..70e3bb4309 100644 --- a/src/lib-oauth2/Makefile.am +++ b/src/lib-oauth2/Makefile.am @@ -15,10 +15,7 @@ noinst_HEADERS = \ liboauth2_la_SOURCES = \ oauth2.c \ - oauth2-token-validate.c \ - oauth2-passwd-grant.c \ - oauth2-introspect.c \ - oauth2-refresh.c + oauth2-request.c test_programs = \ test-oauth2-json diff --git a/src/lib-oauth2/oauth2-private.h b/src/lib-oauth2/oauth2-private.h index 50eb0321bb..b8ffe59879 100644 --- a/src/lib-oauth2/oauth2-private.h +++ b/src/lib-oauth2/oauth2-private.h @@ -22,20 +22,10 @@ struct oauth2_request { ARRAY_TYPE(oauth2_field) fields; char *field_name; - oauth2_token_validation_callback_t *tv_callback; - void *tv_context; - - oauth2_passwd_grant_callback_t *pg_callback; - void *pg_context; - - oauth2_introspection_callback_t *is_callback; - void *is_context; - - oauth2_refresh_callback_t *re_callback; - void *re_context; - + oauth2_request_callback_t *req_callback; + void *req_context; /* indicates whether token is valid */ - bool valid:1; + unsigned int response_status; }; void oauth2_request_set_headers(struct oauth2_request *req, diff --git a/src/lib-oauth2/oauth2-request.c b/src/lib-oauth2/oauth2-request.c new file mode 100644 index 0000000000..ed36ceeb45 --- /dev/null +++ b/src/lib-oauth2/oauth2-request.c @@ -0,0 +1,253 @@ +/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "istream.h" +#include "str.h" +#include "http-client.h" +#include "http-url.h" +#include "json-parser.h" +#include "oauth2.h" +#include "oauth2-private.h" + +static void +oauth2_request_callback(struct oauth2_request *req, + struct oauth2_request_result *res) +{ + i_assert(res->success == (res->error == NULL)); + i_assert(req->req_callback != NULL); + oauth2_request_callback_t *callback = req->req_callback; + req->req_callback = NULL; + callback(res, req->req_context); + oauth2_request_free_internal(req); +} + +static bool +oauth2_request_field_parse(const struct oauth2_field *field, + struct oauth2_request_result *res) +{ + if (strcasecmp(field->name, "expires_in") == 0) { + uint32_t expires_in = 0; + if (str_to_uint32(field->value, &expires_in) < 0) { + res->success = FALSE; + res->error = t_strdup_printf( + "Malformed number '%s' in expires_in", + field->value); + return FALSE; + } else { + res->expires_at = ioloop_time + expires_in; + } + } else if (strcasecmp(field->name, "token_type") == 0) { + if (strcasecmp(field->value, "bearer") != 0) { + res->success = FALSE; + res->error = t_strdup_printf( + "Expected Bearer token, got '%s'", + field->value); + return FALSE; + } + } + return TRUE; +} + +static void +oauth2_request_continue(struct oauth2_request *req, bool success, + const char *error) +{ + struct oauth2_request_result res; + i_zero(&res); + + unsigned int status_hi = req->response_status/100; + + res.success = success && (status_hi == 2 || status_hi == 4); + res.valid = success && (status_hi == 2); + res.error = error; + + if (res.success) { + const struct oauth2_field *field; + /* see if we can figure out when it expires */ + array_foreach(&req->fields, field) { + if (!oauth2_request_field_parse(field, &res)) + break; + } + } else if (res.error == NULL) + res.error = "Internal Server Error"; + + res.fields = &req->fields; + + oauth2_request_callback(req, &res); +} + +static void +oauth2_request_response(const struct http_response *response, + struct oauth2_request *req) +{ + req->response_status = response->status; + p_array_init(&req->fields, req->pool, 1); + req->is = response->payload; + i_stream_ref(req->is); + req->parser = json_parser_init(req->is); + req->json_parsed_cb = oauth2_request_continue; + req->io = io_add_istream(req->is, oauth2_parse_json, req); + oauth2_parse_json(req); +} + +static struct oauth2_request * +oauth2_request_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_request_callback_t *callback, + void *context, + pool_t p, + const char *method, + const char *url, + const string_t *payload, + bool add_auth_bearer) +{ + i_assert(oauth2_valid_token(input->token)); + + pool_t pool = (p == NULL) ? + pool_alloconly_create_clean("oauth2 request", 1024) : p; + struct oauth2_request *req = + p_new(pool, struct oauth2_request, 1); + + req->pool = pool; + req->set = set; + req->req_callback = callback; + req->req_context = context; + + req->req = http_client_request_url_str(req->set->client, method, url, + oauth2_request_response, req); + + oauth2_request_set_headers(req, input); + + if (payload != NULL && strcmp(method, "POST") == 0) { + struct istream *is = i_stream_create_from_string(payload); + + http_client_request_add_header(req->req, "Content-Type", + "application/x-www-form-urlencoded"); + + http_client_request_set_payload(req->req, is, FALSE); + i_stream_unref(&is); + } + if (add_auth_bearer && + http_client_request_get_origin_url(req->req)->user == NULL && + set->introspection_mode == INTROSPECTION_MODE_GET_AUTH) + http_client_request_add_header(req->req, + "Authorization", + t_strdup_printf("Bearer %s", + input->token)); + http_client_request_set_timeout_msecs(req->req, + req->set->timeout_msecs); + http_client_request_submit(req->req); + + return req; +} + +#undef oauth2_refresh_start +struct oauth2_request * +oauth2_refresh_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_request_callback_t *callback, + void *context) +{ + string_t *payload = t_str_new(128); + str_append(payload, "client_secret="); + http_url_escape_param(payload, set->client_secret); + str_append(payload, "&grant_type=refresh_token&refresh_token="); + http_url_escape_param(payload, input->token); + str_append(payload, "&client_id="); + http_url_escape_param(payload, set->client_id); + + return oauth2_request_start(set, input, callback, context, NULL, + "POST", set->refresh_url, NULL, FALSE); +} + +#undef oauth2_introspection_start +struct oauth2_request * +oauth2_introspection_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_request_callback_t *callback, + void *context) +{ + + string_t *enc; + const char *url; + const char *method; + string_t *payload = NULL; + pool_t p = NULL; + switch (set->introspection_mode) { + case INTROSPECTION_MODE_GET: + enc = t_str_new(64); + str_append(enc, set->introspection_url); + http_url_escape_param(enc, input->token); + str_append(enc, "&client_id="); + http_url_escape_param(enc, set->client_id); + str_append(enc, "&client_secret="); + http_url_escape_param(enc, set->client_secret); + url = str_c(enc); + method = "GET"; + break; + case INTROSPECTION_MODE_GET_AUTH: + url = set->introspection_url; + method = "GET"; + break; + case INTROSPECTION_MODE_POST: + p = pool_alloconly_create_clean("oauth2 request", 1024); + payload = str_new(p, strlen(input->token)+6); + str_append(payload, "token="); + http_url_escape_param(payload, input->token); + str_append(payload, "&client_id="); + http_url_escape_param(payload, set->client_id); + str_append(payload, "&client_secret="); + http_url_escape_param(payload, set->client_secret); + url = set->introspection_url; + method = "POST"; + break; + default: + i_unreached(); + break; + } + + return oauth2_request_start(set, input, callback, context, p, + method, url, payload, TRUE); +} + +#undef oauth2_token_validation_start +struct oauth2_request * +oauth2_token_validation_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + oauth2_request_callback_t *callback, + void *context) +{ + string_t *enc = t_str_new(64); + str_append(enc, set->tokeninfo_url); + http_url_escape_param(enc, input->token); + + return oauth2_request_start(set, input, callback, context, + NULL, "GET", str_c(enc), NULL, TRUE); +} + +#undef oauth2_passwd_grant_start +struct oauth2_request * +oauth2_passwd_grant_start(const struct oauth2_settings *set, + const struct oauth2_request_input *input, + const char *username, + const char *password, + oauth2_request_callback_t *callback, + void *context) +{ + pool_t pool = pool_alloconly_create_clean("oauth2 request", 1024); + string_t *payload = str_new(pool, 128); + /* add token */ + str_append(payload, "grant_type=password&username="); + http_url_escape_param(payload, username); + str_append(payload, "&password="); + http_url_escape_param(payload, password); + str_append(payload, "&client_id="); + http_url_escape_param(payload, set->client_id); + str_append(payload, "&client_secret="); + http_url_escape_param(payload, set->client_secret); + + return oauth2_request_start(set, input, callback, context, + pool, "POST", set->grant_url, + payload, FALSE); +} diff --git a/src/lib-oauth2/oauth2.h b/src/lib-oauth2/oauth2.h index 348ea3b6ac..d80c81125c 100644 --- a/src/lib-oauth2/oauth2.h +++ b/src/lib-oauth2/oauth2.h @@ -40,34 +40,18 @@ struct oauth2_settings { bool use_grant_password; }; -struct oauth2_passwd_grant_result { - ARRAY_TYPE(oauth2_field) *fields; - const char *error; - time_t expires_at; - bool success:1; - bool valid:1; -}; - -struct oauth2_token_validation_result { - ARRAY_TYPE(oauth2_field) *fields; - const char *error; - time_t expires_at; - bool success:1; - bool valid:1; -}; -struct oauth2_introspection_result { +struct oauth2_request_result { + /* Oauth2 server response fields */ ARRAY_TYPE(oauth2_field) *fields; + /* Error message */ const char *error; + /* Request handled successfully */ bool success:1; -}; - -struct oauth2_refresh_result { - ARRAY_TYPE(oauth2_field) *fields; - const char *bearer_token; - const char *error; + /* timestamp token expires at */ time_t expires_at; - bool success:1; + /* User authenticated successfully */ + bool valid:1; }; struct oauth2_request_input { @@ -78,16 +62,7 @@ struct oauth2_request_input { }; typedef void -oauth2_passwd_grant_callback_t(struct oauth2_passwd_grant_result*, void*); - -typedef void -oauth2_token_validation_callback_t(struct oauth2_token_validation_result*, void*); - -typedef void -oauth2_introspection_callback_t(struct oauth2_introspection_result*, void*); - -typedef void -oauth2_refresh_callback_t(struct oauth2_refresh_result*, void*); +oauth2_request_callback_t(struct oauth2_request_result*, void*); bool oauth2_valid_token(const char *token); @@ -96,43 +71,43 @@ oauth2_passwd_grant_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, const char *username, const char *password, - oauth2_passwd_grant_callback_t *callback, + oauth2_request_callback_t *callback, void *context); #define oauth2_passwd_grant_start(set, input, username, password, callback, context) \ oauth2_passwd_grant_start(set, input + \ - CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_passwd_grant_result*, typeof(context))), \ + CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_request_result*, typeof(context))), \ username, password, \ - (oauth2_passwd_grant_callback_t*)callback, (void*)context); + (oauth2_request_callback_t*)callback, (void*)context); struct oauth2_request* oauth2_token_validation_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, - oauth2_token_validation_callback_t *callback, + oauth2_request_callback_t *callback, void *context); #define oauth2_token_validation_start(set, input, callback, context) \ oauth2_token_validation_start(set, input + \ - CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_token_validation_result*, typeof(context))), \ - (oauth2_token_validation_callback_t*)callback, (void*)context); + CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_request_result*, typeof(context))), \ + (oauth2_request_callback_t*)callback, (void*)context); struct oauth2_request* oauth2_introspection_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, - oauth2_introspection_callback_t *callback, + oauth2_request_callback_t *callback, void *context); #define oauth2_introspection_start(set, input, callback, context) \ oauth2_introspection_start(set, input + \ - CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_introspection_result*, typeof(context))), \ - (oauth2_introspection_callback_t*)callback, (void*)context); + CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_request_result*, typeof(context))), \ + (oauth2_request_callback_t*)callback, (void*)context); struct oauth2_request * oauth2_refresh_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, - oauth2_refresh_callback_t *callback, + oauth2_request_callback_t *callback, void *context); #define oauth2_refresh_start(set, input, callback, context) \ oauth2_refresh_start(set, input + \ - CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_refresh_result*, typeof(context))), \ - (oauth2_refresh_callback_t*)callback, (void*)context); + CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_request_result*, typeof(context))), \ + (oauth2_request_callback_t*)callback, (void*)context); /* abort without calling callback, use this to cancel the request */ void oauth2_request_abort(struct oauth2_request **); diff --git a/src/lib-oauth2/test-oauth2-json.c b/src/lib-oauth2/test-oauth2-json.c index ac271c1cbb..bd151c5848 100644 --- a/src/lib-oauth2/test-oauth2-json.c +++ b/src/lib-oauth2/test-oauth2-json.c @@ -65,7 +65,6 @@ static void test_oauth2_json_valid(void) pool = pool_alloconly_create_clean("oauth2 json test", 1024); req = p_new(pool, struct oauth2_request, 1); req->pool = pool; - req->valid = TRUE; p_array_init(&req->fields, req->pool, 1); req->is = test_istream_create_data(test_input, strlen(test_input)); req->parser = json_parser_init(req->is);