diff --git a/lib/googleauth/service_account.rb b/lib/googleauth/service_account.rb index de2cfd03..c1f78654 100644 --- a/lib/googleauth/service_account.rb +++ b/lib/googleauth/service_account.rb @@ -129,7 +129,7 @@ def apply_self_signed_jwt! a_hash quota_project_id: @quota_project_id } key_io = StringIO.new MultiJson.dump(cred_json) - alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io + alt = ServiceAccountJwtHeaderCredentials.make_creds json_key_io: key_io, scope: scope alt.apply! a_hash end end @@ -154,15 +154,13 @@ class ServiceAccountJwtHeaderCredentials attr_reader :project_id attr_reader :quota_project_id - # make_creds proxies the construction of a credentials instance + # Create a ServiceAccountJwtHeaderCredentials. # - # make_creds is used by the methods in CredentialsLoader. - # - # By default, it calls #new with 2 args, the second one being an - # optional scope. Here's the constructor only has one param, so - # we modify make_creds to reflect this. - def self.make_creds *args - new json_key_io: args[0][:json_key_io] + # @param json_key_io [IO] an IO from which the JSON key can be read + # @param scope [string|array|nil] the scope(s) to access + def self.make_creds options = {} + json_key_io, scope = options.values_at :json_key_io, :scope + new json_key_io: json_key_io, scope: scope end # Initializes a ServiceAccountJwtHeaderCredentials. @@ -181,6 +179,7 @@ def initialize options = {} end @project_id ||= CredentialsLoader.load_gcloud_project_id @signing_key = OpenSSL::PKey::RSA.new @private_key + @scope = options[:scope] end # Construct a jwt token if the JWT_AUD_URI key is present in the input @@ -189,7 +188,7 @@ def initialize options = {} # The jwt token is used as the value of a 'Bearer '. def apply! a_hash, opts = {} jwt_aud_uri = a_hash.delete JWT_AUD_URI_KEY - return a_hash if jwt_aud_uri.nil? + return a_hash if jwt_aud_uri.nil? && @scope.nil? jwt_token = new_jwt_token jwt_aud_uri, opts a_hash[AUTH_METADATA_KEY] = "Bearer #{jwt_token}" a_hash @@ -211,16 +210,21 @@ def updater_proc protected # Creates a jwt uri token. - def new_jwt_token jwt_aud_uri, options = {} + def new_jwt_token jwt_aud_uri = nil, options = {} now = Time.new skew = options[:skew] || 60 assertion = { "iss" => @issuer, "sub" => @issuer, - "aud" => jwt_aud_uri, "exp" => (now + EXPIRY).to_i, "iat" => (now - skew).to_i } + + jwt_aud_uri = nil if @scope + + assertion["scope"] = Array(@scope).join " " if @scope + assertion["aud"] = jwt_aud_uri if jwt_aud_uri + JWT.encode assertion, @signing_key, SIGNING_ALGORITHM end end diff --git a/spec/googleauth/service_account_spec.rb b/spec/googleauth/service_account_spec.rb index 5083b731..bbea5cc9 100644 --- a/spec/googleauth/service_account_spec.rb +++ b/spec/googleauth/service_account_spec.rb @@ -44,9 +44,10 @@ include Google::Auth::CredentialsLoader -shared_examples "jwt header auth" do +shared_examples "jwt header auth" do |aud="https://www.googleapis.com/myservice"| context "when jwt_aud_uri is present" do - let(:test_uri) { "https://www.googleapis.com/myservice" } + let(:test_uri) { aud } + let(:test_scope) { "scope/1 scope/2" } let(:auth_prefix) { "Bearer " } let(:auth_key) { ServiceAccountJwtHeaderCredentials::AUTH_METADATA_KEY } let(:jwt_uri_key) { ServiceAccountJwtHeaderCredentials::JWT_AUD_URI_KEY } @@ -56,14 +57,16 @@ def expect_is_encoded_jwt hdr expect(hdr.start_with?(auth_prefix)).to be true authorization = hdr[auth_prefix.length..-1] payload, = JWT.decode authorization, @key.public_key, true, algorithm: "RS256" - expect(payload["aud"]).to eq(test_uri) + + expect(payload["aud"]).to eq(test_uri) if not test_uri.nil? + expect(payload["scope"]).to eq(test_scope) if test_uri.nil? expect(payload["iss"]).to eq(client_email) end describe "#apply!" do it "should update the target hash with a jwt token" do md = { foo: "bar" } - md[jwt_uri_key] = test_uri + md[jwt_uri_key] = test_uri if test_uri @client.apply! md auth_header = md[auth_key] expect_is_encoded_jwt auth_header @@ -74,31 +77,31 @@ def expect_is_encoded_jwt hdr describe "updater_proc" do it "should provide a proc that updates a hash with a jwt token" do md = { foo: "bar" } - md[jwt_uri_key] = test_uri + md[jwt_uri_key] = test_uri if test_uri the_proc = @client.updater_proc got = the_proc.call md auth_header = got[auth_key] expect_is_encoded_jwt auth_header expect(got[jwt_uri_key]).to be_nil - expect(md[jwt_uri_key]).to_not be_nil + expect(md[jwt_uri_key]).to_not be_nil if test_uri end end describe "#apply" do it "should not update the original hash with a jwt token" do md = { foo: "bar" } - md[jwt_uri_key] = test_uri + md[jwt_uri_key] = test_uri if test_uri the_proc = @client.updater_proc got = the_proc.call md auth_header = md[auth_key] expect(auth_header).to be_nil expect(got[jwt_uri_key]).to be_nil - expect(md[jwt_uri_key]).to_not be_nil + expect(md[jwt_uri_key]).to_not be_nil if test_uri end it "should add a jwt token to the returned hash" do md = { foo: "bar" } - md[jwt_uri_key] = test_uri + md[jwt_uri_key] = test_uri if test_uri got = @client.apply md auth_header = got[auth_key] expect_is_encoded_jwt auth_header @@ -107,6 +110,7 @@ def expect_is_encoded_jwt hdr end end + describe Google::Auth::ServiceAccountCredentials do ServiceAccountCredentials = Google::Auth::ServiceAccountCredentials let(:client_email) { "app@developer.gserviceaccount.com" } @@ -169,14 +173,24 @@ def cred_json_text it_behaves_like "jwt header auth" end - context "when enable_self_signed_jwt is set" do + context "when enable_self_signed_jwt is set with aud" do before :example do + @client.scope = nil @client.instance_variable_set(:@enable_self_signed_jwt, true) end it_behaves_like "jwt header auth" end + context "when enable_self_signed_jwt is set with scope" do + before :example do + @client.scope = ['scope/1', 'scope/2'] + @client.instance_variable_set(:@enable_self_signed_jwt, true) + end + + it_behaves_like "jwt header auth", nil + end + describe "#from_env" do before :example do @var_name = ENV_VAR