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