Merge pull request #1282 from tbetbetbe/grpc_ruby_add_jwt_auth_interop

Grpc ruby enable jwt auth and and an interop test for it
diff --git a/src/ruby/bin/apis/pubsub_demo.rb b/src/ruby/bin/apis/pubsub_demo.rb
index 9bb324f..6d69b0f 100755
--- a/src/ruby/bin/apis/pubsub_demo.rb
+++ b/src/ruby/bin/apis/pubsub_demo.rb
@@ -71,7 +71,7 @@
 
 # Builds the metadata authentication update proc.
 def auth_proc(opts)
-  auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
+  auth_creds = Google::Auth.get_application_default
   return auth_creds.updater_proc
 end
 
@@ -213,17 +213,14 @@
 end
 
 # Args is used to hold the command line info.
-Args = Struct.new(:host, :oauth_scope, :port, :action, :project_id, :topic_name,
+Args = Struct.new(:host, :port, :action, :project_id, :topic_name,
                   :sub_name)
 
 # validates the the command line options, returning them as an Arg.
 def parse_args
   args = Args.new('pubsub-staging.googleapis.com',
-                  'https://www.googleapis.com/auth/pubsub',
                    443, 'list_some_topics', 'stoked-keyword-656')
   OptionParser.new do |opts|
-    opts.on('--oauth_scope scope',
-            'Scope for OAuth tokens') { |v| args['oauth_scope'] = v }
     opts.on('--server_host SERVER_HOST', 'server hostname') do |v|
       args.host = v
     end
@@ -250,7 +247,7 @@
 end
 
 def _check_args(args)
-  %w(host port action oauth_scope).each do |a|
+  %w(host port action).each do |a|
     if args[a].nil?
       raise OptionParser::MissingArgument.new("please specify --#{a}")
     end
diff --git a/src/ruby/bin/interop/interop_client.rb b/src/ruby/bin/interop/interop_client.rb
index b2a8711..af7a1d5 100755
--- a/src/ruby/bin/interop/interop_client.rb
+++ b/src/ruby/bin/interop/interop_client.rb
@@ -110,6 +110,11 @@
       end
     end
 
+    if opts.test_case == 'jwt_token_creds'  # don't use a scope
+      auth_creds = Google::Auth.get_application_default
+      stub_opts[:update_metadata] = auth_creds.updater_proc
+    end
+
     logger.info("... connecting securely to #{address}")
     Grpc::Testing::TestService::Stub.new(address, **stub_opts)
   else
@@ -201,6 +206,15 @@
     p 'OK: service_account_creds'
   end
 
+  def jwt_token_creds
+    json_key = File.read(ENV[AUTH_ENV])
+    wanted_email = MultiJson.load(json_key)['client_email']
+    resp = perform_large_unary(fill_username: true)
+    assert_equal(wanted_email, resp.username,
+                 'service_account_creds: incorrect username')
+    p 'OK: jwt_token_creds'
+  end
+
   def compute_engine_creds
     resp = perform_large_unary(fill_username: true,
                                fill_oauth_scope: true)
diff --git a/src/ruby/lib/grpc/generic/client_stub.rb b/src/ruby/lib/grpc/generic/client_stub.rb
index 6547a14..dc7672d 100644
--- a/src/ruby/lib/grpc/generic/client_stub.rb
+++ b/src/ruby/lib/grpc/generic/client_stub.rb
@@ -52,6 +52,14 @@
       Core::Channel.new(host, kw, creds)
     end
 
+    def self.update_with_jwt_aud_uri(a_hash, host, method)
+      last_slash_idx, res = method.rindex('/'), a_hash.clone
+      return res if last_slash_idx.nil?
+      service_name = method[0..(last_slash_idx - 1)]
+      res[:jwt_aud_uri] = "https://#{host}#{service_name}"
+      res
+    end
+
     # check_update_metadata is used by #initialize verify that it's a Proc.
     def self.check_update_metadata(update_metadata)
       return update_metadata if update_metadata.nil?
@@ -147,7 +155,8 @@
     def request_response(method, req, marshal, unmarshal, timeout = nil,
                          return_op: false, **kw)
       c = new_active_call(method, marshal, unmarshal, timeout)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
+      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
+      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.request_response(req, **md) unless return_op
 
       # return the operation view of the active_call; define #execute as a
@@ -204,7 +213,8 @@
     def client_streamer(method, requests, marshal, unmarshal, timeout = nil,
                         return_op: false, **kw)
       c = new_active_call(method, marshal, unmarshal, timeout)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
+      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
+      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.client_streamer(requests, **md) unless return_op
 
       # return the operation view of the active_call; define #execute as a
@@ -270,7 +280,8 @@
     def server_streamer(method, req, marshal, unmarshal, timeout = nil,
                         return_op: false, **kw, &blk)
       c = new_active_call(method, marshal, unmarshal, timeout)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
+      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
+      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.server_streamer(req, **md, &blk) unless return_op
 
       # return the operation view of the active_call; define #execute
@@ -375,7 +386,8 @@
     def bidi_streamer(method, requests, marshal, unmarshal, timeout = nil,
                       return_op: false, **kw, &blk)
       c = new_active_call(method, marshal, unmarshal, timeout)
-      md = @update_metadata.nil? ? kw : @update_metadata.call(kw.clone)
+      kw_with_jwt_uri = self.class.update_with_jwt_aud_uri(kw, @host, method)
+      md = @update_metadata.nil? ? kw : @update_metadata.call(kw_with_jwt_uri)
       return c.bidi_streamer(requests, **md, &blk) unless return_op
 
       # return the operation view of the active_call; define #execute
diff --git a/src/ruby/spec/generic/rpc_server_spec.rb b/src/ruby/spec/generic/rpc_server_spec.rb
index f409d73..245999e 100644
--- a/src/ruby/spec/generic/rpc_server_spec.rb
+++ b/src/ruby/spec/generic/rpc_server_spec.rb
@@ -400,7 +400,8 @@
         end
         stub = EchoStub.new(@host, **@client_opts)
         expect(stub.an_rpc(req, k1: 'v1', k2: 'v2')).to be_a(EchoMsg)
-        wanted_md = [{ 'k1' => 'updated-v1', 'k2' => 'v2' }]
+        wanted_md = [{ 'k1' => 'updated-v1', 'k2' => 'v2',
+                       'jwt_aud_uri' => "https://#{@host}/EchoService" }]
         expect(service.received_md).to eq(wanted_md)
         @srv.stop
         t.join
diff --git a/tools/gce_setup/grpc_docker.sh b/tools/gce_setup/grpc_docker.sh
index d142432..c8481c4 100755
--- a/tools/gce_setup/grpc_docker.sh
+++ b/tools/gce_setup/grpc_docker.sh
@@ -1163,6 +1163,23 @@
   echo $the_cmd
 }
 
+# constructs the full dockerized ruby jwt_tokens auth interop test cmd.
+#
+# call-seq:
+#   flags= .... # generic flags to include the command
+#   cmd=$($grpc_gen_test_cmd $flags)
+grpc_cloud_prod_auth_jwt_token_creds_gen_ruby_cmd() {
+  local cmd_prefix="sudo docker run grpc/ruby bin/bash -l -c";
+  local test_script="/var/local/git/grpc/src/ruby/bin/interop/interop_client.rb"
+  local test_script+=" --use_tls"
+  local gfe_flags=$(_grpc_prod_gfe_flags)
+  local added_gfe_flags=$(_grpc_jwt_token_test_flags)
+  local env_prefix="SSL_CERT_FILE=/cacerts/roots.pem"
+  env_prefix+=" GOOGLE_APPLICATION_CREDENTIALS=/service_account/stubbyCloudTestingTest-7dd63462c60c.json"
+  local the_cmd="$cmd_prefix '$env_prefix ruby $test_script $gfe_flags $added_gfe_flags $@'"
+  echo $the_cmd
+}
+
 # constructs the full dockerized Go interop test cmd.
 #
 # call-seq: