]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
auth: mech-oauth2 - Validate token in mechanism
authorAki Tuomi <aki.tuomi@open-xchange.com>
Wed, 3 Jun 2020 05:36:18 +0000 (08:36 +0300)
committerAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 17 Jan 2025 08:39:58 +0000 (10:39 +0200)
This is how the validation should have been done all along.

src/auth/auth-settings.c
src/auth/auth-settings.h
src/auth/db-oauth2.c
src/auth/mech-oauth2.c

index 018b8067110612b1a5998b4de16f77a6d33c9e31..ade37cf83752bbbaa2e5b63b74d34756f3c4ce18 100644 (file)
@@ -309,6 +309,7 @@ const struct setting_parser_info auth_userdb_post_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),
@@ -379,6 +380,7 @@ 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,
index feb1fe75351a89a3d3c9ffd3cf0a7ab487ee11c8..2b4b7ecbb341573551c972ef94ca7c0e8e460926 100644 (file)
@@ -66,6 +66,7 @@ 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 06ebfb73c12795b3a11f1705c7a209a90aba3a6e..02727ed136aa27f1a3c92c0027d93d0f70e4e57a 100644 (file)
@@ -808,7 +808,7 @@ void db_oauth2_lookup(struct db_oauth2 *db, struct db_oauth2_request *req,
                /* try to validate token locally */
                e_debug(authdb_event(req->auth_request),
                        "Attempting to locally validate token");
-               db_oauth2_local_validation(req, request->mech_password);
+               db_oauth2_local_validation(req, token);
                return;
 
        }
index 4891de902e33704d9b99250c5dfe01c0b6ade7f1..f352bacc4f80aefea9dfca703e53f29fffed960c 100644 (file)
@@ -5,38 +5,19 @@
 #include "str.h"
 #include "mech.h"
 #include "passdb.h"
+#include "db-oauth2.h"
 #include "oauth2.h"
 #include "json-ostream.h"
 #include <ctype.h>
 
 struct oauth2_auth_request {
        struct auth_request auth;
-       bool failed;
+       struct db_oauth2 *db;
+       struct db_oauth2_request db_req;
+       lookup_credentials_callback_t *callback;
+       bool failed:1;
 };
 
-static bool oauth2_find_oidc_url(struct auth_request *req, const char **url_r)
-{
-       struct auth_passdb *db = req->passdb;
-       if (req->openid_config_url != NULL) {
-               *url_r = req->openid_config_url;
-               return TRUE;
-       }
-
-       /* keep looking until you get a value */
-       for (; db != NULL; db = db->next) {
-               if (strcmp(db->passdb->iface.name, "oauth2") == 0) {
-                       const char *url =
-                               passdb_oauth2_get_oidc_url(db->passdb);
-                       if (url == NULL || *url == '\0')
-                               continue;
-                       *url_r = url;
-                       return TRUE;
-               }
-       }
-
-       return FALSE;
-}
-
 /* RFC5801 based unescaping */
 static bool oauth2_unescape_username(const char *in, const char **username_r)
 {
@@ -61,87 +42,94 @@ static bool oauth2_unescape_username(const char *in, const char **username_r)
        return TRUE;
 }
 
