1 /* 2 * 3 * Copyright 2015 gRPC authors. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 * 17 */ 18 19 #include <grpc/support/port_platform.h> 20 21 #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h" 22 23 #include <string.h> 24 25 #include "src/core/lib/security/util/json_util.h" 26 #include "src/core/lib/surface/api_trace.h" 27 28 #include <grpc/support/alloc.h> 29 #include <grpc/support/log.h> 30 #include <grpc/support/string_util.h> 31 32 // 33 // Auth Refresh Token. 34 // 35 36 int grpc_auth_refresh_token_is_valid( 37 const grpc_auth_refresh_token* refresh_token) { 38 return (refresh_token != nullptr) && 39 strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID); 40 } 41 42 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json( 43 const grpc_json* json) { 44 grpc_auth_refresh_token result; 45 const char* prop_value; 46 int success = 0; 47 48 memset(&result, 0, sizeof(grpc_auth_refresh_token)); 49 result.type = GRPC_AUTH_JSON_TYPE_INVALID; 50 if (json == nullptr) { 51 gpr_log(GPR_ERROR, "Invalid json."); 52 goto end; 53 } 54 55 prop_value = grpc_json_get_string_property(json, "type"); 56 if (prop_value == nullptr || 57 strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) { 58 goto end; 59 } 60 result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER; 61 62 if (!grpc_copy_json_string_property(json, "client_secret", 63 &result.client_secret) || 64 !grpc_copy_json_string_property(json, "client_id", &result.client_id) || 65 !grpc_copy_json_string_property(json, "refresh_token", 66 &result.refresh_token)) { 67 goto end; 68 } 69 success = 1; 70 71 end: 72 if (!success) grpc_auth_refresh_token_destruct(&result); 73 return result; 74 } 75 76 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string( 77 const char* json_string) { 78 char* scratchpad = gpr_strdup(json_string); 79 grpc_json* json = grpc_json_parse_string(scratchpad); 80 grpc_auth_refresh_token result = 81 grpc_auth_refresh_token_create_from_json(json); 82 if (json != nullptr) grpc_json_destroy(json); 83 gpr_free(scratchpad); 84 return result; 85 } 86 87 void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token) { 88 if (refresh_token == nullptr) return; 89 refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID; 90 if (refresh_token->client_id != nullptr) { 91 gpr_free(refresh_token->client_id); 92 refresh_token->client_id = nullptr; 93 } 94 if (refresh_token->client_secret != nullptr) { 95 gpr_free(refresh_token->client_secret); 96 refresh_token->client_secret = nullptr; 97 } 98 if (refresh_token->refresh_token != nullptr) { 99 gpr_free(refresh_token->refresh_token); 100 refresh_token->refresh_token = nullptr; 101 } 102 } 103 104 // 105 // Oauth2 Token Fetcher credentials. 106 // 107 108 static void oauth2_token_fetcher_destruct(grpc_call_credentials* creds) { 109 grpc_oauth2_token_fetcher_credentials* c = 110 reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(creds); 111 GRPC_MDELEM_UNREF(c->access_token_md); 112 gpr_mu_destroy(&c->mu); 113 grpc_pollset_set_destroy(grpc_polling_entity_pollset_set(&c->pollent)); 114 grpc_httpcli_context_destroy(&c->httpcli_context); 115 } 116 117 grpc_credentials_status 118 grpc_oauth2_token_fetcher_credentials_parse_server_response( 119 const grpc_http_response* response, grpc_mdelem* token_md, 120 grpc_millis* token_lifetime) { 121 char* null_terminated_body = nullptr; 122 char* new_access_token = nullptr; 123 grpc_credentials_status status = GRPC_CREDENTIALS_OK; 124 grpc_json* json = nullptr; 125 126 if (response == nullptr) { 127 gpr_log(GPR_ERROR, "Received NULL response."); 128 status = GRPC_CREDENTIALS_ERROR; 129 goto end; 130 } 131 132 if (response->body_length > 0) { 133 null_terminated_body = 134 static_cast<char*>(gpr_malloc(response->body_length + 1)); 135 null_terminated_body[response->body_length] = '\0'; 136 memcpy(null_terminated_body, response->body, response->body_length); 137 } 138 139 if (response->status != 200) { 140 gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].", 141 response->status, 142 null_terminated_body != nullptr ? null_terminated_body : ""); 143 status = GRPC_CREDENTIALS_ERROR; 144 goto end; 145 } else { 146 grpc_json* access_token = nullptr; 147 grpc_json* token_type = nullptr; 148 grpc_json* expires_in = nullptr; 149 grpc_json* ptr; 150 json = grpc_json_parse_string(null_terminated_body); 151 if (json == nullptr) { 152 gpr_log(GPR_ERROR, "Could not parse JSON from %s", null_terminated_body); 153 status = GRPC_CREDENTIALS_ERROR; 154 goto end; 155 } 156 if (json->type != GRPC_JSON_OBJECT) { 157 gpr_log(GPR_ERROR, "Response should be a JSON object"); 158 status = GRPC_CREDENTIALS_ERROR; 159 goto end; 160 } 161 for (ptr = json->child; ptr; ptr = ptr->next) { 162 if (strcmp(ptr->key, "access_token") == 0) { 163 access_token = ptr; 164 } else if (strcmp(ptr->key, "token_type") == 0) { 165 token_type = ptr; 166 } else if (strcmp(ptr->key, "expires_in") == 0) { 167 expires_in = ptr; 168 } 169 } 170 if (access_token == nullptr || access_token->type != GRPC_JSON_STRING) { 171 gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON."); 172 status = GRPC_CREDENTIALS_ERROR; 173 goto end; 174 } 175 if (token_type == nullptr || token_type->type != GRPC_JSON_STRING) { 176 gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON."); 177 status = GRPC_CREDENTIALS_ERROR; 178 goto end; 179 } 180 if (expires_in == nullptr || expires_in->type != GRPC_JSON_NUMBER) { 181 gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON."); 182 status = GRPC_CREDENTIALS_ERROR; 183 goto end; 184 } 185 gpr_asprintf(&new_access_token, "%s %s", token_type->value, 186 access_token->value); 187 *token_lifetime = strtol(expires_in->value, nullptr, 10) * GPR_MS_PER_SEC; 188 if (!GRPC_MDISNULL(*token_md)) GRPC_MDELEM_UNREF(*token_md); 189 *token_md = grpc_mdelem_from_slices( 190 grpc_slice_from_static_string(GRPC_AUTHORIZATION_METADATA_KEY), 191 grpc_slice_from_copied_string(new_access_token)); 192 status = GRPC_CREDENTIALS_OK; 193 } 194 195 end: 196 if (status != GRPC_CREDENTIALS_OK && !GRPC_MDISNULL(*token_md)) { 197 GRPC_MDELEM_UNREF(*token_md); 198 *token_md = GRPC_MDNULL; 199 } 200 if (null_terminated_body != nullptr) gpr_free(null_terminated_body); 201 if (new_access_token != nullptr) gpr_free(new_access_token); 202 if (json != nullptr) grpc_json_destroy(json); 203 return status; 204 } 205 206 static void on_oauth2_token_fetcher_http_response(void* user_data, 207 grpc_error* error) { 208 GRPC_LOG_IF_ERROR("oauth_fetch", GRPC_ERROR_REF(error)); 209 grpc_credentials_metadata_request* r = 210 static_cast<grpc_credentials_metadata_request*>(user_data); 211 grpc_oauth2_token_fetcher_credentials* c = 212 reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(r->creds); 213 grpc_mdelem access_token_md = GRPC_MDNULL; 214 grpc_millis token_lifetime; 215 grpc_credentials_status status = 216 grpc_oauth2_token_fetcher_credentials_parse_server_response( 217 &r->response, &access_token_md, &token_lifetime); 218 // Update cache and grab list of pending requests. 219 gpr_mu_lock(&c->mu); 220 c->token_fetch_pending = false; 221 c->access_token_md = GRPC_MDELEM_REF(access_token_md); 222 c->token_expiration = 223 status == GRPC_CREDENTIALS_OK 224 ? gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC), 225 gpr_time_from_millis(token_lifetime, GPR_TIMESPAN)) 226 : gpr_inf_past(GPR_CLOCK_MONOTONIC); 227 grpc_oauth2_pending_get_request_metadata* pending_request = 228 c->pending_requests; 229 c->pending_requests = nullptr; 230 gpr_mu_unlock(&c->mu); 231 // Invoke callbacks for all pending requests. 232 while (pending_request != nullptr) { 233 if (status == GRPC_CREDENTIALS_OK) { 234 grpc_credentials_mdelem_array_add(pending_request->md_array, 235 access_token_md); 236 } else { 237 error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING( 238 "Error occurred when fetching oauth2 token.", &error, 1); 239 } 240 GRPC_CLOSURE_SCHED(pending_request->on_request_metadata, error); 241 grpc_polling_entity_del_from_pollset_set( 242 pending_request->pollent, grpc_polling_entity_pollset_set(&c->pollent)); 243 grpc_oauth2_pending_get_request_metadata* prev = pending_request; 244 pending_request = pending_request->next; 245 gpr_free(prev); 246 } 247 GRPC_MDELEM_UNREF(access_token_md); 248 grpc_call_credentials_unref(r->creds); 249 grpc_credentials_metadata_request_destroy(r); 250 } 251 252 static bool oauth2_token_fetcher_get_request_metadata( 253 grpc_call_credentials* creds, grpc_polling_entity* pollent, 254 grpc_auth_metadata_context context, grpc_credentials_mdelem_array* md_array, 255 grpc_closure* on_request_metadata, grpc_error** error) { 256 grpc_oauth2_token_fetcher_credentials* c = 257 reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(creds); 258 // Check if we can use the cached token. 259 grpc_millis refresh_threshold = 260 GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS * GPR_MS_PER_SEC; 261 grpc_mdelem cached_access_token_md = GRPC_MDNULL; 262 gpr_mu_lock(&c->mu); 263 if (!GRPC_MDISNULL(c->access_token_md) && 264 gpr_time_cmp( 265 gpr_time_sub(c->token_expiration, gpr_now(GPR_CLOCK_MONOTONIC)), 266 gpr_time_from_seconds(GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS, 267 GPR_TIMESPAN)) > 0) { 268 cached_access_token_md = GRPC_MDELEM_REF(c->access_token_md); 269 } 270 if (!GRPC_MDISNULL(cached_access_token_md)) { 271 gpr_mu_unlock(&c->mu); 272 grpc_credentials_mdelem_array_add(md_array, cached_access_token_md); 273 GRPC_MDELEM_UNREF(cached_access_token_md); 274 return true; 275 } 276 // Couldn't get the token from the cache. 277 // Add request to c->pending_requests and start a new fetch if needed. 278 grpc_oauth2_pending_get_request_metadata* pending_request = 279 static_cast<grpc_oauth2_pending_get_request_metadata*>( 280 gpr_malloc(sizeof(*pending_request))); 281 pending_request->md_array = md_array; 282 pending_request->on_request_metadata = on_request_metadata; 283 pending_request->pollent = pollent; 284 grpc_polling_entity_add_to_pollset_set( 285 pollent, grpc_polling_entity_pollset_set(&c->pollent)); 286 pending_request->next = c->pending_requests; 287 c->pending_requests = pending_request; 288 bool start_fetch = false; 289 if (!c->token_fetch_pending) { 290 c->token_fetch_pending = true; 291 start_fetch = true; 292 } 293 gpr_mu_unlock(&c->mu); 294 if (start_fetch) { 295 grpc_call_credentials_ref(creds); 296 c->fetch_func(grpc_credentials_metadata_request_create(creds), 297 &c->httpcli_context, &c->pollent, 298 on_oauth2_token_fetcher_http_response, 299 grpc_core::ExecCtx::Get()->Now() + refresh_threshold); 300 } 301 return false; 302 } 303 304 static void oauth2_token_fetcher_cancel_get_request_metadata( 305 grpc_call_credentials* creds, grpc_credentials_mdelem_array* md_array, 306 grpc_error* error) { 307 grpc_oauth2_token_fetcher_credentials* c = 308 reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(creds); 309 gpr_mu_lock(&c->mu); 310 grpc_oauth2_pending_get_request_metadata* prev = nullptr; 311 grpc_oauth2_pending_get_request_metadata* pending_request = 312 c->pending_requests; 313 while (pending_request != nullptr) { 314 if (pending_request->md_array == md_array) { 315 // Remove matching pending request from the list. 316 if (prev != nullptr) { 317 prev->next = pending_request->next; 318 } else { 319 c->pending_requests = pending_request->next; 320 } 321 // Invoke the callback immediately with an error. 322 GRPC_CLOSURE_SCHED(pending_request->on_request_metadata, 323 GRPC_ERROR_REF(error)); 324 gpr_free(pending_request); 325 break; 326 } 327 prev = pending_request; 328 pending_request = pending_request->next; 329 } 330 gpr_mu_unlock(&c->mu); 331 GRPC_ERROR_UNREF(error); 332 } 333 334 static void init_oauth2_token_fetcher(grpc_oauth2_token_fetcher_credentials* c, 335 grpc_fetch_oauth2_func fetch_func) { 336 memset(c, 0, sizeof(grpc_oauth2_token_fetcher_credentials)); 337 c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2; 338 gpr_ref_init(&c->base.refcount, 1); 339 gpr_mu_init(&c->mu); 340 c->token_expiration = gpr_inf_past(GPR_CLOCK_MONOTONIC); 341 c->fetch_func = fetch_func; 342 c->pollent = 343 grpc_polling_entity_create_from_pollset_set(grpc_pollset_set_create()); 344 grpc_httpcli_context_init(&c->httpcli_context); 345 } 346 347 // 348 // Google Compute Engine credentials. 349 // 350 351 static grpc_call_credentials_vtable compute_engine_vtable = { 352 oauth2_token_fetcher_destruct, oauth2_token_fetcher_get_request_metadata, 353 oauth2_token_fetcher_cancel_get_request_metadata}; 354 355 static void compute_engine_fetch_oauth2( 356 grpc_credentials_metadata_request* metadata_req, 357 grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent, 358 grpc_iomgr_cb_func response_cb, grpc_millis deadline) { 359 grpc_http_header header = {(char*)"Metadata-Flavor", (char*)"Google"}; 360 grpc_httpcli_request request; 361 memset(&request, 0, sizeof(grpc_httpcli_request)); 362 request.host = (char*)GRPC_COMPUTE_ENGINE_METADATA_HOST; 363 request.http.path = (char*)GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH; 364 request.http.hdr_count = 1; 365 request.http.hdrs = &header; 366 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host 367 channel. This would allow us to cancel an authentication query when under 368 extreme memory pressure. */ 369 grpc_resource_quota* resource_quota = 370 grpc_resource_quota_create("oauth2_credentials"); 371 grpc_httpcli_get( 372 httpcli_context, pollent, resource_quota, &request, deadline, 373 GRPC_CLOSURE_CREATE(response_cb, metadata_req, grpc_schedule_on_exec_ctx), 374 &metadata_req->response); 375 grpc_resource_quota_unref_internal(resource_quota); 376 } 377 378 grpc_call_credentials* grpc_google_compute_engine_credentials_create( 379 void* reserved) { 380 grpc_oauth2_token_fetcher_credentials* c = 381 static_cast<grpc_oauth2_token_fetcher_credentials*>( 382 gpr_malloc(sizeof(grpc_oauth2_token_fetcher_credentials))); 383 GRPC_API_TRACE("grpc_compute_engine_credentials_create(reserved=%p)", 1, 384 (reserved)); 385 GPR_ASSERT(reserved == nullptr); 386 init_oauth2_token_fetcher(c, compute_engine_fetch_oauth2); 387 c->base.vtable = &compute_engine_vtable; 388 return &c->base; 389 } 390 391 // 392 // Google Refresh Token credentials. 393 // 394 395 static void refresh_token_destruct(grpc_call_credentials* creds) { 396 grpc_google_refresh_token_credentials* c = 397 reinterpret_cast<grpc_google_refresh_token_credentials*>(creds); 398 grpc_auth_refresh_token_destruct(&c->refresh_token); 399 oauth2_token_fetcher_destruct(&c->base.base); 400 } 401 402 static grpc_call_credentials_vtable refresh_token_vtable = { 403 refresh_token_destruct, oauth2_token_fetcher_get_request_metadata, 404 oauth2_token_fetcher_cancel_get_request_metadata}; 405 406 static void refresh_token_fetch_oauth2( 407 grpc_credentials_metadata_request* metadata_req, 408 grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent, 409 grpc_iomgr_cb_func response_cb, grpc_millis deadline) { 410 grpc_google_refresh_token_credentials* c = 411 reinterpret_cast<grpc_google_refresh_token_credentials*>( 412 metadata_req->creds); 413 grpc_http_header header = {(char*)"Content-Type", 414 (char*)"application/x-www-form-urlencoded"}; 415 grpc_httpcli_request request; 416 char* body = nullptr; 417 gpr_asprintf(&body, GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING, 418 c->refresh_token.client_id, c->refresh_token.client_secret, 419 c->refresh_token.refresh_token); 420 memset(&request, 0, sizeof(grpc_httpcli_request)); 421 request.host = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_HOST; 422 request.http.path = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH; 423 request.http.hdr_count = 1; 424 request.http.hdrs = &header; 425 request.handshaker = &grpc_httpcli_ssl; 426 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host 427 channel. This would allow us to cancel an authentication query when under 428 extreme memory pressure. */ 429 grpc_resource_quota* resource_quota = 430 grpc_resource_quota_create("oauth2_credentials_refresh"); 431 grpc_httpcli_post( 432 httpcli_context, pollent, resource_quota, &request, body, strlen(body), 433 deadline, 434 GRPC_CLOSURE_CREATE(response_cb, metadata_req, grpc_schedule_on_exec_ctx), 435 &metadata_req->response); 436 grpc_resource_quota_unref_internal(resource_quota); 437 gpr_free(body); 438 } 439 440 grpc_call_credentials* 441 grpc_refresh_token_credentials_create_from_auth_refresh_token( 442 grpc_auth_refresh_token refresh_token) { 443 grpc_google_refresh_token_credentials* c; 444 if (!grpc_auth_refresh_token_is_valid(&refresh_token)) { 445 gpr_log(GPR_ERROR, "Invalid input for refresh token credentials creation"); 446 return nullptr; 447 } 448 c = static_cast<grpc_google_refresh_token_credentials*>( 449 gpr_zalloc(sizeof(grpc_google_refresh_token_credentials))); 450 init_oauth2_token_fetcher(&c->base, refresh_token_fetch_oauth2); 451 c->base.base.vtable = &refresh_token_vtable; 452 c->refresh_token = refresh_token; 453 return &c->base.base; 454 } 455 456 static char* create_loggable_refresh_token(grpc_auth_refresh_token* token) { 457 if (strcmp(token->type, GRPC_AUTH_JSON_TYPE_INVALID) == 0) { 458 return gpr_strdup("<Invalid json token>"); 459 } 460 char* loggable_token = nullptr; 461 gpr_asprintf(&loggable_token, 462 "{\n type: %s\n client_id: %s\n client_secret: " 463 "<redacted>\n refresh_token: <redacted>\n}", 464 token->type, token->client_id); 465 return loggable_token; 466 } 467 468 grpc_call_credentials* grpc_google_refresh_token_credentials_create( 469 const char* json_refresh_token, void* reserved) { 470 grpc_auth_refresh_token token = 471 grpc_auth_refresh_token_create_from_string(json_refresh_token); 472 if (grpc_api_trace.enabled()) { 473 char* loggable_token = create_loggable_refresh_token(&token); 474 gpr_log(GPR_INFO, 475 "grpc_refresh_token_credentials_create(json_refresh_token=%s, " 476 "reserved=%p)", 477 loggable_token, reserved); 478 gpr_free(loggable_token); 479 } 480 GPR_ASSERT(reserved == nullptr); 481 return grpc_refresh_token_credentials_create_from_auth_refresh_token(token); 482 } 483 484 // 485 // Oauth2 Access Token credentials. 486 // 487 488 static void access_token_destruct(grpc_call_credentials* creds) { 489 grpc_access_token_credentials* c = 490 reinterpret_cast<grpc_access_token_credentials*>(creds); 491 GRPC_MDELEM_UNREF(c->access_token_md); 492 } 493 494 static bool access_token_get_request_metadata( 495 grpc_call_credentials* creds, grpc_polling_entity* pollent, 496 grpc_auth_metadata_context context, grpc_credentials_mdelem_array* md_array, 497 grpc_closure* on_request_metadata, grpc_error** error) { 498 grpc_access_token_credentials* c = 499 reinterpret_cast<grpc_access_token_credentials*>(creds); 500 grpc_credentials_mdelem_array_add(md_array, c->access_token_md); 501 return true; 502 } 503 504 static void access_token_cancel_get_request_metadata( 505 grpc_call_credentials* c, grpc_credentials_mdelem_array* md_array, 506 grpc_error* error) { 507 GRPC_ERROR_UNREF(error); 508 } 509 510 static grpc_call_credentials_vtable access_token_vtable = { 511 access_token_destruct, access_token_get_request_metadata, 512 access_token_cancel_get_request_metadata}; 513 514 grpc_call_credentials* grpc_access_token_credentials_create( 515 const char* access_token, void* reserved) { 516 grpc_access_token_credentials* c = 517 static_cast<grpc_access_token_credentials*>( 518 gpr_zalloc(sizeof(grpc_access_token_credentials))); 519 GRPC_API_TRACE( 520 "grpc_access_token_credentials_create(access_token=<redacted>, " 521 "reserved=%p)", 522 1, (reserved)); 523 GPR_ASSERT(reserved == nullptr); 524 c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2; 525 c->base.vtable = &access_token_vtable; 526 gpr_ref_init(&c->base.refcount, 1); 527 char* token_md_value; 528 gpr_asprintf(&token_md_value, "Bearer %s", access_token); 529 grpc_core::ExecCtx exec_ctx; 530 c->access_token_md = grpc_mdelem_from_slices( 531 grpc_slice_from_static_string(GRPC_AUTHORIZATION_METADATA_KEY), 532 grpc_slice_from_copied_string(token_md_value)); 533 534 gpr_free(token_md_value); 535 return &c->base; 536 } 537