Imported Upstream version 1.21.0
[platform/upstream/grpc.git] / src / core / lib / security / credentials / oauth2 / oauth2_credentials.cc
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/security/credentials/oauth2/oauth2_credentials.h"
22
23 #include <string.h>
24
25 #include "src/core/lib/gprpp/ref_counted_ptr.h"
26 #include "src/core/lib/security/util/json_util.h"
27 #include "src/core/lib/surface/api_trace.h"
28
29 #include <grpc/support/alloc.h>
30 #include <grpc/support/log.h>
31 #include <grpc/support/string_util.h>
32
33 //
34 // Auth Refresh Token.
35 //
36
37 int grpc_auth_refresh_token_is_valid(
38     const grpc_auth_refresh_token* refresh_token) {
39   return (refresh_token != nullptr) &&
40          strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID);
41 }
42
43 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json(
44     const grpc_json* json) {
45   grpc_auth_refresh_token result;
46   const char* prop_value;
47   int success = 0;
48
49   memset(&result, 0, sizeof(grpc_auth_refresh_token));
50   result.type = GRPC_AUTH_JSON_TYPE_INVALID;
51   if (json == nullptr) {
52     gpr_log(GPR_ERROR, "Invalid json.");
53     goto end;
54   }
55
56   prop_value = grpc_json_get_string_property(json, "type");
57   if (prop_value == nullptr ||
58       strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) {
59     goto end;
60   }
61   result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER;
62
63   if (!grpc_copy_json_string_property(json, "client_secret",
64                                       &result.client_secret) ||
65       !grpc_copy_json_string_property(json, "client_id", &result.client_id) ||
66       !grpc_copy_json_string_property(json, "refresh_token",
67                                       &result.refresh_token)) {
68     goto end;
69   }
70   success = 1;
71
72 end:
73   if (!success) grpc_auth_refresh_token_destruct(&result);
74   return result;
75 }
76
77 grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string(
78     const char* json_string) {
79   char* scratchpad = gpr_strdup(json_string);
80   grpc_json* json = grpc_json_parse_string(scratchpad);
81   grpc_auth_refresh_token result =
82       grpc_auth_refresh_token_create_from_json(json);
83   grpc_json_destroy(json);
84   gpr_free(scratchpad);
85   return result;
86 }
87
88 void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token* refresh_token) {
89   if (refresh_token == nullptr) return;
90   refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID;
91   if (refresh_token->client_id != nullptr) {
92     gpr_free(refresh_token->client_id);
93     refresh_token->client_id = nullptr;
94   }
95   if (refresh_token->client_secret != nullptr) {
96     gpr_free(refresh_token->client_secret);
97     refresh_token->client_secret = nullptr;
98   }
99   if (refresh_token->refresh_token != nullptr) {
100     gpr_free(refresh_token->refresh_token);
101     refresh_token->refresh_token = nullptr;
102   }
103 }
104
105 //
106 // Oauth2 Token Fetcher credentials.
107 //
108
109 grpc_oauth2_token_fetcher_credentials::
110     ~grpc_oauth2_token_fetcher_credentials() {
111   GRPC_MDELEM_UNREF(access_token_md_);
112   gpr_mu_destroy(&mu_);
113   grpc_pollset_set_destroy(grpc_polling_entity_pollset_set(&pollent_));
114   grpc_httpcli_context_destroy(&httpcli_context_);
115 }
116
117 grpc_credentials_status
118 grpc_oauth2_token_fetcher_credentials_parse_server_response(
119     const grpc_http_response* response, grpc_mdelem* token_md,
120     grpc_millis* token_lifetime) {
121   char* null_terminated_body = nullptr;
122   char* new_access_token = nullptr;
123   grpc_credentials_status status = GRPC_CREDENTIALS_OK;
124   grpc_json* json = nullptr;
125
126   if (response == nullptr) {
127     gpr_log(GPR_ERROR, "Received NULL response.");
128     status = GRPC_CREDENTIALS_ERROR;
129     goto end;
130   }
131
132   if (response->body_length > 0) {
133     null_terminated_body =
134         static_cast<char*>(gpr_malloc(response->body_length + 1));
135     null_terminated_body[response->body_length] = '\0';
136     memcpy(null_terminated_body, response->body, response->body_length);
137   }
138
139   if (response->status != 200) {
140     gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].",
141             response->status,
142             null_terminated_body != nullptr ? null_terminated_body : "");
143     status = GRPC_CREDENTIALS_ERROR;
144     goto end;
145   } else {
146     grpc_json* access_token = nullptr;
147     grpc_json* token_type = nullptr;
148     grpc_json* expires_in = nullptr;
149     grpc_json* ptr;
150     json = grpc_json_parse_string(null_terminated_body);
151     if (json == nullptr) {
152       gpr_log(GPR_ERROR, "Could not parse JSON from %s", null_terminated_body);
153       status = GRPC_CREDENTIALS_ERROR;
154       goto end;
155     }
156     if (json->type != GRPC_JSON_OBJECT) {
157       gpr_log(GPR_ERROR, "Response should be a JSON object");
158       status = GRPC_CREDENTIALS_ERROR;
159       goto end;
160     }
161     for (ptr = json->child; ptr; ptr = ptr->next) {
162       if (strcmp(ptr->key, "access_token") == 0) {
163         access_token = ptr;
164       } else if (strcmp(ptr->key, "token_type") == 0) {
165         token_type = ptr;
166       } else if (strcmp(ptr->key, "expires_in") == 0) {
167         expires_in = ptr;
168       }
169     }
170     if (access_token == nullptr || access_token->type != GRPC_JSON_STRING) {
171       gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON.");
172       status = GRPC_CREDENTIALS_ERROR;
173       goto end;
174     }
175     if (token_type == nullptr || token_type->type != GRPC_JSON_STRING) {
176       gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON.");
177       status = GRPC_CREDENTIALS_ERROR;
178       goto end;
179     }
180     if (expires_in == nullptr || expires_in->type != GRPC_JSON_NUMBER) {
181       gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON.");
182       status = GRPC_CREDENTIALS_ERROR;
183       goto end;
184     }
185     gpr_asprintf(&new_access_token, "%s %s", token_type->value,
186                  access_token->value);
187     *token_lifetime = strtol(expires_in->value, nullptr, 10) * GPR_MS_PER_SEC;
188     if (!GRPC_MDISNULL(*token_md)) GRPC_MDELEM_UNREF(*token_md);
189     *token_md = grpc_mdelem_from_slices(
190         grpc_slice_from_static_string(GRPC_AUTHORIZATION_METADATA_KEY),
191         grpc_slice_from_copied_string(new_access_token));
192     status = GRPC_CREDENTIALS_OK;
193   }
194
195 end:
196   if (status != GRPC_CREDENTIALS_OK && !GRPC_MDISNULL(*token_md)) {
197     GRPC_MDELEM_UNREF(*token_md);
198     *token_md = GRPC_MDNULL;
199   }
200   if (null_terminated_body != nullptr) gpr_free(null_terminated_body);
201   if (new_access_token != nullptr) gpr_free(new_access_token);
202   grpc_json_destroy(json);
203   return status;
204 }
205
206 static void on_oauth2_token_fetcher_http_response(void* user_data,
207                                                   grpc_error* error) {
208   GRPC_LOG_IF_ERROR("oauth_fetch", GRPC_ERROR_REF(error));
209   grpc_credentials_metadata_request* r =
210       static_cast<grpc_credentials_metadata_request*>(user_data);
211   grpc_oauth2_token_fetcher_credentials* c =
212       reinterpret_cast<grpc_oauth2_token_fetcher_credentials*>(r->creds.get());
213   c->on_http_response(r, error);
214 }
215
216 void grpc_oauth2_token_fetcher_credentials::on_http_response(
217     grpc_credentials_metadata_request* r, grpc_error* error) {
218   grpc_mdelem access_token_md = GRPC_MDNULL;
219   grpc_millis token_lifetime;
220   grpc_credentials_status status =
221       grpc_oauth2_token_fetcher_credentials_parse_server_response(
222           &r->response, &access_token_md, &token_lifetime);
223   // Update cache and grab list of pending requests.
224   gpr_mu_lock(&mu_);
225   token_fetch_pending_ = false;
226   access_token_md_ = GRPC_MDELEM_REF(access_token_md);
227   token_expiration_ =
228       status == GRPC_CREDENTIALS_OK
229           ? gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
230                          gpr_time_from_millis(token_lifetime, GPR_TIMESPAN))
231           : gpr_inf_past(GPR_CLOCK_MONOTONIC);
232   grpc_oauth2_pending_get_request_metadata* pending_request = pending_requests_;
233   pending_requests_ = nullptr;
234   gpr_mu_unlock(&mu_);
235   // Invoke callbacks for all pending requests.
236   while (pending_request != nullptr) {
237     if (status == GRPC_CREDENTIALS_OK) {
238       grpc_credentials_mdelem_array_add(pending_request->md_array,
239                                         access_token_md);
240     } else {
241       error = GRPC_ERROR_CREATE_REFERENCING_FROM_STATIC_STRING(
242           "Error occurred when fetching oauth2 token.", &error, 1);
243     }
244     GRPC_CLOSURE_SCHED(pending_request->on_request_metadata, error);
245     grpc_polling_entity_del_from_pollset_set(
246         pending_request->pollent, grpc_polling_entity_pollset_set(&pollent_));
247     grpc_oauth2_pending_get_request_metadata* prev = pending_request;
248     pending_request = pending_request->next;
249     gpr_free(prev);
250   }
251   GRPC_MDELEM_UNREF(access_token_md);
252   Unref();
253   grpc_credentials_metadata_request_destroy(r);
254 }
255
256 bool grpc_oauth2_token_fetcher_credentials::get_request_metadata(
257     grpc_polling_entity* pollent, grpc_auth_metadata_context context,
258     grpc_credentials_mdelem_array* md_array, grpc_closure* on_request_metadata,
259     grpc_error** error) {
260   // Check if we can use the cached token.
261   grpc_millis refresh_threshold =
262       GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS * GPR_MS_PER_SEC;
263   grpc_mdelem cached_access_token_md = GRPC_MDNULL;
264   gpr_mu_lock(&mu_);
265   if (!GRPC_MDISNULL(access_token_md_) &&
266       gpr_time_cmp(
267           gpr_time_sub(token_expiration_, gpr_now(GPR_CLOCK_MONOTONIC)),
268           gpr_time_from_seconds(GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS,
269                                 GPR_TIMESPAN)) > 0) {
270     cached_access_token_md = GRPC_MDELEM_REF(access_token_md_);
271   }
272   if (!GRPC_MDISNULL(cached_access_token_md)) {
273     gpr_mu_unlock(&mu_);
274     grpc_credentials_mdelem_array_add(md_array, cached_access_token_md);
275     GRPC_MDELEM_UNREF(cached_access_token_md);
276     return true;
277   }
278   // Couldn't get the token from the cache.
279   // Add request to pending_requests_ and start a new fetch if needed.
280   grpc_oauth2_pending_get_request_metadata* pending_request =
281       static_cast<grpc_oauth2_pending_get_request_metadata*>(
282           gpr_malloc(sizeof(*pending_request)));
283   pending_request->md_array = md_array;
284   pending_request->on_request_metadata = on_request_metadata;
285   pending_request->pollent = pollent;
286   grpc_polling_entity_add_to_pollset_set(
287       pollent, grpc_polling_entity_pollset_set(&pollent_));
288   pending_request->next = pending_requests_;
289   pending_requests_ = pending_request;
290   bool start_fetch = false;
291   if (!token_fetch_pending_) {
292     token_fetch_pending_ = true;
293     start_fetch = true;
294   }
295   gpr_mu_unlock(&mu_);
296   if (start_fetch) {
297     Ref().release();
298     fetch_oauth2(grpc_credentials_metadata_request_create(this->Ref()),
299                  &httpcli_context_, &pollent_,
300                  on_oauth2_token_fetcher_http_response,
301                  grpc_core::ExecCtx::Get()->Now() + refresh_threshold);
302   }
303   return false;
304 }
305
306 void grpc_oauth2_token_fetcher_credentials::cancel_get_request_metadata(
307     grpc_credentials_mdelem_array* md_array, grpc_error* error) {
308   gpr_mu_lock(&mu_);
309   grpc_oauth2_pending_get_request_metadata* prev = nullptr;
310   grpc_oauth2_pending_get_request_metadata* pending_request = pending_requests_;
311   while (pending_request != nullptr) {
312     if (pending_request->md_array == md_array) {
313       // Remove matching pending request from the list.
314       if (prev != nullptr) {
315         prev->next = pending_request->next;
316       } else {
317         pending_requests_ = pending_request->next;
318       }
319       // Invoke the callback immediately with an error.
320       GRPC_CLOSURE_SCHED(pending_request->on_request_metadata,
321                          GRPC_ERROR_REF(error));
322       gpr_free(pending_request);
323       break;
324     }
325     prev = pending_request;
326     pending_request = pending_request->next;
327   }
328   gpr_mu_unlock(&mu_);
329   GRPC_ERROR_UNREF(error);
330 }
331
332 grpc_oauth2_token_fetcher_credentials::grpc_oauth2_token_fetcher_credentials()
333     : grpc_call_credentials(GRPC_CALL_CREDENTIALS_TYPE_OAUTH2),
334       token_expiration_(gpr_inf_past(GPR_CLOCK_MONOTONIC)),
335       pollent_(grpc_polling_entity_create_from_pollset_set(
336           grpc_pollset_set_create())) {
337   gpr_mu_init(&mu_);
338   grpc_httpcli_context_init(&httpcli_context_);
339 }
340
341 //
342 //  Google Compute Engine credentials.
343 //
344
345 namespace {
346
347 class grpc_compute_engine_token_fetcher_credentials
348     : public grpc_oauth2_token_fetcher_credentials {
349  public:
350   grpc_compute_engine_token_fetcher_credentials() = default;
351   ~grpc_compute_engine_token_fetcher_credentials() override = default;
352
353  protected:
354   void fetch_oauth2(grpc_credentials_metadata_request* metadata_req,
355                     grpc_httpcli_context* http_context,
356                     grpc_polling_entity* pollent,
357                     grpc_iomgr_cb_func response_cb,
358                     grpc_millis deadline) override {
359     grpc_http_header header = {(char*)"Metadata-Flavor", (char*)"Google"};
360     grpc_httpcli_request request;
361     memset(&request, 0, sizeof(grpc_httpcli_request));
362     request.host = (char*)GRPC_COMPUTE_ENGINE_METADATA_HOST;
363     request.http.path = (char*)GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH;
364     request.http.hdr_count = 1;
365     request.http.hdrs = &header;
366     /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
367        channel. This would allow us to cancel an authentication query when under
368        extreme memory pressure. */
369     grpc_resource_quota* resource_quota =
370         grpc_resource_quota_create("oauth2_credentials");
371     grpc_httpcli_get(http_context, pollent, resource_quota, &request, deadline,
372                      GRPC_CLOSURE_CREATE(response_cb, metadata_req,
373                                          grpc_schedule_on_exec_ctx),
374                      &metadata_req->response);
375     grpc_resource_quota_unref_internal(resource_quota);
376   }
377 };
378
379 }  // namespace
380
381 grpc_call_credentials* grpc_google_compute_engine_credentials_create(
382     void* reserved) {
383   GRPC_API_TRACE("grpc_compute_engine_credentials_create(reserved=%p)", 1,
384                  (reserved));
385   GPR_ASSERT(reserved == nullptr);
386   return grpc_core::MakeRefCounted<
387              grpc_compute_engine_token_fetcher_credentials>()
388       .release();
389 }
390
391 //
392 // Google Refresh Token credentials.
393 //
394
395 grpc_google_refresh_token_credentials::
396     ~grpc_google_refresh_token_credentials() {
397   grpc_auth_refresh_token_destruct(&refresh_token_);
398 }
399
400 void grpc_google_refresh_token_credentials::fetch_oauth2(
401     grpc_credentials_metadata_request* metadata_req,
402     grpc_httpcli_context* httpcli_context, grpc_polling_entity* pollent,
403     grpc_iomgr_cb_func response_cb, grpc_millis deadline) {
404   grpc_http_header header = {(char*)"Content-Type",
405                              (char*)"application/x-www-form-urlencoded"};
406   grpc_httpcli_request request;
407   char* body = nullptr;
408   gpr_asprintf(&body, GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING,
409                refresh_token_.client_id, refresh_token_.client_secret,
410                refresh_token_.refresh_token);
411   memset(&request, 0, sizeof(grpc_httpcli_request));
412   request.host = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_HOST;
413   request.http.path = (char*)GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH;
414   request.http.hdr_count = 1;
415   request.http.hdrs = &header;
416   request.handshaker = &grpc_httpcli_ssl;
417   /* TODO(ctiller): Carry the resource_quota in ctx and share it with the host
418      channel. This would allow us to cancel an authentication query when under
419      extreme memory pressure. */
420   grpc_resource_quota* resource_quota =
421       grpc_resource_quota_create("oauth2_credentials_refresh");
422   grpc_httpcli_post(
423       httpcli_context, pollent, resource_quota, &request, body, strlen(body),
424       deadline,
425       GRPC_CLOSURE_CREATE(response_cb, metadata_req, grpc_schedule_on_exec_ctx),
426       &metadata_req->response);
427   grpc_resource_quota_unref_internal(resource_quota);
428   gpr_free(body);
429 }
430
431 grpc_google_refresh_token_credentials::grpc_google_refresh_token_credentials(
432     grpc_auth_refresh_token refresh_token)
433     : refresh_token_(refresh_token) {}
434
435 grpc_core::RefCountedPtr<grpc_call_credentials>
436 grpc_refresh_token_credentials_create_from_auth_refresh_token(
437     grpc_auth_refresh_token refresh_token) {
438   if (!grpc_auth_refresh_token_is_valid(&refresh_token)) {
439     gpr_log(GPR_ERROR, "Invalid input for refresh token credentials creation");
440     return nullptr;
441   }
442   return grpc_core::MakeRefCounted<grpc_google_refresh_token_credentials>(
443       refresh_token);
444 }
445
446 static char* create_loggable_refresh_token(grpc_auth_refresh_token* token) {
447   if (strcmp(token->type, GRPC_AUTH_JSON_TYPE_INVALID) == 0) {
448     return gpr_strdup("<Invalid json token>");
449   }
450   char* loggable_token = nullptr;
451   gpr_asprintf(&loggable_token,
452                "{\n type: %s\n client_id: %s\n client_secret: "
453                "<redacted>\n refresh_token: <redacted>\n}",
454                token->type, token->client_id);
455   return loggable_token;
456 }
457
458 grpc_call_credentials* grpc_google_refresh_token_credentials_create(
459     const char* json_refresh_token, void* reserved) {
460   grpc_auth_refresh_token token =
461       grpc_auth_refresh_token_create_from_string(json_refresh_token);
462   if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace)) {
463     char* loggable_token = create_loggable_refresh_token(&token);
464     gpr_log(GPR_INFO,
465             "grpc_refresh_token_credentials_create(json_refresh_token=%s, "
466             "reserved=%p)",
467             loggable_token, reserved);
468     gpr_free(loggable_token);
469   }
470   GPR_ASSERT(reserved == nullptr);
471   return grpc_refresh_token_credentials_create_from_auth_refresh_token(token)
472       .release();
473 }
474
475 //
476 // Oauth2 Access Token credentials.
477 //
478
479 grpc_access_token_credentials::~grpc_access_token_credentials() {
480   GRPC_MDELEM_UNREF(access_token_md_);
481 }
482
483 bool grpc_access_token_credentials::get_request_metadata(
484     grpc_polling_entity* pollent, grpc_auth_metadata_context context,
485     grpc_credentials_mdelem_array* md_array, grpc_closure* on_request_metadata,
486     grpc_error** error) {
487   grpc_credentials_mdelem_array_add(md_array, access_token_md_);
488   return true;
489 }
490
491 void grpc_access_token_credentials::cancel_get_request_metadata(
492     grpc_credentials_mdelem_array* md_array, grpc_error* error) {
493   GRPC_ERROR_UNREF(error);
494 }
495
496 grpc_access_token_credentials::grpc_access_token_credentials(
497     const char* access_token)
498     : grpc_call_credentials(GRPC_CALL_CREDENTIALS_TYPE_OAUTH2) {
499   char* token_md_value;
500   gpr_asprintf(&token_md_value, "Bearer %s", access_token);
501   grpc_core::ExecCtx exec_ctx;
502   access_token_md_ = grpc_mdelem_from_slices(
503       grpc_slice_from_static_string(GRPC_AUTHORIZATION_METADATA_KEY),
504       grpc_slice_from_copied_string(token_md_value));
505   gpr_free(token_md_value);
506 }
507
508 grpc_call_credentials* grpc_access_token_credentials_create(
509     const char* access_token, void* reserved) {
510   GRPC_API_TRACE(
511       "grpc_access_token_credentials_create(access_token=<redacted>, "
512       "reserved=%p)",
513       1, (reserved));
514   GPR_ASSERT(reserved == nullptr);
515   return grpc_core::MakeRefCounted<grpc_access_token_credentials>(access_token)
516       .release();
517 }