]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-oauth2: Refactor oauth request code.
authorSergey Kitov <sergey.kitov@open-xchange.com>
Mon, 11 Feb 2019 14:37:53 +0000 (16:37 +0200)
committerVille Savolainen <ville.savolainen@dovecot.fi>
Mon, 1 Apr 2019 07:39:53 +0000 (10:39 +0300)
Use one parameterized function instead of three copy-paste functions.

src/auth/db-oauth2.c
src/lib-oauth2/Makefile.am
src/lib-oauth2/oauth2-private.h
src/lib-oauth2/oauth2-request.c [new file with mode: 0644]
src/lib-oauth2/oauth2.h
src/lib-oauth2/test-oauth2-json.c

index 16d0bc35a7ebdcbf76a1c064dd7eea5a10c4033f..b0e9d8989e96a7b05fe6f6b2ce845acb04e66058 100644 (file)
@@ -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;
index af09349404f691da28a947060fdfcdaeee4fe25d..70e3bb430903d1e3326f315b7a2b35eb32850c2a 100644 (file)
@@ -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
index 50eb0321bb5f24ec59958a1665a023713513aaa9..b8ffe59879e0684d13cc4008c589c800d39128a4 100644 (file)
@@ -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 (file)
index 0000000..ed36cee
--- /dev/null
@@ -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);
+}
index 348ea3b6ac2b686988c80bb9280c753d91bc467f..d80c81125c4312562579c35843d52b9284ac04a8 100644 (file)
@@ -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 **);
index ac271c1cbbb5837124dbd3dcaa638864add451c7..bd151c58484c99e98abacf87a7cbee252aa42203 100644 (file)
@@ -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);