]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
auth: mech-oauth2 - Allow validating tokens in worker
authorAki Tuomi <aki.tuomi@open-xchange.com>
Tue, 13 Dec 2022 18:06:35 +0000 (20:06 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 17 Jan 2025 08:39:58 +0000 (10:39 +0200)
src/auth/db-oauth2.c
src/auth/db-oauth2.h
src/auth/mech-oauth2.c
src/auth/test-auth.c
src/auth/test-mech.c

index 621f350eae7e46a00293296ff6c94371331940e7..d3ee1fdb1ee1a3ec1919ef0add3e9ee355c64e86 100644 (file)
@@ -68,6 +68,7 @@ struct passdb_oauth2_settings {
        /* Should we send service and local/remote endpoints as X-Dovecot-Auth headers */
        bool send_auth_headers;
        bool use_grant_password;
+       bool blocking;
 };
 
 struct db_oauth2 {
@@ -113,7 +114,7 @@ static struct setting_def setting_defs[] = {
        DEF_STR(openid_configuration_url),
        DEF_BOOL(send_auth_headers),
        DEF_BOOL(use_grant_password),
-
+       DEF_BOOL(blocking),
        DEF_BOOL(debug),
 
        { 0, NULL, 0 }
@@ -138,9 +139,14 @@ static struct passdb_oauth2_settings default_oauth2_settings = {
        .local_validation_key_dict = "",
        .send_auth_headers = FALSE,
        .use_grant_password = FALSE,
+       .blocking = TRUE,
        .debug = FALSE,
 };
 
+static void db_oauth2_callback(struct db_oauth2_request *req,
+                              enum passdb_result result,
+                              const char *error_prefix, const char *error);
+
 static const char *parse_setting(const char *key, const char *value,
                                 struct db_oauth2 *db)
 {
@@ -263,9 +269,16 @@ static void db_oauth2_free(struct db_oauth2 **_db)
        i_assert(ptr != NULL && ptr == db);
 
        /* make sure all requests are aborted */
-       while (db->head != NULL)
-               oauth2_request_abort(&db->head->req);
-
+       while (db->head != NULL) {
+               if (db->head->req != NULL)
+                       oauth2_request_abort(&db->head->req);
+               else {
+                       struct db_oauth2_request *req = db->head;
+                       DLLIST_REMOVE(&db->head, req);
+                       db_oauth2_callback(req, PASSDB_RESULT_INTERNAL_FAILURE,
+                                          "", "aborted");
+               }
+       }
        http_client_deinit(&db->client);
        if (db->oauth2_set.key_dict != NULL)
                dict_deinit(&db->oauth2_set.key_dict);
@@ -825,6 +838,11 @@ bool db_oauth2_uses_password_grant(const struct db_oauth2 *db)
        return db->set.use_grant_password;
 }
 
+bool db_oauth2_is_blocking(const struct db_oauth2 *db)
+{
+       return db->set.blocking;
+}
+
 void db_oauth2_deinit(void)
 {
        while (db_oauth2_head != NULL) {
index b4313743d810a81f47b31919c0680d8cc6bbf7dc..fe6594a84e9024b526f6cdf588d574e35479fb4b 100644 (file)
@@ -33,6 +33,7 @@ struct db_oauth2_request {
 struct db_oauth2 *db_oauth2_init(const char *config_path);
 
 bool db_oauth2_uses_password_grant(const struct db_oauth2 *db);
+bool db_oauth2_is_blocking(const struct db_oauth2 *db);
 
 const char *db_oauth2_get_openid_configuration_url(const struct db_oauth2 *db);
 
index 931406862554ea5c061f2a26b6b2bd61767b86d0..87feb1c6836066859b2a43be4b9f887c71fcd0ae 100644 (file)
@@ -1,7 +1,10 @@
 /* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
 
 #include "auth-common.h"
+#include "auth-fields.h"
+#include "auth-worker-connection.h"
 #include "str.h"
+#include "strescape.h"
 #include "json-generator.h"
 #include "mech.h"
 #include "passdb.h"
@@ -103,29 +106,94 @@ oauth2_verify_callback(enum passdb_result result,
 }
 
 static void
-mech_oauth2_verify_token_continue(struct db_oauth2_request *db_request,
-                                 enum passdb_result result,
-                                 const char *error,
-                                 struct oauth2_auth_request *oauth2_req)
+mech_oauth2_verify_token_continue(struct oauth2_auth_request *oauth2_req,
+                                 const char *const *args)
 {
-       struct auth_request *auth_request = &oauth2_req->auth;
-       if (result != PASSDB_RESULT_OK) {
-               e_error(auth_request->mech_event, "%s", error);
-               oauth2_verify_callback(result, NULL, 0, auth_request);
+       struct auth_request *request = &oauth2_req->auth;
+       int parsed;
+       enum passdb_result result;
+
+       /* OK result user fields */
+       if (args[0] == NULL || args[1] == NULL) {
+               result = PASSDB_RESULT_INTERNAL_FAILURE;
+               e_error(request->mech_event, "BUG: Invalid auth worker response: empty");
+       } else if (str_to_int(args[1], &parsed) < 0) {
+               result = PASSDB_RESULT_INTERNAL_FAILURE;
+               e_error(request->mech_event, "BUG: Invalid auth worker response: cannot parse '%s'", args[1]);
+       } else if (args[2] == NULL) {
+               result = PASSDB_RESULT_INTERNAL_FAILURE;
+               e_error(request->mech_event, "BUG: Invalid auth worker response: cannot parse '%s'", args[1]);
+       } else {
+               result = parsed;
+       }
+
+       if (result == PASSDB_RESULT_OK) {
+               request->passdb_success = TRUE;
+               auth_request_set_password_verified(request);
+               auth_request_set_fields(request, args + 3, NULL);
+               auth_request_lookup_credentials(request, "", oauth2_verify_callback);
+               auth_request_unref(&request);
                return;
        }
-       db_request->auth_request->passdb_success = TRUE;
 
-       auth_request_set_field(auth_request, "token", db_request->token, NULL);
-       auth_request_lookup_credentials(auth_request, "", oauth2_verify_callback);
+       oauth2_verify_callback(result, uchar_empty_ptr, 0, request);
+       auth_request_unref(&request);
+}
+
+static bool
+mech_oauth2_verify_token_input_args(struct auth_worker_connection *conn ATTR_UNUSED,
+                                    const char *const *args, void *context)
+{
+       struct oauth2_auth_request *oauth2_req = context;
+       mech_oauth2_verify_token_continue(oauth2_req, args);
+       return TRUE;
+}
+
+static void
+mech_oauth2_verify_token_local_continue(struct db_oauth2_request *db_req,
+                                       enum passdb_result result,
+                                       const char *error,
+                                       struct oauth2_auth_request *oauth2_req)
+{
+       struct auth_request *request = &oauth2_req->auth;
+       if (result == PASSDB_RESULT_OK) {
+               auth_request_set_password_verified(request);
+               auth_request_set_field(request, "token", db_req->token, NULL);
+               auth_request_lookup_credentials(request, "", oauth2_verify_callback);
+               auth_request_unref(&request);
+               pool_unref(&db_req->pool);
+               return;
+       } else {
+               e_error(request->mech_event, "Token verification failed: %s", error);
+       }
+       oauth2_verify_callback(result, uchar_empty_ptr, 0, request);
+       auth_request_unref(&request);
+       pool_unref(&db_req->pool);
 }
 
 static void
 mech_oauth2_verify_token(struct oauth2_auth_request *oauth2_req, const char *token)
 {
-       /* decode token */
-       db_oauth2_lookup(oauth2_req->db, &oauth2_req->db_req, token, &oauth2_req->auth,
-                        mech_oauth2_verify_token_continue, oauth2_req);
+       struct auth_request *auth_request = &oauth2_req->auth;
+       auth_request_ref(auth_request);
+
+       if (!db_oauth2_is_blocking(oauth2_req->db)) {
+               pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"oauth2 request", 256);
+               struct db_oauth2_request *db_req =
+                       p_new(pool, struct db_oauth2_request, 1);
+               db_req->pool = pool;
+               db_req->auth_request = auth_request;
+               db_oauth2_lookup(oauth2_req->db, db_req, token, db_req->auth_request,
+                                mech_oauth2_verify_token_local_continue, oauth2_req);
+       } else {
+               string_t *str = t_str_new(128);
+               str_append(str, "TOKEN\tOAUTH2\t");
+               str_append_tabescaped(str, token);
+               str_append_c(str, '\t');
+               auth_request_export(auth_request, str);
+               auth_worker_call(oauth2_req->db_req.pool, auth_request->fields.user,
+                                str_c(str), mech_oauth2_verify_token_input_args, oauth2_req);
+       }
 }
 
 static void oauth2_init_db(struct oauth2_auth_request *oauth2_req)
index fa05b9eccbee8a4ad6fb9dac4fabc66c23cbf59c..389d87def9f8ed1c18b928ca85a4454c1f9b1af1 100644 (file)
@@ -1,6 +1,7 @@
 /* Copyright (c) 2024 Dovecot authors, see the included COPYING file */
 
 #include "test-auth.h"
+#include "ostream.h"
 #include "auth-common.h"
 #include "settings-parser.h"
 #include "auth-settings.h"
 struct auth_settings test_auth_set;
 static struct mechanisms_register *mech_reg;
 
+#define TEST_OAUTH2_CONFIG_FILE "test-oauth2-config"
+
 void test_auth_init(void)
 {
        const char *const protocols[] = {NULL};
        process_start_time = time(NULL);
 
+       /* create oauth2 config file */
+       struct ostream *os =
+               o_stream_create_file(TEST_OAUTH2_CONFIG_FILE, 0, 0600, 0);
+       o_stream_nsend_str(os, "tokeninfo_url = http://localhost\nclient_id=foo\nblocking=no\n");
+       test_assert(o_stream_finish(os) == 1);
+       o_stream_unref(&os);
+
        /* Copy default settings */
        test_auth_set = *(const struct auth_settings *)auth_setting_parser_info.defaults;
        test_auth_set.pool = pool_alloconly_create("test settings", 128);
@@ -51,6 +61,8 @@ void test_auth_init(void)
        test_auth_set.realms_arr = t_strsplit_spaces("example.com ", " ");
        /* For tests of mech-anonymous. */
        test_auth_set.anonymous_username = "anonuser";
+       /* For oauth2 tests */
+       test_auth_set.oauth2_config_file = TEST_OAUTH2_CONFIG_FILE;
 
        mech_init(global_auth_settings);
        mech_reg = mech_register_init(global_auth_settings);
@@ -71,6 +83,7 @@ void test_auth_deinit(void)
 {
        auth_penalty_deinit(&auth_penalty);
        mech_otp_deinit();
+       db_oauth2_deinit();
        auths_deinit();
        auth_token_deinit();
        password_schemes_deinit();
@@ -83,4 +96,5 @@ void test_auth_deinit(void)
        auths_free();
        pool_unref(&test_auth_set.pool);
        i_unlink_if_exists("auth-token-secret.dat");
+       i_unlink_if_exists(TEST_OAUTH2_CONFIG_FILE);
 }
index c6947994f061788fba5e127ecfaa2e567aa81f69..3445f30e12554f64fa7cfb51f74da5d4b31248bd 100644 (file)
@@ -47,6 +47,7 @@ verify_plain_continue_mock_callback(struct auth_request *request,
 {
        request->passdb_success = TRUE;
        callback(PASSDB_RESULT_OK, request);
+       io_loop_stop(current_ioloop);
 }
 
 static void
@@ -102,7 +103,6 @@ static void test_mech_prepare_request(struct auth_request **request_r,
        handler->refcount = 1;
 
        request->failure_nodelay = TRUE;
-       auth_request_ref(request);
        auth_request_state_count[AUTH_REQUEST_STATE_NEW] = 1;
 
        if (test_case->set_username_before_test || test_case->set_cert_username)
@@ -333,14 +333,13 @@ static void test_mechs(void)
                else
                        test_assert_idx(request->failed == TRUE, running_test);
 
-               event_unref(&request->event);
-               event_unref(&request->mech_event);
                i_free(input_dup);
-               mech->auth_free(request);
+               auth_request_unref(&request);
 
                test_end();
        } T_END;
 
+       test_expect_error_string("Token verification failed: aborted");
        test_auth_deinit();
 }