+ ##
+ # 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.
+ def verify_id_token!
+ require 'jwt'
+ require 'openssl'
+ @certificates ||= {}
+ if !self.authorization.respond_to?(:id_token)
+ raise ArgumentError, (
+ "Current authorization mechanism does not support ID tokens: " +
+ "#{self.authorization.class.to_s}"
+ )
+ elsif !self.authorization.id_token
+ raise ArgumentError, (
+ "Could not verify ID token, ID token missing. " +
+ "Scopes were: #{self.authorization.scope.inspect}"
+ )
+ else
+ check_cached_certs = lambda do
+ valid = false
+ for key, cert in @certificates
+ begin
+ self.authorization.decoded_id_token(cert.public_key)
+ valid = true
+ rescue JWT::DecodeError, Signet::UnsafeOperationError
+ # Expected exception. Ignore, ID token has not been validated.
+ end
+ end
+ valid
+ end
+ if check_cached_certs.call()
+ return true
+ end
+ response = self.execute!(
+ :http_method => :get,
+ :uri => 'https://www.googleapis.com/oauth2/v1/certs',
+ :authenticated => false
+ )
+ @certificates.merge!(
+ Hash[MultiJson.load(response.body).map do |key, cert|
+ [key, OpenSSL::X509::Certificate.new(cert)]
+ end]
+ )
+ if check_cached_certs.call()
+ return true
+ else
+ raise InvalidIDTokenError,
+ "Could not verify ID token against any available certificate."
+ end
+ end
+ return nil
+ end
+
+ ##
+ # Generates a request.
+ #
+ # @option options [Google::APIClient::Method] :api_method
+ # The method object or the RPC name of the method being executed.
+ # @option options [Hash, Array] :parameters
+ # The parameters to send to the method.
+ # @option options [Hash, Array] :headers The HTTP headers for the request.
+ # @option options [String] :body The body of the request.
+ # @option options [String] :version ("v1")
+ # The service version. Only used if `api_method` is a `String`.
+ # @option options [#generate_authenticated_request] :authorization
+ # The authorization mechanism for the response. Used only if
+ # `:authenticated` is `true`.
+ # @option options [TrueClass, FalseClass] :authenticated (true)
+ # `true` if the request must be signed or somehow
+ # authenticated, `false` otherwise.
+ #
+ # @return [Google::APIClient::Reference] The generated request.
+ #
+ # @example
+ # request = client.generate_request(
+ # :api_method => 'plus.activities.list',
+ # :parameters =>
+ # {'collection' => 'public', 'userId' => 'me'}
+ # )
+ def generate_request(options={})
+ options = {
+ :api_client => self
+ }.merge(options)
+ return Google::APIClient::Request.new(options)
+ end
+
+ ##
+ # Executes a request, wrapping it in a Result object.
+ #
+ # @param [Google::APIClient::Request, Hash, Array] params
+ # Either a Google::APIClient::Request, a Hash, or an Array.
+ #
+ # If a Google::APIClient::Request, no other parameters are expected.
+ #
+ # If a Hash, the below parameters are handled. If an Array, the
+ # parameters are assumed to be in the below order:
+ #
+ # - (Google::APIClient::Method) api_method:
+ # The method object or the RPC name of the method being executed.
+ # - (Hash, Array) parameters:
+ # The parameters to send to the method.
+ # - (String) body: The body of the request.
+ # - (Hash, Array) headers: The HTTP headers for the request.
+ # - (Hash) options: A set of options for the request, of which:
+ # - (#generate_authenticated_request) :authorization (default: true) -
+ # The authorization mechanism for the response. Used only if
+ # `:authenticated` is `true`.
+ # - (TrueClass, FalseClass) :authenticated (default: true) -
+ # `true` if the request must be signed or somehow
+ # authenticated, `false` otherwise.
+ #
+ # @return [Google::APIClient::Result] The result from the API, nil if batch.
+ #
+ # @example
+ # result = client.execute(batch_request)
+ #
+ # @example
+ # plus = client.discovered_api('plus')
+ # result = client.execute(
+ # :api_method => plus.activities.list,
+ # :parameters => {'collection' => 'public', 'userId' => 'me'}
+ # )
+ #
+ # @see Google::APIClient#generate_request
+ def execute(*params)
+ if params.last.kind_of?(Google::APIClient::Request) &&
+ params.size == 1
+ request = params.pop
+ options = {}
+ else
+ # This block of code allows us to accept multiple parameter passing
+ # styles, and maintaining some backwards compatibility.
+ #
+ # Note: I'm extremely tempted to deprecate this style of execute call.
+ if params.last.respond_to?(:to_hash) && params.size == 1
+ options = params.pop
+ else
+ options = {}
+ end
+
+ options[:api_method] = params.shift if params.size > 0
+ options[:parameters] = params.shift if params.size > 0
+ options[:body] = params.shift if params.size > 0
+ options[:headers] = params.shift if params.size > 0
+ 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.parameters['key'] ||= self.key unless self.key.nil?
+ request.parameters['userIp'] ||= self.user_ip unless self.user_ip.nil?
+
+ connection = options[:connection] || Faraday.default_connection
+ request.authorization = options[:authorization] || self.authorization unless options[:authenticated] == false
+
+ result = request.send(connection)
+ if result.status == 401 && authorization.respond_to?(:refresh_token) && auto_refresh_token
+ begin
+ logger.debug("Attempting refresh of access token & retry of request")
+ authorization.fetch_access_token!
+ result = request.send(connection)
+ rescue Signet::AuthorizationError
+ # Ignore since we want the original error
+ end
+ end
+
+ return result
+ end
+
+ ##
+ # Same as Google::APIClient#execute, but raises an exception if there was
+ # an error.
+ #
+ # @see Google::APIClient#execute
+ def execute!(*params)
+ result = self.execute(*params)
+ if result.error?
+ error_message = result.error_message
+ case result.response.status
+ when 400...500
+ exception_type = ClientError
+ error_message ||= "A client error has occurred."
+ when 500...600
+ exception_type = ServerError
+ error_message ||= "A server error has occurred."
+ else
+ exception_type = TransmissionError
+ error_message ||= "A transmission error has occurred."
+ end
+ raise exception_type, error_message
+ end
+ return result
+ end
+
+ protected
+
+ ##
+ # Resolves a URI template against the client's configured base.
+ #
+ # @api private
+ # @param [String, Addressable::URI, Addressable::Template] template
+ # The template to resolve.
+ # @param [Hash] mapping The mapping that corresponds to the template.
+ # @return [Addressable::URI] The expanded URI.
+ def resolve_uri(template, mapping={})
+ @base_uri ||= Addressable::URI.new(
+ :scheme => 'https',
+ :host => self.host,
+ :port => self.port
+ ).normalize
+ template = if template.kind_of?(Addressable::Template)
+ template.pattern
+ elsif template.respond_to?(:to_str)
+ template.to_str