Starting the work to fix #3803.

- We still need a way to bubble up this error.
pull/6798/head
Julien Boeuf 9 years ago
parent a71b0afdc2
commit 2e3c9ad6dd
  1. 3
      src/core/lib/security/client_auth_filter.c
  2. 35
      src/core/lib/security/credentials.c
  3. 4
      src/core/lib/security/credentials.h
  4. 49
      test/core/security/credentials_test.c
  5. 3
      test/core/security/oauth2_utils.c
  6. 3
      test/core/security/print_google_default_creds_token.c

@ -98,7 +98,8 @@ static void bubble_up_error(grpc_exec_ctx *exec_ctx, grpc_call_element *elem,
static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *user_data, static void on_credentials_metadata(grpc_exec_ctx *exec_ctx, void *user_data,
grpc_credentials_md *md_elems, grpc_credentials_md *md_elems,
size_t num_md, size_t num_md,
grpc_credentials_status status) { grpc_credentials_status status,
const char *error_details) {
grpc_call_element *elem = (grpc_call_element *)user_data; grpc_call_element *elem = (grpc_call_element *)user_data;
call_data *calld = elem->call_data; call_data *calld = elem->call_data;
grpc_transport_stream_op *op = &calld->op; grpc_transport_stream_op *op = &calld->op;

@ -122,7 +122,7 @@ void grpc_call_credentials_get_request_metadata(
grpc_credentials_metadata_cb cb, void *user_data) { grpc_credentials_metadata_cb cb, void *user_data) {
if (creds == NULL || creds->vtable->get_request_metadata == NULL) { if (creds == NULL || creds->vtable->get_request_metadata == NULL) {
if (cb != NULL) { if (cb != NULL) {
cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK); cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK, NULL);
} }
return; return;
} }
@ -497,10 +497,10 @@ static void jwt_get_request_metadata(grpc_exec_ctx *exec_ctx,
if (jwt_md != NULL) { if (jwt_md != NULL) {
cb(exec_ctx, user_data, jwt_md->entries, jwt_md->num_entries, cb(exec_ctx, user_data, jwt_md->entries, jwt_md->num_entries,
GRPC_CREDENTIALS_OK); GRPC_CREDENTIALS_OK, NULL);
grpc_credentials_md_store_unref(jwt_md); grpc_credentials_md_store_unref(jwt_md);
} else { } else {
cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_ERROR); cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_ERROR, "");
} }
} }
@ -660,10 +660,10 @@ static void on_oauth2_token_fetcher_http_response(
c->token_expiration = c->token_expiration =
gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), token_lifetime); gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), token_lifetime);
r->cb(exec_ctx, r->user_data, c->access_token_md->entries, r->cb(exec_ctx, r->user_data, c->access_token_md->entries,
c->access_token_md->num_entries, status); c->access_token_md->num_entries, status, NULL);
} else { } else {
c->token_expiration = gpr_inf_past(GPR_CLOCK_REALTIME); c->token_expiration = gpr_inf_past(GPR_CLOCK_REALTIME);
r->cb(exec_ctx, r->user_data, NULL, 0, status); r->cb(exec_ctx, r->user_data, NULL, 0, status, "");
} }
gpr_mu_unlock(&c->mu); gpr_mu_unlock(&c->mu);
grpc_credentials_metadata_request_destroy(r); grpc_credentials_metadata_request_destroy(r);
@ -691,7 +691,7 @@ static void oauth2_token_fetcher_get_request_metadata(
} }
if (cached_access_token_md != NULL) { if (cached_access_token_md != NULL) {
cb(exec_ctx, user_data, cached_access_token_md->entries, cb(exec_ctx, user_data, cached_access_token_md->entries,
cached_access_token_md->num_entries, GRPC_CREDENTIALS_OK); cached_access_token_md->num_entries, GRPC_CREDENTIALS_OK, NULL);
grpc_credentials_md_store_unref(cached_access_token_md); grpc_credentials_md_store_unref(cached_access_token_md);
} else { } else {
c->fetch_func( c->fetch_func(
@ -821,7 +821,7 @@ static void on_simulated_token_fetch_done(grpc_exec_ctx *exec_ctx,
(grpc_credentials_metadata_request *)user_data; (grpc_credentials_metadata_request *)user_data;
grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)r->creds; grpc_md_only_test_credentials *c = (grpc_md_only_test_credentials *)r->creds;
r->cb(exec_ctx, r->user_data, c->md_store->entries, c->md_store->num_entries, r->cb(exec_ctx, r->user_data, c->md_store->entries, c->md_store->num_entries,
GRPC_CREDENTIALS_OK); GRPC_CREDENTIALS_OK, NULL);
grpc_credentials_metadata_request_destroy(r); grpc_credentials_metadata_request_destroy(r);
} }
@ -837,7 +837,7 @@ static void md_only_test_get_request_metadata(
grpc_executor_enqueue( grpc_executor_enqueue(
grpc_closure_create(on_simulated_token_fetch_done, cb_arg), true); grpc_closure_create(on_simulated_token_fetch_done, cb_arg), true);
} else { } else {
cb(exec_ctx, user_data, c->md_store->entries, 1, GRPC_CREDENTIALS_OK); cb(exec_ctx, user_data, c->md_store->entries, 1, GRPC_CREDENTIALS_OK, NULL);
} }
} }
@ -870,7 +870,8 @@ static void access_token_get_request_metadata(
grpc_pollset *pollset, grpc_auth_metadata_context context, grpc_pollset *pollset, grpc_auth_metadata_context context,
grpc_credentials_metadata_cb cb, void *user_data) { grpc_credentials_metadata_cb cb, void *user_data) {
grpc_access_token_credentials *c = (grpc_access_token_credentials *)creds; grpc_access_token_credentials *c = (grpc_access_token_credentials *)creds;
cb(exec_ctx, user_data, c->access_token_md->entries, 1, GRPC_CREDENTIALS_OK); cb(exec_ctx, user_data, c->access_token_md->entries, 1, GRPC_CREDENTIALS_OK,
NULL);
} }
static grpc_call_credentials_vtable access_token_vtable = { static grpc_call_credentials_vtable access_token_vtable = {
@ -973,11 +974,12 @@ static void composite_call_md_context_destroy(
static void composite_call_metadata_cb(grpc_exec_ctx *exec_ctx, void *user_data, static void composite_call_metadata_cb(grpc_exec_ctx *exec_ctx, void *user_data,
grpc_credentials_md *md_elems, grpc_credentials_md *md_elems,
size_t num_md, size_t num_md,
grpc_credentials_status status) { grpc_credentials_status status,
const char *error_details) {
grpc_composite_call_credentials_metadata_context *ctx = grpc_composite_call_credentials_metadata_context *ctx =
(grpc_composite_call_credentials_metadata_context *)user_data; (grpc_composite_call_credentials_metadata_context *)user_data;
if (status != GRPC_CREDENTIALS_OK) { if (status != GRPC_CREDENTIALS_OK) {
ctx->cb(exec_ctx, ctx->user_data, NULL, 0, status); ctx->cb(exec_ctx, ctx->user_data, NULL, 0, status, NULL);
return; return;
} }
@ -1002,7 +1004,7 @@ static void composite_call_metadata_cb(grpc_exec_ctx *exec_ctx, void *user_data,
/* We're done!. */ /* We're done!. */
ctx->cb(exec_ctx, ctx->user_data, ctx->md_elems->entries, ctx->cb(exec_ctx, ctx->user_data, ctx->md_elems->entries,
ctx->md_elems->num_entries, GRPC_CREDENTIALS_OK); ctx->md_elems->num_entries, GRPC_CREDENTIALS_OK, NULL);
composite_call_md_context_destroy(ctx); composite_call_md_context_destroy(ctx);
} }
@ -1122,7 +1124,7 @@ static void iam_get_request_metadata(grpc_exec_ctx *exec_ctx,
void *user_data) { void *user_data) {
grpc_google_iam_credentials *c = (grpc_google_iam_credentials *)creds; grpc_google_iam_credentials *c = (grpc_google_iam_credentials *)creds;
cb(exec_ctx, user_data, c->iam_md->entries, c->iam_md->num_entries, cb(exec_ctx, user_data, c->iam_md->entries, c->iam_md->num_entries,
GRPC_CREDENTIALS_OK); GRPC_CREDENTIALS_OK, NULL);
} }
static grpc_call_credentials_vtable iam_vtable = {iam_destruct, static grpc_call_credentials_vtable iam_vtable = {iam_destruct,
@ -1178,7 +1180,8 @@ static void plugin_md_request_metadata_ready(void *request,
gpr_log(GPR_ERROR, "Getting metadata from plugin failed with error: %s", gpr_log(GPR_ERROR, "Getting metadata from plugin failed with error: %s",
error_details); error_details);
} }
r->cb(&exec_ctx, r->user_data, NULL, 0, GRPC_CREDENTIALS_ERROR); r->cb(&exec_ctx, r->user_data, NULL, 0, GRPC_CREDENTIALS_ERROR,
error_details);
} else { } else {
size_t i; size_t i;
grpc_credentials_md *md_array = NULL; grpc_credentials_md *md_array = NULL;
@ -1190,7 +1193,7 @@ static void plugin_md_request_metadata_ready(void *request,
gpr_slice_from_copied_buffer(md[i].value, md[i].value_length); gpr_slice_from_copied_buffer(md[i].value, md[i].value_length);
} }
} }
r->cb(&exec_ctx, r->user_data, md_array, num_md, GRPC_CREDENTIALS_OK); r->cb(&exec_ctx, r->user_data, md_array, num_md, GRPC_CREDENTIALS_OK, NULL);
if (md_array != NULL) { if (md_array != NULL) {
for (i = 0; i < num_md; i++) { for (i = 0; i < num_md; i++) {
gpr_slice_unref(md_array[i].key); gpr_slice_unref(md_array[i].key);
@ -1218,7 +1221,7 @@ static void plugin_get_request_metadata(grpc_exec_ctx *exec_ctx,
c->plugin.get_metadata(c->plugin.state, context, c->plugin.get_metadata(c->plugin.state, context,
plugin_md_request_metadata_ready, request); plugin_md_request_metadata_ready, request);
} else { } else {
cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK); cb(exec_ctx, user_data, NULL, 0, GRPC_CREDENTIALS_OK, NULL);
} }
} }

@ -160,11 +160,13 @@ void grpc_credentials_md_store_unref(grpc_credentials_md_store *store);
/* --- grpc_call_credentials. --- */ /* --- grpc_call_credentials. --- */
/* error_details must be NULL if status is GRPC_CREDENTIALS_OK. */
typedef void (*grpc_credentials_metadata_cb)(grpc_exec_ctx *exec_ctx, typedef void (*grpc_credentials_metadata_cb)(grpc_exec_ctx *exec_ctx,
void *user_data, void *user_data,
grpc_credentials_md *md_elems, grpc_credentials_md *md_elems,
size_t num_md, size_t num_md,
grpc_credentials_status status); grpc_credentials_status status,
const char *error_details);
typedef struct { typedef struct {
void (*destruct)(grpc_call_credentials *c); void (*destruct)(grpc_call_credentials *c);

@ -338,13 +338,15 @@ static void check_metadata(expected_md *expected, grpc_credentials_md *md_elems,
static void check_google_iam_metadata(grpc_exec_ctx *exec_ctx, void *user_data, static void check_google_iam_metadata(grpc_exec_ctx *exec_ctx, void *user_data,
grpc_credentials_md *md_elems, grpc_credentials_md *md_elems,
size_t num_md, size_t num_md,
grpc_credentials_status status) { grpc_credentials_status status,
const char *error_details) {
grpc_call_credentials *c = (grpc_call_credentials *)user_data; grpc_call_credentials *c = (grpc_call_credentials *)user_data;
expected_md emd[] = {{GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY, expected_md emd[] = {{GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY,
test_google_iam_authorization_token}, test_google_iam_authorization_token},
{GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY, {GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY,
test_google_iam_authority_selector}}; test_google_iam_authority_selector}};
GPR_ASSERT(status == GRPC_CREDENTIALS_OK); GPR_ASSERT(status == GRPC_CREDENTIALS_OK);
GPR_ASSERT(error_details == NULL);
GPR_ASSERT(num_md == 2); GPR_ASSERT(num_md == 2);
check_metadata(emd, md_elems, num_md); check_metadata(emd, md_elems, num_md);
grpc_call_credentials_unref(c); grpc_call_credentials_unref(c);
@ -362,14 +364,13 @@ static void test_google_iam_creds(void) {
grpc_exec_ctx_finish(&exec_ctx); grpc_exec_ctx_finish(&exec_ctx);
} }
static void check_access_token_metadata(grpc_exec_ctx *exec_ctx, static void check_access_token_metadata(
void *user_data, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
grpc_credentials_md *md_elems, size_t num_md, grpc_credentials_status status, const char *error_details) {
size_t num_md,
grpc_credentials_status status) {
grpc_call_credentials *c = (grpc_call_credentials *)user_data; grpc_call_credentials *c = (grpc_call_credentials *)user_data;
expected_md emd[] = {{GRPC_AUTHORIZATION_METADATA_KEY, "Bearer blah"}}; expected_md emd[] = {{GRPC_AUTHORIZATION_METADATA_KEY, "Bearer blah"}};
GPR_ASSERT(status == GRPC_CREDENTIALS_OK); GPR_ASSERT(status == GRPC_CREDENTIALS_OK);
GPR_ASSERT(error_details == NULL);
GPR_ASSERT(num_md == 1); GPR_ASSERT(num_md == 1);
check_metadata(emd, md_elems, num_md); check_metadata(emd, md_elems, num_md);
grpc_call_credentials_unref(c); grpc_call_credentials_unref(c);
@ -418,7 +419,7 @@ static void test_channel_oauth2_composite_creds(void) {
static void check_oauth2_google_iam_composite_metadata( static void check_oauth2_google_iam_composite_metadata(
grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
size_t num_md, grpc_credentials_status status) { size_t num_md, grpc_credentials_status status, const char *error_details) {
grpc_call_credentials *c = (grpc_call_credentials *)user_data; grpc_call_credentials *c = (grpc_call_credentials *)user_data;
expected_md emd[] = { expected_md emd[] = {
{GRPC_AUTHORIZATION_METADATA_KEY, test_oauth2_bearer_token}, {GRPC_AUTHORIZATION_METADATA_KEY, test_oauth2_bearer_token},
@ -427,6 +428,7 @@ static void check_oauth2_google_iam_composite_metadata(
{GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY, {GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY,
test_google_iam_authority_selector}}; test_google_iam_authority_selector}};
GPR_ASSERT(status == GRPC_CREDENTIALS_OK); GPR_ASSERT(status == GRPC_CREDENTIALS_OK);
GPR_ASSERT(error_details == NULL);
GPR_ASSERT(num_md == 3); GPR_ASSERT(num_md == 3);
check_metadata(emd, md_elems, num_md); check_metadata(emd, md_elems, num_md);
grpc_call_credentials_unref(c); grpc_call_credentials_unref(c);
@ -511,8 +513,9 @@ static void test_channel_oauth2_google_iam_composite_creds(void) {
static void on_oauth2_creds_get_metadata_success( static void on_oauth2_creds_get_metadata_success(
grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
size_t num_md, grpc_credentials_status status) { size_t num_md, grpc_credentials_status status, const char *error_details) {
GPR_ASSERT(status == GRPC_CREDENTIALS_OK); GPR_ASSERT(status == GRPC_CREDENTIALS_OK);
GPR_ASSERT(error_details == NULL);
GPR_ASSERT(num_md == 1); GPR_ASSERT(num_md == 1);
GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].key, "authorization") == 0); GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].key, "authorization") == 0);
GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].value, GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].value,
@ -524,7 +527,7 @@ static void on_oauth2_creds_get_metadata_success(
static void on_oauth2_creds_get_metadata_failure( static void on_oauth2_creds_get_metadata_failure(
grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
size_t num_md, grpc_credentials_status status) { size_t num_md, grpc_credentials_status status, const char *error_details) {
GPR_ASSERT(status == GRPC_CREDENTIALS_ERROR); GPR_ASSERT(status == GRPC_CREDENTIALS_ERROR);
GPR_ASSERT(num_md == 0); GPR_ASSERT(num_md == 0);
GPR_ASSERT(user_data != NULL); GPR_ASSERT(user_data != NULL);
@ -760,14 +763,13 @@ static char *encode_and_sign_jwt_should_not_be_called(
return NULL; return NULL;
} }
static void on_jwt_creds_get_metadata_success(grpc_exec_ctx *exec_ctx, static void on_jwt_creds_get_metadata_success(
void *user_data, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
grpc_credentials_md *md_elems, size_t num_md, grpc_credentials_status status, const char *error_details) {
size_t num_md,
grpc_credentials_status status) {
char *expected_md_value; char *expected_md_value;
gpr_asprintf(&expected_md_value, "Bearer %s", test_signed_jwt); gpr_asprintf(&expected_md_value, "Bearer %s", test_signed_jwt);
GPR_ASSERT(status == GRPC_CREDENTIALS_OK); GPR_ASSERT(status == GRPC_CREDENTIALS_OK);
GPR_ASSERT(error_details == NULL);
GPR_ASSERT(num_md == 1); GPR_ASSERT(num_md == 1);
GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].key, "authorization") == 0); GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].key, "authorization") == 0);
GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].value, expected_md_value) == 0); GPR_ASSERT(gpr_slice_str_cmp(md_elems[0].value, expected_md_value) == 0);
@ -776,11 +778,9 @@ static void on_jwt_creds_get_metadata_success(grpc_exec_ctx *exec_ctx,
gpr_free(expected_md_value); gpr_free(expected_md_value);
} }
static void on_jwt_creds_get_metadata_failure(grpc_exec_ctx *exec_ctx, static void on_jwt_creds_get_metadata_failure(
void *user_data, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
grpc_credentials_md *md_elems, size_t num_md, grpc_credentials_status status, const char *error_details) {
size_t num_md,
grpc_credentials_status status) {
GPR_ASSERT(status == GRPC_CREDENTIALS_ERROR); GPR_ASSERT(status == GRPC_CREDENTIALS_ERROR);
GPR_ASSERT(num_md == 0); GPR_ASSERT(num_md == 0);
GPR_ASSERT(user_data != NULL); GPR_ASSERT(user_data != NULL);
@ -1024,6 +1024,8 @@ static void plugin_get_metadata_success(void *state,
cb(user_data, md, GPR_ARRAY_SIZE(md), GRPC_STATUS_OK, NULL); cb(user_data, md, GPR_ARRAY_SIZE(md), GRPC_STATUS_OK, NULL);
} }
static const char *plugin_error_details = "Could not get metadata for plugin.";
static void plugin_get_metadata_failure(void *state, static void plugin_get_metadata_failure(void *state,
grpc_auth_metadata_context context, grpc_auth_metadata_context context,
grpc_credentials_plugin_metadata_cb cb, grpc_credentials_plugin_metadata_cb cb,
@ -1034,13 +1036,12 @@ static void plugin_get_metadata_failure(void *state,
GPR_ASSERT(context.channel_auth_context == NULL); GPR_ASSERT(context.channel_auth_context == NULL);
GPR_ASSERT(context.reserved == NULL); GPR_ASSERT(context.reserved == NULL);
*s = PLUGIN_GET_METADATA_CALLED_STATE; *s = PLUGIN_GET_METADATA_CALLED_STATE;
cb(user_data, NULL, 0, GRPC_STATUS_UNAUTHENTICATED, cb(user_data, NULL, 0, GRPC_STATUS_UNAUTHENTICATED, plugin_error_details);
"Could not get metadata for plugin.");
} }
static void on_plugin_metadata_received_success( static void on_plugin_metadata_received_success(
grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
size_t num_md, grpc_credentials_status status) { size_t num_md, grpc_credentials_status status, const char *error_details) {
size_t i = 0; size_t i = 0;
GPR_ASSERT(user_data == NULL); GPR_ASSERT(user_data == NULL);
GPR_ASSERT(md_elems != NULL); GPR_ASSERT(md_elems != NULL);
@ -1053,11 +1054,13 @@ static void on_plugin_metadata_received_success(
static void on_plugin_metadata_received_failure( static void on_plugin_metadata_received_failure(
grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems, grpc_exec_ctx *exec_ctx, void *user_data, grpc_credentials_md *md_elems,
size_t num_md, grpc_credentials_status status) { size_t num_md, grpc_credentials_status status, const char *error_details) {
GPR_ASSERT(user_data == NULL); GPR_ASSERT(user_data == NULL);
GPR_ASSERT(md_elems == NULL); GPR_ASSERT(md_elems == NULL);
GPR_ASSERT(num_md == 0); GPR_ASSERT(num_md == 0);
GPR_ASSERT(status == GRPC_CREDENTIALS_ERROR); GPR_ASSERT(status == GRPC_CREDENTIALS_ERROR);
GPR_ASSERT(error_details != NULL);
GPR_ASSERT(strcmp(error_details, plugin_error_details) == 0);
} }
static void plugin_destroy(void *state) { static void plugin_destroy(void *state) {

@ -53,7 +53,8 @@ typedef struct {
static void on_oauth2_response(grpc_exec_ctx *exec_ctx, void *user_data, static void on_oauth2_response(grpc_exec_ctx *exec_ctx, void *user_data,
grpc_credentials_md *md_elems, size_t num_md, grpc_credentials_md *md_elems, size_t num_md,
grpc_credentials_status status) { grpc_credentials_status status,
const char *error_details) {
oauth2_request *request = user_data; oauth2_request *request = user_data;
char *token = NULL; char *token = NULL;
gpr_slice token_slice; gpr_slice token_slice;

@ -53,7 +53,8 @@ typedef struct {
static void on_metadata_response(grpc_exec_ctx *exec_ctx, void *user_data, static void on_metadata_response(grpc_exec_ctx *exec_ctx, void *user_data,
grpc_credentials_md *md_elems, size_t num_md, grpc_credentials_md *md_elems, size_t num_md,
grpc_credentials_status status) { grpc_credentials_status status,
const char *error_details) {
synchronizer *sync = user_data; synchronizer *sync = user_data;
if (status == GRPC_CREDENTIALS_ERROR) { if (status == GRPC_CREDENTIALS_ERROR) {
fprintf(stderr, "Fetching token failed.\n"); fprintf(stderr, "Fetching token failed.\n");

Loading…
Cancel
Save