diff --git a/lib/googleauth/credentials.rb b/lib/googleauth/credentials.rb index 520371d..f49ca15 100644 --- a/lib/googleauth/credentials.rb +++ b/lib/googleauth/credentials.rb @@ -185,6 +185,13 @@ module Google # attr_reader :project_id + ## + # Identifier for a separate project used for billing/quota, if any. + # + # @return [String,nil] + # + attr_reader :quota_project_id + # @private Delegate client methods to the client object. extend Forwardable @@ -215,8 +222,6 @@ module Google :token_credential_uri, :audience, :scope, :issuer, :signing_key, :updater_proc - # rubocop:disable Metrics/AbcSize - ## # Creates a new Credentials instance with the provided auth credentials, and with the default # values configured on the class. @@ -236,23 +241,15 @@ module Google # * +:default_connection+ - the default connection to use for the client # def initialize keyfile, options = {} - scope = options[:scope] verify_keyfile_provided! keyfile @project_id = options["project_id"] || options["project"] + @quota_project_id = options["quota_project_id"] if keyfile.is_a? Signet::OAuth2::Client - @client = keyfile - @project_id ||= keyfile.project_id if keyfile.respond_to? :project_id + update_from_signet keyfile elsif keyfile.is_a? Hash - hash = stringify_hash_keys keyfile - hash["scope"] ||= scope - @client = init_client hash, options - @project_id ||= (hash["project_id"] || hash["project"]) + update_from_hash keyfile, options else - verify_keyfile_exists! keyfile - json = JSON.parse ::File.read(keyfile) - json["scope"] ||= scope - @project_id ||= (json["project_id"] || json["project"]) - @client = init_client json, options + update_from_filepath keyfile, options end CredentialsLoader.warn_if_cloud_sdk_credentials @client.client_id @project_id ||= CredentialsLoader.load_gcloud_project_id @@ -261,7 +258,6 @@ module Google @paths = nil @scope = nil end - # rubocop:enable Metrics/AbcSize ## # Creates a new Credentials instance with auth credentials acquired by searching the @@ -370,6 +366,29 @@ module Google issuer: options["client_email"], signing_key: OpenSSL::PKey::RSA.new(options["private_key"]) } end + + def update_from_signet client + @project_id ||= client.project_id if client.respond_to? :project_id + @quota_project_id ||= client.quota_project_id if client.respond_to? :quota_project_id + @client = client + end + + def update_from_hash hash, options + hash = stringify_hash_keys hash + hash["scope"] ||= options[:scope] + @project_id ||= (hash["project_id"] || hash["project"]) + @quota_project_id ||= hash["quota_project_id"] + @client = init_client hash, options + end + + def update_from_filepath path, options + verify_keyfile_exists! path + json = JSON.parse ::File.read(path) + json["scope"] ||= options[:scope] + @project_id ||= (json["project_id"] || json["project"]) + @quota_project_id ||= json["quota_project_id"] + @client = init_client json, options + end end end end diff --git a/lib/googleauth/json_key_reader.rb b/lib/googleauth/json_key_reader.rb index 4f5002f..471ee42 100644 --- a/lib/googleauth/json_key_reader.rb +++ b/lib/googleauth/json_key_reader.rb @@ -38,8 +38,12 @@ module Google json_key = MultiJson.load json_key_io.read raise "missing client_email" unless json_key.key? "client_email" raise "missing private_key" unless json_key.key? "private_key" - project_id = json_key["project_id"] - [json_key["private_key"], json_key["client_email"], project_id] + [ + json_key["private_key"], + json_key["client_email"], + json_key["project_id"], + json_key["quota_project_id"] + ] end end end diff --git a/lib/googleauth/service_account.rb b/lib/googleauth/service_account.rb index be931c8..3eb4e81 100644 --- a/lib/googleauth/service_account.rb +++ b/lib/googleauth/service_account.rb @@ -51,6 +51,7 @@ module Google extend CredentialsLoader extend JsonKeyReader attr_reader :project_id + attr_reader :quota_project_id # Creates a ServiceAccountCredentials. # @@ -59,11 +60,12 @@ module Google def self.make_creds options = {} json_key_io, scope = options.values_at :json_key_io, :scope if json_key_io - private_key, client_email, project_id = read_json_key json_key_io + private_key, client_email, project_id, quota_project_id = read_json_key json_key_io else private_key = unescape ENV[CredentialsLoader::PRIVATE_KEY_VAR] client_email = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] project_id = ENV[CredentialsLoader::PROJECT_ID_VAR] + quota_project_id = nil end project_id ||= CredentialsLoader.load_gcloud_project_id @@ -72,7 +74,8 @@ module Google scope: scope, issuer: client_email, signing_key: OpenSSL::PKey::RSA.new(private_key), - project_id: project_id) + project_id: project_id, + quota_project_id: quota_project_id) .configure_connection(options) end @@ -87,6 +90,7 @@ module Google def initialize options = {} @project_id = options[:project_id] + @quota_project_id = options[:quota_project_id] super options end @@ -133,6 +137,7 @@ module Google extend CredentialsLoader extend JsonKeyReader attr_reader :project_id + attr_reader :quota_project_id # make_creds proxies the construction of a credentials instance # @@ -151,12 +156,13 @@ module Google def initialize options = {} json_key_io = options[:json_key_io] if json_key_io - @private_key, @issuer, @project_id = + @private_key, @issuer, @project_id, @quota_project_id = self.class.read_json_key json_key_io else @private_key = ENV[CredentialsLoader::PRIVATE_KEY_VAR] @issuer = ENV[CredentialsLoader::CLIENT_EMAIL_VAR] @project_id = ENV[CredentialsLoader::PROJECT_ID_VAR] + @quota_project_id = nil end @project_id ||= CredentialsLoader.load_gcloud_project_id @signing_key = OpenSSL::PKey::RSA.new @private_key diff --git a/spec/googleauth/credentials_spec.rb b/spec/googleauth/credentials_spec.rb index 14a6dd3..de29854 100644 --- a/spec/googleauth/credentials_spec.rb +++ b/spec/googleauth/credentials_spec.rb @@ -36,12 +36,13 @@ require "googleauth" describe Google::Auth::Credentials, :private do let :default_keyfile_hash do { - "private_key_id" => "testabc1234567890xyz", - "private_key" => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n", - "client_email" => "credz-testabc1234567890xyz@developer.gserviceaccount.com", - "client_id" => "credz-testabc1234567890xyz.apps.googleusercontent.com", - "type" => "service_account", - "project_id" => "a_project_id" + "private_key_id" => "testabc1234567890xyz", + "private_key" => "-----BEGIN RSA PRIVATE KEY-----\nMIIBOwIBAAJBAOyi0Hy1l4Ym2m2o71Q0TF4O9E81isZEsX0bb+Bqz1SXEaSxLiXM\nUZE8wu0eEXivXuZg6QVCW/5l+f2+9UPrdNUCAwEAAQJAJkqubA/Chj3RSL92guy3\nktzeodarLyw8gF8pOmpuRGSiEo/OLTeRUMKKD1/kX4f9sxf3qDhB4e7dulXR1co/\nIQIhAPx8kMW4XTTL6lJYd2K5GrH8uBMp8qL5ya3/XHrBgw3dAiEA7+3Iw3ULTn2I\n1J34WlJ2D5fbzMzB4FAHUNEV7Ys3f1kCIQDtUahCMChrl7+H5t9QS+xrn77lRGhs\nB50pjvy95WXpgQIhAI2joW6JzTfz8fAapb+kiJ/h9Vcs1ZN3iyoRlNFb61JZAiA8\nNy5NyNrMVwtB/lfJf1dAK/p/Bwd8LZLtgM6PapRfgw==\n-----END RSA PRIVATE KEY-----\n", + "client_email" => "credz-testabc1234567890xyz@developer.gserviceaccount.com", + "client_id" => "credz-testabc1234567890xyz.apps.googleusercontent.com", + "type" => "service_account", + "project_id" => "a_project_id", + "quota_project_id" => "b_project_id" } end @@ -118,6 +119,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials1) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses can use PATH_ENV_VARS to get keyfile path" do @@ -153,6 +155,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials2) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses can use JSON_ENV_VARS to get keyfile contents" do @@ -190,6 +193,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials3) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses can use DEFAULT_PATHS to get keyfile path" do @@ -225,6 +229,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials4) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses that find no matches default to Google::Auth.get_application_default" do @@ -266,6 +271,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials5) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end end @@ -305,6 +311,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials11) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses can use PATH_ENV_VARS to get keyfile path" do @@ -339,6 +346,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials12) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses can use JSON_ENV_VARS to get keyfile contents" do @@ -375,6 +383,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials13) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses can use DEFAULT_PATHS to get keyfile path" do @@ -409,6 +418,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials14) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end it "subclasses that find no matches default to Google::Auth.get_application_default" do @@ -449,6 +459,7 @@ describe Google::Auth::Credentials, :private do expect(creds).to be_a_kind_of(TestCredentials15) expect(creds.client).to eq(mocked_signet) expect(creds.project_id).to eq(default_keyfile_hash["project_id"]) + expect(creds.quota_project_id).to eq(default_keyfile_hash["quota_project_id"]) end end diff --git a/spec/googleauth/service_account_spec.rb b/spec/googleauth/service_account_spec.rb index 3e138dc..9d7444d 100644 --- a/spec/googleauth/service_account_spec.rb +++ b/spec/googleauth/service_account_spec.rb @@ -112,12 +112,13 @@ describe Google::Auth::ServiceAccountCredentials do let(:client_email) { "app@developer.gserviceaccount.com" } let :cred_json do { - private_key_id: "a_private_key_id", - private_key: @key.to_pem, - client_email: client_email, - client_id: "app.apps.googleusercontent.com", - type: "service_account", - project_id: "a_project_id" + private_key_id: "a_private_key_id", + private_key: @key.to_pem, + client_email: client_email, + client_id: "app.apps.googleusercontent.com", + type: "service_account", + project_id: "a_project_id", + quota_project_id: "b_project_id" } end @@ -285,6 +286,7 @@ describe Google::Auth::ServiceAccountCredentials do ENV["APPDATA"] = dir credentials = @clz.from_well_known_path @scope expect(credentials.project_id).to eq(cred_json[:project_id]) + expect(credentials.quota_project_id).to eq(cred_json[:quota_project_id]) end end @@ -476,6 +478,7 @@ describe Google::Auth::ServiceAccountJwtHeaderCredentials do ENV["APPDATA"] = dir credentials = clz.from_well_known_path @scope expect(credentials.project_id).to eq(cred_json[:project_id]) + expect(credentials.quota_project_id).to be_nil end end end