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/json/json.h"
22 #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
23
24 #include <string.h>
25
26 #include "absl/container/inlined_vector.h"
27 #include "absl/strings/str_join.h"
28
29 #include <grpc/grpc_security.h>
30 #include <grpc/impl/codegen/slice.h>
31 #include <grpc/slice.h>
32 #include <grpc/support/alloc.h>
33 #include <grpc/support/log.h>
34 #include <grpc/support/string_util.h>
35
36 #include "absl/strings/str_format.h"
37 #include "src/core/lib/gpr/string.h"
38 #include "src/core/lib/gprpp/ref_counted_ptr.h"
39 #include "src/core/lib/iomgr/error.h"
40 #include "src/core/lib/iomgr/load_file.h"
41 #include "src/core/lib/security/util/json_util.h"
42 #include "src/core/lib/slice/slice_internal.h"
43 #include "src/core/lib/surface/api_trace.h"
44 #include "src/core/lib/uri/uri_parser.h"
45
46 using grpc_core::Json;
47
48 //
49 // Auth Refresh Token.
50 //
51
grpc_auth_refresh_token_is_valid(const grpc_auth_refresh_token * refresh_token)52 int grpc_auth_refresh_token_is_valid(
53 const grpc_auth_refresh_token* refresh_token) {
54 return (refresh_token != nullptr) &&
55 strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID);
56 }
57
grpc_auth_refresh_token_create_from_json(const Json & json)58 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
59 const Json& json) {
60 grpc_auth_refresh_token result;
61 const char* prop_value;
62 int success = 0;
63 grpc_error* error = GRPC_ERROR_NONE;
64
65 memset(&result, 0, sizeof(grpc_auth_refresh_token));
66 result.type = GRPC_AUTH_JSON_TYPE_INVALID;
67 if (json.type() != Json::Type::OBJECT) {
68 gpr_log(GPR_ERROR, "Invalid json.");
69 goto end;
70 }
71
72 prop_value = grpc_json_get_string_property(json, "type", &error);
73 GRPC_LOG_IF_ERROR("Parsing refresh token", error);
74 if (prop_value == nullptr ||
75 strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) {
76 goto end;
77 }
78 result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER;
79
80 if (!grpc_copy_json_string_property(json, "client_secret",
81 &result.client_secret) ||
82 !grpc_copy_json_string_property(json, "client_id", &result.client_id) ||
83 !grpc_copy_json_string_property(json, "refresh_token",
84 &result.refresh_token)) {
85 goto end;
86 }
87 success = 1;
88
89 end:
90 if (!success) grpc_auth_refresh_token_destruct(&result);
91 return result;
92 }
93
grpc_auth_refresh_token_create_from_string(const char * json_string)94 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string(
95 const char* json_string) {
96 grpc_error* error = GRPC_ERROR_NONE;
97 Json json = Json::Parse(json_string, &error);
98 if (error != GRPC_ERROR_NONE) {
99 gpr_log(GPR_ERROR, "JSON parsing failed: %s", grpc_error_string(error));
100 GRPC_ERROR_UNREF(error);
101 }
102 return grpc_auth_refresh_token_create_from_json(json);
103 }
104
grpc_auth_refresh_token_destruct(grpc_auth_refresh_token * refresh_token)105 void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token) {
106 if (refresh_token == nullptr) return;
107 refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID;
108 if (refresh_token->client_id != nullptr) {
109 gpr_free(refresh_token->client_id);
110 refresh_token->client_id = nullptr;
111 }
112 if (refresh_token->client_secret != nullptr) {
113 gpr_free(refresh_token->client_secret);
114 refresh_token->client_secret = nullptr;
115 }
116 if (refresh_token->refresh_token != nullptr) {
117 gpr_free(refresh_token->refresh_token);
118 refresh_token->refresh_token = nullptr;
119 }
120 }
121
122 //
123 // Oauth2 Token Fetcher credentials.
124 //
125
126 grpc_oauth2_token_fetcher_credentials::
~grpc_oauth2_token_fetcher_credentials()127 ~grpc_oauth2_token_fetcher_credentials() {
128 GRPC_MDELEM_UNREF(access_token_md_);
129 gpr_mu_destroy(&mu_);
130 grpc_pollset_set_destroy(grpc_polling_entity_pollset_set(&pollent_));
131 grpc_httpcli_context_destroy(&httpcli_context_);
132 }
133
134 grpc_credentials_status
grpc_oauth2_token_fetcher_credentials_parse_server_response(const grpc_http_response * response,grpc_mdelem * token_md,grpc_millis * token_lifetime)135 grpc_oauth2_token_fetcher_credentials_parse_server_response(
136 const grpc_http_response* response, grpc_mdelem* token_md,
137 grpc_millis* token_lifetime) {
138 char* null_terminated_body = nullptr;
139 char* new_access_token = nullptr;
140 grpc_credentials_status status = GRPC_CREDENTIALS_OK;
141 Json json;
142
143 if (response == nullptr) {
144 gpr_log(GPR_ERROR, "Received NULL response.");
145 status = GRPC_CREDENTIALS_ERROR;
146 goto end;
147 }
148
149 if (response->body_length > 0) {
150 null_terminated_body =
151 static_cast<char*>(gpr_malloc(response->body_length + 1));
152 null_terminated_body[response->body_length] = '\0';
153 memcpy(null_terminated_body, response->body, response->body_length);
154 }
155
156 if (response->status != 200) {
157 gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].",
158 response->status,
159 null_terminated_body != nullptr ? null_terminated_body : "");
160 status = GRPC_CREDENTIALS_ERROR;
161 goto end;
162 } else {
163 const char* access_token = nullptr;
164 const char* token_type = nullptr;
165 const char* expires_in = nullptr;
166 Json::Object::const_iterator it;
167 grpc_error* error = GRPC_ERROR_NONE;
168 json = Json::Parse(null_terminated_body, &error);
169 if (error != GRPC_ERROR_NONE) {
170 gpr_log(GPR_ERROR, "Could not parse JSON from %s: %s",
171 null_terminated_body, grpc_error_string(error));
172 GRPC_ERROR_UNREF(error);
173 status = GRPC_CREDENTIALS_ERROR;
174 goto end;
175 }
176 if (json.type() != Json::Type::OBJECT) {
177 gpr_log(GPR_ERROR, "Response should be a JSON object");
178 status = GRPC_CREDENTIALS_ERROR;
179 goto end;
180 }
181 it = json.object_value().find("access_token");
182 if (it == json.object_value().end() ||
183 it->second.type() != Json::Type::STRING) {
184 gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON.");
185 status = GRPC_CREDENTIALS_ERROR;
186 goto end;
187 }
188 access_token = it->second.string_value().c_str();
189 it = json.object_value().find("token_type");
190 if (it == json.object_value().end() ||
191 it->second.type() != Json::Type::STRING) {
192 gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON.");
193 status = GRPC_CREDENTIALS_ERROR;
194 goto end;
195 }
196 token_type = it->second.string_value().c_str();
197 it = json.object_value().find("expires_in");
198 if (it == json.object_value().end() ||
199 it->second.type() != Json::Type::NUMBER) {
200 gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON.");
201 status = GRPC_CREDENTIALS_ERROR;
202 goto end;
203 }
204 expires_in = it->second.string_value().c_str();
205 gpr_asprintf(&new_access_token, "%s %s", token_type, access_token);
206 *token_lifetime = strtol(expires_in, nullptr, 10) * GPR_MS_PER_SEC;
207 if (!GRPC_MDISNULL(*token_md)) GRPC_MDELEM_UNREF(*token_md);
208 *token_md = grpc_mdelem_from_slices(
209 grpc_core::ExternallyManagedSlice(GRPC_AUTHORIZATION_METADATA_KEY),
210 grpc_core::UnmanagedMemorySlice(new_access_token));
211 status = GRPC_CREDENTIALS_OK;
212 }
213
214 end:
215 if (status != GRPC_CREDENTIALS_OK && !GRPC_MDISNULL(*token_md)) {
216 GRPC_MDELEM_UNREF(*token_md);
217 *token_md = GRPC_MDNULL;
218 }
219 if (null_terminated_body != nullptr) gpr_free(null_terminated_body);
220 if (new_access_token != nullptr) gpr_free(new_access_token);
221 return status;
222 }
223
on_oauth2_token_fetcher_http_response(void * user_data,grpc_error * error)224 static void on_oauth2_token_fetcher_http_response(void* user_data,
225 grpc_error* error) {
226 GRPC_LOG_IF_ERROR("oauth_fetch", GRPC_ERROR_REF(error));
227 grpc_credentials_metadata_request* r =
228 static_cast<grpc_credentials_metadata_request*>(user_data);
229 grpc_oauth2_token_fetcher_credentials* c =
230 reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(r->creds.get());
231 c->on_http_response(r, error);
232 }
233
on_http_response(grpc_credentials_metadata_request * r,grpc_error * error)234 void grpc_oauth2_token_fetcher_credentials::on_http_response(
235 grpc_credentials_metadata_request* r, grpc_error* error) {
236 grpc_mdelem access_token_md = GRPC_MDNULL;
237 grpc_millis token_lifetime = 0;
238 grpc_credentials_status status =
239 error == GRPC_ERROR_NONE
240 ? grpc_oauth2_token_fetcher_credentials_parse_server_response(
241 &r->response, &access_token_md, &token_lifetime)
242 : GRPC_CREDENTIALS_ERROR;
243 // Update cache and grab list of pending requests.
244 gpr_mu_lock(&mu_);
245 token_fetch_pending_ = false;
246 access_token_md_ = GRPC_MDELEM_REF(access_token_md);
247 token_expiration_ =
248 status == GRPC_CREDENTIALS_OK
249 ? gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
250 gpr_time_from_millis(token_lifetime, GPR_TIMESPAN))
251 : gpr_inf_past(GPR_CLOCK_MONOTONIC);
252 grpc_oauth2_pending_get_request_metadata* pending_request = pending_requests_;
253 pending_requests_ = nullptr;
254 gpr_mu_unlock(&mu_);
255 // Invoke callbacks for all pending requests.
256 while (pending_request != nullptr) {
257 grpc_error* new_error = GRPC_ERROR_NONE;
258 if (status == GRPC_CREDENTIALS_OK) {
259 grpc_credentials_mdelem_array_add(pending_request->md_array,
260 access_token_md);
261 } else {
262 new_error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
263 "Error occurred when fetching oauth2 token.", &error, 1);
264 }
265 grpc_core::ExecCtx::Run(DEBUG_LOCATION,
266 pending_request->on_request_metadata, new_error);
267 grpc_polling_entity_del_from_pollset_set(
268 pending_request->pollent, grpc_polling_entity_pollset_set(&pollent_));
269 grpc_oauth2_pending_get_request_metadata* prev = pending_request;
270 pending_request = pending_request->next;
271 gpr_free(prev);
272 }
273 GRPC_MDELEM_UNREF(access_token_md);
274 Unref();
275 grpc_credentials_metadata_request_destroy(r);
276 }
277
get_request_metadata(grpc_polling_entity * pollent,grpc_auth_metadata_context,grpc_credentials_mdelem_array * md_array,grpc_closure * on_request_metadata,grpc_error **)278 bool grpc_oauth2_token_fetcher_credentials::get_request_metadata(
279 grpc_polling_entity* pollent, grpc_auth_metadata_context /*context*/,
280 grpc_credentials_mdelem_array* md_array, grpc_closure* on_request_metadata,
281 grpc_error** /*error*/) {
282 // Check if we can use the cached token.
283 grpc_millis refresh_threshold =
284 GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS * GPR_MS_PER_SEC;
285 grpc_mdelem cached_access_token_md = GRPC_MDNULL;
286 gpr_mu_lock(&mu_);
287 if (!GRPC_MDISNULL(access_token_md_) &&
288 gpr_time_cmp(
289 gpr_time_sub(token_expiration_, gpr_now(GPR_CLOCK_MONOTONIC)),
290 gpr_time_from_seconds(GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
291 GPR_TIMESPAN)) > 0) {
292 cached_access_token_md = GRPC_MDELEM_REF(access_token_md_);
293 }
294 if (!GRPC_MDISNULL(cached_access_token_md)) {
295 gpr_mu_unlock(&mu_);
296 grpc_credentials_mdelem_array_add(md_array, cached_access_token_md);
297 GRPC_MDELEM_UNREF(cached_access_token_md);
298 return true;
299 }
300 // Couldn't get the token from the cache.
301 // Add request to pending_requests_ and start a new fetch if needed.
302 grpc_oauth2_pending_get_request_metadata* pending_request =
303 static_cast<grpc_oauth2_pending_get_request_metadata*>(
304 gpr_malloc(sizeof(*pending_request)));
305 pending_request->md_array = md_array;
306 pending_request->on_request_metadata = on_request_metadata;
307 pending_request->pollent = pollent;
308 grpc_polling_entity_add_to_pollset_set(
309 pollent, grpc_polling_entity_pollset_set(&pollent_));
310 pending_request->next = pending_requests_;
311 pending_requests_ = pending_request;
312 bool start_fetch = false;
313 if (!token_fetch_pending_) {
314 token_fetch_pending_ = true;
315 start_fetch = true;
316 }
317 gpr_mu_unlock(&mu_);
318 if (start_fetch) {
319 Ref().release();
320 fetch_oauth2(grpc_credentials_metadata_request_create(this->Ref()),
321 &httpcli_context_, &pollent_,
322 on_oauth2_token_fetcher_http_response,
323 grpc_core::ExecCtx::Get()->Now() + refresh_threshold);
324 }
325 return false;
326 }
327
cancel_get_request_metadata(grpc_credentials_mdelem_array * md_array,grpc_error * error)328 void grpc_oauth2_token_fetcher_credentials::cancel_get_request_metadata(
329 grpc_credentials_mdelem_array* md_array, grpc_error* error) {
330 gpr_mu_lock(&mu_);
331 grpc_oauth2_pending_get_request_metadata* prev = nullptr;
332 grpc_oauth2_pending_get_request_metadata* pending_request = pending_requests_;
333 while (pending_request != nullptr) {
334 if (pending_request->md_array == md_array) {
335 // Remove matching pending request from the list.
336 if (prev != nullptr) {
337 prev->next = pending_request->next;
338 } else {
339 pending_requests_ = pending_request->next;
340 }
341 // Invoke the callback immediately with an error.
342 grpc_core::ExecCtx::Run(DEBUG_LOCATION,
343 pending_request->on_request_metadata,
344 GRPC_ERROR_REF(error));
345 gpr_free(pending_request);
346 break;
347 }
348 prev = pending_request;
349 pending_request = pending_request->next;
350 }
351 gpr_mu_unlock(&mu_);
352 GRPC_ERROR_UNREF(error);
353 }
354
grpc_oauth2_token_fetcher_credentials()355 grpc_oauth2_token_fetcher_credentials::grpc_oauth2_token_fetcher_credentials()
356 : grpc_call_credentials(GRPC_CALL_CREDENTIALS_TYPE_OAUTH2),
357 token_expiration_(gpr_inf_past(GPR_CLOCK_MONOTONIC)),
358 pollent_(grpc_polling_entity_create_from_pollset_set(
359 grpc_pollset_set_create())) {
360 gpr_mu_init(&mu_);
361 grpc_httpcli_context_init(&httpcli_context_);
362 }
363
debug_string()364 std::string grpc_oauth2_token_fetcher_credentials::debug_string() {
365 return "OAuth2TokenFetcherCredentials";
366 }
367
368 //
369 // Google Compute Engine credentials.
370 //
371
372 namespace {
373
374 class grpc_compute_engine_token_fetcher_credentials
375 : public grpc_oauth2_token_fetcher_credentials {
376 public:
377 grpc_compute_engine_token_fetcher_credentials() = default;
378 ~grpc_compute_engine_token_fetcher_credentials() override = default;
379
380 protected:
fetch_oauth2(grpc_credentials_metadata_request * metadata_req,grpc_httpcli_context * http_context,grpc_polling_entity * pollent,grpc_iomgr_cb_func response_cb,grpc_millis deadline)381 void fetch_oauth2(grpc_credentials_metadata_request* metadata_req,
382 grpc_httpcli_context* http_context,
383 grpc_polling_entity* pollent,
384 grpc_iomgr_cb_func response_cb,
385 grpc_millis deadline) override {
386 grpc_http_header header = {const_cast<char*>("Metadata-Flavor"),
387 const_cast<char*>("Google")};
388 grpc_httpcli_request request;
389 memset(&request, 0, sizeof(grpc_httpcli_request));
390 request.host = (char*)GRPC_COMPUTE_ENGINE_METADATA_HOST;
391 request.http.path = (char*)GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH;
392 request.http.hdr_count = 1;
393 request.http.hdrs = &header;
394 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
395 channel. This would allow us to cancel an authentication query when under
396 extreme memory pressure. */
397 grpc_resource_quota* resource_quota =
398 grpc_resource_quota_create("oauth2_credentials");
399 grpc_httpcli_get(http_context, pollent, resource_quota, &request, deadline,
400 GRPC_CLOSURE_INIT(&http_get_cb_closure_, response_cb,
401 metadata_req, grpc_schedule_on_exec_ctx),
402 &metadata_req->response);
403 grpc_resource_quota_unref_internal(resource_quota);
404 }
405
debug_string()406 std::string debug_string() override {
407 return absl::StrFormat(
408 "GoogleComputeEngineTokenFetcherCredentials{%s}",
409 grpc_oauth2_token_fetcher_credentials::debug_string());
410 }
411
412 private:
413 grpc_closure http_get_cb_closure_;
414 };
415
416 } // namespace
417
grpc_google_compute_engine_credentials_create(void * reserved)418 grpc_call_credentials* grpc_google_compute_engine_credentials_create(
419 void* reserved) {
420 GRPC_API_TRACE("grpc_compute_engine_credentials_create(reserved=%p)", 1,
421 (reserved));
422 GPR_ASSERT(reserved == nullptr);
423 return grpc_core::MakeRefCounted<
424 grpc_compute_engine_token_fetcher_credentials>()
425 .release();
426 }
427
428 //
429 // Google Refresh Token credentials.
430 //
431
432 grpc_google_refresh_token_credentials::
~grpc_google_refresh_token_credentials()433 ~grpc_google_refresh_token_credentials() {
434 grpc_auth_refresh_token_destruct(&refresh_token_);
435 }
436
fetch_oauth2(grpc_credentials_metadata_request * metadata_req,grpc_httpcli_context * httpcli_context,grpc_polling_entity * pollent,grpc_iomgr_cb_func response_cb,grpc_millis deadline)437 void grpc_google_refresh_token_credentials::fetch_oauth2(
438 grpc_credentials_metadata_request* metadata_req,
439 grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent,
440 grpc_iomgr_cb_func response_cb, grpc_millis deadline) {
441 grpc_http_header header = {
442 const_cast<char*>("Content-Type"),
443 const_cast<char*>("application/x-www-form-urlencoded")};
444 grpc_httpcli_request request;
445 char* body = nullptr;
446 gpr_asprintf(&body, GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING,
447 refresh_token_.client_id, refresh_token_.client_secret,
448 refresh_token_.refresh_token);
449 memset(&request, 0, sizeof(grpc_httpcli_request));
450 request.host = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_HOST;
451 request.http.path = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH;
452 request.http.hdr_count = 1;
453 request.http.hdrs = &header;
454 request.handshaker = &grpc_httpcli_ssl;
455 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
456 channel. This would allow us to cancel an authentication query when under
457 extreme memory pressure. */
458 grpc_resource_quota* resource_quota =
459 grpc_resource_quota_create("oauth2_credentials_refresh");
460 grpc_httpcli_post(httpcli_context, pollent, resource_quota, &request, body,
461 strlen(body), deadline,
462 GRPC_CLOSURE_INIT(&http_post_cb_closure_, response_cb,
463 metadata_req, grpc_schedule_on_exec_ctx),
464 &metadata_req->response);
465 grpc_resource_quota_unref_internal(resource_quota);
466 gpr_free(body);
467 }
468
grpc_google_refresh_token_credentials(grpc_auth_refresh_token refresh_token)469 grpc_google_refresh_token_credentials::grpc_google_refresh_token_credentials(
470 grpc_auth_refresh_token refresh_token)
471 : refresh_token_(refresh_token) {}
472
473 grpc_core::RefCountedPtr<grpc_call_credentials>
grpc_refresh_token_credentials_create_from_auth_refresh_token(grpc_auth_refresh_token refresh_token)474 grpc_refresh_token_credentials_create_from_auth_refresh_token(
475 grpc_auth_refresh_token refresh_token) {
476 if (!grpc_auth_refresh_token_is_valid(&refresh_token)) {
477 gpr_log(GPR_ERROR, "Invalid input for refresh token credentials creation");
478 return nullptr;
479 }
480 return grpc_core::MakeRefCounted<grpc_google_refresh_token_credentials>(
481 refresh_token);
482 }
483
debug_string()484 std::string grpc_google_refresh_token_credentials::debug_string() {
485 return absl::StrFormat("GoogleRefreshToken{ClientID:%s,%s}",
486 refresh_token_.client_id,
487 grpc_oauth2_token_fetcher_credentials::debug_string());
488 }
489
create_loggable_refresh_token(grpc_auth_refresh_token * token)490 static char* create_loggable_refresh_token(grpc_auth_refresh_token* token) {
491 if (strcmp(token->type, GRPC_AUTH_JSON_TYPE_INVALID) == 0) {
492 return gpr_strdup("<Invalid json token>");
493 }
494 char* loggable_token = nullptr;
495 gpr_asprintf(&loggable_token,
496 "{\n type: %s\n client_id: %s\n client_secret: "
497 "<redacted>\n refresh_token: <redacted>\n}",
498 token->type, token->client_id);
499 return loggable_token;
500 }
501
grpc_google_refresh_token_credentials_create(const char * json_refresh_token,void * reserved)502 grpc_call_credentials* grpc_google_refresh_token_credentials_create(
503 const char* json_refresh_token, void* reserved) {
504 grpc_auth_refresh_token token =
505 grpc_auth_refresh_token_create_from_string(json_refresh_token);
506 if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace)) {
507 char* loggable_token = create_loggable_refresh_token(&token);
508 gpr_log(GPR_INFO,
509 "grpc_refresh_token_credentials_create(json_refresh_token=%s, "
510 "reserved=%p)",
511 loggable_token, reserved);
512 gpr_free(loggable_token);
513 }
514 GPR_ASSERT(reserved == nullptr);
515 return grpc_refresh_token_credentials_create_from_auth_refresh_token(token)
516 .release();
517 }
518
519 //
520 // STS credentials.
521 //
522
523 namespace grpc_core {
524
525 namespace {
526
MaybeAddToBody(const char * field_name,const char * field,std::vector<std::string> * body)527 void MaybeAddToBody(const char* field_name, const char* field,
528 std::vector<std::string>* body) {
529 if (field == nullptr || strlen(field) == 0) return;
530 body->push_back(absl::StrFormat("&%s=%s", field_name, field));
531 }
532
LoadTokenFile(const char * path,gpr_slice * token)533 grpc_error* LoadTokenFile(const char* path, gpr_slice* token) {
534 grpc_error* err = grpc_load_file(path, 1, token);
535 if (err != GRPC_ERROR_NONE) return err;
536 if (GRPC_SLICE_LENGTH(*token) == 0) {
537 gpr_log(GPR_ERROR, "Token file %s is empty", path);
538 err = GRPC_ERROR_CREATE_FROM_STATIC_STRING("Token file is empty.");
539 }
540 return err;
541 }
542
543 class StsTokenFetcherCredentials
544 : public grpc_oauth2_token_fetcher_credentials {
545 public:
StsTokenFetcherCredentials(grpc_uri * sts_url,const grpc_sts_credentials_options * options)546 StsTokenFetcherCredentials(grpc_uri* sts_url, // Ownership transferred.
547 const grpc_sts_credentials_options* options)
548 : sts_url_(sts_url),
549 resource_(gpr_strdup(options->resource)),
550 audience_(gpr_strdup(options->audience)),
551 scope_(gpr_strdup(options->scope)),
552 requested_token_type_(gpr_strdup(options->requested_token_type)),
553 subject_token_path_(gpr_strdup(options->subject_token_path)),
554 subject_token_type_(gpr_strdup(options->subject_token_type)),
555 actor_token_path_(gpr_strdup(options->actor_token_path)),
556 actor_token_type_(gpr_strdup(options->actor_token_type)) {}
557
~StsTokenFetcherCredentials()558 ~StsTokenFetcherCredentials() override { grpc_uri_destroy(sts_url_); }
559
debug_string()560 std::string debug_string() override {
561 return absl::StrFormat(
562 "StsTokenFetcherCredentials{Path:%s,Authority:%s,%s}", sts_url_->path,
563 sts_url_->authority,
564 grpc_oauth2_token_fetcher_credentials::debug_string());
565 }
566
567 private:
fetch_oauth2(grpc_credentials_metadata_request * metadata_req,grpc_httpcli_context * http_context,grpc_polling_entity * pollent,grpc_iomgr_cb_func response_cb,grpc_millis deadline)568 void fetch_oauth2(grpc_credentials_metadata_request* metadata_req,
569 grpc_httpcli_context* http_context,
570 grpc_polling_entity* pollent,
571 grpc_iomgr_cb_func response_cb,
572 grpc_millis deadline) override {
573 char* body = nullptr;
574 size_t body_length = 0;
575 grpc_error* err = FillBody(&body, &body_length);
576 if (err != GRPC_ERROR_NONE) {
577 response_cb(metadata_req, err);
578 GRPC_ERROR_UNREF(err);
579 return;
580 }
581 grpc_http_header header = {
582 const_cast<char*>("Content-Type"),
583 const_cast<char*>("application/x-www-form-urlencoded")};
584 grpc_httpcli_request request;
585 memset(&request, 0, sizeof(grpc_httpcli_request));
586 request.host = (char*)sts_url_->authority;
587 request.http.path = (char*)sts_url_->path;
588 request.http.hdr_count = 1;
589 request.http.hdrs = &header;
590 request.handshaker = (strcmp(sts_url_->scheme, "https") == 0)
591 ? &grpc_httpcli_ssl
592 : &grpc_httpcli_plaintext;
593 /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
594 channel. This would allow us to cancel an authentication query when under
595 extreme memory pressure. */
596 grpc_resource_quota* resource_quota =
597 grpc_resource_quota_create("oauth2_credentials_refresh");
598 grpc_httpcli_post(
599 http_context, pollent, resource_quota, &request, body, body_length,
600 deadline,
601 GRPC_CLOSURE_INIT(&http_post_cb_closure_, response_cb, metadata_req,
602 grpc_schedule_on_exec_ctx),
603 &metadata_req->response);
604 grpc_resource_quota_unref_internal(resource_quota);
605 gpr_free(body);
606 }
607
FillBody(char ** body,size_t * body_length)608 grpc_error* FillBody(char** body, size_t* body_length) {
609 *body = nullptr;
610 std::vector<std::string> body_parts;
611 grpc_slice subject_token = grpc_empty_slice();
612 grpc_slice actor_token = grpc_empty_slice();
613 grpc_error* err = GRPC_ERROR_NONE;
614
615 auto cleanup = [&body, &body_length, &body_parts, &subject_token,
616 &actor_token, &err]() {
617 if (err == GRPC_ERROR_NONE) {
618 std::string body_str = absl::StrJoin(body_parts, "");
619 *body = gpr_strdup(body_str.c_str());
620 *body_length = body_str.size();
621 }
622 grpc_slice_unref_internal(subject_token);
623 grpc_slice_unref_internal(actor_token);
624 return err;
625 };
626
627 err = LoadTokenFile(subject_token_path_.get(), &subject_token);
628 if (err != GRPC_ERROR_NONE) return cleanup();
629 body_parts.push_back(absl::StrFormat(
630 GRPC_STS_POST_MINIMAL_BODY_FORMAT_STRING,
631 reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(subject_token)),
632 subject_token_type_.get()));
633 MaybeAddToBody("resource", resource_.get(), &body_parts);
634 MaybeAddToBody("audience", audience_.get(), &body_parts);
635 MaybeAddToBody("scope", scope_.get(), &body_parts);
636 MaybeAddToBody("requested_token_type", requested_token_type_.get(),
637 &body_parts);
638 if ((actor_token_path_ != nullptr) && *actor_token_path_ != '\0') {
639 err = LoadTokenFile(actor_token_path_.get(), &actor_token);
640 if (err != GRPC_ERROR_NONE) return cleanup();
641 MaybeAddToBody(
642 "actor_token",
643 reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(actor_token)),
644 &body_parts);
645 MaybeAddToBody("actor_token_type", actor_token_type_.get(), &body_parts);
646 }
647 return cleanup();
648 }
649
650 grpc_uri* sts_url_;
651 grpc_closure http_post_cb_closure_;
652 grpc_core::UniquePtr<char> resource_;
653 grpc_core::UniquePtr<char> audience_;
654 grpc_core::UniquePtr<char> scope_;
655 grpc_core::UniquePtr<char> requested_token_type_;
656 grpc_core::UniquePtr<char> subject_token_path_;
657 grpc_core::UniquePtr<char> subject_token_type_;
658 grpc_core::UniquePtr<char> actor_token_path_;
659 grpc_core::UniquePtr<char> actor_token_type_;
660 };
661
662 } // namespace
663
ValidateStsCredentialsOptions(const grpc_sts_credentials_options * options,grpc_uri ** sts_url_out)664 grpc_error* ValidateStsCredentialsOptions(
665 const grpc_sts_credentials_options* options, grpc_uri** sts_url_out) {
666 struct GrpcUriDeleter {
667 void operator()(grpc_uri* uri) { grpc_uri_destroy(uri); }
668 };
669 *sts_url_out = nullptr;
670 absl::InlinedVector<grpc_error*, 3> error_list;
671 std::unique_ptr<grpc_uri, GrpcUriDeleter> sts_url(
672 options->token_exchange_service_uri != nullptr
673 ? grpc_uri_parse(options->token_exchange_service_uri, false)
674 : nullptr);
675 if (sts_url == nullptr) {
676 error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
677 "Invalid or missing STS endpoint URL"));
678 } else {
679 if (strcmp(sts_url->scheme, "https") != 0 &&
680 strcmp(sts_url->scheme, "http") != 0) {
681 error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
682 "Invalid URI scheme, must be https to http."));
683 }
684 }
685 if (options->subject_token_path == nullptr ||
686 strlen(options->subject_token_path) == 0) {
687 error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
688 "subject_token needs to be specified"));
689 }
690 if (options->subject_token_type == nullptr ||
691 strlen(options->subject_token_type) == 0) {
692 error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(
693 "subject_token_type needs to be specified"));
694 }
695 if (error_list.empty()) {
696 *sts_url_out = sts_url.release();
697 return GRPC_ERROR_NONE;
698 } else {
699 return GRPC_ERROR_CREATE_FROM_VECTOR("Invalid STS Credentials Options",
700 &error_list);
701 }
702 }
703
704 } // namespace grpc_core
705
grpc_sts_credentials_create(const grpc_sts_credentials_options * options,void * reserved)706 grpc_call_credentials* grpc_sts_credentials_create(
707 const grpc_sts_credentials_options* options, void* reserved) {
708 GPR_ASSERT(reserved == nullptr);
709 grpc_uri* sts_url;
710 grpc_error* error =
711 grpc_core::ValidateStsCredentialsOptions(options, &sts_url);
712 if (error != GRPC_ERROR_NONE) {
713 gpr_log(GPR_ERROR, "STS Credentials creation failed. Error: %s.",
714 grpc_error_string(error));
715 GRPC_ERROR_UNREF(error);
716 return nullptr;
717 }
718 return grpc_core::MakeRefCounted<grpc_core::StsTokenFetcherCredentials>(
719 sts_url, options)
720 .release();
721 }
722
723 //
724 // Oauth2 Access Token credentials.
725 //
726
~grpc_access_token_credentials()727 grpc_access_token_credentials::~grpc_access_token_credentials() {
728 GRPC_MDELEM_UNREF(access_token_md_);
729 }
730
get_request_metadata(grpc_polling_entity *,grpc_auth_metadata_context,grpc_credentials_mdelem_array * md_array,grpc_closure *,grpc_error **)731 bool grpc_access_token_credentials::get_request_metadata(
732 grpc_polling_entity* /*pollent*/, grpc_auth_metadata_context /*context*/,
733 grpc_credentials_mdelem_array* md_array,
734 grpc_closure* /*on_request_metadata*/, grpc_error** /*error*/) {
735 grpc_credentials_mdelem_array_add(md_array, access_token_md_);
736 return true;
737 }
738
cancel_get_request_metadata(grpc_credentials_mdelem_array *,grpc_error * error)739 void grpc_access_token_credentials::cancel_get_request_metadata(
740 grpc_credentials_mdelem_array* /*md_array*/, grpc_error* error) {
741 GRPC_ERROR_UNREF(error);
742 }
743
grpc_access_token_credentials(const char * access_token)744 grpc_access_token_credentials::grpc_access_token_credentials(
745 const char* access_token)
746 : grpc_call_credentials(GRPC_CALL_CREDENTIALS_TYPE_OAUTH2) {
747 char* token_md_value;
748 gpr_asprintf(&token_md_value, "Bearer %s", access_token);
749 grpc_core::ExecCtx exec_ctx;
750 access_token_md_ = grpc_mdelem_from_slices(
751 grpc_core::ExternallyManagedSlice(GRPC_AUTHORIZATION_METADATA_KEY),
752 grpc_core::UnmanagedMemorySlice(token_md_value));
753 gpr_free(token_md_value);
754 }
755
debug_string()756 std::string grpc_access_token_credentials::debug_string() {
757 bool access_token_present = !GRPC_MDISNULL(access_token_md_);
758 return absl::StrFormat("AccessTokenCredentials{Token:%s}",
759 access_token_present ? "present" : "absent");
760 }
761
grpc_access_token_credentials_create(const char * access_token,void * reserved)762 grpc_call_credentials* grpc_access_token_credentials_create(
763 const char* access_token, void* reserved) {
764 GRPC_API_TRACE(
765 "grpc_access_token_credentials_create(access_token=<redacted>, "
766 "reserved=%p)",
767 1, (reserved));
768 GPR_ASSERT(reserved == nullptr);
769 return grpc_core::MakeRefCounted<grpc_access_token_credentials>(access_token)
770 .release();
771 }
772