Plumb CallCredentials through Ruby code, replacing metadata_updater functionality

pull/4289/head
murgatroid99 9 years ago
parent 9946f2b80a
commit cfa26e1ce7
  1. 10
      src/ruby/bin/apis/pubsub_demo.rb
  2. 82
      src/ruby/lib/grpc/generic/client_stub.rb
  3. 9
      src/ruby/pb/test/client.rb
  4. 57
      src/ruby/spec/call_credentials_spec.rb
  5. 9
      src/ruby/spec/call_spec.rb
  6. 29
      src/ruby/spec/channel_credentials_spec.rb
  7. 29
      src/ruby/spec/client_server_spec.rb
  8. 57
      src/ruby/spec/generic/client_stub_spec.rb
  9. 19
      src/ruby/spec/generic/rpc_server_spec.rb

@ -80,8 +80,9 @@ def publisher_stub(opts)
address = "#{opts.host}:#{opts.port}"
stub_clz = Tech::Pubsub::PublisherService::Stub # shorter
GRPC.logger.info("... access PublisherService at #{address}")
stub_clz.new(address,
creds: ssl_creds, update_metadata: auth_proc(opts),
call_creds = GRPC::Core::CallCredentials.new(auth_proc(opts))
combined_creds = ssl_creds.compose(call_creds)
stub_clz.new(address, creds: combined_creds,
GRPC::Core::Channel::SSL_TARGET => opts.host)
end
@ -90,8 +91,9 @@ def subscriber_stub(opts)
address = "#{opts.host}:#{opts.port}"
stub_clz = Tech::Pubsub::SubscriberService::Stub # shorter
GRPC.logger.info("... access SubscriberService at #{address}")
stub_clz.new(address,
creds: ssl_creds, update_metadata: auth_proc(opts),
call_creds = GRPC::Core::CallCredentials.new(auth_proc(opts))
combined_creds = ssl_creds.compose(call_creds)
stub_clz.new(address, creds: combined_creds,
GRPC::Core::Channel::SSL_TARGET => opts.host)
end

@ -57,21 +57,6 @@ module GRPC
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?
fail(TypeError, '!is_a?Proc') unless update_metadata.is_a?(Proc)
update_metadata
end
# Allows users of the stub to modify the propagate mask.
#
# This is an advanced feature for use when making calls to another gRPC
@ -99,29 +84,21 @@ module GRPC
# - :timeout
# when present, this is the default timeout used for calls
#
# - :update_metadata
# when present, this a func that takes a hash and returns a hash
# it can be used to update metadata, i.e, remove, or amend
# metadata values.
#
# @param host [String] the host the stub connects to
# @param q [Core::CompletionQueue] used to wait for events
# @param channel_override [Core::Channel] a pre-created channel
# @param timeout [Number] the default timeout to use in requests
# @param creds [Core::ChannelCredentials] the channel credentials
# @param update_metadata a func that updates metadata as described above
# @param kw [KeywordArgs]the channel arguments
def initialize(host, q,
channel_override: nil,
timeout: nil,
creds: nil,
propagate_mask: nil,
update_metadata: nil,
**kw)
fail(TypeError, '!CompletionQueue') unless q.is_a?(Core::CompletionQueue)
@queue = q
@ch = ClientStub.setup_channel(channel_override, host, creds, **kw)
@update_metadata = ClientStub.check_update_metadata(update_metadata)
alt_host = kw[Core::Channel::SSL_TARGET]
@host = alt_host.nil? ? host : alt_host
@propagate_mask = propagate_mask
@ -166,6 +143,8 @@ module GRPC
# @param deadline [Time] (optional) the time the request should complete
# @param parent [Core::Call] a prior call whose reserved metadata
# will be propagated by this one.
# @param credentials [Core::CallCredentials] credentials to use when making
# the call
# @param return_op [true|false] return an Operation if true
# @return [Object] the response received from the server
def request_response(method, req, marshal, unmarshal,
@ -173,19 +152,20 @@ module GRPC
timeout: nil,
return_op: false,
parent: nil,
credentials: nil,
**kw)
c = new_active_call(method, marshal, unmarshal,
deadline: deadline,
timeout: timeout,
parent: parent)
md = update_metadata(kw, method)
return c.request_response(req, **md) unless return_op
parent: parent,
credentials: credentials)
return c.request_response(req, **kw) unless return_op
# return the operation view of the active_call; define #execute as a
# new method for this instance that invokes #request_response.
op = c.operation
op.define_singleton_method(:execute) do
c.request_response(req, **md)
c.request_response(req, **kw)
end
op
end
@ -234,25 +214,28 @@ module GRPC
# @param return_op [true|false] return an Operation if true
# @param parent [Core::Call] a prior call whose reserved metadata
# will be propagated by this one.
# @param credentials [Core::CallCredentials] credentials to use when making
# the call
# @return [Object|Operation] the response received from the server
def client_streamer(method, requests, marshal, unmarshal,
deadline: nil,
timeout: nil,
return_op: false,
parent: nil,
credentials: nil,
**kw)
c = new_active_call(method, marshal, unmarshal,
deadline: deadline,
timeout: timeout,
parent: parent)
md = update_metadata(kw, method)
return c.client_streamer(requests, **md) unless return_op
parent: parent,
credentials: credentials)
return c.client_streamer(requests, **kw) unless return_op
# return the operation view of the active_call; define #execute as a
# new method for this instance that invokes #client_streamer.
op = c.operation
op.define_singleton_method(:execute) do
c.client_streamer(requests, **md)
c.client_streamer(requests, **kw)
end
op
end
@ -309,6 +292,8 @@ module GRPC
# @param return_op [true|false]return an Operation if true
# @param parent [Core::Call] a prior call whose reserved metadata
# will be propagated by this one.
# @param credentials [Core::CallCredentials] credentials to use when making
# the call
# @param blk [Block] when provided, is executed for each response
# @return [Enumerator|Operation|nil] as discussed above
def server_streamer(method, req, marshal, unmarshal,
@ -316,20 +301,21 @@ module GRPC
timeout: nil,
return_op: false,
parent: nil,
credentials: nil,
**kw,
&blk)
c = new_active_call(method, marshal, unmarshal,
deadline: deadline,
timeout: timeout,
parent: parent)
md = update_metadata(kw, method)
return c.server_streamer(req, **md, &blk) unless return_op
parent: parent,
credentials: credentials)
return c.server_streamer(req, **kw, &blk) unless return_op
# return the operation view of the active_call; define #execute
# as a new method for this instance that invokes #server_streamer
op = c.operation
op.define_singleton_method(:execute) do
c.server_streamer(req, **md, &blk)
c.server_streamer(req, **kw, &blk)
end
op
end
@ -424,6 +410,8 @@ module GRPC
# @param deadline [Time] (optional) the time the request should complete
# @param parent [Core::Call] a prior call whose reserved metadata
# will be propagated by this one.
# @param credentials [Core::CallCredentials] credentials to use when making
# the call
# @param return_op [true|false] return an Operation if true
# @param blk [Block] when provided, is executed for each response
# @return [Enumerator|nil|Operation] as discussed above
@ -432,36 +420,28 @@ module GRPC
timeout: nil,
return_op: false,
parent: nil,
credentials: nil,
**kw,
&blk)
c = new_active_call(method, marshal, unmarshal,
deadline: deadline,
timeout: timeout,
parent: parent)
md = update_metadata(kw, method)
return c.bidi_streamer(requests, **md, &blk) unless return_op
parent: parent,
credentials: credentials)
return c.bidi_streamer(requests, **kw, &blk) unless return_op
# return the operation view of the active_call; define #execute
# as a new method for this instance that invokes #bidi_streamer
op = c.operation
op.define_singleton_method(:execute) do
c.bidi_streamer(requests, **md, &blk)
c.bidi_streamer(requests, **kw, &blk)
end
op
end
private
def update_metadata(kw, method)
return kw if @update_metadata.nil?
just_jwt_uri = self.class.update_with_jwt_aud_uri({}, @host, method)
updated = @update_metadata.call(just_jwt_uri)
# keys should be lowercase
updated = Hash[updated.each_pair.map { |k, v| [k.downcase, v] }]
kw.merge(updated)
end
# Creates a new active stub
#
# @param method [string] the method being called.
@ -473,7 +453,8 @@ module GRPC
def new_active_call(method, marshal, unmarshal,
deadline: nil,
timeout: nil,
parent: nil)
parent: nil,
credentials: nil)
if deadline.nil?
deadline = from_relative_time(timeout.nil? ? @timeout : timeout)
end
@ -483,6 +464,7 @@ module GRPC
method,
nil, # host use nil,
deadline)
call.set_credentials credentials unless credentials.nil?
ActiveCall.new(call, @queue, marshal, unmarshal, deadline, started: false)
end
end

