blob: 671dea31e68d144b2712c395c17c352134d65bb9 [file] [log] [blame]
Julien Boeuf8ca294e2016-05-02 14:56:30 -07001/*
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
49int 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
55grpc_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
84end:
85 if (!success) grpc_auth_refresh_token_destruct(&result);
86 return result;
87}
88
89grpc_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
100void 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
121static 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
129grpc_credentials_status
130grpc_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
208end:
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 Tiller804ff712016-05-05 16:25:40 -0700219static void on_oauth2_token_fetcher_http_response(grpc_exec_ctx *exec_ctx,
220 void *user_data,
221 grpc_error *error) {
Julien Boeuf8ca294e2016-05-02 14:56:30 -0700222 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 Tiller804ff712016-05-05 16:25:40 -0700231 &r->response, &c->access_token_md, &token_lifetime);
Julien Boeuf8ca294e2016-05-02 14:56:30 -0700232 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
245static 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
278static 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
293static grpc_call_credentials_vtable compute_engine_vtable = {
294 oauth2_token_fetcher_destruct, oauth2_token_fetcher_get_request_metadata};
295
296static 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 Tiller804ff712016-05-05 16:25:40 -0700299 grpc_iomgr_cb_func response_cb, gpr_timespec deadline) {
Julien Boeuf8ca294e2016-05-02 14:56:30 -0700300 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 Tiller804ff712016-05-05 16:25:40 -0700308 grpc_closure_create(response_cb, metadata_req),
309 &metadata_req->response);
Julien Boeuf8ca294e2016-05-02 14:56:30 -0700310}
311
312grpc_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
328static 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
335static grpc_call_credentials_vtable refresh_token_vtable = {
336 refresh_token_destruct, oauth2_token_fetcher_get_request_metadata};
337
338static 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 Tiller804ff712016-05-05 16:25:40 -0700341 grpc_iomgr_cb_func response_cb, gpr_timespec deadline) {
Julien Boeuf8ca294e2016-05-02 14:56:30 -0700342 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 Tiller804ff712016-05-05 16:25:40 -0700358 strlen(body), deadline,
359 grpc_closure_create(response_cb, metadata_req),
360 &metadata_req->response);
Julien Boeuf8ca294e2016-05-02 14:56:30 -0700361 gpr_free(body);
362}
363
364grpc_call_credentials *
365grpc_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
380grpc_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
395static 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
400static 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
408static grpc_call_credentials_vtable access_token_vtable = {
409 access_token_destruct, access_token_get_request_metadata};
410
411grpc_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}