lib/googleauth/credentials.rb in googleauth-0.11.0 vs lib/googleauth/credentials.rb in googleauth-0.12.0

- old
+ new

@@ -45,10 +45,12 @@ ## # The default target audience ID to be used when none is provided during initialization. AUDIENCE = "https://oauth2.googleapis.com/token".freeze + @audience = @scope = @target_audience = @env_vars = @paths = nil + ## # The default token credential URI to be used when none is provided during initialization. # The URI is the authorization server's HTTP endpoint capable of issuing tokens and # refreshing expired tokens. # @@ -95,33 +97,66 @@ ## # The default scope to be used when none is provided during initialization. # A scope is an access range defined by the authorization server. # The scope can be a single value or a list of values. # + # Either {#scope} or {#target_audience}, but not both, should be non-nil. + # If {#scope} is set, this credential will produce access tokens. + # If {#target_audience} is set, this credential will produce ID tokens. + # # @return [String, Array<String>] # def self.scope return @scope unless @scope.nil? - tmp_scope = [] - # Pull in values is the SCOPE constant exists. - tmp_scope << const_get(:SCOPE) if const_defined? :SCOPE - tmp_scope.flatten.uniq + Array(const_get(:SCOPE)).flatten.uniq if const_defined? :SCOPE end ## # Sets the default scope to be used when none is provided during initialization. # + # Either {#scope} or {#target_audience}, but not both, should be non-nil. + # If {#scope} is set, this credential will produce access tokens. + # If {#target_audience} is set, this credential will produce ID tokens. + # # @param [String, Array<String>] new_scope # @return [String, Array<String>] # def self.scope= new_scope new_scope = Array new_scope unless new_scope.nil? @scope = new_scope end ## + # The default final target audience for ID tokens, to be used when none + # is provided during initialization. + # + # Either {#scope} or {#target_audience}, but not both, should be non-nil. + # If {#scope} is set, this credential will produce access tokens. + # If {#target_audience} is set, this credential will produce ID tokens. + # + # @return [String] + # + def self.target_audience + @target_audience + end + + ## + # Sets the default final target audience for ID tokens, to be used when none + # is provided during initialization. + # + # Either {#scope} or {#target_audience}, but not both, should be non-nil. + # If {#scope} is set, this credential will produce access tokens. + # If {#target_audience} is set, this credential will produce ID tokens. + # + # @param [String] new_target_audience + # + def self.target_audience= new_target_audience + @target_audience = new_target_audience + end + + ## # The environment variables to search for credentials. Values can either be a file path to the # credentials file, or the JSON contents of the credentials file. # # @return [Array<String>] # @@ -183,10 +218,17 @@ # # @return [String] # 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 ## # @!attribute [r] token_credential_uri @@ -199,10 +241,13 @@ # # @!attribute [r] scope # @return [String, Array<String>] The scope for this client. A scope is an access range # defined by the authorization server. The scope can be a single value or a list of values. # + # @!attribute [r] target_audience + # @return [String] The final target audience for ID tokens returned by this credential. + # # @!attribute [r] issuer # @return [String] The issuer ID associated with this client. # # @!attribute [r] signing_key # @return [String, OpenSSL::PKey] The signing key associated with this client. @@ -211,14 +256,12 @@ # @return [Proc] Returns a reference to the {Signet::OAuth2::Client#apply} method, # suitable for passing as a closure. # def_delegators :@client, :token_credential_uri, :audience, - :scope, :issuer, :signing_key, :updater_proc + :scope, :issuer, :signing_key, :updater_proc, :target_audience - # rubocop:disable Metrics/AbcSize - ## # Creates a new Credentials instance with the provided auth credentials, and with the default # values configured on the class. # # @param [String, Hash, Signet::OAuth2::Client] keyfile @@ -234,36 +277,27 @@ # * +"project_id"+ (and optionally +"project"+) - the project identifier for the client # * +:connection_builder+ - the connection builder to use for the client # * +: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 @client.fetch_access_token! @env_vars = nil @paths = nil @scope = nil end - # rubocop:enable Metrics/AbcSize ## # Creates a new Credentials instance with auth credentials acquired by searching the # environment variables and paths configured on the class, and with the default values # configured on the class. @@ -321,11 +355,12 @@ ## # @private Lookup Credentials using Google::Auth.get_application_default. def self.from_application_default options scope = options[:scope] || self.scope - client = Google::Auth.get_application_default scope + auth_opts = { target_audience: options[:target_audience] || target_audience } + client = Google::Auth.get_application_default scope, auth_opts new client, options end private_class_method :from_env_vars, :from_default_paths, @@ -360,16 +395,48 @@ def client_options options # Keyfile options have higher priority over constructor defaults options["token_credential_uri"] ||= self.class.token_credential_uri options["audience"] ||= self.class.audience options["scope"] ||= self.class.scope + options["target_audience"] ||= self.class.target_audience + if !Array(options["scope"]).empty? && options["target_audience"] + raise ArgumentError, "Cannot specify both scope and target_audience" + end + + needs_scope = options["target_audience"].nil? # client options for initializing signet client { token_credential_uri: options["token_credential_uri"], audience: options["audience"], - scope: Array(options["scope"]), + scope: (needs_scope ? Array(options["scope"]) : nil), + target_audience: options["target_audience"], 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] + hash["target_audience"] ||= options[:target_audience] + @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] + json["target_audience"] ||= options[:target_audience] + @project_id ||= (json["project_id"] || json["project"]) + @quota_project_id ||= json["quota_project_id"] + @client = init_client json, options end end end end