From: Aki Tuomi Date: Tue, 13 Dec 2022 18:06:35 +0000 (+0200) Subject: auth: mech-oauth2 - Allow validating tokens in worker X-Git-Tag: 2.4.0~738 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=beb6280d2d4cc095a52a2b8bd119bb59413d8bb2;p=thirdparty%2Fdovecot%2Fcore.git auth: mech-oauth2 - Allow validating tokens in worker --- diff --git a/src/auth/db-oauth2.c b/src/auth/db-oauth2.c index 621f350eae..d3ee1fdb1e 100644 --- a/src/auth/db-oauth2.c +++ b/src/auth/db-oauth2.c @@ -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) { diff --git a/src/auth/db-oauth2.h b/src/auth/db-oauth2.h index b4313743d8..fe6594a84e 100644 --- a/src/auth/db-oauth2.h +++ b/src/auth/db-oauth2.h @@ -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); diff --git a/src/auth/mech-oauth2.c b/src/auth/mech-oauth2.c index 9314068625..87feb1c683 100644 --- a/src/auth/mech-oauth2.c +++ b/src/auth/mech-oauth2.c @@ -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) diff --git a/src/auth/test-auth.c b/src/auth/test-auth.c index fa05b9eccb..389d87def9 100644 --- a/src/auth/test-auth.c +++ b/src/auth/test-auth.c @@ -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" @@ -18,11 +19,20 @@ 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); } diff --git a/src/auth/test-mech.c b/src/auth/test-mech.c index c6947994f0..3445f30e12 100644 --- a/src/auth/test-mech.c +++ b/src/auth/test-mech.c @@ -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(); }