-static void oauth2_verify_callback(enum passdb_result result,
-                                  const char *const *error_fields,
-                                  struct auth_request *request)
+static void
+oauth2_send_error(struct oauth2_auth_request *oauth2_req, int code)
 {
-       const char *oidc_url;
+       struct auth_request *request = &oauth2_req->auth;
+       const char *error;
+       if (strcmp(request->mech->mech_name, "XOAUTH2") == 0) {
+               error = t_strdup_printf("{\"status\":\"%d\",\"schemes\":\"bearer\","
+                                       "\"scope\":\"mail\"}", code);
+       } else if (code == 401) {
+               error = "{\"status\":\"invalid_token\",\"scope\":\"mail\"}";
+       } else if (code == 403) {
+               error = "{\"status\":\"access_denied\",\"scope\":\"mail\"}";
+       } else {
+               error = "{\"status\":\"invalid_request\",\"scope\":\"mail\"}";
+       }
+       oauth2_req->failed = TRUE;
+       auth_request_fail_with_reply(request, error, strlen(error));
+}
+
+static void
+oauth2_verify_callback(enum passdb_result result,
+                      const unsigned char *credentials ATTR_UNUSED,
+                      size_t size ATTR_UNUSED, struct auth_request *request)
+{
+       struct oauth2_auth_request *oauth2_req =
+               container_of(request, struct oauth2_auth_request, auth);
 
-       i_assert(result == PASSDB_RESULT_OK || error_fields != NULL);
        switch (result) {
+       case PASSDB_RESULT_INTERNAL_FAILURE:
+               oauth2_send_error(oauth2_req, 500);
+               break;
+       case PASSDB_RESULT_USER_DISABLED:
+       case PASSDB_RESULT_PASS_EXPIRED:
+               /* user is explicitly disabled, don't allow it to log in */
+               oauth2_send_error(oauth2_req, 403);
+               return;
+       case PASSDB_RESULT_PASSWORD_MISMATCH:
+               oauth2_send_error(oauth2_req, 401);
+               break;
+       case PASSDB_RESULT_NEXT:
+       case PASSDB_RESULT_SCHEME_NOT_AVAILABLE:
+       case PASSDB_RESULT_USER_UNKNOWN:
        case PASSDB_RESULT_OK:
+               /* sending success */
                auth_request_success(request, "", 0);
                break;
-       case PASSDB_RESULT_INTERNAL_FAILURE:
-               request->internal_failure = TRUE;
-               /* fall through */
        default:
-               /* we could get new token after this */
-               if (request->mech_password != NULL)
-                       request->mech_password = NULL;
-
-               string_t *error = t_str_new(64);
-               struct json_ostream *joutput =
-                       json_ostream_create_str(error, 0);
-
-               json_ostream_ndescend_object(joutput, NULL);
-               for (unsigned int i = 0; error_fields[i] != NULL; i += 2) {
-                       i_assert(error_fields[i+1] != NULL);
-                       json_ostream_nwrite_string(joutput, error_fields[i],
-                                                   error_fields[i+1]);
-               }
-               /* FIXME: HORRIBLE HACK - REMOVE ME!!!
-                  It is because the mech has not been implemented properly
-                  that we need to pass the config url in this strange way.
-
-                  This **must** be removed from here and db-oauth2 once the
-                  validation result et al is handled here.
-               */
-               if (oauth2_find_oidc_url(request, &oidc_url)) {
-                       json_ostream_nwrite_string(
-                               joutput, "openid-configuration", oidc_url);
-               }
-               json_ostream_nascend_object(joutput);
-               json_ostream_nfinish_destroy(&joutput);
-
-               auth_request_fail_with_reply(request, str_data(error), str_len(error));
-               break;
+               i_unreached();
        }
 }
 
-static void mech_oauth2_verify_token(struct auth_request *request,
-                                    const char *token,
-                                    enum passdb_result result,
-                                    verify_plain_callback_t callback)
+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)
 {
-       i_assert(token != NULL);
+       struct auth_request *auth_request = &oauth2_req->auth;
        if (result != PASSDB_RESULT_OK) {
-               request->passdb_result = result;
-               request->failed = TRUE;
+               e_error(auth_request->mech_event, "%s", error);
+               oauth2_req->callback(result, NULL, 0, auth_request);
+               return;
        }
-       auth_request_verify_plain(request, token, callback);
+       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);
 }
 
 static void
-xoauth2_verify_callback(enum passdb_result result, struct auth_request *request)
+mech_oauth2_verify_token(struct oauth2_auth_request *oauth2_req, const char *token)
 {
-       const char *const error_fields[] = {
-               "status", "401",
-               "schemes", "bearer",
-               "scope", "mail",
-               NULL
-       };
-       oauth2_verify_callback(result, error_fields, request);
+       /* decode token */
+       db_oauth2_lookup(oauth2_req->db, &oauth2_req->db_req, token, &oauth2_req->auth,
+                        mech_oauth2_verify_token_continue, oauth2_req);
 }
 
-static void
-oauthbearer_verify_callback(enum passdb_result result, struct auth_request *request)
+static void oauth2_init_db(struct oauth2_auth_request *oauth2_req)
 {
-       const char *error_fields[] = {
-               "status", "invalid_token",
-               NULL
-       };
-       oauth2_verify_callback(result, error_fields, request);
+       if (oauth2_req->db != NULL)
+               return;
+       if (*oauth2_req->auth.set->oauth2_config_file != '\0')
+               oauth2_req->db = db_oauth2_init(oauth2_req->auth.set->oauth2_config_file);
+       else {
+               e_error(oauth2_req->auth.mech_event, "Cannot initialize oauth2");
+               oauth2_req->failed = TRUE;
+               auth_request_internal_failure(&oauth2_req->auth);
+       }
 }
 
 /* Input syntax:
@@ -152,10 +140,18 @@ mech_xoauth2_auth_continue(struct auth_request *request,
                           const unsigned char *data,
                           size_t data_size)
 {
+       struct oauth2_auth_request *oauth2_req =
+               container_of(request, struct oauth2_auth_request, auth);
+       oauth2_init_db(oauth2_req);
+       /* Specification says that client is sent "invalid token" challenge
+          which the client is supposed to ack with empty response */
+       if (oauth2_req->failed)
+               return;
+
        /* split the data from ^A */
        bool user_given = FALSE;
-       bool fail = FALSE;
-       const char *value, *error;
+       const char *value;
+       const char *error;
        const char *token = NULL;
        const char *const *ptr;
        const char *username;
@@ -174,8 +170,7 @@ mech_xoauth2_auth_continue(struct auth_request *request,
                        } else {
                                e_info(request->mech_event,
                                       "Invalid continued data");
-                               xoauth2_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
-                                                       request);
+                               oauth2_send_error(oauth2_req, 400);
                                return;
                        }
                }
