1 // Copyright 2020 gRPC authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 
16 #include <grpc/support/port_platform.h>
17 
18 #include "src/core/lib/security/credentials/external/external_account_credentials.h"
19 
20 #include <stdint.h>
21 #include <string.h>
22 
23 #include <map>
24 #include <memory>
25 #include <utility>
26 
27 #include "absl/status/status.h"
28 #include "absl/status/statusor.h"
29 #include "absl/strings/escaping.h"
30 #include "absl/strings/match.h"
31 #include "absl/strings/numbers.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/str_format.h"
34 #include "absl/strings/str_join.h"
35 #include "absl/strings/str_split.h"
36 #include "absl/strings/strip.h"
37 #include "absl/time/clock.h"
38 #include "absl/time/time.h"
39 
40 #include <grpc/grpc.h>
41 #include <grpc/grpc_security.h>
42 #include <grpc/support/alloc.h>
43 #include <grpc/support/json.h>
44 #include <grpc/support/log.h>
45 #include <grpc/support/string_util.h>
46 
47 #include "src/core/lib/gprpp/status_helper.h"
48 #include "src/core/lib/http/httpcli_ssl_credentials.h"
49 #include "src/core/lib/http/parser.h"
50 #include "src/core/lib/json/json_reader.h"
51 #include "src/core/lib/json/json_writer.h"
52 #include "src/core/lib/security/credentials/credentials.h"
53 #include "src/core/lib/security/credentials/external/aws_external_account_credentials.h"
54 #include "src/core/lib/security/credentials/external/file_external_account_credentials.h"
55 #include "src/core/lib/security/credentials/external/url_external_account_credentials.h"
56 #include "src/core/lib/security/util/json_util.h"
57 #include "src/core/lib/uri/uri_parser.h"
58 
59 #define EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE \
60   "urn:ietf:params:oauth:grant-type:token-exchange"
61 #define EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE \
62   "urn:ietf:params:oauth:token-type:access_token"
63 #define GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE \
64   "https://www.googleapis.com/auth/cloud-platform"
65 #define IMPERSONATED_CRED_DEFAULT_LIFETIME_IN_SECONDS 3600  // 1 hour
66 #define IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS 600       // 10 mins
67 #define IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS 43200     // 12 hours
68 
69 namespace grpc_core {
70 
71 namespace {
72 
UrlEncode(const absl::string_view & s)73 std::string UrlEncode(const absl::string_view& s) {
74   const char* hex = "0123456789ABCDEF";
75   std::string result;
76   result.reserve(s.length());
77   for (auto c : s) {
78     if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') ||
79         (c >= 'a' && c <= 'z') || c == '-' || c == '_' || c == '!' ||
80         c == '\'' || c == '(' || c == ')' || c == '*' || c == '~' || c == '.') {
81       result.push_back(c);
82     } else {
83       result.push_back('%');
84       result.push_back(hex[static_cast<unsigned char>(c) >> 4]);
85       result.push_back(hex[static_cast<unsigned char>(c) & 15]);
86     }
87   }
88   return result;
89 }
90 
91 // Expression to match:
92 // //iam.googleapis.com/locations/[^/]+/workforcePools/[^/]+/providers/.+
MatchWorkforcePoolAudience(absl::string_view audience)93 bool MatchWorkforcePoolAudience(absl::string_view audience) {
94   // Match "//iam.googleapis.com/locations/"
95   if (!absl::ConsumePrefix(&audience, "//iam.googleapis.com")) return false;
96   if (!absl::ConsumePrefix(&audience, "/locations/")) return false;
97   // Match "[^/]+/workforcePools/"
98   std::pair<absl::string_view, absl::string_view> workforce_pools_split_result =
99       absl::StrSplit(audience, absl::MaxSplits("/workforcePools/", 1));
100   if (absl::StrContains(workforce_pools_split_result.first, '/')) return false;
101   // Match "[^/]+/providers/.+"
102   std::pair<absl::string_view, absl::string_view> providers_split_result =
103       absl::StrSplit(workforce_pools_split_result.second,
104                      absl::MaxSplits("/providers/", 1));
105   return !absl::StrContains(providers_split_result.first, '/');
106 }
107 
108 }  // namespace
109 
Create(const Json & json,std::vector<std::string> scopes,grpc_error_handle * error)110 RefCountedPtr<ExternalAccountCredentials> ExternalAccountCredentials::Create(
111     const Json& json, std::vector<std::string> scopes,
112     grpc_error_handle* error) {
113   GPR_ASSERT(error->ok());
114   Options options;
115   options.type = GRPC_AUTH_JSON_TYPE_INVALID;
116   if (json.type() != Json::Type::kObject) {
117     *error =
118         GRPC_ERROR_CREATE("Invalid json to construct credentials options.");
119     return nullptr;
120   }
121   auto it = json.object().find("type");
122   if (it == json.object().end()) {
123     *error = GRPC_ERROR_CREATE("type field not present.");
124     return nullptr;
125   }
126   if (it->second.type() != Json::Type::kString) {
127     *error = GRPC_ERROR_CREATE("type field must be a string.");
128     return nullptr;
129   }
130   if (it->second.string() != GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT) {
131     *error = GRPC_ERROR_CREATE("Invalid credentials json type.");
132     return nullptr;
133   }
134   options.type = GRPC_AUTH_JSON_TYPE_EXTERNAL_ACCOUNT;
135   it = json.object().find("audience");
136   if (it == json.object().end()) {
137     *error = GRPC_ERROR_CREATE("audience field not present.");
138     return nullptr;
139   }
140   if (it->second.type() != Json::Type::kString) {
141     *error = GRPC_ERROR_CREATE("audience field must be a string.");
142     return nullptr;
143   }
144   options.audience = it->second.string();
145   it = json.object().find("subject_token_type");
146   if (it == json.object().end()) {
147     *error = GRPC_ERROR_CREATE("subject_token_type field not present.");
148     return nullptr;
149   }
150   if (it->second.type() != Json::Type::kString) {
151     *error = GRPC_ERROR_CREATE("subject_token_type field must be a string.");
152     return nullptr;
153   }
154   options.subject_token_type = it->second.string();
155   it = json.object().find("service_account_impersonation_url");
156   if (it != json.object().end()) {
157     options.service_account_impersonation_url = it->second.string();
158   }
159   it = json.object().find("token_url");
160   if (it == json.object().end()) {
161     *error = GRPC_ERROR_CREATE("token_url field not present.");
162     return nullptr;
163   }
164   if (it->second.type() != Json::Type::kString) {
165     *error = GRPC_ERROR_CREATE("token_url field must be a string.");
166     return nullptr;
167   }
168   options.token_url = it->second.string();
169   it = json.object().find("token_info_url");
170   if (it != json.object().end()) {
171     options.token_info_url = it->second.string();
172   }
173   it = json.object().find("credential_source");
174   if (it == json.object().end()) {
175     *error = GRPC_ERROR_CREATE("credential_source field not present.");
176     return nullptr;
177   }
178   options.credential_source = it->second;
179   it = json.object().find("quota_project_id");
180   if (it != json.object().end()) {
181     options.quota_project_id = it->second.string();
182   }
183   it = json.object().find("client_id");
184   if (it != json.object().end()) {
185     options.client_id = it->second.string();
186   }
187   it = json.object().find("client_secret");
188   if (it != json.object().end()) {
189     options.client_secret = it->second.string();
190   }
191   it = json.object().find("workforce_pool_user_project");
192   if (it != json.object().end()) {
193     if (MatchWorkforcePoolAudience(options.audience)) {
194       options.workforce_pool_user_project = it->second.string();
195     } else {
196       *error = GRPC_ERROR_CREATE(
197           "workforce_pool_user_project should not be set for non-workforce "
198           "pool credentials");
199       return nullptr;
200     }
201   }
202   it = json.object().find("service_account_impersonation");
203   options.service_account_impersonation.token_lifetime_seconds =
204       IMPERSONATED_CRED_DEFAULT_LIFETIME_IN_SECONDS;
205   if (it != json.object().end() && it->second.type() == Json::Type::kObject) {
206     auto service_acc_imp_json = it->second;
207     auto service_acc_imp_obj_it =
208         service_acc_imp_json.object().find("token_lifetime_seconds");
209     if (service_acc_imp_obj_it != service_acc_imp_json.object().end()) {
210       if (!absl::SimpleAtoi(
211               service_acc_imp_obj_it->second.string(),
212               &options.service_account_impersonation.token_lifetime_seconds)) {
213         *error = GRPC_ERROR_CREATE("token_lifetime_seconds must be a number");
214         return nullptr;
215       }
216       if (options.service_account_impersonation.token_lifetime_seconds >
217           IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS) {
218         *error = GRPC_ERROR_CREATE(
219             absl::StrFormat("token_lifetime_seconds must be less than %ds",
220                             IMPERSONATED_CRED_MAX_LIFETIME_IN_SECONDS));
221         return nullptr;
222       }
223       if (options.service_account_impersonation.token_lifetime_seconds <
224           IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS) {
225         *error = GRPC_ERROR_CREATE(
226             absl::StrFormat("token_lifetime_seconds must be more than %ds",
227                             IMPERSONATED_CRED_MIN_LIFETIME_IN_SECONDS));
228         return nullptr;
229       }
230     }
231   }
232   RefCountedPtr<ExternalAccountCredentials> creds;
233   if (options.credential_source.object().find("environment_id") !=
234       options.credential_source.object().end()) {
235     creds = MakeRefCounted<AwsExternalAccountCredentials>(
236         std::move(options), std::move(scopes), error);
237   } else if (options.credential_source.object().find("file") !=
238              options.credential_source.object().end()) {
239     creds = MakeRefCounted<FileExternalAccountCredentials>(
240         std::move(options), std::move(scopes), error);
241   } else if (options.credential_source.object().find("url") !=
242              options.credential_source.object().end()) {
243     creds = MakeRefCounted<UrlExternalAccountCredentials>(
244         std::move(options), std::move(scopes), error);
245   } else {
246     *error = GRPC_ERROR_CREATE(
247         "Invalid options credential source to create "
248         "ExternalAccountCredentials.");
249   }
250   if (error->ok()) {
251     return creds;
252   } else {
253     return nullptr;
254   }
255 }
256 
ExternalAccountCredentials(Options options,std::vector<std::string> scopes)257 ExternalAccountCredentials::ExternalAccountCredentials(
258     Options options, std::vector<std::string> scopes)
259     : options_(std::move(options)) {
260   if (scopes.empty()) {
261     scopes.push_back(GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE);
262   }
263   scopes_ = std::move(scopes);
264 }
265 
~ExternalAccountCredentials()266 ExternalAccountCredentials::~ExternalAccountCredentials() {}
267 
debug_string()268 std::string ExternalAccountCredentials::debug_string() {
269   return absl::StrFormat("ExternalAccountCredentials{Audience:%s,%s}",
270                          options_.audience,
271                          grpc_oauth2_token_fetcher_credentials::debug_string());
272 }
273 
MetricsHeaderValue()274 std::string ExternalAccountCredentials::MetricsHeaderValue() {
275   return absl::StrFormat(
276       "gl-cpp/unknown auth/%s google-byoid-sdk source/%s sa-impersonation/%v "
277       "config-lifetime/%v",
278       grpc_version_string(), CredentialSourceType(),
279       !options_.service_account_impersonation_url.empty(),
280       options_.service_account_impersonation.token_lifetime_seconds !=
281           IMPERSONATED_CRED_DEFAULT_LIFETIME_IN_SECONDS);
282 }
283 
CredentialSourceType()284 absl::string_view ExternalAccountCredentials::CredentialSourceType() {
285   return "unknown";
286 }
287 
288 // The token fetching flow:
289 // 1. Retrieve subject token - Subclass's RetrieveSubjectToken() gets called
290 // and the subject token is received in OnRetrieveSubjectTokenInternal().
291 // 2. Exchange token - ExchangeToken() gets called with the
292 // subject token from #1. Receive the response in OnExchangeTokenInternal().
293 // 3. (Optional) Impersonate service account - ImpersenateServiceAccount() gets
294 // called with the access token of the response from #2. Get an impersonated
295 // access token in OnImpersenateServiceAccountInternal().
296 // 4. Finish token fetch - Return back the response that contains an access
297 // token in FinishTokenFetch().
298 // TODO(chuanr): Avoid starting the remaining requests if the channel gets shut
299 // down.
fetch_oauth2(grpc_credentials_metadata_request * metadata_req,grpc_polling_entity * pollent,grpc_iomgr_cb_func response_cb,Timestamp deadline)300 void ExternalAccountCredentials::fetch_oauth2(
301     grpc_credentials_metadata_request* metadata_req,
302     grpc_polling_entity* pollent, grpc_iomgr_cb_func response_cb,
303     Timestamp deadline) {
304   GPR_ASSERT(ctx_ == nullptr);
305   ctx_ = new HTTPRequestContext(pollent, deadline);
306   metadata_req_ = metadata_req;
307   response_cb_ = response_cb;
308   auto cb = [this](std::string token, grpc_error_handle error) {
309     OnRetrieveSubjectTokenInternal(token, error);
310   };
311   RetrieveSubjectToken(ctx_, options_, cb);
312 }
313 
OnRetrieveSubjectTokenInternal(absl::string_view subject_token,grpc_error_handle error)314 void ExternalAccountCredentials::OnRetrieveSubjectTokenInternal(
315     absl::string_view subject_token, grpc_error_handle error) {
316   if (!error.ok()) {
317     FinishTokenFetch(error);
318   } else {
319     ExchangeToken(subject_token);
320   }
321 }
322 
ExchangeToken(absl::string_view subject_token)323 void ExternalAccountCredentials::ExchangeToken(
324     absl::string_view subject_token) {
325   absl::StatusOr<URI> uri = URI::Parse(options_.token_url);
326   if (!uri.ok()) {
327     FinishTokenFetch(GRPC_ERROR_CREATE(
328         absl::StrFormat("Invalid token url: %s. Error: %s", options_.token_url,
329                         uri.status().ToString())));
330     return;
331   }
332   grpc_http_request request;
333   memset(&request, 0, sizeof(grpc_http_request));
334   const bool add_authorization_header =
335       !options_.client_id.empty() && !options_.client_secret.empty();
336   request.hdr_count = add_authorization_header ? 3 : 2;
337   auto* headers = static_cast<grpc_http_header*>(
338       gpr_malloc(sizeof(grpc_http_header) * request.hdr_count));
339   headers[0].key = gpr_strdup("Content-Type");
340   headers[0].value = gpr_strdup("application/x-www-form-urlencoded");
341   headers[1].key = gpr_strdup("x-goog-api-client");
342   headers[1].value = gpr_strdup(MetricsHeaderValue().c_str());
343   if (add_authorization_header) {
344     std::string raw_cred =
345         absl::StrFormat("%s:%s", options_.client_id, options_.client_secret);
346     std::string str = absl::StrFormat("Basic %s", absl::Base64Escape(raw_cred));
347     headers[2].key = gpr_strdup("Authorization");
348     headers[2].value = gpr_strdup(str.c_str());
349   }
350   request.hdrs = headers;
351   std::vector<std::string> body_parts;
352   body_parts.push_back(
353       absl::StrFormat("audience=%s", UrlEncode(options_.audience).c_str()));
354   body_parts.push_back(absl::StrFormat(
355       "grant_type=%s",
356       UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE).c_str()));
357   body_parts.push_back(absl::StrFormat(
358       "requested_token_type=%s",
359       UrlEncode(EXTERNAL_ACCOUNT_CREDENTIALS_REQUESTED_TOKEN_TYPE).c_str()));
360   body_parts.push_back(absl::StrFormat(
361       "subject_token_type=%s", UrlEncode(options_.subject_token_type).c_str()));
362   body_parts.push_back(
363       absl::StrFormat("subject_token=%s", UrlEncode(subject_token).c_str()));
364   std::string scope = GOOGLE_CLOUD_PLATFORM_DEFAULT_SCOPE;
365   if (options_.service_account_impersonation_url.empty()) {
366     scope = absl::StrJoin(scopes_, " ");
367   }
368   body_parts.push_back(absl::StrFormat("scope=%s", UrlEncode(scope).c_str()));
369   Json::Object addtional_options_json_object;
370   if (options_.client_id.empty() && options_.client_secret.empty()) {
371     addtional_options_json_object["userProject"] =
372         Json::FromString(options_.workforce_pool_user_project);
373   }
374   Json addtional_options_json =
375       Json::FromObject(std::move(addtional_options_json_object));
376   body_parts.push_back(absl::StrFormat(
377       "options=%s", UrlEncode(JsonDump(addtional_options_json)).c_str()));
378   std::string body = absl::StrJoin(body_parts, "&");
379   request.body = const_cast<char*>(body.c_str());
380   request.body_length = body.size();
381   grpc_http_response_destroy(&ctx_->response);
382   ctx_->response = {};
383   GRPC_CLOSURE_INIT(&ctx_->closure, OnExchangeToken, this, nullptr);
384   GPR_ASSERT(http_request_ == nullptr);
385   RefCountedPtr<grpc_channel_credentials> http_request_creds;
386   if (uri->scheme() == "http") {
387     http_request_creds = RefCountedPtr<grpc_channel_credentials>(
388         grpc_insecure_credentials_create());
389   } else {
390     http_request_creds = CreateHttpRequestSSLCredentials();
391   }
392   http_request_ =
393       HttpRequest::Post(std::move(*uri), nullptr /* channel args */,
394                         ctx_->pollent, &request, ctx_->deadline, &ctx_->closure,
395                         &ctx_->response, std::move(http_request_creds));
396   http_request_->Start();
397   request.body = nullptr;
398   grpc_http_request_destroy(&request);
399 }
400 
OnExchangeToken(void * arg,grpc_error_handle error)401 void ExternalAccountCredentials::OnExchangeToken(void* arg,
402                                                  grpc_error_handle error) {
403   ExternalAccountCredentials* self =
404       static_cast<ExternalAccountCredentials*>(arg);
405   self->OnExchangeTokenInternal(error);
406 }
407 
OnExchangeTokenInternal(grpc_error_handle error)408 void ExternalAccountCredentials::OnExchangeTokenInternal(
409     grpc_error_handle error) {
410   http_request_.reset();
411   if (!error.ok()) {
412     FinishTokenFetch(error);
413   } else {
414     if (options_.service_account_impersonation_url.empty()) {
415       metadata_req_->response = ctx_->response;
416       metadata_req_->response.body = gpr_strdup(
417           std::string(ctx_->response.body, ctx_->response.body_length).c_str());
418       metadata_req_->response.hdrs = static_cast<grpc_http_header*>(
419           gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count));
420       for (size_t i = 0; i < ctx_->response.hdr_count; i++) {
421         metadata_req_->response.hdrs[i].key =
422             gpr_strdup(ctx_->response.hdrs[i].key);
423         metadata_req_->response.hdrs[i].value =
424             gpr_strdup(ctx_->response.hdrs[i].value);
425       }
426       FinishTokenFetch(absl::OkStatus());
427     } else {
428       ImpersenateServiceAccount();
429     }
430   }
431 }
432 
ImpersenateServiceAccount()433 void ExternalAccountCredentials::ImpersenateServiceAccount() {
434   absl::string_view response_body(ctx_->response.body,
435                                   ctx_->response.body_length);
436   auto json = JsonParse(response_body);
437   if (!json.ok()) {
438     FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrCat(
439         "Invalid token exchange response: ", json.status().ToString())));
440     return;
441   }
442   if (json->type() != Json::Type::kObject) {
443     FinishTokenFetch(GRPC_ERROR_CREATE(
444         "Invalid token exchange response: JSON type is not object"));
445     return;
446   }
447   auto it = json->object().find("access_token");
448   if (it == json->object().end() || it->second.type() != Json::Type::kString) {
449     FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
450         "Missing or invalid access_token in %s.", response_body)));
451     return;
452   }
453   std::string access_token = it->second.string();
454   absl::StatusOr<URI> uri =
455       URI::Parse(options_.service_account_impersonation_url);
456   if (!uri.ok()) {
457     FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
458         "Invalid service account impersonation url: %s. Error: %s",
459         options_.service_account_impersonation_url, uri.status().ToString())));
460     return;
461   }
462   grpc_http_request request;
463   memset(&request, 0, sizeof(grpc_http_request));
464   request.hdr_count = 2;
465   grpc_http_header* headers = static_cast<grpc_http_header*>(
466       gpr_malloc(sizeof(grpc_http_header) * request.hdr_count));
467   headers[0].key = gpr_strdup("Content-Type");
468   headers[0].value = gpr_strdup("application/x-www-form-urlencoded");
469   std::string str = absl::StrFormat("Bearer %s", access_token);
470   headers[1].key = gpr_strdup("Authorization");
471   headers[1].value = gpr_strdup(str.c_str());
472   request.hdrs = headers;
473   std::vector<std::string> body_members;
474   std::string scope = absl::StrJoin(scopes_, " ");
475   body_members.push_back(absl::StrFormat("scope=%s", UrlEncode(scope).c_str()));
476   body_members.push_back(absl::StrFormat(
477       "lifetime=%ds",
478       options_.service_account_impersonation.token_lifetime_seconds));
479   std::string body = absl::StrJoin(body_members, "&");
480   request.body = const_cast<char*>(body.c_str());
481   request.body_length = body.size();
482   grpc_http_response_destroy(&ctx_->response);
483   ctx_->response = {};
484   GRPC_CLOSURE_INIT(&ctx_->closure, OnImpersenateServiceAccount, this, nullptr);
485   // TODO(ctiller): Use the callers resource quota.
486   GPR_ASSERT(http_request_ == nullptr);
487   RefCountedPtr<grpc_channel_credentials> http_request_creds;
488   if (uri->scheme() == "http") {
489     http_request_creds = RefCountedPtr<grpc_channel_credentials>(
490         grpc_insecure_credentials_create());
491   } else {
492     http_request_creds = CreateHttpRequestSSLCredentials();
493   }
494   http_request_ = HttpRequest::Post(
495       std::move(*uri), nullptr, ctx_->pollent, &request, ctx_->deadline,
496       &ctx_->closure, &ctx_->response, std::move(http_request_creds));
497   http_request_->Start();
498   request.body = nullptr;
499   grpc_http_request_destroy(&request);
500 }
501 
OnImpersenateServiceAccount(void * arg,grpc_error_handle error)502 void ExternalAccountCredentials::OnImpersenateServiceAccount(
503     void* arg, grpc_error_handle error) {
504   ExternalAccountCredentials* self =
505       static_cast<ExternalAccountCredentials*>(arg);
506   self->OnImpersenateServiceAccountInternal(error);
507 }
508 
OnImpersenateServiceAccountInternal(grpc_error_handle error)509 void ExternalAccountCredentials::OnImpersenateServiceAccountInternal(
510     grpc_error_handle error) {
511   http_request_.reset();
512   if (!error.ok()) {
513     FinishTokenFetch(error);
514     return;
515   }
516   absl::string_view response_body(ctx_->response.body,
517                                   ctx_->response.body_length);
518   auto json = JsonParse(response_body);
519   if (!json.ok()) {
520     FinishTokenFetch(GRPC_ERROR_CREATE(
521         absl::StrCat("Invalid service account impersonation response: ",
522                      json.status().ToString())));
523     return;
524   }
525   if (json->type() != Json::Type::kObject) {
526     FinishTokenFetch(
527         GRPC_ERROR_CREATE("Invalid service account impersonation response: "
528                           "JSON type is not object"));
529     return;
530   }
531   auto it = json->object().find("accessToken");
532   if (it == json->object().end() || it->second.type() != Json::Type::kString) {
533     FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
534         "Missing or invalid accessToken in %s.", response_body)));
535     return;
536   }
537   std::string access_token = it->second.string();
538   it = json->object().find("expireTime");
539   if (it == json->object().end() || it->second.type() != Json::Type::kString) {
540     FinishTokenFetch(GRPC_ERROR_CREATE(absl::StrFormat(
541         "Missing or invalid expireTime in %s.", response_body)));
542     return;
543   }
544   std::string expire_time = it->second.string();
545   absl::Time t;
546   if (!absl::ParseTime(absl::RFC3339_full, expire_time, &t, nullptr)) {
547     FinishTokenFetch(GRPC_ERROR_CREATE(
548         "Invalid expire time of service account impersonation response."));
549     return;
550   }
551   int64_t expire_in = (t - absl::Now()) / absl::Seconds(1);
552   std::string body = absl::StrFormat(
553       "{\"access_token\":\"%s\",\"expires_in\":%d,\"token_type\":\"Bearer\"}",
554       access_token, expire_in);
555   metadata_req_->response = ctx_->response;
556   metadata_req_->response.body = gpr_strdup(body.c_str());
557   metadata_req_->response.body_length = body.length();
558   metadata_req_->response.hdrs = static_cast<grpc_http_header*>(
559       gpr_malloc(sizeof(grpc_http_header) * ctx_->response.hdr_count));
560   for (size_t i = 0; i < ctx_->response.hdr_count; i++) {
561     metadata_req_->response.hdrs[i].key =
562         gpr_strdup(ctx_->response.hdrs[i].key);
563     metadata_req_->response.hdrs[i].value =
564         gpr_strdup(ctx_->response.hdrs[i].value);
565   }
566   FinishTokenFetch(absl::OkStatus());
567 }
568 
FinishTokenFetch(grpc_error_handle error)569 void ExternalAccountCredentials::FinishTokenFetch(grpc_error_handle error) {
570   GRPC_LOG_IF_ERROR("Fetch external account credentials access token", error);
571   // Move object state into local variables.
572   auto* cb = response_cb_;
573   response_cb_ = nullptr;
574   auto* metadata_req = metadata_req_;
575   metadata_req_ = nullptr;
576   auto* ctx = ctx_;
577   ctx_ = nullptr;
578   // Invoke the callback.
579   cb(metadata_req, error);
580   // Delete context.
581   delete ctx;
582 }
583 
584 }  // namespace grpc_core
585 
grpc_external_account_credentials_create(const char * json_string,const char * scopes_string)586 grpc_call_credentials* grpc_external_account_credentials_create(
587     const char* json_string, const char* scopes_string) {
588   auto json = grpc_core::JsonParse(json_string);
589   if (!json.ok()) {
590     gpr_log(GPR_ERROR,
591             "External account credentials creation failed. Error: %s.",
592             json.status().ToString().c_str());
593     return nullptr;
594   }
595   std::vector<std::string> scopes = absl::StrSplit(scopes_string, ',');
596   grpc_error_handle error;
597   auto creds = grpc_core::ExternalAccountCredentials::Create(
598                    *json, std::move(scopes), &error)
599                    .release();
600   if (!error.ok()) {
601     gpr_log(GPR_ERROR,
602             "External account credentials creation failed. Error: %s.",
603             grpc_core::StatusToString(error).c_str());
604     return nullptr;
605   }
606   return creds;
607 }
608