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