]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-oauth2: Implement password grant authentication.
authorSergey Kitov <sergey.kitov@open-xchange.com>
Tue, 5 Feb 2019 07:45:07 +0000 (09:45 +0200)
committerVille Savolainen <ville.savolainen@dovecot.fi>
Thu, 21 Mar 2019 08:02:52 +0000 (10:02 +0200)
src/lib-oauth2/Makefile.am
src/lib-oauth2/oauth2-passwd-grant.c [new file with mode: 0644]
src/lib-oauth2/oauth2-private.h
src/lib-oauth2/oauth2.h

index 65250cfcb6934f2d10c3481e92ef97af3ae7cd8c..79ade626b3b4022560ecf51bf8d015a7f0573ac0 100644 (file)
@@ -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 (file)
index 0000000..d8abab2
--- /dev/null
@@ -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;
+}
index 28fccee3ceeb195c88865964ad224269bef00038..50eb0321bb5f24ec59958a1665a023713513aaa9 100644 (file)
@@ -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;
 
index ac338120fd516215df663c8329c83e2be1679908..348ea3b6ac2b686988c80bb9280c753d91bc467f 100644 (file)
@@ -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,