]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-oauth2: Fix recursive field copying
authorAki Tuomi <aki.tuomi@open-xchange.com>
Fri, 13 Oct 2023 06:43:53 +0000 (09:43 +0300)
committeraki.tuomi <aki.tuomi@open-xchange.com>
Wed, 25 Oct 2023 12:01:18 +0000 (12:01 +0000)
Now arrays are properly copied, and fields are
prefixed with object keys when nested.

src/lib-oauth2/oauth2-jwt.c
src/lib-oauth2/test-oauth2-jwt.c

index ac56748e2379da02a4559c9b6be7a503e35081fc..562c8642a4aff10901de152e642decf904616821 100644 (file)
@@ -327,33 +327,68 @@ oauth2_validate_signature(const struct oauth2_settings *set, const char *azp,
        return -1;
 }
 
+struct jwt_node {
+       const char *prefix;
+       const struct json_tree_node *root;
+       bool array:1;
+};
+
 static void
 oauth2_jwt_copy_fields(ARRAY_TYPE(oauth2_field) *fields, struct json_tree *tree)
 {
        pool_t pool = array_get_pool(fields);
-       ARRAY(const struct json_tree_node*) nodes;
-       const struct json_tree_node *root = json_tree_root(tree);
-
+       ARRAY(struct jwt_node) nodes;
        t_array_init(&nodes, 1);
-       array_push_back(&nodes, &root);
+       struct jwt_node *root = array_append_space(&nodes);
+       root->prefix = "";
+       root->root = json_tree_root(tree);
 
        while (array_count(&nodes) > 0) {
-               const struct json_tree_node *const *pnode = array_front(&nodes);
-               const struct json_tree_node *node = *pnode;
-               array_pop_front(&nodes);
+               const struct jwt_node *subroot = array_front(&nodes);
+               const struct json_tree_node *node = subroot->root;
                while (node != NULL) {
-                       if (node->value_type == JSON_TYPE_OBJECT) {
-                               root = node->value.child;
-                               array_push_back(&nodes, &root);
-                       } else if (node->key != NULL) {
-                               struct oauth2_field *field =
-                                       array_append_space(fields);
-                               field->name = p_strdup(pool, node->key);
-                               field->value = p_strdup(
-                                       pool, json_tree_get_value_str(node));
+                       if (node->value_type == JSON_TYPE_OBJECT ||
+                           node->value_type == JSON_TYPE_ARRAY) {
+                               root = array_append_space(&nodes);
+                               root->root = node->value.child;
+                               root->array = node->value_type == JSON_TYPE_ARRAY;
+                               if (node->key == NULL)
+                                       root->prefix = subroot->prefix;
+                               else if (*subroot->prefix != '\0')
+                                       root->prefix = t_strconcat(subroot->prefix, node->key, "_", NULL);
+                               else
+                                       root->prefix = t_strconcat(node->key, "_", NULL);
+                       } else {
+                               struct oauth2_field *field;
+                               const char *name;
+                               if (subroot->array) {
+                                       name = strrchr(subroot->prefix, '_');
+                                       if (name != NULL)
+                                               name = t_strdup_until(subroot->prefix, name);
+                                       else
+                                               name = subroot->prefix;
+                                       array_foreach_modifiable(fields, field) {
+                                               if (strcmp(field->name, name) == 0)
+                                                       break;
+                                       }
+                                       if (field == NULL || field->name == NULL) {
+                                               field = array_append_space(fields);
+                                               field->name = p_strdup(pool, name);
+                                       }
+                               } else {
+                                       field = array_append_space(fields);
+                                       field->name = p_strconcat(pool, subroot->prefix, node->key, NULL);
+                               }
+                               const char *value = str_tabescape(json_tree_get_value_str(node));
+                               if (field->value != NULL) {
+                                       field->value = p_strconcat(pool, field->value, "\t", value, NULL);
+                               } else {
+                                       field->value = p_strdup(pool, value);
+                               }
                        }
                        node = node->next;
                }
+               array_pop_front(&nodes);
        }
 }
 
index 68a64ed114ec72fea71e90680f84d0a1183a44d2..41604cbb63be562eac3ec0ee12e15bac20ae0592 100644 (file)
@@ -737,6 +737,78 @@ static void test_jwt_kid_escape(void)
        test_end();
 }
 