@ -132,7 +132,8 @@ def create_stub(opts)
if wants_creds.include?(opts.test_case)
unless opts.oauth_scope.nil?
auth_creds = Google::Auth.get_application_default(opts.oauth_scope)
stub_opts[:update_metadata] = auth_creds.updater_proc
call_creds = GRPC::Core::CallCredentials.new(auth_creds.updater_proc)
stub_opts[:creds] = stub_opts[:creds].compose call_creds
end
end
@ -141,12 +142,14 @@ def create_stub(opts)
kw = auth_creds.updater_proc.call({}) # gives as an auth token
# use a metadata update proc that just adds the auth token.
stub_opts[:update_metadata] = proc { |md| md.merge(kw) }
call_creds = GRPC::Core::CallCredentials.new(proc { |md| md.merge(kw) })
stub_opts[:creds] = stub_opts[:creds].compose call_creds
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
call_creds = GRPC::Core::CallCredentials.new(auth_creds.updater_proc)
stub_opts[:creds] = stub_opts[:creds].compose call_creds
end
GRPC.logger.info("... connecting securely to #{address}")

@ -0,0 +1,57 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'grpc'
describe GRPC::Core::CallCredentials do
CallCredentials = GRPC::Core::CallCredentials
let(:auth_proc) { proc { { 'plugin_key' => 'plugin_value' } } }
describe '#new' do
it 'can successfully create a CallCredentials from a proc' do
expect { CallCredentials.new(auth_proc) }.not_to raise_error
end
end
describe '#compose' do
it 'can compose with another CallCredentials' do
creds1 = CallCredentials.new(auth_proc)
creds2 = CallCredentials.new(auth_proc)
expect { creds1.compose creds2 }.not_to raise_error
end
it 'can compose with multiple CallCredentials' do
creds1 = CallCredentials.new(auth_proc)
creds2 = CallCredentials.new(auth_proc)
creds3 = CallCredentials.new(auth_proc)
expect { creds1.compose(creds2, creds3) }.not_to raise_error
end
end
end

