Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 1 | /* |
| 2 | * |
| 3 | * Copyright 2015, Google Inc. |
| 4 | * All rights reserved. |
| 5 | * |
| 6 | * Redistribution and use in source and binary forms, with or without |
| 7 | * modification, are permitted provided that the following conditions are |
| 8 | * met: |
| 9 | * |
| 10 | * * Redistributions of source code must retain the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer. |
| 12 | * * Redistributions in binary form must reproduce the above |
| 13 | * copyright notice, this list of conditions and the following disclaimer |
| 14 | * in the documentation and/or other materials provided with the |
| 15 | * distribution. |
| 16 | * * Neither the name of Google Inc. nor the names of its |
| 17 | * contributors may be used to endorse or promote products derived from |
| 18 | * this software without specific prior written permission. |
| 19 | * |
| 20 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 22 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 23 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 24 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 25 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 26 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 27 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 28 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 | * |
| 32 | */ |
| 33 | |
| 34 | #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h" |
| 35 | |
| 36 | #include <string.h> |
| 37 | |
| 38 | #include "src/core/lib/security/util/json_util.h" |
| 39 | #include "src/core/lib/surface/api_trace.h" |
| 40 | |
| 41 | #include <grpc/support/alloc.h> |
| 42 | #include <grpc/support/log.h> |
| 43 | #include <grpc/support/string_util.h> |
| 44 | |
| 45 | // |
| 46 | // Auth Refresh Token. |
| 47 | // |
| 48 | |
| 49 | int grpc_auth_refresh_token_is_valid( |
| 50 | const grpc_auth_refresh_token *refresh_token) { |
| 51 | return (refresh_token != NULL) && |
| 52 | strcmp(refresh_token->type, GRPC_AUTH_JSON_TYPE_INVALID); |
| 53 | } |
| 54 | |
| 55 | grpc_auth_refresh_token grpc_auth_refresh_token_create_from_json( |
| 56 | const grpc_json *json) { |
| 57 | grpc_auth_refresh_token result; |
| 58 | const char *prop_value; |
| 59 | int success = 0; |
| 60 | |
| 61 | memset(&result, 0, sizeof(grpc_auth_refresh_token)); |
| 62 | result.type = GRPC_AUTH_JSON_TYPE_INVALID; |
| 63 | if (json == NULL) { |
| 64 | gpr_log(GPR_ERROR, "Invalid json."); |
| 65 | goto end; |
| 66 | } |
| 67 | |
| 68 | prop_value = grpc_json_get_string_property(json, "type"); |
| 69 | if (prop_value == NULL || |
| 70 | strcmp(prop_value, GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER)) { |
| 71 | goto end; |
| 72 | } |
| 73 | result.type = GRPC_AUTH_JSON_TYPE_AUTHORIZED_USER; |
| 74 | |
| 75 | if (!grpc_copy_json_string_property(json, "client_secret", |
| 76 | &result.client_secret) || |
| 77 | !grpc_copy_json_string_property(json, "client_id", &result.client_id) || |
| 78 | !grpc_copy_json_string_property(json, "refresh_token", |
| 79 | &result.refresh_token)) { |
| 80 | goto end; |
| 81 | } |
| 82 | success = 1; |
| 83 | |
| 84 | end: |
| 85 | if (!success) grpc_auth_refresh_token_destruct(&result); |
| 86 | return result; |
| 87 | } |
| 88 | |
| 89 | grpc_auth_refresh_token grpc_auth_refresh_token_create_from_string( |
| 90 | const char *json_string) { |
| 91 | char *scratchpad = gpr_strdup(json_string); |
| 92 | grpc_json *json = grpc_json_parse_string(scratchpad); |
| 93 | grpc_auth_refresh_token result = |
| 94 | grpc_auth_refresh_token_create_from_json(json); |
| 95 | if (json != NULL) grpc_json_destroy(json); |
| 96 | gpr_free(scratchpad); |
| 97 | return result; |
| 98 | } |
| 99 | |
| 100 | void grpc_auth_refresh_token_destruct(grpc_auth_refresh_token *refresh_token) { |
| 101 | if (refresh_token == NULL) return; |
| 102 | refresh_token->type = GRPC_AUTH_JSON_TYPE_INVALID; |
| 103 | if (refresh_token->client_id != NULL) { |
| 104 | gpr_free(refresh_token->client_id); |
| 105 | refresh_token->client_id = NULL; |
| 106 | } |
| 107 | if (refresh_token->client_secret != NULL) { |
| 108 | gpr_free(refresh_token->client_secret); |
| 109 | refresh_token->client_secret = NULL; |
| 110 | } |
| 111 | if (refresh_token->refresh_token != NULL) { |
| 112 | gpr_free(refresh_token->refresh_token); |
| 113 | refresh_token->refresh_token = NULL; |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // |
| 118 | // Oauth2 Token Fetcher credentials. |
| 119 | // |
| 120 | |
| 121 | static void oauth2_token_fetcher_destruct(grpc_call_credentials *creds) { |
| 122 | grpc_oauth2_token_fetcher_credentials *c = |
| 123 | (grpc_oauth2_token_fetcher_credentials *)creds; |
| 124 | grpc_credentials_md_store_unref(c->access_token_md); |
| 125 | gpr_mu_destroy(&c->mu); |
| 126 | grpc_httpcli_context_destroy(&c->httpcli_context); |
| 127 | } |
| 128 | |
| 129 | grpc_credentials_status |
| 130 | grpc_oauth2_token_fetcher_credentials_parse_server_response( |
| 131 | const grpc_http_response *response, grpc_credentials_md_store **token_md, |
| 132 | gpr_timespec *token_lifetime) { |
| 133 | char *null_terminated_body = NULL; |
| 134 | char *new_access_token = NULL; |
| 135 | grpc_credentials_status status = GRPC_CREDENTIALS_OK; |
| 136 | grpc_json *json = NULL; |
| 137 | |
| 138 | if (response == NULL) { |
| 139 | gpr_log(GPR_ERROR, "Received NULL response."); |
| 140 | status = GRPC_CREDENTIALS_ERROR; |
| 141 | goto end; |
| 142 | } |
| 143 | |
| 144 | if (response->body_length > 0) { |
| 145 | null_terminated_body = gpr_malloc(response->body_length + 1); |
| 146 | null_terminated_body[response->body_length] = '\0'; |
| 147 | memcpy(null_terminated_body, response->body, response->body_length); |
| 148 | } |
| 149 | |
| 150 | if (response->status != 200) { |
| 151 | gpr_log(GPR_ERROR, "Call to http server ended with error %d [%s].", |
| 152 | response->status, |
| 153 | null_terminated_body != NULL ? null_terminated_body : ""); |
| 154 | status = GRPC_CREDENTIALS_ERROR; |
| 155 | goto end; |
| 156 | } else { |
| 157 | grpc_json *access_token = NULL; |
| 158 | grpc_json *token_type = NULL; |
| 159 | grpc_json *expires_in = NULL; |
| 160 | grpc_json *ptr; |
| 161 | json = grpc_json_parse_string(null_terminated_body); |
| 162 | if (json == NULL) { |
| 163 | gpr_log(GPR_ERROR, "Could not parse JSON from %s", null_terminated_body); |
| 164 | status = GRPC_CREDENTIALS_ERROR; |
| 165 | goto end; |
| 166 | } |
| 167 | if (json->type != GRPC_JSON_OBJECT) { |
| 168 | gpr_log(GPR_ERROR, "Response should be a JSON object"); |
| 169 | status = GRPC_CREDENTIALS_ERROR; |
| 170 | goto end; |
| 171 | } |
| 172 | for (ptr = json->child; ptr; ptr = ptr->next) { |
| 173 | if (strcmp(ptr->key, "access_token") == 0) { |
| 174 | access_token = ptr; |
| 175 | } else if (strcmp(ptr->key, "token_type") == 0) { |
| 176 | token_type = ptr; |
| 177 | } else if (strcmp(ptr->key, "expires_in") == 0) { |
| 178 | expires_in = ptr; |
| 179 | } |
| 180 | } |
| 181 | if (access_token == NULL || access_token->type != GRPC_JSON_STRING) { |
| 182 | gpr_log(GPR_ERROR, "Missing or invalid access_token in JSON."); |
| 183 | status = GRPC_CREDENTIALS_ERROR; |
| 184 | goto end; |
| 185 | } |
| 186 | if (token_type == NULL || token_type->type != GRPC_JSON_STRING) { |
| 187 | gpr_log(GPR_ERROR, "Missing or invalid token_type in JSON."); |
| 188 | status = GRPC_CREDENTIALS_ERROR; |
| 189 | goto end; |
| 190 | } |
| 191 | if (expires_in == NULL || expires_in->type != GRPC_JSON_NUMBER) { |
| 192 | gpr_log(GPR_ERROR, "Missing or invalid expires_in in JSON."); |
| 193 | status = GRPC_CREDENTIALS_ERROR; |
| 194 | goto end; |
| 195 | } |
| 196 | gpr_asprintf(&new_access_token, "%s %s", token_type->value, |
| 197 | access_token->value); |
| 198 | token_lifetime->tv_sec = strtol(expires_in->value, NULL, 10); |
| 199 | token_lifetime->tv_nsec = 0; |
| 200 | token_lifetime->clock_type = GPR_TIMESPAN; |
| 201 | if (*token_md != NULL) grpc_credentials_md_store_unref(*token_md); |
| 202 | *token_md = grpc_credentials_md_store_create(1); |
| 203 | grpc_credentials_md_store_add_cstrings( |
| 204 | *token_md, GRPC_AUTHORIZATION_METADATA_KEY, new_access_token); |
| 205 | status = GRPC_CREDENTIALS_OK; |
| 206 | } |
| 207 | |
| 208 | end: |
| 209 | if (status != GRPC_CREDENTIALS_OK && (*token_md != NULL)) { |
| 210 | grpc_credentials_md_store_unref(*token_md); |
| 211 | *token_md = NULL; |
| 212 | } |
| 213 | if (null_terminated_body != NULL) gpr_free(null_terminated_body); |
| 214 | if (new_access_token != NULL) gpr_free(new_access_token); |
| 215 | if (json != NULL) grpc_json_destroy(json); |
| 216 | return status; |
| 217 | } |
| 218 | |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame^] | 219 | static void on_oauth2_token_fetcher_http_response(grpc_exec_ctx *exec_ctx, |
| 220 | void *user_data, |
| 221 | grpc_error *error) { |
Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 222 | grpc_credentials_metadata_request *r = |
| 223 | (grpc_credentials_metadata_request *)user_data; |
| 224 | grpc_oauth2_token_fetcher_credentials *c = |
| 225 | (grpc_oauth2_token_fetcher_credentials *)r->creds; |
| 226 | gpr_timespec token_lifetime; |
| 227 | grpc_credentials_status status; |
| 228 | |
| 229 | gpr_mu_lock(&c->mu); |
| 230 | status = grpc_oauth2_token_fetcher_credentials_parse_server_response( |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame^] | 231 | &r->response, &c->access_token_md, &token_lifetime); |
Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 232 | if (status == GRPC_CREDENTIALS_OK) { |
| 233 | c->token_expiration = |
| 234 | gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), token_lifetime); |
| 235 | r->cb(exec_ctx, r->user_data, c->access_token_md->entries, |
| 236 | c->access_token_md->num_entries, status); |
| 237 | } else { |
| 238 | c->token_expiration = gpr_inf_past(GPR_CLOCK_REALTIME); |
| 239 | r->cb(exec_ctx, r->user_data, NULL, 0, status); |
| 240 | } |
| 241 | gpr_mu_unlock(&c->mu); |
| 242 | grpc_credentials_metadata_request_destroy(r); |
| 243 | } |
| 244 | |
| 245 | static void oauth2_token_fetcher_get_request_metadata( |
| 246 | grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, |
| 247 | grpc_pollset *pollset, grpc_auth_metadata_context context, |
| 248 | grpc_credentials_metadata_cb cb, void *user_data) { |
| 249 | grpc_oauth2_token_fetcher_credentials *c = |
| 250 | (grpc_oauth2_token_fetcher_credentials *)creds; |
| 251 | gpr_timespec refresh_threshold = gpr_time_from_seconds( |
| 252 | GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS, GPR_TIMESPAN); |
| 253 | grpc_credentials_md_store *cached_access_token_md = NULL; |
| 254 | { |
| 255 | gpr_mu_lock(&c->mu); |
| 256 | if (c->access_token_md != NULL && |
| 257 | (gpr_time_cmp( |
| 258 | gpr_time_sub(c->token_expiration, gpr_now(GPR_CLOCK_REALTIME)), |
| 259 | refresh_threshold) > 0)) { |
| 260 | cached_access_token_md = |
| 261 | grpc_credentials_md_store_ref(c->access_token_md); |
| 262 | } |
| 263 | gpr_mu_unlock(&c->mu); |
| 264 | } |
| 265 | if (cached_access_token_md != NULL) { |
| 266 | cb(exec_ctx, user_data, cached_access_token_md->entries, |
| 267 | cached_access_token_md->num_entries, GRPC_CREDENTIALS_OK); |
| 268 | grpc_credentials_md_store_unref(cached_access_token_md); |
| 269 | } else { |
| 270 | c->fetch_func( |
| 271 | exec_ctx, |
| 272 | grpc_credentials_metadata_request_create(creds, cb, user_data), |
| 273 | &c->httpcli_context, pollset, on_oauth2_token_fetcher_http_response, |
| 274 | gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), refresh_threshold)); |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | static void init_oauth2_token_fetcher(grpc_oauth2_token_fetcher_credentials *c, |
| 279 | grpc_fetch_oauth2_func fetch_func) { |
| 280 | memset(c, 0, sizeof(grpc_oauth2_token_fetcher_credentials)); |
| 281 | c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2; |
| 282 | gpr_ref_init(&c->base.refcount, 1); |
| 283 | gpr_mu_init(&c->mu); |
| 284 | c->token_expiration = gpr_inf_past(GPR_CLOCK_REALTIME); |
| 285 | c->fetch_func = fetch_func; |
| 286 | grpc_httpcli_context_init(&c->httpcli_context); |
| 287 | } |
| 288 | |
| 289 | // |
| 290 | // Google Compute Engine credentials. |
| 291 | // |
| 292 | |
| 293 | static grpc_call_credentials_vtable compute_engine_vtable = { |
| 294 | oauth2_token_fetcher_destruct, oauth2_token_fetcher_get_request_metadata}; |
| 295 | |
| 296 | static void compute_engine_fetch_oauth2( |
| 297 | grpc_exec_ctx *exec_ctx, grpc_credentials_metadata_request *metadata_req, |
| 298 | grpc_httpcli_context *httpcli_context, grpc_pollset *pollset, |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame^] | 299 | grpc_iomgr_cb_func response_cb, gpr_timespec deadline) { |
Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 300 | grpc_http_header header = {"Metadata-Flavor", "Google"}; |
| 301 | grpc_httpcli_request request; |
| 302 | memset(&request, 0, sizeof(grpc_httpcli_request)); |
| 303 | request.host = GRPC_COMPUTE_ENGINE_METADATA_HOST; |
| 304 | request.http.path = GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH; |
| 305 | request.http.hdr_count = 1; |
| 306 | request.http.hdrs = &header; |
| 307 | grpc_httpcli_get(exec_ctx, httpcli_context, pollset, &request, deadline, |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame^] | 308 | grpc_closure_create(response_cb, metadata_req), |
| 309 | &metadata_req->response); |
Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 310 | } |
| 311 | |
| 312 | grpc_call_credentials *grpc_google_compute_engine_credentials_create( |
| 313 | void *reserved) { |
| 314 | grpc_oauth2_token_fetcher_credentials *c = |
| 315 | gpr_malloc(sizeof(grpc_oauth2_token_fetcher_credentials)); |
| 316 | GRPC_API_TRACE("grpc_compute_engine_credentials_create(reserved=%p)", 1, |
| 317 | (reserved)); |
| 318 | GPR_ASSERT(reserved == NULL); |
| 319 | init_oauth2_token_fetcher(c, compute_engine_fetch_oauth2); |
| 320 | c->base.vtable = &compute_engine_vtable; |
| 321 | return &c->base; |
| 322 | } |
| 323 | |
| 324 | // |
| 325 | // Google Refresh Token credentials. |
| 326 | // |
| 327 | |
| 328 | static void refresh_token_destruct(grpc_call_credentials *creds) { |
| 329 | grpc_google_refresh_token_credentials *c = |
| 330 | (grpc_google_refresh_token_credentials *)creds; |
| 331 | grpc_auth_refresh_token_destruct(&c->refresh_token); |
| 332 | oauth2_token_fetcher_destruct(&c->base.base); |
| 333 | } |
| 334 | |
| 335 | static grpc_call_credentials_vtable refresh_token_vtable = { |
| 336 | refresh_token_destruct, oauth2_token_fetcher_get_request_metadata}; |
| 337 | |
| 338 | static void refresh_token_fetch_oauth2( |
| 339 | grpc_exec_ctx *exec_ctx, grpc_credentials_metadata_request *metadata_req, |
| 340 | grpc_httpcli_context *httpcli_context, grpc_pollset *pollset, |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame^] | 341 | grpc_iomgr_cb_func response_cb, gpr_timespec deadline) { |
Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 342 | grpc_google_refresh_token_credentials *c = |
| 343 | (grpc_google_refresh_token_credentials *)metadata_req->creds; |
| 344 | grpc_http_header header = {"Content-Type", |
| 345 | "application/x-www-form-urlencoded"}; |
| 346 | grpc_httpcli_request request; |
| 347 | char *body = NULL; |
| 348 | gpr_asprintf(&body, GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING, |
| 349 | c->refresh_token.client_id, c->refresh_token.client_secret, |
| 350 | c->refresh_token.refresh_token); |
| 351 | memset(&request, 0, sizeof(grpc_httpcli_request)); |
| 352 | request.host = GRPC_GOOGLE_OAUTH2_SERVICE_HOST; |
| 353 | request.http.path = GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH; |
| 354 | request.http.hdr_count = 1; |
| 355 | request.http.hdrs = &header; |
| 356 | request.handshaker = &grpc_httpcli_ssl; |
| 357 | grpc_httpcli_post(exec_ctx, httpcli_context, pollset, &request, body, |
Craig Tiller | 804ff71 | 2016-05-05 16:25:40 -0700 | [diff] [blame^] | 358 | strlen(body), deadline, |
| 359 | grpc_closure_create(response_cb, metadata_req), |
| 360 | &metadata_req->response); |
Julien Boeuf | 8ca294e | 2016-05-02 14:56:30 -0700 | [diff] [blame] | 361 | gpr_free(body); |
| 362 | } |
| 363 | |
| 364 | grpc_call_credentials * |
| 365 | grpc_refresh_token_credentials_create_from_auth_refresh_token( |
| 366 | grpc_auth_refresh_token refresh_token) { |
| 367 | grpc_google_refresh_token_credentials *c; |
| 368 | if (!grpc_auth_refresh_token_is_valid(&refresh_token)) { |
| 369 | gpr_log(GPR_ERROR, "Invalid input for refresh token credentials creation"); |
| 370 | return NULL; |
| 371 | } |
| 372 | c = gpr_malloc(sizeof(grpc_google_refresh_token_credentials)); |
| 373 | memset(c, 0, sizeof(grpc_google_refresh_token_credentials)); |
| 374 | init_oauth2_token_fetcher(&c->base, refresh_token_fetch_oauth2); |
| 375 | c->base.base.vtable = &refresh_token_vtable; |
| 376 | c->refresh_token = refresh_token; |
| 377 | return &c->base.base; |
| 378 | } |
| 379 | |
| 380 | grpc_call_credentials *grpc_google_refresh_token_credentials_create( |
| 381 | const char *json_refresh_token, void *reserved) { |
| 382 | GRPC_API_TRACE( |
| 383 | "grpc_refresh_token_credentials_create(json_refresh_token=%s, " |
| 384 | "reserved=%p)", |
| 385 | 2, (json_refresh_token, reserved)); |
| 386 | GPR_ASSERT(reserved == NULL); |
| 387 | return grpc_refresh_token_credentials_create_from_auth_refresh_token( |
| 388 | grpc_auth_refresh_token_create_from_string(json_refresh_token)); |
| 389 | } |
| 390 | |
| 391 | // |
| 392 | // Oauth2 Access Token credentials. |
| 393 | // |
| 394 | |
| 395 | static void access_token_destruct(grpc_call_credentials *creds) { |
| 396 | grpc_access_token_credentials *c = (grpc_access_token_credentials *)creds; |
| 397 | grpc_credentials_md_store_unref(c->access_token_md); |
| 398 | } |
| 399 | |
| 400 | static void access_token_get_request_metadata( |
| 401 | grpc_exec_ctx *exec_ctx, grpc_call_credentials *creds, |
| 402 | grpc_pollset *pollset, grpc_auth_metadata_context context, |
| 403 | grpc_credentials_metadata_cb cb, void *user_data) { |
| 404 | grpc_access_token_credentials *c = (grpc_access_token_credentials *)creds; |
| 405 | cb(exec_ctx, user_data, c->access_token_md->entries, 1, GRPC_CREDENTIALS_OK); |
| 406 | } |
| 407 | |
| 408 | static grpc_call_credentials_vtable access_token_vtable = { |
| 409 | access_token_destruct, access_token_get_request_metadata}; |
| 410 | |
| 411 | grpc_call_credentials *grpc_access_token_credentials_create( |
| 412 | const char *access_token, void *reserved) { |
| 413 | grpc_access_token_credentials *c = |
| 414 | gpr_malloc(sizeof(grpc_access_token_credentials)); |
| 415 | char *token_md_value; |
| 416 | GRPC_API_TRACE( |
| 417 | "grpc_access_token_credentials_create(access_token=%s, " |
| 418 | "reserved=%p)", |
| 419 | 2, (access_token, reserved)); |
| 420 | GPR_ASSERT(reserved == NULL); |
| 421 | memset(c, 0, sizeof(grpc_access_token_credentials)); |
| 422 | c->base.type = GRPC_CALL_CREDENTIALS_TYPE_OAUTH2; |
| 423 | c->base.vtable = &access_token_vtable; |
| 424 | gpr_ref_init(&c->base.refcount, 1); |
| 425 | c->access_token_md = grpc_credentials_md_store_create(1); |
| 426 | gpr_asprintf(&token_md_value, "Bearer %s", access_token); |
| 427 | grpc_credentials_md_store_add_cstrings( |
| 428 | c->access_token_md, GRPC_AUTHORIZATION_METADATA_KEY, token_md_value); |
| 429 | gpr_free(token_md_value); |
| 430 | return &c->base; |
| 431 | } |