From: Sergey Kitov Date: Tue, 5 Feb 2019 07:45:07 +0000 (+0200) Subject: lib-oauth2: Implement password grant authentication. X-Git-Tag: 2.3.6~62 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e9b1be3badd5f54e832bac5ac9d95e18bc86736a;p=thirdparty%2Fdovecot%2Fcore.git lib-oauth2: Implement password grant authentication. --- diff --git a/src/lib-oauth2/Makefile.am b/src/lib-oauth2/Makefile.am index 65250cfcb6..79ade626b3 100644 --- a/src/lib-oauth2/Makefile.am +++ b/src/lib-oauth2/Makefile.am @@ -15,6 +15,7 @@ noinst_HEADERS = \ liboauth2_la_SOURCES = \ oauth2.c \ oauth2-token-validate.c \ + oauth2-passwd-grant.c \ oauth2-introspect.c \ oauth2-refresh.c diff --git a/src/lib-oauth2/oauth2-passwd-grant.c b/src/lib-oauth2/oauth2-passwd-grant.c new file mode 100644 index 0000000000..d8abab235b --- /dev/null +++ b/src/lib-oauth2/oauth2-passwd-grant.c @@ -0,0 +1,132 @@ +/* Copyright (c) 2019 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "str.h" +#include "ioloop.h" +#include "istream.h" +#include "strnum.h" +#include "http-client.h" +#include "http-url.h" +#include "json-parser.h" +#include "oauth2.h" +#include "oauth2-private.h" + +static void +oauth2_passwd_grant_callback(struct oauth2_request *req, + struct oauth2_passwd_grant_result *res) +{ + i_assert(res->success == (res->error == NULL)); + i_assert(req->pg_callback != NULL); + oauth2_passwd_grant_callback_t *callback = req->pg_callback; + req->pg_callback = NULL; + callback(res, req->pg_context); + oauth2_request_free_internal(req); +} + +static void +oauth2_passwd_grant_continue(struct oauth2_request *req, bool success, + const char *error) +{ + struct oauth2_passwd_grant_result res; + i_zero(&res); + + i_assert(array_is_created(&req->fields) || !success); + + res.success = success; + res.error = error; + res.valid = req->valid; + + if (res.success) { + const struct oauth2_field *field; + /* see if we can figure out when it expires */ + array_foreach(&req->fields, field) { + 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 = "Malformed number in expires_in"; + } else { + res.expires_at = ioloop_time + expires_in; + } + break; + } + } + } + + res.fields = &req->fields; + + oauth2_passwd_grant_callback(req, &res); +} + +static void +oauth2_passwd_grant_response(const struct http_response *response, + struct oauth2_request *req) +{ + unsigned int status_1 = response->status / 100; + + if (status_1 != 2 && status_1 != 4) { + oauth2_passwd_grant_continue(req, FALSE, response->reason); + } else { + if (status_1 == 2) + req->valid = TRUE; + else + req->valid = FALSE; + p_array_init(&req->fields, req->pool, 1); + /* 2xx is sufficient for token validation */ + if (response->payload == NULL) { + oauth2_passwd_grant_continue(req, TRUE, NULL); + return; + } + req->is = response->payload; + i_stream_ref(req->is); + req->parser = json_parser_init(req->is); + req->json_parsed_cb = oauth2_passwd_grant_continue; + req->io = io_add_istream(req->is, oauth2_parse_json, req); + oauth2_parse_json(req); + } +} + +#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_passwd_grant_callback_t *callback, + void *context) +{ + i_assert(oauth2_valid_token(input->token)); + + pool_t pool = pool_alloconly_create_clean("oauth2 passwd_grant", 1024); + struct oauth2_request *req = + p_new(pool, struct oauth2_request, 1); + + req->pool = pool; + req->set = set; + req->pg_callback = callback; + req->pg_context = context; + + req->req = http_client_request_url_str(req->set->client, "POST", + req->set->grant_url, + oauth2_passwd_grant_response, + req); + 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, req->set->client_id); + http_client_request_add_header(req->req, "Content-Type", + "application/x-www-form-urlencoded"); + http_client_request_set_payload_data(req->req, payload->data, payload->used); + + oauth2_request_set_headers(req, input); + + http_client_request_set_timeout_msecs(req->req, + req->set->timeout_msecs); + http_client_request_submit(req->req); + + return req; +} diff --git a/src/lib-oauth2/oauth2-private.h b/src/lib-oauth2/oauth2-private.h index 28fccee3ce..50eb0321bb 100644 --- a/src/lib-oauth2/oauth2-private.h +++ b/src/lib-oauth2/oauth2-private.h @@ -25,6 +25,9 @@ struct oauth2_request { 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; diff --git a/src/lib-oauth2/oauth2.h b/src/lib-oauth2/oauth2.h index ac338120fd..348ea3b6ac 100644 --- a/src/lib-oauth2/oauth2.h +++ b/src/lib-oauth2/oauth2.h @@ -18,11 +18,15 @@ struct oauth2_settings { /* GET tokeninfo from this URL, token is appended to URL http://some.host/path?access_token= */ const char *tokeninfo_url; + /* POST grant password here, needs user credentials and client_* settings */ + const char *grant_url; /* GET more information from this URL, uses Bearer authentication */ const char *introspection_url; /* POST refresh here, needs refresh token and client_* settings */ const char *refresh_url; + /* client identificator for oauth2 server */ const char *client_id; + /* client secret for oauth2 server */ const char *client_secret; enum { INTROSPECTION_MODE_GET_AUTH, @@ -32,6 +36,16 @@ struct oauth2_settings { unsigned int timeout_msecs; /* Should X-Dovecot-Auth-* headers be sent */ bool send_auth_headers; + /* Should use grant password mechanism for authentication */ + 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 { @@ -63,6 +77,9 @@ struct oauth2_request_input { in_port_t local_port, real_local_port, remote_port, real_remote_port; }; +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*); @@ -74,6 +91,19 @@ oauth2_refresh_callback_t(struct oauth2_refresh_result*, void*); bool oauth2_valid_token(const char *token); +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_passwd_grant_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))), \ + username, password, \ + (oauth2_passwd_grant_callback_t*)callback, (void*)context); + struct oauth2_request* oauth2_token_validation_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, @@ -94,7 +124,7 @@ oauth2_introspection_start(const struct oauth2_settings *set, CALLBACK_TYPECHECK(callback, void(*)(struct oauth2_introspection_result*, typeof(context))), \ (oauth2_introspection_callback_t*)callback, (void*)context); -struct oauth2_request* +struct oauth2_request * oauth2_refresh_start(const struct oauth2_settings *set, const struct oauth2_request_input *input, oauth2_refresh_callback_t *callback,