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);
}
}
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;
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,