]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
auth: Use new settings with db_oauth2
authorAki Tuomi <aki.tuomi@open-xchange.com>
Mon, 11 Mar 2024 07:24:30 +0000 (09:24 +0200)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 17 Jan 2025 08:39:59 +0000 (10:39 +0200)
src/auth/auth-settings.c
src/auth/auth-settings.h
src/auth/auth-worker-server.c
src/auth/db-oauth2.c
src/auth/db-oauth2.h
src/auth/mech-oauth2.c
src/auth/mech.h
src/auth/passdb-oauth2.c
src/auth/test-auth.c

index a45d861d35ab9a3a05735de4512fd37768791fab..1c4fab0da71b72565cd1e0d6b611c02107d8b38c 100644 (file)
@@ -336,7 +336,6 @@ const struct setting_parser_info auth_static_setting_parser_info = {
 
 static const struct setting_define auth_setting_defines[] = {
        DEF(BOOLLIST, mechanisms),
-       DEF(STR, oauth2_config_file),
        DEF(STR, realms),
        DEF(STR, default_domain),
        DEF(SIZE, cache_size),
@@ -374,8 +373,6 @@ static const struct setting_define auth_setting_defines[] = {
        DEF(BOOL, policy_log_only),
        DEF(UINT_HIDDEN, policy_hash_truncate),
 
-       { .type = SET_FILTER_NAME, .key = "oauth2", },
-
        DEF(BOOL, verbose),
        DEF(BOOL, debug),
        DEF(BOOL, debug_passwords),
@@ -407,7 +404,6 @@ static const struct setting_define auth_setting_defines[] = {
 };
 
 static const struct auth_settings auth_default_settings = {
-       .oauth2_config_file = "",
        .realms = "",
        .default_domain = "",
        .cache_size = 0,
@@ -464,12 +460,6 @@ static const struct setting_keyvalue auth_default_settings_keyvalue[] = {
        { "auth_policy/http_client_max_idle_time", "10s" },
        { "auth_policy/http_client_max_parallel_connections", "100" },
        { "auth_policy/http_client_user_agent", "dovecot/auth-policy-client" },
-       { "oauth2/ssl_prefer_server_ciphers", "yes" },
-       { "oauth2/http_client_user_agent", "dovecot-oauth2-passdb/"DOVECOT_VERSION },
-       { "oauth2/http_client_max_idle_time", "60s" },
-       { "oauth2/http_client_max_parallel_connections", "10" },
-       { "oauth2/http_client_max_pipelined_requests", "1" },
-       { "oauth2/http_client_request_max_attempts", "1" },
        { NULL, NULL }
 };
 
index f9cd52ce69d4b8939d1a04d561da5d48957eaebb..7185684cae8adef7515763cce70b4adc634b5386 100644 (file)
@@ -66,7 +66,6 @@ struct auth_userdb_settings {
 struct auth_settings {
        pool_t pool;
        ARRAY_TYPE(const_string) mechanisms;
-       const char *oauth2_config_file;
        const char *realms;
        const char *default_domain;
        uoff_t cache_size;
index a43af1156de301144d6ea42aa3370ef9098f956b..c893dc11fe6a8e838a0e10da32a1ee22eeb344fe 100644 (file)
@@ -798,11 +798,7 @@ static bool
 auth_worker_handler_oauth2_token(struct auth_worker_command *cmd, unsigned int id,
                                 const char *const *args, const char **error_r)
 {
-       if (cmd->server->oauth2 == NULL) {
-               *error_r = "BUG: oauth2 not available";
-               return FALSE;
-       }
-
+       const char *error;
        const char *token = *args;
        pool_t pool = pool_alloconly_create(MEMPOOL_GROWING"oauth2 request", 256);
        struct db_oauth2_request *db_req =
@@ -814,8 +810,18 @@ auth_worker_handler_oauth2_token(struct auth_worker_command *cmd, unsigned int i
                pool_unref(&pool);
                return FALSE;
        }
-       connection_input_halt(&cmd->server->conn);
 
+       if (cmd->server->oauth2 == NULL) {
+               if (db_oauth2_init(cmd->event, &cmd->server->oauth2, &error) < 0) {
+                       e_error(cmd->event, "%s", error);
+                       auth_worker_handle_token_continue(db_req,
+                                       PASSDB_RESULT_INTERNAL_FAILURE, error,
+                                       db_req->auth_request);
+                       return TRUE;
+               }
+       }
+
+       connection_input_halt(&cmd->server->conn);
        db_oauth2_lookup(cmd->server->oauth2, db_req, token, db_req->auth_request,
                         auth_worker_handle_token_continue, db_req->auth_request);
        return TRUE;
@@ -1006,7 +1012,6 @@ auth_worker_server_create(struct auth *auth,
                          const struct master_service_connection *master_conn)
 {
        struct auth_worker_server *server;
-       const char *error;
 
        if (clients == NULL)
                clients = connection_list_init(&auth_worker_server_set,
@@ -1023,14 +1028,6 @@ auth_worker_server_create(struct auth *auth,
 
        auth_worker_refresh_proctitle(WORKER_STATE_HANDSHAKE);
 
-       if (*auth->protocol_set->oauth2_config_file != '\0') {
-               if (db_oauth2_init(auth->protocol_set->oauth2_config_file,
-                                  &server->oauth2, &error) < 0) {
-                       e_error(auth_event, "Cannot initialize oauth2: %s",
-                               error);
-                       auth_worker_server_error = TRUE;
-               }
-       }
        if (auth_worker_server_error)
                auth_worker_server_send_error();
        return server;
index 6cf7197a65a25cb6ce651685aa025d0d52b3394a..df714f93bbd0c5bb099bb9cfb5253c8e615aa4ce 100644 (file)
@@ -8,7 +8,6 @@
 #include "env-util.h"
 #include "var-expand.h"
 #include "settings.h"
-#include "settings-legacy.h"
 #include "oauth2.h"
 #include "http-client.h"
 #include "http-url.h"
 #include "dcrypt.h"
 #include "dict.h"
 
-struct passdb_oauth2_settings {
-       /* tokeninfo endpoint, format https://endpoint/somewhere?token= */
-       const char *tokeninfo_url;
-       /* password grant endpoint, format https://endpoint/somewhere */
-       const char *grant_url;
-       /* introspection endpoint, format https://endpoint/somewhere */
-       const char *introspection_url;
-       /* expected scope, optional */
-       const char *scope;
-       /* mode of introspection, one of get, get-auth, post
-          - get: append token to url
-          - get-auth: send token with header Authorization: Bearer token
-          - post: send token=<token> as POST request
-       */
-       const char *introspection_mode;
-       /* normalization var-expand template for username, defaults to %Lu */
-       const char *username_format;
-       /* name of username attribute to lookup, mandatory */
-       const char *username_attribute;
-       /* name of account is active attribute, optional */
-       const char *active_attribute;
-       /* expected active value for active attribute, optional */
-       const char *active_value;
-       /* client identificator for oauth2 server */
-       const char *client_id;
-       /* not really used, but have to present by oauth2 specs */
-       const char *client_secret;
-       /* template to expand into passdb */
-       const char *pass_attrs;
-       /* template to expand into key path, turns on local validation support */
-       const char *local_validation_key_dict;
-       /* valid token issuers */
-       const char *issuers;
-       /* The URL for a document following the OpenID Provider Configuration
-          Information schema, see
-
-          https://datatracker.ietf.org/doc/html/rfc7628#section-3.2.2
-       */
-       const char *openid_configuration_url;
-
-       bool debug;
-       /* Should introspection be done even if not necessary */
-       bool force_introspection;
-       /* Should we send service and local/remote endpoints as X-Dovecot-Auth headers */
-       bool send_auth_headers;
-       bool use_grant_password;
-       bool blocking;
+#undef DEF
+#define DEF(type, name) \
+       SETTING_DEFINE_STRUCT_##type("oauth2_"#name, name, struct auth_oauth2_settings)
+
+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(ENUM, introspection_mode),
+       DEF(STR_NOVARS, username_format),
+       DEF(STR, username_attribute),
+       DEF(STR, active_attribute),
+       DEF(STR, active_value),
+       DEF(STR, client_id),
+       DEF(STR, client_secret),
+       DEF(STR_NOVARS, pass_attrs),
+       DEF(BOOLLIST, issuers),
+       DEF(STR, openid_configuration_url),
+       DEF(BOOL, force_introspection),
+       DEF(BOOL, send_auth_headers),
+       DEF(BOOL, use_grant_password),
+       DEF(BOOL, use_worker_with_mech),
+       { .type = SET_FILTER_NAME, .key = "oauth2_local_validation",
+               .required_setting = "dict", },
+       { .type = SET_FILTER_NAME, .key = "oauth2", },
+       SETTING_DEFINE_LIST_END
 };
 
-struct db_oauth2 {
-       struct db_oauth2 *prev,*next;
-
-       pool_t pool;
-
-       const char *config_path;
-       const struct passdb_oauth2_settings *set;
-       struct passdb_oauth2_settings set_store;
-       struct http_client *client;
-       struct passdb_template *tmpl;
-       struct oauth2_settings oauth2_set;
-
-       struct db_oauth2_request *head;
-};
-
-static struct db_oauth2 *db_oauth2_head = NULL;
-
-#undef DEF_STR
-#undef DEF_BOOL
-#undef DEF_INT
-
-#define DEF_STR(name) DEF_STRUCT_STR(name, passdb_oauth2_settings)
-#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, passdb_oauth2_settings)
-#define DEF_INT(name) DEF_STRUCT_INT(name, passdb_oauth2_settings)
-
-static struct setting_def setting_defs[] = {
-       DEF_STR(tokeninfo_url),
-       DEF_STR(grant_url),
-       DEF_STR(introspection_url),
-       DEF_STR(scope),
-       DEF_BOOL(force_introspection),
-       DEF_STR(introspection_mode),
-       DEF_STR(username_format),
-       DEF_STR(username_attribute),
-       DEF_STR(pass_attrs),
-       DEF_STR(local_validation_key_dict),
-       DEF_STR(active_attribute),
-       DEF_STR(active_value),
-       DEF_STR(client_id),
-       DEF_STR(client_secret),
-       DEF_STR(issuers),
-       DEF_STR(openid_configuration_url),
-       DEF_BOOL(send_auth_headers),
-       DEF_BOOL(use_grant_password),
-       DEF_BOOL(blocking),
-       DEF_BOOL(debug),
-
-       { 0, NULL, 0 }
-};
-
-static struct passdb_oauth2_settings default_oauth2_settings = {
+static const struct auth_oauth2_settings auth_oauth2_default_settings = {
        .tokeninfo_url = "",
        .grant_url = "",
        .introspection_url = "",
-       .scope = "",
+       .scope = ARRAY_INIT,
        .force_introspection = FALSE,
-       .introspection_mode = "",
-       .username_format = "%Lu",
+       .introspection_mode = ":auth:get:post:local",
+       .username_format = "%u",
        .username_attribute = "email",
        .active_attribute = "",
        .active_value = "",
        .client_id = "",
        .client_secret = "",
-       .issuers = "",
+       .issuers = ARRAY_INIT,
        .openid_configuration_url = "",
        .pass_attrs = "",
-       .local_validation_key_dict = "",
        .send_auth_headers = FALSE,
        .use_grant_password = FALSE,
-       .blocking = TRUE,
-       .debug = FALSE,
+       .use_worker_with_mech = FALSE,
 };
 
+/* <settings checks> */
+
+static bool auth_oauth2_settings_check(struct event *event ATTR_UNUSED, void *_set,
+                                      pool_t pool ATTR_UNUSED, const char **error_r)
+{
+       const struct auth_oauth2_settings *set = _set;
+
+       if (*set->introspection_mode == '\0') {
+               if (*set->grant_url != '\0' ||
+                   *set->tokeninfo_url != '\0' ||
+                   *set->introspection_url != '\0') {
+                       *error_r = "Missing oauth2_introspection_mode";
+                       return FALSE;
+               }
+       } else if (strcmp(set->introspection_mode, "auth") == 0 ||
+                strcmp(set->introspection_mode, "get") == 0 ||
+                strcmp(set->introspection_mode, "post") == 0) {
+               if (*set->tokeninfo_url == '\0' &&
+                   *set->introspection_url == '\0') {
+                       *error_r = "Need at least one of oauth2_tokeninfo_url or oauth2_introspection_url";
+                       return FALSE;
+               }
+       }
+
+       if (*set->grant_url != '\0' && *set->client_id == '\0') {
+               *error_r = "oauth2_client_id is required with oauth2_grant_url";
+               return FALSE;
+       }
+
+       if ((*set->client_id != '\0' && *set->client_secret == '\0') ||
+           (*set->client_id == '\0' && *set->client_secret != '\0')) {
+               *error_r = "oauth2_client_id and oauth2_client_secret must be provided together";
+               return FALSE;
+       }
+
+       if (*set->active_attribute == '\0' &&
+           *set->active_value != '\0') {
+               *error_r = "Cannot have empty active_attribute if active_value is set";
+               return FALSE;
+       }
+
+       return TRUE;
+}
+
+/* </settings checks> */
+
+static const struct setting_keyvalue auth_oauth2_default_settings_keyvalue[] = {
+       { "oauth2/ssl_prefer_server_ciphers", "yes" },
+       { "oauth2/http_client_user_agent", "dovecot-oauth2-passdb/"DOVECOT_VERSION },
+       { "oauth2/http_client_max_idle_time", "60s" },
+       { "oauth2/http_client_max_parallel_connections", "10" },
+       { "oauth2/http_client_max_pipelined_requests", "1" },
+       { "oauth2/http_client_request_max_attempts", "1" },
+       { NULL, NULL }
+};
+
+const struct setting_parser_info auth_oauth2_setting_parser_info = {
+       .name = "auth_oauth2",
+
+       .defines = auth_oauth2_setting_defines,
+       .defaults = &auth_oauth2_default_settings,
+       .default_settings = auth_oauth2_default_settings_keyvalue,
+
+       .struct_size = sizeof(struct auth_oauth2_settings),
+       .pool_offset1 = 1 + offsetof(struct auth_oauth2_settings, pool),
+       .ext_check_func = auth_oauth2_settings_check,
+};
+
+static struct event_category event_category_oauth2 = {
+       .parent = &event_category_auth,
+       .name = "oauth2",
+};
+
+struct db_oauth2 {
+       struct db_oauth2 *prev,*next;
+
+       pool_t pool;
+
+       struct event *event;
+       const struct auth_oauth2_settings *set;
+       struct http_client *client;
+       struct passdb_template *tmpl;
+       struct oauth2_settings oauth2_set;
+
+       struct db_oauth2_request *head;
+};
+
+static struct db_oauth2 *db_oauth2_head = NULL;
+
 static void db_oauth2_callback(struct db_oauth2_request *req,
                               enum passdb_result result,
                               const char *error_prefix, const char *error);
 static void db_oauth2_free(struct db_oauth2 **_db);
 
-static const char *parse_setting(const char *key, const char *value,
-                                struct db_oauth2 *db)
-{
-       return parse_setting_from_defs(db->pool, setting_defs,
-                                      &db->set_store, key, value);
-}
-
 static int db_oauth2_setup(struct db_oauth2 *db, const char **error_r)
 {
-       const char *error;
-       if (!settings_read_nosection(db->config_path, parse_setting, db, &error)) {
-               *error_r = t_strdup_printf("oauth2 %s: %s", db->config_path, error);
+       if (*db->set->introspection_mode == '\0') {
+               *error_r = "Missing oauth2_introspection_mode setting";
                return -1;
        }
 
-       db->tmpl = passdb_template_build(db->pool, db->set->pass_attrs);
-
-       if (*db->set->local_validation_key_dict == '\0' &&
-           *db->set->tokeninfo_url == '\0' &&
-           (*db->set->grant_url == '\0' || *db->set->client_id == '\0') &&
-           *db->set->introspection_url == '\0') {
-               *error_r = "oauth2: Password grant, tokeninfo, introspection URL or validation key dictionary must be given";
+       if (http_client_init_auto(db->event, &db->client, error_r) < 0)
                return -1;
-       }
 
-       struct event *event = event_create(auth_event);
-       event_set_ptr(event, SETTINGS_EVENT_FILTER_NAME, "oauth2");
-       if (http_client_init_auto(event, &db->client, &error) < 0) {
-               *error_r = t_strdup_printf("%s", error);
-               return -1;
-       }
-       event_unref(&event);
+       db->tmpl = passdb_template_build(db->pool, db->set->pass_attrs);
 
        i_zero(&db->oauth2_set);
        db->oauth2_set.client = db->client;
@@ -191,7 +186,31 @@ static int db_oauth2_setup(struct db_oauth2 *db, const char **error_r)
        db->oauth2_set.client_secret = db->set->client_secret;
        db->oauth2_set.send_auth_headers = db->set->send_auth_headers;
        db->oauth2_set.use_grant_password = db->set->use_grant_password;
-       db->oauth2_set.scope = db->set->scope;
+       if (!array_is_empty(&db->set->scope)) {
+               db->oauth2_set.scope =
+                       p_array_const_string_join(db->pool, &db->set->scope, " ");
+       } else
+               db->oauth2_set.scope = "";
+       if (!array_is_empty(&db->set->issuers)) {
+               const char *elem;
+               ARRAY_TYPE(const_string) dup;
+               p_array_init(&dup, db->pool, array_count(&db->set->issuers));
+               array_foreach_elem(&db->set->issuers, elem) {
+                       array_push_back(&dup, &elem);
+               }
+               array_append_space(&dup);
+               db->oauth2_set.issuers = array_front(&dup);
+       }
+
+       if (strcmp(db->set->introspection_mode, "local") == 0) {
+               struct event *event = event_create(db->event);
+               event_set_ptr(event, SETTINGS_EVENT_FILTER_NAME,
+                             "oauth2_local_validation");
+               int ret = dict_init_auto(event, &db->oauth2_set.key_dict, error_r);
+               event_unref(&event);
+               if (ret < 0)
+                       return ret;
+       }
 
        if (*db->set->active_attribute == '\0' &&
            *db->set->active_value != '\0') {
@@ -199,18 +218,13 @@ static int db_oauth2_setup(struct db_oauth2 *db, const char **error_r)
                return -1;
        }
 
-       if (*db->set->introspection_mode == '\0' ||
-           strcmp(db->set->introspection_mode, "auth") == 0) {
+       if (strcmp(db->set->introspection_mode, "auth") == 0) {
                db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET_AUTH;
        } else if (strcmp(db->set->introspection_mode, "get") == 0) {
                db->oauth2_set.introspection_mode = INTROSPECTION_MODE_GET;
        } else if (strcmp(db->set->introspection_mode, "post") == 0) {
                db->oauth2_set.introspection_mode = INTROSPECTION_MODE_POST;
        } else if (strcmp(db->set->introspection_mode, "local") == 0) {
-               if (*db->set->local_validation_key_dict == '\0') {
-                       *error_r = "oauth2: local_validation_key_dict is required for local introspection.";
-                       return -1;
-               }
                db->oauth2_set.introspection_mode = INTROSPECTION_MODE_LOCAL;
        } else {
                *error_r = t_strdup_printf("oauth2: Invalid value '%s' for introspection mode, must be on auth, get, post or local",
@@ -219,16 +233,7 @@ static int db_oauth2_setup(struct db_oauth2 *db, const char **error_r)
        }
 
        if (db->oauth2_set.introspection_mode == INTROSPECTION_MODE_LOCAL) {
-               struct dict_legacy_settings dict_set = {
-                       .base_dir = global_auth_settings->base_dir,
-                       .event_parent = auth_event,
-               };
-               if (dict_init_legacy(db->set->local_validation_key_dict, &dict_set,
-                                    &db->oauth2_set.key_dict, &error) < 0) {
-                       *error_r = t_strdup_printf("Cannot initialize key dict: %s",
-                                                  error);
-                       return -1;
-               }
+               const char *error ATTR_UNUSED;
                /* failure to initialize dcrypt is not fatal - we can still
                   validate HMAC based keys */
                (void)dcrypt_initialize(NULL, NULL, &error);
@@ -236,12 +241,9 @@ static int db_oauth2_setup(struct db_oauth2 *db, const char **error_r)
                db->oauth2_set.key_cache = oauth2_validation_key_cache_init();
        }
 
-       if (*db->set->issuers != '\0')
-               db->oauth2_set.issuers = (const char *const *)
-                       p_strsplit_spaces(db->pool, db->set->issuers, " ");
-
        if (*db->set->openid_configuration_url != '\0') {
                struct http_url *parsed_url ATTR_UNUSED;
+               const char *error;
                if (http_url_parse(db->set->openid_configuration_url, NULL, 0,
                                   pool_datastack_create(), &parsed_url,
                                   &error) < 0) {
@@ -254,23 +256,38 @@ static int db_oauth2_setup(struct db_oauth2 *db, const char **error_r)
        return 0;
 }
 
-int db_oauth2_init(const char *config_path, struct db_oauth2 **db_r,
+int db_oauth2_init(struct event *event, struct db_oauth2 **db_r,
                   const char **error_r)
 {
        struct db_oauth2 *db;
+       const struct auth_oauth2_settings *db_set;
+       struct event *db_event = event_create(event);
+       event_add_category(db_event, &event_category_oauth2);
+       event_set_ptr(db_event, SETTINGS_EVENT_FILTER_NAME, "oauth2");
+       if (settings_get(db_event, &auth_oauth2_setting_parser_info, 0, &db_set,
+                        error_r) < 0) {
+               event_unref(&db_event);
+               return -1;
+       }
 
-       for(db = db_oauth2_head; db != NULL; db = db->next) {
-               if (strcmp(db->config_path, config_path) == 0) {
-                       *db_r = db;
-                       return 0;
-               }
+       for (db = db_oauth2_head; db != NULL; db = db->next) {
+               if (settings_equal(&auth_oauth2_setting_parser_info, db->set,
+                                  db_set, NULL))
+                       break;
+       }
+
+       if (db != NULL) {
+               settings_free(db_set);
+               event_unref(&db_event);
+               *db_r = db;
+               return 0;
        }
 
        pool_t pool = pool_alloconly_create("db_oauth2", 128);
        db = p_new(pool, struct db_oauth2, 1);
        db->pool = pool;
-       db->config_path = p_strdup(pool, config_path);
-       db->set = &default_oauth2_settings;
+       db->event = db_event;
+       db->set = db_set;
        DLLIST_PREPEND(&db_oauth2_head, db);
 
        if (db_oauth2_setup(db, error_r) < 0) {
@@ -311,6 +328,8 @@ static void db_oauth2_free(struct db_oauth2 **_db)
        if (db->oauth2_set.key_dict != NULL)
                dict_deinit(&db->oauth2_set.key_dict);
        oauth2_validation_key_cache_deinit(&db->oauth2_set.key_cache);
+       settings_free(db->set);
+       event_unref(&db->event);
        pool_unref(&db->pool);
 }
 
@@ -521,9 +540,10 @@ db_oauth2_validate_username(struct db_oauth2_request *req,
        string_t *username_req = t_str_new(32);
        string_t *username_val = t_str_new(strlen(username_value));
 
-       if (auth_request_var_expand(username_req, req->db->set->username_format, req->auth_request, escape_none, &error) <= 0 ||
-           var_expand_with_table(username_val, req->db->set->username_format,
-                                 table, &error) <= 0) {
+       if (auth_request_var_expand(username_req, req->db->set->username_format,
+                                   req->auth_request, escape_none, &error) <= 0 ||
+           var_expand_with_table(username_val, req->db->set->username_format, table,
+                                 &error) <= 0) {
                *error_r = t_strdup_printf("var_expand(%s) failed: %s",
                                        req->db->set->username_format, error);
                *result_r = PASSDB_RESULT_INTERNAL_FAILURE;
@@ -588,8 +608,9 @@ static bool
 db_oauth2_token_in_scope(struct db_oauth2_request *req,
                         enum passdb_result *result_r, const char **error_r)
 {
-       if (*req->db->set->scope != '\0') {
-               bool found = FALSE;
+       bool found = TRUE;
+       if (!array_is_empty(&req->db->set->scope)) {
+               found = FALSE;
                const char *value = auth_fields_find(req->fields, "scope");
                bool has_scope = value != NULL;
                if (!has_scope)
@@ -598,22 +619,22 @@ db_oauth2_token_in_scope(struct db_oauth2_request *req,
                        "Token scope(s): %s",
                        value);
                if (value != NULL) {
-                       const char **wanted_scopes =
-                               t_strsplit_spaces(req->db->set->scope, " ");
+                       const char *wanted_scope;
                        const char *const *entries = has_scope ?
                                t_strsplit_spaces(value, " ") :
                                t_strsplit_tabescaped(value);
-                       for (; !found && *wanted_scopes != NULL; wanted_scopes++)
-                               found = str_array_find(entries, *wanted_scopes);
+                       array_foreach_elem(&req->db->set->scope, wanted_scope) {
+                               if ((found = str_array_find(entries, wanted_scope)))
+                                       break;
+                       }
                }
                if (!found) {
                        *error_r = t_strdup_printf("Token is not valid for scope '%s'",
-                                                  req->db->set->scope);
+                                                  req->db->oauth2_set.scope);
                        *result_r = PASSDB_RESULT_USER_DISABLED;
-                       return FALSE;
                }
        }
-       return TRUE;
+       return found;
 }
 
 static void db_oauth2_process_fields(struct db_oauth2_request *req,
@@ -869,7 +890,7 @@ bool db_oauth2_uses_password_grant(const struct db_oauth2 *db)
 
 bool db_oauth2_use_worker(const struct db_oauth2 *db)
 {
-       return db->set->blocking;
+       return db->set->use_worker_with_mech;
 }
 
 void db_oauth2_deinit(void)
index a23f56a3625c51b8f4f011a88f8932ad9205f67d..636fe1409118491aa37cbd4ca60f0f9dd73a3f47 100644 (file)
@@ -5,6 +5,54 @@ struct db_oauth2;
 struct oauth2_request;
 struct db_oauth2_request;
 
+struct auth_oauth2_settings {
+       pool_t pool;
+       /* tokeninfo endpoint, format https://endpoint/somewhere?token= */
+       const char *tokeninfo_url;
+       /* password grant endpoint, format https://endpoint/somewhere */
+       const char *grant_url;
+       /* introspection endpoint, format https://endpoint/somewhere */
+       const char *introspection_url;
+       /* expected scope(s), optional */
+       ARRAY_TYPE(const_string) scope;
+       /* mode of introspection, one of auth, get, post, local
+          - auth: send token with header Authorization: Bearer token
+          - get: append token to url
+          - post: send token=<token> as POST request
+          - local: perform local validation
+       */
+       const char *introspection_mode;
+       /* normalization var-expand template for username, defaults to %Lu */
+       const char *username_format;
+       /* name of username attribute to lookup, mandatory */
+       const char *username_attribute;
+       /* name of account is active attribute, optional */
+       const char *active_attribute;
+       /* expected active value for active attribute, optional */
+       const char *active_value;
+       /* client identificator for oauth2 server */
+       const char *client_id;
+       /* not really used, but have to present by oauth2 specs */
+       const char *client_secret;
+       /* template to expand into passdb */
+       const char *pass_attrs;
+       /* valid token issuers */
+       ARRAY_TYPE(const_string) issuers;
+       /* The URL for a document following the OpenID Provider Configuration
+          Information schema, see
+
+          https://datatracker.ietf.org/doc/html/rfc7628#section-3.2.2
+       */
+       const char *openid_configuration_url;
+
+       /* Should introspection be done even if not necessary */
+       bool force_introspection;
+       /* Should we send service and local/remote endpoints as X-Dovecot-Auth headers */
+       bool send_auth_headers;
+       bool use_grant_password;
+       bool use_worker_with_mech;
+};
+
 typedef void db_oauth2_lookup_callback_t(struct db_oauth2_request *request,
                                         enum passdb_result result,
                                         const char *error,
@@ -30,7 +78,7 @@ struct db_oauth2_request {
 };
 
 
-int db_oauth2_init(const char *config_path, struct db_oauth2 **db_r,
+int db_oauth2_init(struct event *event, struct db_oauth2 **db_r,
                   const char **error_r);
 
 bool db_oauth2_uses_password_grant(const struct db_oauth2 *db);
@@ -46,4 +94,6 @@ void db_oauth2_lookup(struct db_oauth2 *db, struct db_oauth2_request *req, const
 
 void db_oauth2_deinit(void);
 
+extern const struct setting_parser_info auth_oauth2_setting_parser_info;
+
 #endif
index 133fbfe0e55a982d5fdbf48cdc25df1044d128cc..864c58524fbe3691fb8545eebb543d3ad2e542c8 100644 (file)
@@ -3,6 +3,7 @@
 #include "auth-common.h"
 #include "auth-fields.h"
 #include "auth-worker-connection.h"
+#include "ioloop.h"
 #include "str.h"
 #include "strescape.h"
 #include "json-generator.h"
@@ -423,8 +424,7 @@ void mech_oauth2_initialize(void)
        array_foreach_elem(&global_auth_settings->mechanisms, mech) {
                if (strcasecmp(mech, mech_xoauth2.mech_name) == 0 ||
                    strcasecmp(mech, mech_oauthbearer.mech_name) == 0) {
-                       if (db_oauth2_init(global_auth_settings->oauth2_config_file,
-                                          &db_oauth2, &error) < 0)
+                       if (db_oauth2_init(auth_event, &db_oauth2, &error) < 0)
                                i_fatal("Cannot initialize oauth2: %s", error);
                }
        }
index f7d4b03b49d5ebef44ea2d223869a141ae17eb4c..885d8e027ca1880a90356fe8e3170bc722ec1e3a 100644 (file)
@@ -75,4 +75,5 @@ void mech_init(const struct auth_settings *set);
 void mech_deinit(const struct auth_settings *set);
 
 void mech_oauth2_initialize(void);
+
 #endif
index dd59b1b4a50626f3fe68f4093470b0a41588f6da..e0a3a39c4dfd3969bc77d0a8e4eca874b832ec42 100644 (file)
@@ -43,15 +43,15 @@ oauth2_verify_plain(struct auth_request *request, const char *password,
        db_oauth2_lookup(module->db, req, password, request, oauth2_verify_plain_continue, request);
 }
 
-static struct passdb_module *
-oauth2_preinit(pool_t pool, const char *args)
+static int
+oauth2_preinit(pool_t pool, struct event *event, struct passdb_module **module_r,
+              const char **error_r)
 {
        struct oauth2_passdb_module *module;
-       const char *error;
 
        module = p_new(pool, struct oauth2_passdb_module, 1);
-       if (db_oauth2_init(args, &module->db, &error) < 0)
-               i_fatal("%s", error);
+       if (db_oauth2_init(event, &module->db, error_r) < 0)
+               return -1;
        module->module.default_pass_scheme = "PLAIN";
 
        if (db_oauth2_uses_password_grant(module->db)) {
@@ -60,7 +60,8 @@ oauth2_preinit(pool_t pool, const char *args)
                module->module.default_cache_key = "%u%w";
        }
 
-       return &module->module;
+       *module_r = &module->module;
+       return 0;
 }
 
 static void oauth2_deinit(struct passdb_module *passdb ATTR_UNUSED)
@@ -70,7 +71,8 @@ static void oauth2_deinit(struct passdb_module *passdb ATTR_UNUSED)
 struct passdb_module_interface passdb_oauth2 = {
        .name = "oauth2",
 
-       .preinit_legacy = oauth2_preinit,
+       .preinit = oauth2_preinit,
        .deinit = oauth2_deinit,
        .verify_plain = oauth2_verify_plain,
+       .fields_supported = TRUE,
 };
index 07a1bc65987136faa5a0d934edf29b08ca5f5f8c..6e36df2771c34f5878204439fb262744fc9120fb 100644 (file)
@@ -1,10 +1,8 @@
 /* Copyright (c) 2024 Dovecot authors, see the included COPYING file */
 
 #include "test-auth.h"
-#include "ostream.h"
 #include "auth-common.h"
 #include "settings.h"
-#include "settings-parser.h"
 #include "auth-settings.h"
 #include "auth-token.h"
 #include "auth-penalty.h"
 
 #include <time.h>
 
-#define TEST_OAUTH2_CONFIG_FILE "test-oauth2-config"
-
 static const char *const settings[] = {
        "base_dir", ".",
-       "auth_mechanisms", "plain",
+       "auth_mechanisms", "plain xoauth2",
        "auth_username_chars", "",
        "auth_username_format", "",
        /* For tests of digest-md5. */
@@ -29,7 +25,11 @@ static const char *const settings[] = {
        /* For tests of mech-anonymous. */
        "auth_anonymous_username", "anonuser",
        /* For oauth2 tests */
-       "auth_oauth2_config_file", TEST_OAUTH2_CONFIG_FILE,
+       "oauth2_introspection_mode", "auth",
+       "oauth2_tokeninfo_url", "http://localhost",
+       "oauth2_client_id", "foo",
+       "oauth2_client_secret", "foo",
+       "oauth2_use_worker", "no",
 
        "passdb", "mock1 mock2",
        "passdb/mock1/name", "mock1",
@@ -49,17 +49,11 @@ 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);
-
        settings_simple_init(&simple_set, settings);
        global_auth_settings = settings_get_or_fatal(simple_set.event,
                                                     &auth_setting_parser_info);
-
+       /* this is needed to get oauth2 initialized */
+       auth_event = simple_set.event;
        mech_init(global_auth_settings);
        mech_reg = mech_register_init(global_auth_settings);
        passdbs_init();
@@ -93,5 +87,4 @@ void test_auth_deinit(void)
        settings_free(global_auth_settings);
        settings_simple_deinit(&simple_set);
        i_unlink_if_exists("auth-token-secret.dat");
-       i_unlink_if_exists(TEST_OAUTH2_CONFIG_FILE);
 }