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 "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
20
21 #include <grpc/credentials.h>
22 #include <grpc/grpc.h>
23 #include <grpc/grpc_security.h>
24 #include <grpc/slice.h>
25 #include <grpc/support/alloc.h>
26 #include <grpc/support/json.h>
27 #include <grpc/support/port_platform.h>
28 #include <grpc/support/string_util.h>
29 #include <grpc/support/time.h>
30 #include <string.h>
31
32 #include <algorithm>
33 #include <atomic>
34 #include <map>
35 #include <memory>
36 #include <vector>
37
38 #include "absl/log/check.h"
39 #include "absl/log/log.h"
40 #include "absl/status/status.h"
41 #include "absl/strings/numbers.h"
42 #include "absl/strings/str_cat.h"
43 #include "absl/strings/str_format.h"
44 #include "absl/strings/str_join.h"
45 #include "absl/strings/string_view.h"
46 #include "src/core/lib/debug/trace.h"
47 #include "src/core/lib/iomgr/error.h"
48 #include "src/core/lib/iomgr/pollset_set.h"
49 #include "src/core/lib/promise/context.h"
50 #include "src/core/lib/promise/poll.h"
51 #include "src/core/lib/promise/promise.h"
52 #include "src/core/lib/security/util/json_util.h"
53 #include "src/core/lib/transport/error_utils.h"
54 #include "src/core/lib/transport/metadata_batch.h"
55 #include "src/core/util/http_client/httpcli_ssl_credentials.h"
56 #include "src/core/util/json/json.h"
57 #include "src/core/util/json/json_reader.h"
58 #include "src/core/util/load_file.h"
59 #include "src/core/util/memory.h"
60 #include "src/core/util/ref_counted_ptr.h"
61 #include "src/core/util/status_helper.h"
62 #include "src/core/util/uri.h"
63
64 using grpc_core::Json;
65
66 //
67 // Auth Refresh Token.
68 //
69
grpc_auth_refresh_token_is_valid(const grpc_auth_refresh_token * refresh_token)70 int grpc_auth_refresh_token_is_valid(
71 const grpc_auth_refresh_token* refresh_token) {
72 return (refresh_token != nullptr) &&
73 strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID) != 0;
74 }
75
grpc_auth_refresh_token_create_from_json(const Json & json)76 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
77 const Json& json) {
78 grpc_auth_refresh_token result;
79 const char* prop_value;
80 int success = 0;
81 grpc_error_handle error;
82
83 memset(&result, 0, sizeof(grpc_auth_refresh_token));
84 result.type = GRPC_AUTH_JSON_TYPE_INVALID;
85 if (json.type() != Json::Type::kObject) {
86 LOG(ERROR) << "Invalid json.";
87 goto end;
88 }
89
90 prop_value = grpc_json_get_string_property(json, "type", &error);
91 GRPC_LOG_IF_ERROR("Parsing refresh token", error);
92 if (prop_value == nullptr ||
93 strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER) != 0) {
94 goto end;
95 }
96 result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER;
97
98 if (!grpc_copy_json_string_property(json, "client_secret",
99 &result.client_secret) ||
100 !grpc_copy_json_string_property(json, "client_id", &result.client_id) ||
101 !grpc_copy_json_string_property(json, "refresh_token",
102 &result.refresh_token)) {
103 goto end;
104 }
105 success = 1;
106
107 end:
108 if (!success) grpc_auth_refresh_token_destruct(&result);
109 return result;
110 }
111
grpc_auth_refresh_token_create_from_string(const char * json_string)112 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string(
113 const char* json_string) {
114 Json json;
115 auto json_or = grpc_core::JsonParse(json_string);
116 if (!json_or.ok()) {
117 LOG(ERROR) << "JSON parsing failed: " << json_or.status();
118 } else {
119 json = std::move(*json_or);
120 }
121 return grpc_auth_refresh_token_create_from_json(json);
122 }
123
grpc_auth_refresh_token_destruct(grpc_auth_refresh_token * refresh_token)124 void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token) {
125 if (refresh_token == nullptr) return;
126 refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID;
127 if (refresh_token->client_id != nullptr) {
128 gpr_free(refresh_token->client_id);
129 refresh_token->client_id = nullptr;
130 }
131 if (refresh_token->client_secret != nullptr) {
132 gpr_free(refresh_token->client_secret);
133 refresh_token->client_secret = nullptr;
134 }
135 if (refresh_token->refresh_token != nullptr) {
136 gpr_free(refresh_token->refresh_token);
137 refresh_token->refresh_token = nullptr;
138 }
139 }
140
141 //
142 // Oauth2 Token parsing.
143 //
144
145 grpc_credentials_status
grpc_oauth2_token_fetcher_credentials_parse_server_response_body(absl::string_view body,absl::optional<grpc_core::Slice> * token_value,grpc_core::Duration * token_lifetime)146 grpc_oauth2_token_fetcher_credentials_parse_server_response_body(
147 absl::string_view body, absl::optional<grpc_core::Slice>* token_value,
148 grpc_core::Duration* token_lifetime) {
149 auto json = grpc_core::JsonParse(body);
150 if (!json.ok()) {
151 LOG(ERROR) << "Could not parse JSON from " << body << ": " << json.status();
152 return GRPC_CREDENTIALS_ERROR;
153 }
154 if (json->type() != Json::Type::kObject) {
155 LOG(ERROR) << "Response should be a JSON object";
156 return GRPC_CREDENTIALS_ERROR;
157 }
158 auto it = json->object().find("access_token");
159 if (it == json->object().end() || it->second.type() != Json::Type::kString) {
160 LOG(ERROR) << "Missing or invalid access_token in JSON.";
161 return GRPC_CREDENTIALS_ERROR;
162 }
163 absl::string_view access_token = it->second.string();
164 it = json->object().find("token_type");
165 if (it == json->object().end() || it->second.type() != Json::Type::kString) {
166 LOG(ERROR) << "Missing or invalid token_type in JSON.";
167 return GRPC_CREDENTIALS_ERROR;
168 }
169 absl::string_view token_type = it->second.string();
170 it = json->object().find("expires_in");
171 if (it == json->object().end() || it->second.type() != Json::Type::kNumber) {
172 LOG(ERROR) << "Missing or invalid expires_in in JSON.";
173 return GRPC_CREDENTIALS_ERROR;
174 }
175 absl::string_view expires_in = it->second.string();
176 long seconds;
177 if (!absl::SimpleAtoi(expires_in, &seconds)) {
178 LOG(ERROR) << "Invalid expires_in in JSON.";
179 return GRPC_CREDENTIALS_ERROR;
180 }
181 *token_lifetime = grpc_core::Duration::Seconds(seconds);
182 *token_value = grpc_core::Slice::FromCopiedString(
183 absl::StrCat(token_type, " ", access_token));
184 return GRPC_CREDENTIALS_OK;
185 }
186
187 grpc_credentials_status
grpc_oauth2_token_fetcher_credentials_parse_server_response(const grpc_http_response * response,absl::optional<grpc_core::Slice> * token_value,grpc_core::Duration * token_lifetime)188 grpc_oauth2_token_fetcher_credentials_parse_server_response(
189 const grpc_http_response* response,
190 absl::optional<grpc_core::Slice>* token_value,
191 grpc_core::Duration* token_lifetime) {
192 *token_value = absl::nullopt;
193 if (response == nullptr) {
194 LOG(ERROR) << "Received NULL response.";
195 return GRPC_CREDENTIALS_ERROR;
196 }
197 absl::string_view body(response->body, response->body_length);
198 if (response->status != 200) {
199 LOG(ERROR) << "Call to http server ended with error " << response->status
200 << " [" << body << "]";
201 return GRPC_CREDENTIALS_ERROR;
202 }
203 return grpc_oauth2_token_fetcher_credentials_parse_server_response_body(
204 body, token_value, token_lifetime);
205 }
206
207 //
208 // Oauth2TokenFetcherCredentials
209 //
210
211 namespace grpc_core {
212
213 // State held for a pending HTTP request.
214 class Oauth2TokenFetcherCredentials::HttpFetchRequest final
215 : public TokenFetcherCredentials::FetchRequest {
216 public:
HttpFetchRequest(Oauth2TokenFetcherCredentials * creds,Timestamp deadline,absl::AnyInvocable<void (absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)> on_done)217 HttpFetchRequest(
218 Oauth2TokenFetcherCredentials* creds, Timestamp deadline,
219 absl::AnyInvocable<
220 void(absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)>
221 on_done)
222 : on_done_(std::move(on_done)) {
223 GRPC_CLOSURE_INIT(&on_http_response_, OnHttpResponse, this, nullptr);
224 Ref().release(); // Ref held by HTTP request callback.
225 http_request_ = creds->StartHttpRequest(creds->pollent(), deadline,
226 &response_, &on_http_response_);
227 }
228
~HttpFetchRequest()229 ~HttpFetchRequest() override { grpc_http_response_destroy(&response_); }
230
Orphan()231 void Orphan() override {
232 http_request_.reset();
233 Unref();
234 }
235
236 private:
OnHttpResponse(void * arg,grpc_error_handle error)237 static void OnHttpResponse(void* arg, grpc_error_handle error) {
238 RefCountedPtr<HttpFetchRequest> self(static_cast<HttpFetchRequest*>(arg));
239 if (!error.ok()) {
240 self->on_done_(std::move(error));
241 return;
242 }
243 // Parse oauth2 token.
244 absl::optional<Slice> access_token_value;
245 Duration token_lifetime;
246 grpc_credentials_status status =
247 grpc_oauth2_token_fetcher_credentials_parse_server_response(
248 &self->response_, &access_token_value, &token_lifetime);
249 if (status != GRPC_CREDENTIALS_OK) {
250 self->on_done_(absl::UnavailableError("error parsing oauth2 token"));
251 return;
252 }
253 self->on_done_(MakeRefCounted<Token>(std::move(*access_token_value),
254 Timestamp::Now() + token_lifetime));
255 }
256
257 OrphanablePtr<HttpRequest> http_request_;
258 grpc_closure on_http_response_;
259 grpc_http_response response_;
260 absl::AnyInvocable<void(
261 absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)>
262 on_done_;
263 };
264
debug_string()265 std::string Oauth2TokenFetcherCredentials::debug_string() {
266 return "OAuth2TokenFetcherCredentials";
267 }
268
type() const269 UniqueTypeName Oauth2TokenFetcherCredentials::type() const {
270 static UniqueTypeName::Factory kFactory("Oauth2");
271 return kFactory.Create();
272 }
273
274 OrphanablePtr<TokenFetcherCredentials::FetchRequest>
FetchToken(Timestamp deadline,absl::AnyInvocable<void (absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)> on_done)275 Oauth2TokenFetcherCredentials::FetchToken(
276 Timestamp deadline,
277 absl::AnyInvocable<
278 void(absl::StatusOr<RefCountedPtr<TokenFetcherCredentials::Token>>)>
279 on_done) {
280 return MakeOrphanable<HttpFetchRequest>(this, deadline, std::move(on_done));
281 }
282
283 } // namespace grpc_core
284
285 //
286 // Google Compute Engine credentials.
287 //
288
289 namespace {
290
291 class grpc_compute_engine_token_fetcher_credentials
292 : public grpc_core::Oauth2TokenFetcherCredentials {
293 public:
294 grpc_compute_engine_token_fetcher_credentials() = default;
295 ~grpc_compute_engine_token_fetcher_credentials() override = default;
296
debug_string()297 std::string debug_string() override {
298 return absl::StrFormat(
299 "GoogleComputeEngineTokenFetcherCredentials{%s}",
300 grpc_core::Oauth2TokenFetcherCredentials::debug_string());
301 }
302
303 private:
StartHttpRequest(grpc_polling_entity * pollent,grpc_core::Timestamp deadline,grpc_http_response * response,grpc_closure * on_complete)304 grpc_core::OrphanablePtr<grpc_core::HttpRequest> StartHttpRequest(
305 grpc_polling_entity* pollent, grpc_core::Timestamp deadline,
306 grpc_http_response* response, grpc_closure* on_complete) override {
307 grpc_http_header header = {const_cast<char*>("Metadata-Flavor"),
308 const_cast<char*>("Google")};
309 grpc_http_request request;
310 memset(&request, 0, sizeof(grpc_http_request));
311 request.hdr_count = 1;
312 request.hdrs = &header;
313 // TODO(ctiller): Carry the memory quota in ctx and share it with the host
314 // channel. This would allow us to cancel an authentication query when under
315 // extreme memory pressure.
316 auto uri = grpc_core::URI::Create("http", GRPC_COMPUTE_ENGINE_METADATA_HOST,
317 GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH,
318 {} /* query params */, "" /* fragment */);
319 CHECK(uri.ok()); // params are hardcoded
320 auto http_request = grpc_core::HttpRequest::Get(
321 std::move(*uri), /*args=*/nullptr, pollent, &request, deadline,
322 on_complete, response,
323 grpc_core::RefCountedPtr<grpc_channel_credentials>(
324 grpc_insecure_credentials_create()));
325 http_request->Start();
326 return http_request;
327 }
328 };
329
330 } // namespace
331
grpc_google_compute_engine_credentials_create(void * reserved)332 grpc_call_credentials* grpc_google_compute_engine_credentials_create(
333 void* reserved) {
334 GRPC_TRACE_LOG(api, INFO)
335 << "grpc_compute_engine_credentials_create(reserved=" << reserved << ")";
336 CHECK_EQ(reserved, nullptr);
337 return grpc_core::MakeRefCounted<
338 grpc_compute_engine_token_fetcher_credentials>()
339 .release();
340 }
341
342 //
343 // Google Refresh Token credentials.
344 //
345
grpc_google_refresh_token_credentials(grpc_auth_refresh_token refresh_token)346 grpc_google_refresh_token_credentials::grpc_google_refresh_token_credentials(
347 grpc_auth_refresh_token refresh_token)
348 : refresh_token_(refresh_token) {}
349
350 grpc_google_refresh_token_credentials::
~grpc_google_refresh_token_credentials()351 ~grpc_google_refresh_token_credentials() {
352 grpc_auth_refresh_token_destruct(&refresh_token_);
353 }
354
355 grpc_core::OrphanablePtr<grpc_core::HttpRequest>
StartHttpRequest(grpc_polling_entity * pollent,grpc_core::Timestamp deadline,grpc_http_response * response,grpc_closure * on_complete)356 grpc_google_refresh_token_credentials::StartHttpRequest(
357 grpc_polling_entity* pollent, grpc_core::Timestamp deadline,
358 grpc_http_response* response, grpc_closure* on_complete) {
359 grpc_http_header header = {
360 const_cast<char*>("Content-Type"),
361 const_cast<char*>("application/x-www-form-urlencoded")};
362 std::string body = absl::StrFormat(
363 GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING, refresh_token_.client_id,
364 refresh_token_.client_secret, refresh_token_.refresh_token);
365 grpc_http_request request;
366 memset(&request, 0, sizeof(grpc_http_request));
367 request.hdr_count = 1;
368 request.hdrs = &header;
369 request.body = const_cast<char*>(body.c_str());
370 request.body_length = body.size();
371 // TODO(ctiller): Carry the memory quota in ctx and share it with the host
372 // channel. This would allow us to cancel an authentication query when under
373 // extreme memory pressure.
374 auto uri = grpc_core::URI::Create("https", GRPC_GOOGLE_OAUTH2_SERVICE_HOST,
375 GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH,
376 {} /* query params */, "" /* fragment */);
377 CHECK(uri.ok()); // params are hardcoded
378 auto http_request = grpc_core::HttpRequest::Post(
379 std::move(*uri), /*args=*/nullptr, pollent, &request, deadline,
380 on_complete, response, grpc_core::CreateHttpRequestSSLCredentials());
381 http_request->Start();
382 return http_request;
383 }
384
385 grpc_core::RefCountedPtr<grpc_call_credentials>
grpc_refresh_token_credentials_create_from_auth_refresh_token(grpc_auth_refresh_token refresh_token)386 grpc_refresh_token_credentials_create_from_auth_refresh_token(
387 grpc_auth_refresh_token refresh_token) {
388 if (!grpc_auth_refresh_token_is_valid(&refresh_token)) {
389 LOG(ERROR) << "Invalid input for refresh token credentials creation";
390 return nullptr;
391 }
392 return grpc_core::MakeRefCounted<grpc_google_refresh_token_credentials>(
393 refresh_token);
394 }
395
debug_string()396 std::string grpc_google_refresh_token_credentials::debug_string() {
397 return absl::StrFormat(
398 "GoogleRefreshToken{ClientID:%s,%s}", refresh_token_.client_id,
399 grpc_core::Oauth2TokenFetcherCredentials::debug_string());
400 }
401
type() const402 grpc_core::UniqueTypeName grpc_google_refresh_token_credentials::type() const {
403 static grpc_core::UniqueTypeName::Factory kFactory("GoogleRefreshToken");
404 return kFactory.Create();
405 }
406
create_loggable_refresh_token(grpc_auth_refresh_token * token)407 static std::string create_loggable_refresh_token(
408 grpc_auth_refresh_token* token) {
409 if (strcmp(token->type, GRPC_AUTH_JSON_TYPE_INVALID) == 0) {
410 return "<Invalid json token>";
411 }
412 return absl::StrFormat(
413 "{\n type: %s\n client_id: %s\n client_secret: "
414 "<redacted>\n refresh_token: <redacted>\n}",
415 token->type, token->client_id);
416 }
417
grpc_google_refresh_token_credentials_create(const char * json_refresh_token,void * reserved)418 grpc_call_credentials* grpc_google_refresh_token_credentials_create(
419 const char* json_refresh_token, void* reserved) {
420 grpc_auth_refresh_token token =
421 grpc_auth_refresh_token_create_from_string(json_refresh_token);
422 GRPC_TRACE_LOG(api, INFO)
423 << "grpc_refresh_token_credentials_create(json_refresh_token="
424 << create_loggable_refresh_token(&token) << ", reserved=" << reserved
425 << ")";
426 CHECK_EQ(reserved, nullptr);
427 return grpc_refresh_token_credentials_create_from_auth_refresh_token(token)
428 .release();
429 }
430
431 //
432 // STS credentials.
433 //
434
435 namespace grpc_core {
436
437 namespace {
438
MaybeAddToBody(const char * field_name,const char * field,std::vector<std::string> * body)439 void MaybeAddToBody(const char* field_name, const char* field,
440 std::vector<std::string>* body) {
441 if (field == nullptr || strlen(field) == 0) return;
442 body->push_back(absl::StrFormat("&%s=%s", field_name, field));
443 }
444
LoadTokenFile(const char * path,grpc_slice * token)445 grpc_error_handle LoadTokenFile(const char* path, grpc_slice* token) {
446 auto slice = LoadFile(path, /*add_null_terminator=*/true);
447 if (!slice.ok()) return slice.status();
448 if (slice->empty()) {
449 LOG(ERROR) << "Token file " << path << " is empty";
450 return GRPC_ERROR_CREATE("Token file is empty.");
451 }
452 *token = slice->TakeCSlice();
453 return absl::OkStatus();
454 }
455
456 class StsTokenFetcherCredentials : public Oauth2TokenFetcherCredentials {
457 public:
StsTokenFetcherCredentials(URI sts_url,const grpc_sts_credentials_options * options)458 StsTokenFetcherCredentials(URI sts_url,
459 const grpc_sts_credentials_options* options)
460 : sts_url_(std::move(sts_url)),
461 resource_(gpr_strdup(options->resource)),
462 audience_(gpr_strdup(options->audience)),
463 scope_(gpr_strdup(options->scope)),
464 requested_token_type_(gpr_strdup(options->requested_token_type)),
465 subject_token_path_(gpr_strdup(options->subject_token_path)),
466 subject_token_type_(gpr_strdup(options->subject_token_type)),
467 actor_token_path_(gpr_strdup(options->actor_token_path)),
468 actor_token_type_(gpr_strdup(options->actor_token_type)) {}
469
debug_string()470 std::string debug_string() override {
471 return absl::StrFormat(
472 "StsTokenFetcherCredentials{Path:%s,Authority:%s,%s}", sts_url_.path(),
473 sts_url_.authority(), Oauth2TokenFetcherCredentials::debug_string());
474 }
475
476 private:
StartHttpRequest(grpc_polling_entity * pollent,Timestamp deadline,grpc_http_response * response,grpc_closure * on_complete)477 OrphanablePtr<HttpRequest> StartHttpRequest(
478 grpc_polling_entity* pollent, Timestamp deadline,
479 grpc_http_response* response, grpc_closure* on_complete) override {
480 grpc_http_request request;
481 memset(&request, 0, sizeof(grpc_http_request));
482 grpc_error_handle err = FillBody(&request.body, &request.body_length);
483 if (!err.ok()) {
484 ExecCtx::Run(DEBUG_LOCATION, on_complete, std::move(err));
485 return nullptr;
486 }
487 grpc_http_header header = {
488 const_cast<char*>("Content-Type"),
489 const_cast<char*>("application/x-www-form-urlencoded")};
490 request.hdr_count = 1;
491 request.hdrs = &header;
492 // TODO(ctiller): Carry the memory quota in ctx and share it with the host
493 // channel. This would allow us to cancel an authentication query when under
494 // extreme memory pressure.
495 RefCountedPtr<grpc_channel_credentials> http_request_creds;
496 if (sts_url_.scheme() == "http") {
497 http_request_creds = RefCountedPtr<grpc_channel_credentials>(
498 grpc_insecure_credentials_create());
499 } else {
500 http_request_creds = CreateHttpRequestSSLCredentials();
501 }
502 auto http_request = HttpRequest::Post(
503 sts_url_, /*args=*/nullptr, pollent, &request, deadline, on_complete,
504 response, std::move(http_request_creds));
505 http_request->Start();
506 gpr_free(request.body);
507 return http_request;
508 }
509
FillBody(char ** body,size_t * body_length)510 grpc_error_handle FillBody(char** body, size_t* body_length) {
511 *body = nullptr;
512 std::vector<std::string> body_parts;
513 grpc_slice subject_token = grpc_empty_slice();
514 grpc_slice actor_token = grpc_empty_slice();
515 grpc_error_handle err;
516
517 auto cleanup = [&body, &body_length, &body_parts, &subject_token,
518 &actor_token, &err]() {
519 if (err.ok()) {
520 std::string body_str = absl::StrJoin(body_parts, "");
521 *body = gpr_strdup(body_str.c_str());
522 *body_length = body_str.size();
523 }
524 CSliceUnref(subject_token);
525 CSliceUnref(actor_token);
526 return err;
527 };
528
529 err = LoadTokenFile(subject_token_path_.get(), &subject_token);
530 if (!err.ok()) return cleanup();
531 body_parts.push_back(absl::StrFormat(
532 GRPC_STS_POST_MINIMAL_BODY_FORMAT_STRING,
533 reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(subject_token)),
534 subject_token_type_.get()));
535 MaybeAddToBody("resource", resource_.get(), &body_parts);
536 MaybeAddToBody("audience", audience_.get(), &body_parts);
537 MaybeAddToBody("scope", scope_.get(), &body_parts);
538 MaybeAddToBody("requested_token_type", requested_token_type_.get(),
539 &body_parts);
540 if ((actor_token_path_ != nullptr) && *actor_token_path_ != '\0') {
541 err = LoadTokenFile(actor_token_path_.get(), &actor_token);
542 if (!err.ok()) return cleanup();
543 MaybeAddToBody(
544 "actor_token",
545 reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(actor_token)),
546 &body_parts);
547 MaybeAddToBody("actor_token_type", actor_token_type_.get(), &body_parts);
548 }
549 return cleanup();
550 }
551
552 URI sts_url_;
553 UniquePtr<char> resource_;
554 UniquePtr<char> audience_;
555 UniquePtr<char> scope_;
556 UniquePtr<char> requested_token_type_;
557 UniquePtr<char> subject_token_path_;
558 UniquePtr<char> subject_token_type_;
559 UniquePtr<char> actor_token_path_;
560 UniquePtr<char> actor_token_type_;
561 OrphanablePtr<HttpRequest> http_request_;
562 };
563
564 } // namespace
565
ValidateStsCredentialsOptions(const grpc_sts_credentials_options * options)566 absl::StatusOr<URI> ValidateStsCredentialsOptions(
567 const grpc_sts_credentials_options* options) {
568 std::vector<grpc_error_handle> error_list;
569 absl::StatusOr<URI> sts_url =
570 URI::Parse(options->token_exchange_service_uri == nullptr
571 ? ""
572 : options->token_exchange_service_uri);
573 if (!sts_url.ok()) {
574 error_list.push_back(GRPC_ERROR_CREATE(
575 absl::StrFormat("Invalid or missing STS endpoint URL. Error: %s",
576 sts_url.status().ToString())));
577 } else if (sts_url->scheme() != "https" && sts_url->scheme() != "http") {
578 error_list.push_back(
579 GRPC_ERROR_CREATE("Invalid URI scheme, must be https to http."));
580 }
581 if (options->subject_token_path == nullptr ||
582 strlen(options->subject_token_path) == 0) {
583 error_list.push_back(
584 GRPC_ERROR_CREATE("subject_token needs to be specified"));
585 }
586 if (options->subject_token_type == nullptr ||
587 strlen(options->subject_token_type) == 0) {
588 error_list.push_back(
589 GRPC_ERROR_CREATE("subject_token_type needs to be specified"));
590 }
591 if (error_list.empty()) {
592 return sts_url;
593 }
594 auto grpc_error_vec = GRPC_ERROR_CREATE_FROM_VECTOR(
595 "Invalid STS Credentials Options", &error_list);
596 auto retval = absl::InvalidArgumentError(StatusToString(grpc_error_vec));
597 return retval;
598 }
599
600 } // namespace grpc_core
601
grpc_sts_credentials_create(const grpc_sts_credentials_options * options,void * reserved)602 grpc_call_credentials* grpc_sts_credentials_create(
603 const grpc_sts_credentials_options* options, void* reserved) {
604 CHECK_EQ(reserved, nullptr);
605 absl::StatusOr<grpc_core::URI> sts_url =
606 grpc_core::ValidateStsCredentialsOptions(options);
607 if (!sts_url.ok()) {
608 LOG(ERROR) << "STS Credentials creation failed. Error: "
609 << sts_url.status();
610 return nullptr;
611 }
612 return grpc_core::MakeRefCounted<grpc_core::StsTokenFetcherCredentials>(
613 std::move(*sts_url), options)
614 .release();
615 }
616
617 //
618 // Oauth2 Access Token credentials.
619 //
620
621 grpc_core::ArenaPromise<absl::StatusOr<grpc_core::ClientMetadataHandle>>
GetRequestMetadata(grpc_core::ClientMetadataHandle initial_metadata,const grpc_call_credentials::GetRequestMetadataArgs *)622 grpc_access_token_credentials::GetRequestMetadata(
623 grpc_core::ClientMetadataHandle initial_metadata,
624 const grpc_call_credentials::GetRequestMetadataArgs*) {
625 initial_metadata->Append(
626 GRPC_AUTHORIZATION_METADATA_KEY, access_token_value_.Ref(),
627 [](absl::string_view, const grpc_core::Slice&) { abort(); });
628 return grpc_core::Immediate(std::move(initial_metadata));
629 }
630
Type()631 grpc_core::UniqueTypeName grpc_access_token_credentials::Type() {
632 static grpc_core::UniqueTypeName::Factory kFactory("AccessToken");
633 return kFactory.Create();
634 }
635
grpc_access_token_credentials(const char * access_token)636 grpc_access_token_credentials::grpc_access_token_credentials(
637 const char* access_token)
638 : access_token_value_(grpc_core::Slice::FromCopiedString(
639 absl::StrCat("Bearer ", access_token))) {}
640
debug_string()641 std::string grpc_access_token_credentials::debug_string() {
642 return "AccessTokenCredentials{Token:present}";
643 }
644
grpc_access_token_credentials_create(const char * access_token,void * reserved)645 grpc_call_credentials* grpc_access_token_credentials_create(
646 const char* access_token, void* reserved) {
647 GRPC_TRACE_LOG(api, INFO) << "grpc_access_token_credentials_create(access_"
648 "token=<redacted>, reserved="
649 << reserved << ")";
650 CHECK_EQ(reserved, nullptr);
651 return grpc_core::MakeRefCounted<grpc_access_token_credentials>(access_token)
652 .release();
653 }
654