diff --git a/src/flb_oauth2.c b/src/flb_oauth2.c index 1dbc11b2918..e4fd405292c 100644 --- a/src/flb_oauth2.c +++ b/src/flb_oauth2.c @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -436,17 +437,21 @@ int flb_oauth2_parse_json_response(const char *json_data, size_t json_size, { int i; int ret; + int copy_len; int key_len; int val_len; + char *end; int tokens_size = 32; const char *key; const char *val; + unsigned long long parsed_expires_in; jsmn_parser parser; + flb_sds_t new_access_token = NULL; + flb_sds_t new_token_type = NULL; jsmntok_t *t; jsmntok_t *tokens; - uint64_t expires_in = 0; - flb_sds_t access_token = NULL; - flb_sds_t token_type = NULL; + char tmp_num[32]; + uint64_t new_expires_in = 0; jsmn_init(&parser); tokens = flb_calloc(1, sizeof(jsmntok_t) * tokens_size); @@ -457,14 +462,14 @@ int flb_oauth2_parse_json_response(const char *json_data, size_t json_size, ret = jsmn_parse(&parser, json_data, json_size, tokens, tokens_size); if (ret <= 0) { - flb_error("[oauth2] cannot parse payload"); + flb_error("[oauth2] cannot parse payload (size=%zu)", json_size); flb_free(tokens); return -1; } t = &tokens[0]; if (t->type != JSMN_OBJECT) { - flb_error("[oauth2] invalid JSON response"); + flb_error("[oauth2] invalid JSON response (size=%zu)", json_size); flb_free(tokens); return -1; } @@ -483,46 +488,76 @@ int flb_oauth2_parse_json_response(const char *json_data, size_t json_size, key = json_data + t->start; key_len = (t->end - t->start); + if (i + 1 >= ret) { + break; + } + i++; t = &tokens[i]; val = json_data + t->start; val_len = (t->end - t->start); if (key_cmp(key, key_len, "access_token") == 0) { - access_token = flb_sds_create_len(val, val_len); + if (new_access_token) { + flb_sds_destroy(new_access_token); + } + + new_access_token = flb_sds_create_len(val, val_len); + if (!new_access_token) { + flb_errno(); + break; + } } else if (key_cmp(key, key_len, "token_type") == 0) { - token_type = flb_sds_create_len(val, val_len); + if (new_token_type) { + flb_sds_destroy(new_token_type); + } + + new_token_type = flb_sds_create_len(val, val_len); + if (!new_token_type) { + flb_errno(); + break; + } } else if (key_cmp(key, key_len, "expires_in") == 0) { - expires_in = strtoull(val, NULL, 10); + if (val_len <= 0 || val_len >= sizeof(tmp_num)) { + break; + } + + copy_len = val_len < (sizeof(tmp_num) - 1) ? val_len : (sizeof(tmp_num) - 1); + strncpy(tmp_num, val, copy_len); + tmp_num[copy_len] = '\0'; + + if (tmp_num[0] == '-') { + break; + } + + errno = 0; + parsed_expires_in = strtoull(tmp_num, &end, 10); + + if (errno != 0 || end == tmp_num || *end != '\0') { + break; + } + + new_expires_in = parsed_expires_in; + new_expires_in -= (new_expires_in / 10); } } flb_free(tokens); - if (!access_token) { - oauth2_reset_state(ctx); + if (!new_access_token || !new_token_type || new_expires_in <= ctx->refresh_skew) { + flb_sds_destroy(new_access_token); + flb_sds_destroy(new_token_type); return -1; } - if (!token_type) { - token_type = flb_sds_create("Bearer"); - flb_debug("[oauth2] token_type missing; defaulting to Bearer"); - } - - if (expires_in == 0) { - expires_in = FLB_OAUTH2_DEFAULT_EXPIRES; - flb_warn("[oauth2] expires_in missing; defaulting to %d seconds", - FLB_OAUTH2_DEFAULT_EXPIRES); - } - oauth2_reset_state(ctx); - ctx->access_token = access_token; - ctx->token_type = token_type; - ctx->expires_in = expires_in; - ctx->expires_at = time(NULL) + expires_in; + ctx->access_token = new_access_token; + ctx->token_type = new_token_type; + ctx->expires_in = new_expires_in; + ctx->expires_at = time(NULL) + new_expires_in; return 0; } diff --git a/tests/internal/oauth2.c b/tests/internal/oauth2.c index 6448084e626..95491968b3a 100644 --- a/tests/internal/oauth2.c +++ b/tests/internal/oauth2.c @@ -762,23 +762,142 @@ static struct flb_oauth2 *create_private_key_jwt_ctx(struct flb_config *config, return ctx; } -void test_parse_defaults(void) +static void destroy_parse_ctx(struct flb_oauth2 *ctx) +{ + flb_sds_destroy(ctx->access_token); + flb_sds_destroy(ctx->token_type); +} + +static void populate_parse_ctx(struct flb_oauth2 *ctx, + const char *access_token, + const char *token_type, + uint64_t expires_in) +{ + ctx->access_token = flb_sds_create(access_token); + ctx->token_type = flb_sds_create(token_type); + ctx->expires_in = expires_in; +} + +void test_parse_refreshes_token_transactionally(void) { int ret; - struct flb_oauth2 ctx; - const char *payload = "{\"access_token\":\"abc\"}"; + struct flb_oauth2 ctx = {0}; + const char *payload = "{\"access_token\":\"new-token\"," + "\"token_type\":\"Bearer\"," + "\"expires_in\":3600}"; - memset(&ctx, 0, sizeof(ctx)); + populate_parse_ctx(&ctx, "old-token", "OldBearer", 1200); ctx.refresh_skew = FLB_OAUTH2_DEFAULT_SKEW_SECS; ret = flb_oauth2_parse_json_response(payload, strlen(payload), &ctx); + + TEST_CHECK(ret == 0); + TEST_CHECK(strcmp(ctx.access_token, "new-token") == 0); + TEST_CHECK(strcmp(ctx.token_type, "Bearer") == 0); + TEST_CHECK(ctx.expires_in == 3240); + + destroy_parse_ctx(&ctx); +} + +void test_parse_accepts_quoted_expires_in(void) +{ + int ret; + struct flb_oauth2 ctx = {0}; + const char *payload = "{\"access_token\":\"quoted-token\"," + "\"token_type\":\"Bearer\"," + "\"expires_in\":\"3600\"}"; + + ctx.refresh_skew = FLB_OAUTH2_DEFAULT_SKEW_SECS; + ret = flb_oauth2_parse_json_response(payload, strlen(payload), &ctx); + + TEST_CHECK(ret == 0); + TEST_CHECK(strcmp(ctx.access_token, "quoted-token") == 0); + TEST_CHECK(strcmp(ctx.token_type, "Bearer") == 0); + TEST_CHECK(ctx.expires_in == 3240); + + destroy_parse_ctx(&ctx); +} + +void test_parse_duplicate_keys_last_wins(void) +{ + int ret; + struct flb_oauth2 ctx = {0}; + const char *payload = "{\"access_token\":\"first\"," + "\"token_type\":\"Bearer\"," + "\"expires_in\":3600," + "\"access_token\":\"second\"}"; + + ctx.refresh_skew = FLB_OAUTH2_DEFAULT_SKEW_SECS; + ret = flb_oauth2_parse_json_response(payload, strlen(payload), &ctx); + TEST_CHECK(ret == 0); - TEST_CHECK(ctx.access_token != NULL); + TEST_CHECK(strcmp(ctx.access_token, "second") == 0); TEST_CHECK(strcmp(ctx.token_type, "Bearer") == 0); - TEST_CHECK(ctx.expires_in == FLB_OAUTH2_DEFAULT_EXPIRES); + TEST_CHECK(ctx.expires_in == 3240); + + destroy_parse_ctx(&ctx); +} - flb_sds_destroy(ctx.access_token); - flb_sds_destroy(ctx.token_type); +void test_parse_rejects_missing_required_fields(void) +{ + int index; + int ret; + struct flb_oauth2 ctx; + const char *payloads[] = { + "{\"token_type\":\"Bearer\",\"expires_in\":3600}", + "{\"access_token\":\"new-token\",\"expires_in\":3600}", + "{\"access_token\":\"new-token\",\"token_type\":\"Bearer\"}", + "{\"error\":\"invalid_request\"}" + }; + + for (index = 0; index < 4; index++) { + memset(&ctx, 0, sizeof(ctx)); + populate_parse_ctx(&ctx, "old-token", "OldBearer", 1200); + ctx.refresh_skew = FLB_OAUTH2_DEFAULT_SKEW_SECS; + + ret = flb_oauth2_parse_json_response(payloads[index], + strlen(payloads[index]), + &ctx); + + TEST_CHECK(ret == -1); + TEST_CHECK(strcmp(ctx.access_token, "old-token") == 0); + TEST_CHECK(strcmp(ctx.token_type, "OldBearer") == 0); + TEST_CHECK(ctx.expires_in == 1200); + + destroy_parse_ctx(&ctx); + } +} + +void test_parse_rejects_invalid_expires_in(void) +{ + int index; + int ret; + struct flb_oauth2 ctx; + const char *payloads[] = { + "{\"access_token\":\"t\",\"token_type\":\"Bearer\",\"expires_in\":\"\"}", + "{\"access_token\":\"t\",\"token_type\":\"Bearer\",\"expires_in\":-1}", + "{\"access_token\":\"t\",\"token_type\":\"Bearer\",\"expires_in\":\"3600x\"}", + "{\"access_token\":\"t\",\"token_type\":\"Bearer\",\"expires_in\":0}", + "{\"access_token\":\"t\",\"token_type\":\"Bearer\",\"expires_in\":50}", + "{\"access_token\":\"t\",\"token_type\":\"Bearer\",\"expires_in\":66}" + }; + + for (index = 0; index < 6; index++) { + memset(&ctx, 0, sizeof(ctx)); + populate_parse_ctx(&ctx, "old-token", "OldBearer", 1200); + ctx.refresh_skew = FLB_OAUTH2_DEFAULT_SKEW_SECS; + + ret = flb_oauth2_parse_json_response(payloads[index], + strlen(payloads[index]), + &ctx); + + TEST_CHECK(ret == -1); + TEST_CHECK(strcmp(ctx.access_token, "old-token") == 0); + TEST_CHECK(strcmp(ctx.token_type, "OldBearer") == 0); + TEST_CHECK(ctx.expires_in == 1200); + + destroy_parse_ctx(&ctx); + } } void test_caching_and_refresh(void) @@ -792,10 +911,10 @@ void test_caching_and_refresh(void) config = flb_config_init(); TEST_CHECK(config != NULL); - ret = oauth2_mock_server_start(&server, 2, 0); + ret = oauth2_mock_server_start(&server, 65, 0); TEST_CHECK(ret == 0); - ctx = create_oauth_ctx(config, &server, 1); + ctx = create_oauth_ctx(config, &server, 58); TEST_CHECK(ctx != NULL); #ifdef FLB_SYSTEM_MACOS @@ -846,7 +965,7 @@ void test_private_key_jwt_body(void) cert_path, sizeof(cert_path)); TEST_CHECK(ret == 0); - ret = oauth2_mock_server_start(&server, 30, 0); + ret = oauth2_mock_server_start(&server, 3600, 0); TEST_CHECK(ret == 0); ctx = create_private_key_jwt_ctx(config, &server, key_path, cert_path, "kid"); @@ -901,7 +1020,7 @@ void test_private_key_jwt_x5t_header(void) cert_path, sizeof(cert_path)); TEST_CHECK(ret == 0); - ret = oauth2_mock_server_start(&server, 30, 0); + ret = oauth2_mock_server_start(&server, 3600, 0); TEST_CHECK(ret == 0); ctx = create_private_key_jwt_ctx(config, &server, key_path, cert_path, "x5t"); @@ -951,7 +1070,13 @@ void test_private_key_jwt_x5t_header(void) } TEST_LIST = { - {"parse_defaults", test_parse_defaults}, + {"parse_refreshes_token_transactionally", + test_parse_refreshes_token_transactionally}, + {"parse_accepts_quoted_expires_in", test_parse_accepts_quoted_expires_in}, + {"parse_duplicate_keys_last_wins", test_parse_duplicate_keys_last_wins}, + {"parse_rejects_missing_required_fields", + test_parse_rejects_missing_required_fields}, + {"parse_rejects_invalid_expires_in", test_parse_rejects_invalid_expires_in}, {"caching_and_refresh", test_caching_and_refresh}, {"private_key_jwt_body", test_private_key_jwt_body}, {"private_key_jwt_x5t_header", test_private_key_jwt_x5t_header},