From: Aki Tuomi Date: Fri, 13 Oct 2023 06:43:53 +0000 (+0300) Subject: lib-oauth2: Fix recursive field copying X-Git-Tag: 2.4.0~2485 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=84be751f5f7d46d34f9a45a00076a6c36324a850;p=thirdparty%2Fdovecot%2Fcore.git lib-oauth2: Fix recursive field copying Now arrays are properly copied, and fields are prefixed with object keys when nested. --- diff --git a/src/lib-oauth2/oauth2-jwt.c b/src/lib-oauth2/oauth2-jwt.c index ac56748e23..562c8642a4 100644 --- a/src/lib-oauth2/oauth2-jwt.c +++ b/src/lib-oauth2/oauth2-jwt.c @@ -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); } } diff --git a/src/lib-oauth2/test-oauth2-jwt.c b/src/lib-oauth2/test-oauth2-jwt.c index 68a64ed114..41604cbb63 100644 --- a/src/lib-oauth2/test-oauth2-jwt.c +++ b/src/lib-oauth2/test-oauth2-jwt.c @@ -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,