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