Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 60 additions & 25 deletions src/flb_oauth2.c
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include <fluent-bit/flb_crypto.h>

#include <time.h>
#include <errno.h>
#include <string.h>
#include <stddef.h>
#include <stdio.h>
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand All @@ -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;
}
Expand Down
151 changes: 138 additions & 13 deletions tests/internal/oauth2.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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},
Expand Down
Loading