@@ -185,17 +180,15 @@ mech_xoauth2_auth_continue(struct auth_request *request,
        if (user_given && !auth_request_set_username(request, username, &error)) {
                e_info(request->mech_event,
                       "%s", error);
-               fail = TRUE;
-       } else if (!user_given || token == NULL) {
-               e_info(request->mech_event, "Username or token missing");
-               fail = TRUE;
-               token = "";
+               auth_request_fail(request);
+               return;
+       }
+       if (user_given && token != NULL)
+               mech_oauth2_verify_token(oauth2_req, token);
+       else {
+               e_info(request->mech_event, "Missing username or token");
+               auth_request_fail(request);
        }
-       /* need to go through the database ... */
-       mech_oauth2_verify_token(request, token, fail ?
-                                       PASSDB_RESULT_PASSWORD_MISMATCH :
-                                       PASSDB_RESULT_OK,
-                                xoauth2_verify_callback);
 }
 
 /* Input syntax for data:
@@ -206,8 +199,13 @@ mech_oauthbearer_auth_continue(struct auth_request *request,
                               const unsigned char *data,
                               size_t data_size)
 {
+       struct oauth2_auth_request *oauth2_req =
+               container_of(request, struct oauth2_auth_request, auth);
+       oauth2_init_db(oauth2_req);
+       if (oauth2_req->failed)
+               return;
+
        bool user_given = FALSE;
-       bool fail = FALSE;
        const char *value, *error;
        const char *username;
        const char *const *ptr;
@@ -219,8 +217,7 @@ mech_oauthbearer_auth_continue(struct auth_request *request,
        if (*fields == NULL || *(fields[0]) == '\0') {
                e_info(request->mech_event,
                       "Invalid continued data");
-               oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
-                                           request);
+               oauth2_send_error(oauth2_req, 400);
                return;
        }
 
@@ -230,15 +227,13 @@ mech_oauthbearer_auth_continue(struct auth_request *request,
                case 'f':
                        e_info(request->mech_event,
                               "Client requested non-standard mechanism");
-                       oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
-                                                   request);
+                       oauth2_send_error(oauth2_req, 400);
                        return;
                case 'p':
                        /* channel binding is not supported */
                        e_info(request->mech_event,
                               "Client requested and used channel-binding");
-                       oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
-                                                   request);
+                       oauth2_send_error(oauth2_req, 400);
                        return;
                case 'n':
                case 'y':
@@ -249,8 +244,7 @@ mech_oauthbearer_auth_continue(struct auth_request *request,
                            !oauth2_unescape_username((*ptr)+2, &username)) {
                                 e_info(request->mech_event,
                                        "Invalid username escaping");
-                               oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
-                                                           request);
+                               oauth2_send_error(oauth2_req, 400);
                                return;
                        } else {
                                user_given = TRUE;
@@ -259,8 +253,7 @@ mech_oauthbearer_auth_continue(struct auth_request *request,
                default:
                        e_info(request->mech_event,
                               "Invalid gs2-header in request");
-                       oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
-                                                   request);
+                       oauth2_send_error(oauth2_req, 400);
                        return;
                }
        }
@@ -273,8 +266,7 @@ mech_oauthbearer_auth_continue(struct auth_request *request,
                        } else {
                                e_info(request->mech_event,
                                       "Invalid continued data");
-                               oauthbearer_verify_callback(PASSDB_RESULT_PASSWORD_MISMATCH,
-                                                           request);
+                               oauth2_send_error(oauth2_req, 400);
                                return;
                        }
                }
@@ -283,17 +275,15 @@ mech_oauthbearer_auth_continue(struct auth_request *request,
        if (user_given && !auth_request_set_username(request, username, &error)) {
                e_info(request->mech_event,
                       "%s", error);
-               fail = TRUE;
-       } else if (!user_given || token == NULL) {
-               e_info(request->mech_event, "Username or token missing");
-               fail = TRUE;
-               token = "";
+               auth_request_fail(request);
+               return;
+       }
+       if (user_given && token != NULL)
+               mech_oauth2_verify_token(oauth2_req, token);
+       else {
+               e_info(request->mech_event, "Missing username or token");
+               auth_request_fail(request);
        }
-       /* need to go through the database ... */
-       mech_oauth2_verify_token(request, token, fail ?
-                                       PASSDB_RESULT_PASSWORD_MISMATCH :
-                                       PASSDB_RESULT_OK,
-                                oauthbearer_verify_callback);
 }
 
 static struct auth_request *mech_oauth2_auth_new(void)
@@ -304,6 +294,7 @@ static struct auth_request *mech_oauth2_auth_new(void)
        pool = pool_alloconly_create(MEMPOOL_GROWING"oauth2_auth_request", 2048);
        request = p_new(pool, struct oauth2_auth_request, 1);
        request->auth.pool = pool;
+       request->db_req.pool = pool;
        return &request->auth;
 }