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