+static void test_jwt_nested_fields(void)
+{
+       test_begin("JWT nested fields");
+
+       buffer_t *tokenbuf = t_str_new(128);
+       const char *error ATTR_UNUSED;
+       bool is_jwt;
+       time_t now = time(NULL);
+       time_t exp = now+500;
+       time_t iat = now-500;
+       time_t nbf = now-250;
+
+       base64url_encode_str("{\"alg\":\"HS256\",\"typ\":\"JWT\"}", tokenbuf);
+       str_append_c(tokenbuf, '.');
+       base64url_encode_str(t_strdup_printf("{\"sub\":\"testuser\","
+                                            "\"nbf\":%"PRIdTIME_T","
+                                            "\"aud\":[\"dacity\",\"iron\"],"
+                                            "\"user\":{"
+                                               "\"name\":\"test\","
+                                               "\"features\":[\"one\",\"two\",\"tap\tdance\"],"
+                                               "\"enabled\":true,"
+                                               "\"locations\":{\"shared\":true,\"private\":false}"
+                                            "},"
+                                            "\"borders_\":{\"_left\":\"left\","
+                                               "\"right_\":{\"_ok_\":\"right\"},"
+                                               "\"_both_\":\"both\","
+                                               "\"middle_one\":\"middle\","
+                                               "\"middle_inner\":{\"middle_two\":\"middle_in\"}"
+                                            "},"
+                                            "\"exp\":%"PRIdTIME_T","
+                                            "\"iat\":%"PRIdTIME_T"}",
+                                            nbf, exp, iat),
+                            tokenbuf);
+       sign_jwt_token_hs256(tokenbuf, hs_sign_key);
+       struct oauth2_request req;
+
+       test_assert(parse_jwt_token(&req, str_c(tokenbuf), &is_jwt, &error) == 0);
+       test_assert(is_jwt == TRUE);
+
+       const struct oauth2_field test_fields[] = {
+               { .name = "sub", .value = "testuser" },
+               { .name = "nbf", .value = dec2str(nbf) },
+               { .name = "exp", .value = dec2str(exp) },
+               { .name = "iat", .value = dec2str(iat) },
+               { .name = "aud", .value = "dacity\tiron" },
+               { .name = "user_name", .value = "test" },
+               { .name = "user_enabled", .value = "true" },
+               { .name = "borders___left", .value = "left" },
+               { .name = "borders___both_", .value = "both" },
+               { .name = "borders__middle_one", .value = "middle" },
+               { .name = "user_features", .value = "one\ttwo\ttap\1tdance" },
+               { .name = "user_locations_shared", .value = "true" },
+               { .name = "user_locations_private", .value = "false" },
+               { .name = "borders__right___ok_", .value = "right" },
+               { .name = "borders__middle_inner_middle_two", .value = "middle_in" },
+               { .name = NULL, .value = NULL }
+       };
+
+
+       /* lets see them fields */
+       const struct oauth2_field *field;
+       unsigned int i = 0;
+       array_foreach(&req.fields, field) {
+               i_assert(i < N_ELEMENTS(test_fields));
+               test_assert_strcmp_idx(field->name, test_fields[i].name, i);
+               test_assert_strcmp_idx(field->value, test_fields[i].value, i);
+               i++;
+       }
+       test_assert_ucmp(i, ==, N_ELEMENTS(test_fields) - 1);
+       test_end();
+}
+
 static void test_jwt_rs_token(void)
 {
        const char *error;
@@ -910,6 +982,7 @@ int main(void)
                test_jwt_dates,
                test_jwt_key_files,
                test_jwt_kid_escape,
+               test_jwt_nested_fields,
                test_jwt_rs_token,
                test_jwt_ps_token,
                test_jwt_ec_token,