@ -144,6 +144,15 @@ describe GRPC::Core::Call do
end
end
describe '#set_credentials!' do
it 'can set a valid CallCredentials object' do
call = make_test_call
auth_proc = proc { { 'plugin_key' => 'plugin_value' } }
creds = GRPC::Core::CallCredentials.new auth_proc
expect { call.set_credentials! creds }.not_to raise_error
end
end
def make_test_call
@ch.create_call(client_queue, nil, nil, 'dummy_method', nil, deadline)
end

@ -31,6 +31,7 @@ require 'grpc'
describe GRPC::Core::ChannelCredentials do
ChannelCredentials = GRPC::Core::ChannelCredentials
CallCredentials = GRPC::Core::CallCredentials
def load_test_certs
test_root = File.join(File.dirname(__FILE__), 'testdata')
@ -60,4 +61,32 @@ describe GRPC::Core::ChannelCredentials do
expect(&blk).to raise_error
end
end
describe '#compose' do
it 'can compose with a CallCredentials' do
certs = load_test_certs
channel_creds = ChannelCredentials.new(*certs)
auth_proc = proc { { 'plugin_key' => 'plugin_value' } }
call_creds = CallCredentials.new auth_proc
expect { channel_creds.compose call_creds }.not_to raise_error
end
it 'can compose with multiple CallCredentials' do
certs = load_test_certs
channel_creds = ChannelCredentials.new(*certs)
auth_proc = proc { { 'plugin_key' => 'plugin_value' } }
call_creds1 = CallCredentials.new auth_proc
call_creds2 = CallCredentials.new auth_proc
expect do
channel_creds.compose(call_creds1, call_creds2)
end.not_to raise_error
end
it 'cannot compose with ChannelCredentials' do
certs = load_test_certs
channel_creds1 = ChannelCredentials.new(*certs)
channel_creds2 = ChannelCredentials.new(*certs)
expect { channel_creds1.compose channel_creds2 }.to raise_error(TypeError)
end
end
end

