X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/fda72888594ff5b203b0fc7bbb6c5c4897917f9c..349c26fa8bf0de53d915c81a03da65d99c53de0c:/lib/google/api_client.rb diff --git a/lib/google/api_client.rb b/lib/google/api_client.rb index eeaf259339..ef3c2b1d34 100644 --- a/lib/google/api_client.rb +++ b/lib/google/api_client.rb @@ -41,7 +41,7 @@ module Google # This class manages APIs communication. class APIClient include Google::APIClient::Logging - + ## # Creates a new Google API client. # @@ -54,13 +54,16 @@ module Google #
  • :two_legged_oauth_1
  • #
  • :oauth_1
  • #
  • :oauth_2
  • + #
  • :google_app_default
  • # # @option options [Boolean] :auto_refresh_token (true) # The setting that controls whether or not the api client attempts to - # refresh authorization when a 401 is hit in #execute. If the token does + # refresh authorization when a 401 is hit in #execute. If the token does # not support it, this option is ignored. # @option options [String] :application_name # The name of the application using the client. + # @option options [String | Array | nil] :scope + # The scope(s) used when using google application default credentials # @option options [String] :application_version # The version number of the application using the client. # @option options [String] :user_agent @@ -83,7 +86,7 @@ module Google # Pass through of options to set on the Faraday connection def initialize(options={}) logger.debug { "#{self.class} - Initializing client with options #{options}" } - + # Normalize key to String to allow indifferent access. options = options.inject({}) do |accu, (key, value)| accu[key.to_sym] = value @@ -114,10 +117,14 @@ module Google # default authentication mechanisms. self.authorization = options.key?(:authorization) ? options[:authorization] : :oauth_2 + if !options['scope'].nil? and self.authorization.respond_to?(:scope=) + self.authorization.scope = options['scope'] + end self.auto_refresh_token = options.fetch(:auto_refresh_token) { true } self.key = options[:key] self.user_ip = options[:user_ip] self.retries = options.fetch(:retries) { 0 } + self.expired_auth_retry = options.fetch(:expired_auth_retry) { true } @discovery_uris = {} @discovery_documents = {} @discovered_apis = {} @@ -173,6 +180,10 @@ module Google :client_credential_secret => nil, :two_legged => true ) + when :google_app_default + require 'googleauth' + new_authorization = Google::Auth.get_application_default + when :oauth_2 require 'signet/oauth_2/client' # NOTE: Do not rely on this default value, as it may change @@ -203,7 +214,7 @@ module Google ## # The setting that controls whether or not the api client attempts to - # refresh authorization when a 401 is hit in #execute. + # refresh authorization when a 401 is hit in #execute. # # @return [Boolean] attr_accessor :auto_refresh_token @@ -250,11 +261,18 @@ module Google ## # Number of times to retry on recoverable errors - # + # # @return [FixNum] # Number of retries attr_accessor :retries + ## + # Whether or not an expired auth token should be re-acquired + # (and the operation retried) regardless of retries setting + # @return [Boolean] + # Auto retry on auth expiry + attr_accessor :expired_auth_retry + ## # Returns the URI for the directory document. # @@ -453,7 +471,7 @@ module Google # Verifies an ID token against a server certificate. Used to ensure that # an ID token supplied by an untrusted client-side mechanism is valid. # Raises an error if the token is invalid or missing. - # + # # @deprecated Use the google-id-token gem for verifying JWTs def verify_id_token! require 'jwt' @@ -472,7 +490,7 @@ module Google else check_cached_certs = lambda do valid = false - for key, cert in @certificates + for _key, cert in @certificates begin self.authorization.decoded_id_token(cert.public_key) valid = true @@ -562,7 +580,7 @@ module Google # - (TrueClass, FalseClass) :authenticated (default: true) - # `true` if the request must be signed or somehow # authenticated, `false` otherwise. - # - (TrueClass, FalseClass) :gzip (default: true) - + # - (TrueClass, FalseClass) :gzip (default: true) - # `true` if gzip enabled, `false` otherwise. # - (FixNum) :retries - # # of times to retry on recoverable errors @@ -602,7 +620,7 @@ module Google options.update(params.shift) if params.size > 0 request = self.generate_request(options) end - + request.headers['User-Agent'] ||= '' + self.user_agent unless self.user_agent.nil? request.headers['Accept-Encoding'] ||= 'gzip' unless options[:gzip] == false request.headers['Content-Type'] ||= '' @@ -611,30 +629,42 @@ module Google connection = options[:connection] || self.connection request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false - + tries = 1 + (options[:retries] || self.retries) + attempt = 0 - Retriable.retriable :tries => tries, + Retriable.retriable :tries => tries, :on => [TransmissionError], - :on_retry => client_error_handler(request.authorization), + :on_retry => client_error_handler, :interval => lambda {|attempts| (2 ** attempts) + rand} do - result = request.send(connection, true) - - case result.status - when 200...300 - result - when 301, 302, 303, 307 - request = generate_request(request.to_hash.merge({ - :uri => result.headers['location'], - :api_method => nil - })) - raise RedirectError.new(result.headers['location'], result) - when 400...500 - raise ClientError.new(result.error_message || "A client error has occurred", result) - when 500...600 - raise ServerError.new(result.error_message || "A server error has occurred", result) - else - raise TransmissionError.new(result.error_message || "A transmission error has occurred", result) + attempt += 1 + + # This 2nd level retriable only catches auth errors, and supports 1 retry, which allows + # auth to be re-attempted without having to retry all sorts of other failures like + # NotFound, etc + Retriable.retriable :tries => ((expired_auth_retry || tries > 1) && attempt == 1) ? 2 : 1, + :on => [AuthorizationError], + :on_retry => authorization_error_handler(request.authorization) do + result = request.send(connection, true) + + case result.status + when 200...300 + result + when 301, 302, 303, 307 + request = generate_request(request.to_hash.merge({ + :uri => result.headers['location'], + :api_method => nil + })) + raise RedirectError.new(result.headers['location'], result) + when 401 + raise AuthorizationError.new(result.error_message || 'Invalid/Expired Authentication', result) + when 400, 402...500 + raise ClientError.new(result.error_message || "A client error has occurred", result) + when 500...600 + raise ServerError.new(result.error_message || "A server error has occurred", result) + else + raise TransmissionError.new(result.error_message || "A transmission error has occurred", result) + end end end end @@ -679,21 +709,20 @@ module Google end return Addressable::Template.new(@base_uri + template).expand(mapping) end - + ## - # Returns on proc for special processing of retries as not all client errors - # are recoverable. Only 401s should be retried and only if the credentials - # are refreshable + # Returns on proc for special processing of retries for authorization errors + # Only 401s should be retried and only if the credentials are refreshable # # @param [#fetch_access_token!] authorization # OAuth 2 credentials - # @return [Proc] - def client_error_handler(authorization) - can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token + # @return [Proc] + def authorization_error_handler(authorization) + can_refresh = authorization.respond_to?(:refresh_token) && auto_refresh_token Proc.new do |exception, tries| - next unless exception.kind_of?(ClientError) - if exception.result.status == 401 && can_refresh && tries == 1 + next unless exception.kind_of?(AuthorizationError) + if can_refresh begin logger.debug("Attempting refresh of access token & retry of request") authorization.fetch_access_token! @@ -705,6 +734,17 @@ module Google end end + ## + # Returns on proc for special processing of retries as not all client errors + # are recoverable. Only 401s should be retried (via authorization_error_handler) + # + # @return [Proc] + def client_error_handler + Proc.new do |exception, tries| + raise exception if exception.kind_of?(ClientError) + end + end + end end