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 <ruby/ruby.h>
20
21 #include "rb_call_credentials.h"
22
23 #include <ruby/thread.h>
24
25 #include "rb_call.h"
26 #include "rb_event_thread.h"
27 #include "rb_grpc.h"
28 #include "rb_grpc_imports.generated.h"
29
30 #include <grpc/grpc.h>
31 #include <grpc/grpc_security.h>
32 #include <grpc/support/alloc.h>
33 #include <grpc/support/log.h>
34
35 /* grpc_rb_cCallCredentials is the ruby class that proxies
36 * grpc_call_credentials */
37 static VALUE grpc_rb_cCallCredentials = Qnil;
38
39 /* grpc_rb_call_credentials wraps a grpc_call_credentials. It provides a mark
40 * object that is used to hold references to any objects used to create the
41 * credentials. */
42 typedef struct grpc_rb_call_credentials {
43 /* Holder of ruby objects involved in contructing the credentials */
44 VALUE mark;
45
46 /* The actual credentials */
47 grpc_call_credentials* wrapped;
48 } grpc_rb_call_credentials;
49
50 typedef struct callback_params {
51 VALUE get_metadata;
52 grpc_auth_metadata_context context;
53 void* user_data;
54 grpc_credentials_plugin_metadata_cb callback;
55 } callback_params;
56
grpc_rb_call_credentials_callback(VALUE args)57 static VALUE grpc_rb_call_credentials_callback(VALUE args) {
58 VALUE result = rb_hash_new();
59 VALUE callback_func = rb_ary_entry(args, 0);
60 VALUE callback_args = rb_ary_entry(args, 1);
61 VALUE md_ary_obj = rb_ary_entry(args, 2);
62 if (gpr_should_log(GPR_LOG_SEVERITY_DEBUG)) {
63 VALUE callback_func_str = rb_funcall(callback_func, rb_intern("to_s"), 0);
64 VALUE callback_args_str = rb_funcall(callback_args, rb_intern("to_s"), 0);
65 VALUE callback_source_info =
66 rb_funcall(callback_func, rb_intern("source_location"), 0);
67 if (callback_source_info != Qnil) {
68 VALUE source_filename = rb_ary_entry(callback_source_info, 0);
69 VALUE source_line_number = rb_funcall(
70 rb_ary_entry(callback_source_info, 1), rb_intern("to_s"), 0);
71 gpr_log(GPR_DEBUG,
72 "GRPC_RUBY: grpc_rb_call_credentials invoking user callback:|%s| "
73 "source_filename:%s line_number:%s with arguments:|%s|",
74 StringValueCStr(callback_func_str),
75 StringValueCStr(source_filename),
76 StringValueCStr(source_line_number),
77 StringValueCStr(callback_args_str));
78 } else {
79 gpr_log(GPR_DEBUG,
80 "GRPC_RUBY: grpc_rb_call_credentials invoking user callback:|%s| "
81 "(failed to get source filename and line) with arguments:|%s|",
82 StringValueCStr(callback_func_str),
83 StringValueCStr(callback_args_str));
84 }
85 }
86 VALUE metadata =
87 rb_funcall(callback_func, rb_intern("call"), 1, callback_args);
88 grpc_metadata_array* md_ary = NULL;
89 TypedData_Get_Struct(md_ary_obj, grpc_metadata_array,
90 &grpc_rb_md_ary_data_type, md_ary);
91 grpc_rb_md_ary_convert(metadata, md_ary);
92 rb_hash_aset(result, rb_str_new2("metadata"), metadata);
93 rb_hash_aset(result, rb_str_new2("status"), INT2NUM(GRPC_STATUS_OK));
94 rb_hash_aset(result, rb_str_new2("details"), rb_str_new2(""));
95 return result;
96 }
97
grpc_rb_call_credentials_callback_rescue(VALUE args,VALUE exception_object)98 static VALUE grpc_rb_call_credentials_callback_rescue(VALUE args,
99 VALUE exception_object) {
100 VALUE result = rb_hash_new();
101 VALUE backtrace = rb_funcall(exception_object, rb_intern("backtrace"), 0);
102 VALUE backtrace_str;
103 if (backtrace != Qnil) {
104 backtrace_str =
105 rb_funcall(backtrace, rb_intern("join"), 1, rb_str_new2("\n\tfrom "));
106 } else {
107 backtrace_str = rb_str_new2(
108 "failed to get backtrace, this exception was likely thrown from native "
109 "code");
110 }
111 VALUE rb_exception_info =
112 rb_funcall(exception_object, rb_intern("inspect"), 0);
113 (void)args;
114 gpr_log(GPR_INFO,
115 "GRPC_RUBY call credentials callback failed, exception inspect:|%s| "
116 "backtrace:|%s|",
117 StringValueCStr(rb_exception_info), StringValueCStr(backtrace_str));
118 rb_hash_aset(result, rb_str_new2("metadata"), Qnil);
119 rb_hash_aset(result, rb_str_new2("status"),
120 INT2NUM(GRPC_STATUS_UNAUTHENTICATED));
121 rb_hash_aset(result, rb_str_new2("details"), rb_exception_info);
122 return result;
123 }
124
grpc_rb_call_credentials_callback_with_gil(void * param)125 static void grpc_rb_call_credentials_callback_with_gil(void* param) {
126 callback_params* const params = (callback_params*)param;
127 VALUE auth_uri = rb_str_new_cstr(params->context.service_url);
128 /* Pass the arguments to the proc in a hash, which currently only has they key
129 'auth_uri' */
130 VALUE callback_args = rb_ary_new();
131 VALUE args = rb_hash_new();
132 VALUE result;
133 grpc_metadata_array md_ary;
134 grpc_status_code status;
135 VALUE details;
136 char* error_details;
137 grpc_metadata_array_init(&md_ary);
138 rb_hash_aset(args, ID2SYM(rb_intern("jwt_aud_uri")), auth_uri);
139 rb_ary_push(callback_args, params->get_metadata);
140 rb_ary_push(callback_args, args);
141 // Wrap up the grpc_metadata_array into a ruby object and do the conversion
142 // from hash to grpc_metadata_array within the rescue block, because the
143 // conversion can throw exceptions.
144 rb_ary_push(callback_args,
145 TypedData_Wrap_Struct(grpc_rb_cMdAry, &grpc_rb_md_ary_data_type,
146 &md_ary));
147 result = rb_rescue(grpc_rb_call_credentials_callback, callback_args,
148 grpc_rb_call_credentials_callback_rescue, Qnil);
149 // Both callbacks return a hash, so result should be a hash
150 status = NUM2INT(rb_hash_aref(result, rb_str_new2("status")));
151 details = rb_hash_aref(result, rb_str_new2("details"));
152 error_details = StringValueCStr(details);
153 params->callback(params->user_data, md_ary.metadata, md_ary.count, status,
154 error_details);
155 grpc_rb_metadata_array_destroy_including_entries(&md_ary);
156 grpc_auth_metadata_context_reset(¶ms->context);
157 gpr_free(params);
158 }
159
grpc_rb_call_credentials_plugin_get_metadata(void * state,grpc_auth_metadata_context context,grpc_credentials_plugin_metadata_cb cb,void * user_data,grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],size_t * num_creds_md,grpc_status_code * status,const char ** error_details)160 static int grpc_rb_call_credentials_plugin_get_metadata(
161 void* state, grpc_auth_metadata_context context,
162 grpc_credentials_plugin_metadata_cb cb, void* user_data,
163 grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
164 size_t* num_creds_md, grpc_status_code* status,
165 const char** error_details) {
166 callback_params* params = gpr_zalloc(sizeof(callback_params));
167 params->get_metadata = (VALUE)state;
168 grpc_auth_metadata_context_copy(&context, ¶ms->context);
169 params->user_data = user_data;
170 params->callback = cb;
171
172 grpc_rb_event_queue_enqueue(grpc_rb_call_credentials_callback_with_gil,
173 (void*)(params));
174 return 0; // Async return.
175 }
176
grpc_rb_call_credentials_plugin_destroy(void * state)177 static void grpc_rb_call_credentials_plugin_destroy(void* state) {
178 (void)state;
179 // Not sure what needs to be done here
180 }
181
grpc_rb_call_credentials_free_internal(void * p)182 static void grpc_rb_call_credentials_free_internal(void* p) {
183 grpc_rb_call_credentials* wrapper;
184 if (p == NULL) {
185 return;
186 }
187 wrapper = (grpc_rb_call_credentials*)p;
188 grpc_call_credentials_release(wrapper->wrapped);
189 wrapper->wrapped = NULL;
190 xfree(p);
191 }
192
193 /* Destroys the credentials instances. */
grpc_rb_call_credentials_free(void * p)194 static void grpc_rb_call_credentials_free(void* p) {
195 grpc_rb_call_credentials_free_internal(p);
196 }
197
198 /* Protects the mark object from GC */
grpc_rb_call_credentials_mark(void * p)199 static void grpc_rb_call_credentials_mark(void* p) {
200 grpc_rb_call_credentials* wrapper = NULL;
201 if (p == NULL) {
202 return;
203 }
204 wrapper = (grpc_rb_call_credentials*)p;
205 if (wrapper->mark != Qnil) {
206 rb_gc_mark(wrapper->mark);
207 }
208 }
209
210 static rb_data_type_t grpc_rb_call_credentials_data_type = {
211 "grpc_call_credentials",
212 {grpc_rb_call_credentials_mark,
213 grpc_rb_call_credentials_free,
214 GRPC_RB_MEMSIZE_UNAVAILABLE,
215 {NULL, NULL}},
216 NULL,
217 NULL,
218 #ifdef RUBY_TYPED_FREE_IMMEDIATELY
219 RUBY_TYPED_FREE_IMMEDIATELY
220 #endif
221 };
222
223 /* Allocates CallCredentials instances.
224 Provides safe initial defaults for the instance fields. */
grpc_rb_call_credentials_alloc(VALUE cls)225 static VALUE grpc_rb_call_credentials_alloc(VALUE cls) {
226 grpc_ruby_init();
227 grpc_rb_call_credentials* wrapper = ALLOC(grpc_rb_call_credentials);
228 wrapper->wrapped = NULL;
229 wrapper->mark = Qnil;
230 return TypedData_Wrap_Struct(cls, &grpc_rb_call_credentials_data_type,
231 wrapper);
232 }
233
234 /* Creates a wrapping object for a given call credentials. This should only be
235 * called with grpc_call_credentials objects that are not already associated
236 * with any Ruby object */
grpc_rb_wrap_call_credentials(grpc_call_credentials * c,VALUE mark)237 VALUE grpc_rb_wrap_call_credentials(grpc_call_credentials* c, VALUE mark) {
238 VALUE rb_wrapper;
239 grpc_rb_call_credentials* wrapper;
240 if (c == NULL) {
241 return Qnil;
242 }
243 rb_wrapper = grpc_rb_call_credentials_alloc(grpc_rb_cCallCredentials);
244 TypedData_Get_Struct(rb_wrapper, grpc_rb_call_credentials,
245 &grpc_rb_call_credentials_data_type, wrapper);
246 wrapper->wrapped = c;
247 wrapper->mark = mark;
248 return rb_wrapper;
249 }
250
251 /* The attribute used on the mark object to hold the callback */
252 static ID id_callback;
253
254 /*
255 call-seq:
256 creds = Credentials.new auth_proc
257 proc: (required) Proc that generates auth metadata
258 Initializes CallCredential instances. */
grpc_rb_call_credentials_init(VALUE self,VALUE proc)259 static VALUE grpc_rb_call_credentials_init(VALUE self, VALUE proc) {
260 grpc_rb_call_credentials* wrapper = NULL;
261 grpc_call_credentials* creds = NULL;
262 grpc_metadata_credentials_plugin plugin;
263
264 TypedData_Get_Struct(self, grpc_rb_call_credentials,
265 &grpc_rb_call_credentials_data_type, wrapper);
266
267 plugin.get_metadata = grpc_rb_call_credentials_plugin_get_metadata;
268 plugin.destroy = grpc_rb_call_credentials_plugin_destroy;
269 if (!rb_obj_is_proc(proc)) {
270 rb_raise(rb_eTypeError, "Argument to CallCredentials#new must be a proc");
271 return Qnil;
272 }
273 plugin.state = (void*)proc;
274 plugin.type = "";
275
276 // TODO(yihuazhang): Expose min_security_level via the Ruby API so that
277 // applications can decide what minimum security level their plugins require.
278 creds = grpc_metadata_credentials_create_from_plugin(
279 plugin, GRPC_PRIVACY_AND_INTEGRITY, NULL);
280 if (creds == NULL) {
281 rb_raise(rb_eRuntimeError, "could not create a credentials, not sure why");
282 return Qnil;
283 }
284
285 wrapper->mark = proc;
286 wrapper->wrapped = creds;
287 rb_ivar_set(self, id_callback, proc);
288
289 return self;
290 }
291
grpc_rb_call_credentials_compose(int argc,VALUE * argv,VALUE self)292 static VALUE grpc_rb_call_credentials_compose(int argc, VALUE* argv,
293 VALUE self) {
294 grpc_call_credentials* creds;
295 grpc_call_credentials* other;
296 grpc_call_credentials* prev = NULL;
297 VALUE mark;
298 if (argc == 0) {
299 return self;
300 }
301 mark = rb_ary_new();
302 creds = grpc_rb_get_wrapped_call_credentials(self);
303 for (int i = 0; i < argc; i++) {
304 rb_ary_push(mark, argv[i]);
305 other = grpc_rb_get_wrapped_call_credentials(argv[i]);
306 creds = grpc_composite_call_credentials_create(creds, other, NULL);
307 if (prev != NULL) {
308 grpc_call_credentials_release(prev);
309 }
310 prev = creds;
311 }
312 return grpc_rb_wrap_call_credentials(creds, mark);
313 }
314
Init_grpc_call_credentials()315 void Init_grpc_call_credentials() {
316 grpc_rb_cCallCredentials =
317 rb_define_class_under(grpc_rb_mGrpcCore, "CallCredentials", rb_cObject);
318
319 /* Allocates an object managed by the ruby runtime */
320 rb_define_alloc_func(grpc_rb_cCallCredentials,
321 grpc_rb_call_credentials_alloc);
322
323 /* Provides a ruby constructor and support for dup/clone. */
324 rb_define_method(grpc_rb_cCallCredentials, "initialize",
325 grpc_rb_call_credentials_init, 1);
326 rb_define_method(grpc_rb_cCallCredentials, "initialize_copy",
327 grpc_rb_cannot_init_copy, 1);
328 rb_define_method(grpc_rb_cCallCredentials, "compose",
329 grpc_rb_call_credentials_compose, -1);
330
331 id_callback = rb_intern("__callback");
332 }
333
334 /* Gets the wrapped grpc_call_credentials from the ruby wrapper */
grpc_rb_get_wrapped_call_credentials(VALUE v)335 grpc_call_credentials* grpc_rb_get_wrapped_call_credentials(VALUE v) {
336 grpc_rb_call_credentials* wrapper = NULL;
337 TypedData_Get_Struct(v, grpc_rb_call_credentials,
338 &grpc_rb_call_credentials_data_type, wrapper);
339 return wrapper->wrapped;
340 }
341