@ -413,6 +413,8 @@ describe 'the http client/server' do
end
describe 'the secure http client/server' do
include_context 'setup: tags'
def load_test_certs
test_root = File.join(File.dirname(__FILE__), 'testdata')
files = ['ca.pem', 'server1.key', 'server1.pem']
@ -443,4 +445,31 @@ describe 'the secure http client/server' do
it_behaves_like 'GRPC metadata delivery works OK' do
end
it 'modifies metadata with CallCredentials' do
auth_proc = proc { { 'k1' => 'updated-v1' } }
call_creds = GRPC::Core::CallCredentials.new(auth_proc)
md = { 'k2' => 'v2' }
expected_md = { 'k1' => 'updated-v1', 'k2' => 'v2' }
recvd_rpc = nil
rcv_thread = Thread.new do
recvd_rpc = @server.request_call(@server_queue, @server_tag, deadline)
end
call = new_client_call
call.set_credentials! call_creds
client_ops = {
CallOps::SEND_INITIAL_METADATA => md
}
batch_result = call.run_batch(@client_queue, @client_tag, deadline,
client_ops)
expect(batch_result.send_metadata).to be true
# confirm the server can receive the client metadata
rcv_thread.join
expect(recvd_rpc).to_not eq nil
recvd_md = recvd_rpc.metadata
replace_symbols = Hash[expected_md.each_pair.collect { |x, y| [x.to_s, y] }]
expect(recvd_md).to eq(recvd_md.merge(replace_symbols))
end
end

@ -145,34 +145,6 @@ describe 'ClientStub' do
th.join
end
it 'should update the sent metadata with a provided metadata updater' do
server_port = create_test_server
host = "localhost:#{server_port}"
th = run_request_response(@sent_msg, @resp, @pass,
k1: 'updated-v1', k2: 'v2')
update_md = proc do |md|
md[:k1] = 'updated-v1'
md
end
stub = GRPC::ClientStub.new(host, @cq, update_metadata: update_md)
expect(get_response(stub)).to eq(@resp)
th.join
end
it 'should downcase the keys provided by the metadata updater' do
server_port = create_test_server
host = "localhost:#{server_port}"
th = run_request_response(@sent_msg, @resp, @pass,
k1: 'downcased-key-v1', k2: 'v2')
update_md = proc do |md|
md[:K1] = 'downcased-key-v1'
md
end
stub = GRPC::ClientStub.new(host, @cq, update_metadata: update_md)
expect(get_response(stub)).to eq(@resp)
th.join
end
it 'should send a request when configured using an override channel' do
server_port = create_test_server
alt_host = "localhost:#{server_port}"
@ -241,20 +213,6 @@ describe 'ClientStub' do
th.join
end
it 'should update the sent metadata with a provided metadata updater' do
server_port = create_test_server
host = "localhost:#{server_port}"
th = run_client_streamer(@sent_msgs, @resp, @pass,
k1: 'updated-v1', k2: 'v2')
update_md = proc do |md|
md[:k1] = 'updated-v1'
md
end
stub = GRPC::ClientStub.new(host, @cq, update_metadata: update_md)
expect(get_response(stub)).to eq(@resp)
th.join
end
it 'should raise an error if the status is not ok' do
server_port = create_test_server
host = "localhost:#{server_port}"
@ -323,21 +281,6 @@ describe 'ClientStub' do
expect { e.collect { |r| r } }.to raise_error(GRPC::BadStatus)
th.join
end
it 'should update the sent metadata with a provided metadata updater' do
server_port = create_test_server
host = "localhost:#{server_port}"
th = run_server_streamer(@sent_msg, @replys, @pass,
k1: 'updated-v1', k2: 'v2')
update_md = proc do |md|
md[:k1] = 'updated-v1'
md
end
stub = GRPC::ClientStub.new(host, @cq, update_metadata: update_md)
e = get_responses(stub)
expect(e.collect { |r| r }).to eq(@replys)
th.join
end
end
describe 'without a call operation' do

@ -422,25 +422,6 @@ describe GRPC::RpcServer do
t.join
end
it 'should receive updated metadata', server: true do
service = EchoService.new
@srv.handle(service)
t = Thread.new { @srv.run }
@srv.wait_till_running
req = EchoMsg.new
client_opts[:update_metadata] = proc do |md|
md[:k1] = 'updated-v1'
md
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',
'jwt_aud_uri' => "https://#{@host}/EchoService" }]
check_md(wanted_md, service.received_md)
@srv.stop
t.join
end
it 'should handle multiple parallel requests', server: true do
@srv.handle(EchoService)
t = Thread.new { @srv.run }

Loading…
Cancel
Save