From: Stephan Bosch Date: Tue, 31 Oct 2023 01:48:00 +0000 (+0100) Subject: auth: sasl-server-mech-oauth2 - Implement backend API for token validation X-Git-Tag: 2.4.2~172 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d4a71f20ab3d7432038ae3a25e6c6d26b42fa160;p=thirdparty%2Fdovecot%2Fcore.git auth: sasl-server-mech-oauth2 - Implement backend API for token validation --- diff --git a/src/auth/Makefile.am b/src/auth/Makefile.am index 4331791ff7..5db3ac24c8 100644 --- a/src/auth/Makefile.am +++ b/src/auth/Makefile.am @@ -119,6 +119,7 @@ auth_common_sources = \ auth-request-var-expand.c \ auth-sasl-mech-apop.c \ auth-sasl-mech-dovecot-token.c \ + auth-sasl-mech-oauth2.c \ auth-sasl.c \ auth-settings.c \ auth-fields.c \ diff --git a/src/auth/auth-sasl-mech-oauth2.c b/src/auth/auth-sasl-mech-oauth2.c index f8739bf4df..1409f844c0 100644 --- a/src/auth/auth-sasl-mech-oauth2.c +++ b/src/auth/auth-sasl-mech-oauth2.c @@ -1,25 +1,34 @@ /* Copyright (c) 2023 Dovecot authors, see the included COPYING file */ #include "auth-common.h" -#include "auth-sasl.h" -#include "auth-sasl-oauth2.h" +#include "str.h" +#include "strescape.h" +#include "sasl-server-oauth2.h" +#include "auth-worker-connection.h" #include "auth-request.h" - +#include "db-oauth2.h" +#include "auth-sasl.h" #include "auth-sasl-oauth2.h" /* * Token verification */ +static struct db_oauth2 *db_oauth2 = NULL; + +struct oauth2_token_lookup { + struct sasl_server_oauth2_request request; + + struct db_oauth2 *db; + struct db_oauth2_request db_req; + lookup_credentials_callback_t *callback; +}; + static void oauth2_verify_finish(enum passdb_result result, struct auth_request *auth_request) { struct sasl_server_req_ctx *srctx = &auth_request->sasl.req; - struct sasl_server_mech_request *request = - sasl_server_request_get_mech_request(srctx); - struct oauth2_auth_request *oauth2_req = - container_of(request, struct oauth2_auth_request, request); struct sasl_server_oauth2_failure failure; i_zero(&failure); @@ -47,10 +56,6 @@ oauth2_verify_finish(enum passdb_result result, i_unreached(); } - if (oauth2_req->db != NULL) { - failure.openid_configuration = - db_oauth2_get_openid_configuration_url(oauth2_req->db); - } sasl_server_oauth2_request_fail(srctx, &failure); } @@ -66,10 +71,12 @@ oauth2_verify_callback(enum passdb_result result, } static void -mech_oauth2_verify_token_continue(struct oauth2_auth_request *oauth2_req, +mech_oauth2_verify_token_continue(struct oauth2_token_lookup *lookup, const char *const *args) { - struct auth_request *auth_request = oauth2_req->request.request; + struct sasl_server_req_ctx *srctx = lookup->request.rctx; + struct auth_request *auth_request = + container_of(srctx, struct auth_request, sasl.req); int parsed; enum passdb_result result; @@ -111,9 +118,9 @@ 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; + struct oauth2_token_lookup *lookup = context; - mech_oauth2_verify_token_continue(oauth2_req, args); + mech_oauth2_verify_token_continue(lookup, args); return TRUE; } @@ -121,9 +128,11 @@ 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 oauth2_token_lookup *lookup) { - struct auth_request *auth_request = oauth2_req->request.request; + struct sasl_server_req_ctx *srctx = lookup->request.rctx; + struct auth_request *auth_request = + container_of(srctx, struct auth_request, sasl.req); if (result == PASSDB_RESULT_OK) { auth_request_set_password_verified(auth_request); @@ -144,14 +153,26 @@ mech_oauth2_verify_token_local_continue(struct db_oauth2_request *db_req, pool_unref(&db_req->pool); } -static void -auth_sasl_oauth2_verify_token(struct oauth2_auth_request *oauth2_req, - const char *token) +static int +oauth2_auth_new(struct sasl_server_req_ctx *srctx, pool_t pool, + const char *token, struct sasl_server_oauth2_request **req_r) { - struct auth_request *auth_request = oauth2_req->request.request; + struct auth_request *auth_request = + container_of(srctx, struct auth_request, sasl.req); + struct oauth2_token_lookup *lookup; + + if (db_oauth2 == NULL) { + e_error(auth_request->event, "BUG: oauth2 database missing"); + return -1; + } + + lookup = p_new(pool, struct oauth2_token_lookup, 1); + sasl_server_oauth2_request_init(&lookup->request, pool, srctx); + lookup->db_req.pool = pool; + lookup->db = db_oauth2; auth_request_ref(auth_request); - if (!db_oauth2_use_worker(oauth2_req->db)) { + if (!db_oauth2_use_worker(lookup->db)) { pool_t pool = pool_alloconly_create( MEMPOOL_GROWING"oauth2 request", 256); struct db_oauth2_request *db_req = @@ -159,8 +180,8 @@ auth_sasl_oauth2_verify_token(struct oauth2_auth_request *oauth2_req, 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); + lookup->db, db_req, token, db_req->auth_request, + mech_oauth2_verify_token_local_continue, lookup); } else { string_t *str = t_str_new(128); str_append(str, "TOKEN\tOAUTH2\t"); @@ -168,12 +189,19 @@ auth_sasl_oauth2_verify_token(struct oauth2_auth_request *oauth2_req, str_append_c(str, '\t'); auth_request_export(auth_request, str); auth_worker_call( - oauth2_req->db_req.pool, + lookup->db_req.pool, auth_request->fields.user, str_c(str), - mech_oauth2_verify_token_input_args, oauth2_req); + mech_oauth2_verify_token_input_args, lookup); } + + *req_r = &lookup->request; + return 0; } +static const struct sasl_server_oauth2_funcs mech_funcs = { + .auth_new = oauth2_auth_new, +}; + void auth_sasl_oauth2_initialize(void) { const char *mech, *error; @@ -186,3 +214,59 @@ void auth_sasl_oauth2_initialize(void) } } } + +/* + * Mechanisms + */ + +static void +mech_oauth_init_settings(struct sasl_server_oauth2_settings *oauth2_set) +{ + i_assert(db_oauth2 != NULL); + + i_zero(oauth2_set); + oauth2_set->openid_configuration_url = + db_oauth2_get_openid_configuration_url(db_oauth2); +} + +static bool +mech_oauthbearer_register(struct sasl_server_instance *sasl_inst, + const struct auth_settings *set ATTR_UNUSED) +{ + struct sasl_server_oauth2_settings oauth2_set; + + mech_oauth_init_settings(&oauth2_set); + sasl_server_mech_register_oauthbearer(sasl_inst, &mech_funcs, + &oauth2_set); + return TRUE; +} + +static bool +mech_xoauth2_register(struct sasl_server_instance *sasl_inst, + const struct auth_settings *set ATTR_UNUSED) +{ + struct sasl_server_oauth2_settings oauth2_set; + + mech_oauth_init_settings(&oauth2_set); + sasl_server_mech_register_xoauth2(sasl_inst, &mech_funcs, + &oauth2_set); + return TRUE; +} + +static const struct auth_sasl_mech_module mech_oauthbearer = { + .mech_name = SASL_MECH_NAME_OAUTHBEARER, + + .mech_register = mech_oauthbearer_register, +}; + +static const struct auth_sasl_mech_module mech_xoauth2 = { + .mech_name = SASL_MECH_NAME_XOAUTH2, + + .mech_register = mech_xoauth2_register, +}; + +void auth_sasl_mech_oauth2_register(void) +{ + auth_sasl_mech_register_module(&mech_oauthbearer); + auth_sasl_mech_register_module(&mech_xoauth2); +} diff --git a/src/auth/auth-sasl-oauth2.h b/src/auth/auth-sasl-oauth2.h index ebb574e266..def7b84038 100644 --- a/src/auth/auth-sasl-oauth2.h +++ b/src/auth/auth-sasl-oauth2.h @@ -3,4 +3,6 @@ void auth_sasl_oauth2_initialize(void); +void auth_sasl_mech_oauth2_register(void); + #endif diff --git a/src/auth/auth-sasl.c b/src/auth/auth-sasl.c index 2b2df775f5..e098402f71 100644 --- a/src/auth/auth-sasl.c +++ b/src/auth/auth-sasl.c @@ -463,8 +463,6 @@ MECH_SIMPLE_REGISTER__TEMPLATE(cram_md5) MECH_SIMPLE_REGISTER__TEMPLATE(digest_md5) MECH_SIMPLE_REGISTER__TEMPLATE(external) MECH_SIMPLE_REGISTER__TEMPLATE(login) -MECH_SIMPLE_REGISTER__TEMPLATE(oauthbearer) -MECH_SIMPLE_REGISTER__TEMPLATE(xoauth2) MECH_SIMPLE_REGISTER__TEMPLATE(otp) MECH_SIMPLE_REGISTER__TEMPLATE(plain) MECH_SIMPLE_REGISTER__TEMPLATE(scram_sha1) @@ -532,12 +530,6 @@ static const struct auth_sasl_mech_module mech_login = { .mech_register = mech_login_register, }; -static const struct auth_sasl_mech_module mech_oauthbearer = { - .mech_name = SASL_MECH_NAME_OAUTHBEARER, - - .mech_register = mech_oauthbearer_register, -}; - static const struct auth_sasl_mech_module mech_otp = { .mech_name = SASL_MECH_NAME_OTP, @@ -586,12 +578,6 @@ static const struct auth_sasl_mech_module mech_winbind_gss_spnego = { .mech_register = mech_winbind_gss_spnego_register, }; -static const struct auth_sasl_mech_module mech_xoauth2 = { - .mech_name = SASL_MECH_NAME_XOAUTH2, - - .mech_register = mech_xoauth2_register, -}; - static const struct auth_sasl_mech_module mech_apop = { .mech_name = AUTH_SASL_MECH_NAME_APOP, @@ -616,14 +602,13 @@ static void auth_sasl_mechs_init(const struct auth_settings *set) auth_sasl_mech_register_module(&mech_login); if (set->use_winbind) auth_sasl_mech_register_module(&mech_winbind_ntlm); - auth_sasl_mech_register_module(&mech_oauthbearer); + auth_sasl_mech_oauth2_register(); auth_sasl_mech_register_module(&mech_otp); auth_sasl_mech_register_module(&mech_plain); auth_sasl_mech_register_module(&mech_scram_sha1); auth_sasl_mech_register_module(&mech_scram_sha1_plus); auth_sasl_mech_register_module(&mech_scram_sha256); auth_sasl_mech_register_module(&mech_scram_sha256_plus); - auth_sasl_mech_register_module(&mech_xoauth2); auth_sasl_mech_register_module(&mech_apop); } diff --git a/src/auth/sasl-server-mech-oauth2.c b/src/auth/sasl-server-mech-oauth2.c index c8141b8151..ac82079aec 100644 --- a/src/auth/sasl-server-mech-oauth2.c +++ b/src/auth/sasl-server-mech-oauth2.c @@ -2,13 +2,11 @@ #include "auth-common.h" #include "auth-fields.h" -#include "auth-worker-connection.h" #include "ioloop.h" #include "str.h" -#include "strescape.h" +#include "buffer.h" #include "json-ostream.h" #include "auth-gs2.h" -#include "db-oauth2.h" #include "oauth2.h" #include "sasl-server-protected.h" @@ -16,24 +14,32 @@ struct oauth2_auth_request { struct sasl_server_mech_request request; - struct db_oauth2 *db; - struct db_oauth2_request db_req; - lookup_credentials_callback_t *callback; + struct sasl_server_oauth2_request *backend; bool failed:1; bool verifying_token:1; }; +struct oauth2_auth_mech { + struct sasl_server_mech mech; + const struct sasl_server_oauth2_funcs *funcs; + + struct sasl_server_oauth2_settings set; +}; + static const struct sasl_server_mech_def mech_oauthbearer; static const struct sasl_server_mech_def mech_xoauth2; -static struct db_oauth2 *db_oauth2 = NULL; - static void oauth2_fail(struct oauth2_auth_request *oauth2_req, const struct sasl_server_oauth2_failure *failure) { struct sasl_server_mech_request *request = &oauth2_req->request; + const struct oauth2_auth_mech *oauth2_mech = + container_of(request->mech, + const struct oauth2_auth_mech, mech); + + oauth2_req->verifying_token = FALSE; if (failure == NULL) { sasl_server_request_internal_failure(request); @@ -61,11 +67,11 @@ oauth2_fail(struct oauth2_auth_request *oauth2_req, json_ostream_nwrite_string(joutput, "scope", "mail"); else json_ostream_nwrite_string(joutput, "scope", failure->scope); - if (failure->openid_configuration != NULL && - *failure->openid_configuration != '\0') { + if (oauth2_mech->set.openid_configuration_url != NULL && + *oauth2_mech->set.openid_configuration_url != '\0') { json_ostream_nwrite_string( joutput, "openid-configuration", - failure->openid_configuration); + oauth2_mech->set.openid_configuration_url); } json_ostream_nascend_object(joutput); json_ostream_nfinish_destroy(&joutput); @@ -107,7 +113,10 @@ void sasl_server_oauth2_request_succeed(struct sasl_server_req_ctx *rctx) container_of(request, struct oauth2_auth_request, request); i_assert(oauth2_req->verifying_token); + oauth2_req->verifying_token = FALSE; sasl_server_request_success(request, "", 0); + + sasl_server_mech_request_unref(&request); } void sasl_server_oauth2_request_fail( @@ -124,18 +133,63 @@ void sasl_server_oauth2_request_fail( container_of(request, struct oauth2_auth_request, request); i_assert(oauth2_req->verifying_token); + oauth2_req->verifying_token = FALSE; oauth2_fail(oauth2_req, failure); + + sasl_server_mech_request_unref(&request); } -#include "auth-sasl-mech-oauth2.c" +struct sasl_server_oauth2_request * +sasl_server_oauth2_request_get(struct sasl_server_req_ctx *rctx) +{ + struct sasl_server_mech_request *request = + sasl_server_request_get_mech_request(rctx); + + i_assert(request->mech->def == &mech_oauthbearer || + request->mech->def == &mech_xoauth2); + + struct oauth2_auth_request *oauth2_req = + container_of(request, struct oauth2_auth_request, request); + + return oauth2_req->backend; +} static void mech_oauth2_verify_token(struct oauth2_auth_request *oauth2_req, const char *token) { + struct sasl_server_mech_request *request = &oauth2_req->request; + struct sasl_server_req_ctx *rctx = + sasl_server_request_get_req_ctx(request); + const struct oauth2_auth_mech *oauth2_mech = + container_of(request->mech, + const struct oauth2_auth_mech, mech); + + /* Add reference for the lookup; dropped upon success/failure */ + sasl_server_mech_request_ref(request); + i_assert(token != NULL); + i_assert(oauth2_mech->funcs != NULL && + oauth2_mech->funcs->auth_new != NULL); oauth2_req->verifying_token = TRUE; - auth_sasl_oauth2_verify_token(oauth2_req, token); + + /* Call the backend auth_new() function under an additional reference in + case it fails the request immediately. */ + sasl_server_mech_request_ref(request); + if (oauth2_mech->funcs->auth_new(rctx, request->pool, token, + &oauth2_req->backend) < 0) { + if (!oauth2_req->failed) + sasl_server_oauth2_request_fail(rctx, NULL); + i_assert(oauth2_req->backend == NULL); + } else { + i_assert(!oauth2_req->verifying_token || + oauth2_req->backend != NULL); + i_assert(oauth2_req->backend == NULL || + oauth2_req->backend->pool != NULL); + i_assert(oauth2_req->backend == NULL || + oauth2_req->backend->rctx != NULL); + } + sasl_server_mech_request_unref(&request); } /* Input syntax for data: @@ -149,11 +203,6 @@ mech_oauthbearer_auth_continue(struct sasl_server_mech_request *request, struct oauth2_auth_request *oauth2_req = container_of(request, struct oauth2_auth_request, request); - if (oauth2_req->db == NULL) { - e_error(request->mech_event, "BUG: oauth2 database missing"); - sasl_server_request_internal_failure(request); - return; - } if (data_size == 0) { oauth2_fail_invalid_request(oauth2_req); return; @@ -245,11 +294,6 @@ mech_xoauth2_auth_continue(struct sasl_server_mech_request *request, struct oauth2_auth_request *oauth2_req = container_of(request, struct oauth2_auth_request, request); - if (oauth2_req->db == NULL) { - e_error(request->mech_event, "BUG: oauth2 database missing"); - sasl_server_request_internal_failure(request); - return; - } if (data_size == 0) { oauth2_fail_invalid_request(oauth2_req); return; @@ -310,16 +354,40 @@ mech_oauth2_auth_new(const struct sasl_server_mech *mech ATTR_UNUSED, struct oauth2_auth_request *request; request = p_new(pool, struct oauth2_auth_request, 1); - request->db_req.pool = pool; - request->db = db_oauth2; return &request->request; } +static void mech_oauth2_auth_free(struct sasl_server_mech_request *request) +{ + struct oauth2_auth_request *oauth2_req = + container_of(request, struct oauth2_auth_request, request); + const struct oauth2_auth_mech *oauth2_mech = + container_of(request->mech, + const struct oauth2_auth_mech, mech); + + i_assert(oauth2_mech->funcs != NULL); + if (oauth2_req->backend != NULL && + oauth2_mech->funcs->auth_free != NULL) + oauth2_mech->funcs->auth_free(oauth2_req->backend); +} + +static struct sasl_server_mech *mech_oauth2_mech_new(pool_t pool) +{ + struct oauth2_auth_mech *oauth2_mech; + + oauth2_mech = p_new(pool, struct oauth2_auth_mech, 1); + + return &oauth2_mech->mech; +} + static const struct sasl_server_mech_funcs mech_oauthbearer_funcs = { .auth_new = mech_oauth2_auth_new, .auth_initial = sasl_server_mech_generic_auth_initial, .auth_continue = mech_oauthbearer_auth_continue, + .auth_free = mech_oauth2_auth_free, + + .mech_new = mech_oauth2_mech_new, }; static const struct sasl_server_mech_def mech_oauthbearer = { @@ -337,6 +405,9 @@ static const struct sasl_server_mech_funcs mech_xoauth2_funcs = { .auth_new = mech_oauth2_auth_new, .auth_initial = sasl_server_mech_generic_auth_initial, .auth_continue = mech_xoauth2_auth_continue, + .auth_free = mech_oauth2_auth_free, + + .mech_new = mech_oauth2_mech_new, }; static const struct sasl_server_mech_def mech_xoauth2 = { @@ -348,12 +419,41 @@ static const struct sasl_server_mech_def mech_xoauth2 = { .funcs = &mech_xoauth2_funcs, }; -void sasl_server_mech_register_oauthbearer(struct sasl_server_instance *sinst) +static void +mech_oauth2_register(struct sasl_server_instance *sinst, + const struct sasl_server_mech_def *mech_def, + const struct sasl_server_oauth2_funcs *funcs, + const struct sasl_server_oauth2_settings *set) +{ + struct sasl_server_mech *mech; + + i_assert(funcs != NULL && funcs->auth_new != NULL); + + mech = sasl_server_mech_register(sinst, mech_def, NULL); + + struct oauth2_auth_mech *oauth2_mech = + container_of(mech, struct oauth2_auth_mech, mech); + + oauth2_mech->funcs = funcs; + + if (set != NULL) { + oauth2_mech->set.openid_configuration_url = + p_strdup(mech->pool, set->openid_configuration_url); + } +} + +void sasl_server_mech_register_oauthbearer( + struct sasl_server_instance *sinst, + const struct sasl_server_oauth2_funcs *funcs, + const struct sasl_server_oauth2_settings *set) { - sasl_server_mech_register(sinst, &mech_oauthbearer, NULL); + mech_oauth2_register(sinst, &mech_oauthbearer, funcs, set); } -void sasl_server_mech_register_xoauth2(struct sasl_server_instance *sinst) +void sasl_server_mech_register_xoauth2( + struct sasl_server_instance *sinst, + const struct sasl_server_oauth2_funcs *funcs, + const struct sasl_server_oauth2_settings *set) { - sasl_server_mech_register(sinst, &mech_xoauth2, NULL); + mech_oauth2_register(sinst, &mech_xoauth2, funcs, set); } diff --git a/src/auth/sasl-server-oauth2.h b/src/auth/sasl-server-oauth2.h index cbe2580566..cf396accb2 100644 --- a/src/auth/sasl-server-oauth2.h +++ b/src/auth/sasl-server-oauth2.h @@ -6,12 +6,48 @@ struct sasl_server_oauth2_failure { const char *status; const char *scope; - const char *openid_configuration; }; +struct sasl_server_oauth2_request { + pool_t pool; + struct sasl_server_req_ctx *rctx; +}; + +struct sasl_server_oauth2_settings { + const char *openid_configuration_url; +}; + +struct sasl_server_oauth2_funcs { + int (*auth_new)(struct sasl_server_req_ctx *rctx, pool_t pool, + const char *token, + struct sasl_server_oauth2_request **req_r); + void (*auth_free)(struct sasl_server_oauth2_request *req); +}; + +static inline void +sasl_server_oauth2_request_init(struct sasl_server_oauth2_request *request_r, + pool_t pool, struct sasl_server_req_ctx *srctx) +{ + i_zero(request_r); + request_r->pool = pool; + request_r->rctx = srctx; +} + void sasl_server_oauth2_request_succeed(struct sasl_server_req_ctx *rctx); void sasl_server_oauth2_request_fail( struct sasl_server_req_ctx *rctx, const struct sasl_server_oauth2_failure *failure); +struct sasl_server_oauth2_request * +sasl_server_oauth2_request_get(struct sasl_server_req_ctx *rctx); + +void sasl_server_mech_register_oauthbearer( + struct sasl_server_instance *sinst, + const struct sasl_server_oauth2_funcs *funcs, + const struct sasl_server_oauth2_settings *set); +void sasl_server_mech_register_xoauth2( + struct sasl_server_instance *sinst, + const struct sasl_server_oauth2_funcs *funcs, + const struct sasl_server_oauth2_settings *set); + #endif diff --git a/src/auth/sasl-server.h b/src/auth/sasl-server.h index 90354dbb49..44810add98 100644 --- a/src/auth/sasl-server.h +++ b/src/auth/sasl-server.h @@ -191,11 +191,6 @@ void sasl_server_mech_register_scram_sha256_plus( void sasl_server_mech_register_otp(struct sasl_server_instance *sinst); -/* OAUTH2 */ - -void sasl_server_mech_register_oauthbearer(struct sasl_server_instance *sinst); -void sasl_server_mech_register_xoauth2(struct sasl_server_instance *sinst); - /* Winbind */ struct sasl_server_winbind_settings {