From: Aki Tuomi Date: Fri, 29 May 2026 07:31:50 +0000 (+0000) Subject: [PATCH 3/4] auth: db-oauth2: Add oauth2_audience setting, deprecate aud-as-scope... X-Git-Tag: archive/raspbian/1%2.4.1+dfsg1-6+rpi1+deb13u7^2~72 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=efa51dd66f150c0476cb34699a34d758e9761bb9;p=dovecot.git [PATCH 3/4] auth: db-oauth2: Add oauth2_audience setting, deprecate aud-as-scope fallback Add a dedicated oauth2_audience setting checked against the token's aud claim (RFC 7519 section 4.1.3) using the same tab-split AND-semantics as oauth2_scope. Emit a deprecation warning when the existing aud fallback in db_oauth2_token_in_scope() is triggered so operators know to migrate. Gbp-Pq: Name 0003-auth-db-oauth2-Add-oauth2_audience-setting-deprecate.patch --- diff --git a/src/auth/db-oauth2.c b/src/auth/db-oauth2.c index 159344f..8bf2618 100644 --- a/src/auth/db-oauth2.c +++ b/src/auth/db-oauth2.c @@ -17,16 +17,21 @@ #include "db-oauth2.h" #include "dcrypt.h" #include "dict.h" +#include #undef DEF #define DEF(type, name) \ SETTING_DEFINE_STRUCT_##type("oauth2_"#name, name, struct auth_oauth2_settings) +#define WARN_AUD_FALLBACK_PERIOD 600 +static time_t last_warned_aud_fallback = 0; + static const struct setting_define auth_oauth2_setting_defines[] = { DEF(STR, tokeninfo_url), DEF(STR, grant_url), DEF(STR, introspection_url), DEF(BOOLLIST, scope), + DEF(BOOLLIST, audience), DEF(ENUM, introspection_mode), DEF(STR_NOVARS, username_validation_format), DEF(STR, username_attribute), @@ -50,6 +55,7 @@ static const struct auth_oauth2_settings auth_oauth2_default_settings = { .grant_url = "", .introspection_url = "", .scope = ARRAY_INIT, + .audience = ARRAY_INIT, .force_introspection = FALSE, .introspection_mode = ":auth:get:post:local", .username_validation_format = "%{user}", @@ -558,8 +564,19 @@ db_oauth2_token_in_scope(struct db_oauth2_request *req, const char *value = auth_fields_find(req->fields, "scope"); bool has_scope = value != NULL; - if (!has_scope) + if (!has_scope && array_is_empty(&req->db->set->audience)) { value = auth_fields_find(req->fields, "aud"); + if (value != NULL) { + time_t t0 = time(NULL); + if (t0 - last_warned_aud_fallback > WARN_AUD_FALLBACK_PERIOD) { + e_warning(authdb_event(req->auth_request), + "Token has no 'scope' claim; falling back to " + "'aud' for scope check is deprecated - " + "use oauth2_audience instead"); + last_warned_aud_fallback = t0; + } + } + } e_debug(authdb_event(req->auth_request), "Token scope(s): %s", value); @@ -585,6 +602,37 @@ db_oauth2_token_in_scope(struct db_oauth2_request *req, return found; } +static bool +db_oauth2_token_in_audience(struct db_oauth2_request *req, + enum passdb_result *result_r, const char **error_r) +{ + if (array_is_empty(&req->db->set->audience)) + return TRUE; + + const char *value = auth_fields_find(req->fields, "aud"); + e_debug(authdb_event(req->auth_request), + "Token audience(s): %s", value != NULL ? value : "(none)"); + + bool found = FALSE; + if (value != NULL && *value != '\0') { + const char *const *entries = t_strsplit_tabescaped(value); + const char *wanted; + found = TRUE; + array_foreach_elem(&req->db->set->audience, wanted) { + if (!str_array_find(entries, wanted)) { + found = FALSE; + break; + } + } + } + if (!found) { + *error_r = t_strdup_printf("Token audience does not include '%s'", + t_array_const_string_join(&req->db->set->audience, " ")); + *result_r = PASSDB_RESULT_USER_DISABLED; + } + return found; +} + static void db_oauth2_process_fields(struct db_oauth2_request *req, enum passdb_result *result_r, const char **error_r) @@ -593,7 +641,8 @@ static void db_oauth2_process_fields(struct db_oauth2_request *req, if (db_oauth2_user_is_enabled(req, result_r, error_r) && db_oauth2_validate_username(req, result_r, error_r) && - db_oauth2_token_in_scope(req, result_r, error_r)) { + db_oauth2_token_in_scope(req, result_r, error_r) && + db_oauth2_token_in_audience(req, result_r, error_r)) { /* The user has now been successfully authenticated, mark the request as such. This allows having no passdb in config. */ diff --git a/src/auth/db-oauth2.h b/src/auth/db-oauth2.h index 3f362c6..fe503da 100644 --- a/src/auth/db-oauth2.h +++ b/src/auth/db-oauth2.h @@ -15,6 +15,8 @@ struct auth_oauth2_settings { const char *introspection_url; /* expected scope(s), optional */ ARRAY_TYPE(const_string) scope; + /* expected audience(s) (aud claim), optional */ + ARRAY_TYPE(const_string) audience; /* mode of introspection, one of auth, get, post, local - auth: send token with header Authorization: Bearer token - get: append token to url