# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'addressable/uri' require 'signet/oauth_1' require 'signet/oauth_1/credential' require 'signet/errors' module Signet #:nodoc: module OAuth1 class Client ## # Creates an OAuth 1.0 client. # # @param [Hash] options # The configuration parameters for the client. # - :temporary_credential_uri — # The OAuth temporary credentials URI. # - :authorization_uri — The OAuth authorization URI. # - :token_credential_uri — # The OAuth token credentials URI. # - :client_credential_key — # The OAuth client credential key. # - :client_credential_secret — # The OAuth client credential secret. # - :callback — The OAuth callback. Defaults to 'oob'. # # @example # client = Signet::OAuth1::Client.new( # :temporary_credential_uri => # 'https://www.google.com/accounts/OAuthGetRequestToken', # :authorization_uri => # 'https://www.google.com/accounts/OAuthAuthorizeToken', # :token_credential_uri => # 'https://www.google.com/accounts/OAuthGetAccessToken', # :client_credential_key => 'anonymous', # :client_credential_secret => 'anonymous' # ) def initialize(options={}) self.temporary_credential_uri = options[:temporary_credential_uri] self.authorization_uri = options[:authorization_uri] self.token_credential_uri = options[:token_credential_uri] # Technically... this would allow you to pass in a :client key... # But that would be weird. Don't do that. self.client_credential_key = Signet::OAuth1.extract_credential_key_option(:client, options) self.client_credential_secret = Signet::OAuth1.extract_credential_secret_option(:client, options) self.temporary_credential_key = Signet::OAuth1.extract_credential_key_option(:temporary, options) self.temporary_credential_secret = Signet::OAuth1.extract_credential_secret_option(:temporary, options) self.token_credential_key = Signet::OAuth1.extract_credential_key_option(:token, options) self.token_credential_secret = Signet::OAuth1.extract_credential_secret_option(:token, options) self.callback = options[:callback] end ## # Returns the temporary credentials URI for this client. # # @return [Addressable::URI] The temporary credentials URI. def temporary_credential_uri return @temporary_credential_uri end alias_method :request_token_uri, :temporary_credential_uri ## # Sets the temporary credentials URI for this client. # # @param [Addressable::URI, String, #to_str] # new_temporary_credential_uri # The temporary credentials URI. def temporary_credential_uri=(new_temporary_credential_uri) if new_temporary_credential_uri != nil new_temporary_credential_uri = Addressable::URI.parse(new_temporary_credential_uri) @temporary_credential_uri = new_temporary_credential_uri else @temporary_credential_uri = nil end end alias_method :request_token_uri=, :temporary_credential_uri= ## # Returns the authorization URI that the user should be redirected to. # # @return [Addressable::URI] The authorization URI. # # @see Signet::OAuth1.generate_authorization_uri def authorization_uri(options={}) options = options.merge( :temporary_credential_key => self.temporary_credential_key, :callback => self.callback ) return nil if @authorization_uri == nil return Addressable::URI.parse( ::Signet::OAuth1.generate_authorization_uri( @authorization_uri, options ) ) end ## # Sets the authorization URI for this client. # # @param [Addressable::URI, String, #to_str] new_authorization_uri # The authorization URI. def authorization_uri=(new_authorization_uri) if new_authorization_uri != nil new_authorization_uri = Addressable::URI.parse(new_authorization_uri) @authorization_uri = new_authorization_uri else @authorization_uri = nil end end ## # Returns the token credential URI for this client. # # @return [Addressable::URI] The token credential URI. def token_credential_uri return @token_credential_uri end alias_method :access_token_uri, :token_credential_uri ## # Sets the token credential URI for this client. # # @param [Addressable::URI, String, #to_str] new_token_credential_uri # The token credential URI. def token_credential_uri=(new_token_credential_uri) if new_token_credential_uri != nil new_token_credential_uri = Addressable::URI.parse(new_token_credential_uri) @token_credential_uri = new_token_credential_uri else @token_credential_uri = nil end end alias_method :access_token_uri=, :token_credential_uri= # Lots of duplicated code here, but for the sake of auto-generating # documentation, we're going to let it slide. Oh well. ## # Returns the client credential for this client. # # @return [Signet::OAuth1::Credential] The client credentials. def client_credential if self.client_credential_key && self.client_credential_secret return ::Signet::OAuth1::Credential.new( self.client_credential_key, self.client_credential_secret ) elsif !self.client_credential_key && !self.client_credential_secret return nil else raise ArgumentError, "The client credential key and secret must be set." end end alias_method :consumer_token, :client_credential ## # Sets the client credential for this client. # # @param [Signet::OAuth1::Credential] new_client_credential # The client credentials. def client_credential=(new_client_credential) if new_client_credential != nil if !new_client_credential.kind_of?(::Signet::OAuth1::Credential) raise TypeError, "Expected Signet::OAuth1::Credential, " + "got #{new_client_credential.class}." end @client_credential_key = new_client_credential.key @client_credential_secret = new_client_credential.secret else @client_credential_key = nil @client_credential_secret = nil end end alias_method :consumer_token=, :client_credential= ## # Returns the client credential key for this client. # # @return [String] The client credential key. def client_credential_key return @client_credential_key end alias_method :consumer_key, :client_credential_key ## # Sets the client credential key for this client. # # @param [String, #to_str] new_client_credential_key # The client credential key. def client_credential_key=(new_client_credential_key) if new_client_credential_key != nil if !new_client_credential_key.respond_to?(:to_str) raise TypeError, "Can't convert #{new_client_credential_key.class} into String." end new_client_credential_key = new_client_credential_key.to_str @client_credential_key = new_client_credential_key else @client_credential_key = nil end end alias_method :consumer_key=, :client_credential_key= ## # Returns the client credential secret for this client. # # @return [String] The client credential secret. def client_credential_secret return @client_credential_secret end alias_method :consumer_secret, :client_credential_secret ## # Sets the client credential secret for this client. # # @param [String, #to_str] new_client_credential_secret # The client credential secret. def client_credential_secret=(new_client_credential_secret) if new_client_credential_secret != nil if !new_client_credential_secret.respond_to?(:to_str) raise TypeError, "Can't convert #{new_client_credential_secret.class} " + "into String." end new_client_credential_secret = new_client_credential_secret.to_str @client_credential_secret = new_client_credential_secret else @client_credential_secret = nil end end alias_method :consumer_secret=, :client_credential_secret= ## # Returns the temporary credential for this client. # # @return [Signet::OAuth1::Credential] The temporary credentials. def temporary_credential if self.temporary_credential_key && self.temporary_credential_secret return ::Signet::OAuth1::Credential.new( self.temporary_credential_key, self.temporary_credential_secret ) elsif !self.temporary_credential_key && !self.temporary_credential_secret return nil else raise ArgumentError, "The temporary credential key and secret must be set." end end alias_method :request_token, :temporary_credential ## # Sets the temporary credential for this client. # # @param [Signet::OAuth1::Credential] new_temporary_credential # The temporary credentials. def temporary_credential=(new_temporary_credential) if new_temporary_credential != nil if !new_temporary_credential.kind_of?(::Signet::OAuth1::Credential) raise TypeError, "Expected Signet::OAuth1::Credential, " + "got #{new_temporary_credential.class}." end @temporary_credential_key = new_temporary_credential.key @temporary_credential_secret = new_temporary_credential.secret else @temporary_credential_key = nil @temporary_credential_secret = nil end end alias_method :request_token=, :temporary_credential= ## # Returns the temporary credential key for this client. # # @return [String] The temporary credential key. def temporary_credential_key return @temporary_credential_key end alias_method :request_token_key, :temporary_credential_key ## # Sets the temporary credential key for this client. # # @param [String, #to_str] new_temporary_credential_key # The temporary credential key. def temporary_credential_key=(new_temporary_credential_key) if new_temporary_credential_key != nil if !new_temporary_credential_key.respond_to?(:to_str) raise TypeError, "Can't convert #{new_temporary_credential_key.class} " + "into String." end new_temporary_credential_key = new_temporary_credential_key.to_str @temporary_credential_key = new_temporary_credential_key else @temporary_credential_key = nil end end alias_method :request_token_key=, :temporary_credential_key= ## # Returns the temporary credential secret for this client. # # @return [String] The temporary credential secret. def temporary_credential_secret return @temporary_credential_secret end alias_method :request_token_secret, :temporary_credential_secret ## # Sets the temporary credential secret for this client. # # @param [String, #to_str] new_temporary_credential_secret # The temporary credential secret. def temporary_credential_secret=(new_temporary_credential_secret) if new_temporary_credential_secret != nil if !new_temporary_credential_secret.respond_to?(:to_str) raise TypeError, "Can't convert #{new_temporary_credential_secret.class} " + "into String." end new_temporary_credential_secret = new_temporary_credential_secret.to_str @temporary_credential_secret = new_temporary_credential_secret else @temporary_credential_secret = nil end end alias_method :request_token_secret=, :temporary_credential_secret= ## # Returns the token credential for this client. # # @return [Signet::OAuth1::Credential] The token credentials. def token_credential if self.token_credential_key && self.token_credential_secret return ::Signet::OAuth1::Credential.new( self.token_credential_key, self.token_credential_secret ) elsif !self.token_credential_key && !self.token_credential_secret return nil else raise ArgumentError, "The token credential key and secret must be set." end end alias_method :access_token, :token_credential ## # Sets the token credential for this client. # # @param [Signet::OAuth1::Credential] new_token_credential # The token credentials. def token_credential=(new_token_credential) if new_token_credential != nil if !new_token_credential.kind_of?(::Signet::OAuth1::Credential) raise TypeError, "Expected Signet::OAuth1::Credential, " + "got #{new_token_credential.class}." end @token_credential_key = new_token_credential.key @token_credential_secret = new_token_credential.secret else @token_credential_key = nil @token_credential_secret = nil end end alias_method :access_token=, :token_credential= ## # Returns the token credential key for this client. # # @return [String] The token credential key. def token_credential_key return @token_credential_key end alias_method :access_token_key, :token_credential_key ## # Sets the token credential key for this client. # # @param [String, #to_str] new_token_credential_key # The token credential key. def token_credential_key=(new_token_credential_key) if new_token_credential_key != nil if !new_token_credential_key.respond_to?(:to_str) raise TypeError, "Can't convert #{new_token_credential_key.class} " + "into String." end new_token_credential_key = new_token_credential_key.to_str @token_credential_key = new_token_credential_key else @token_credential_key = nil end end alias_method :access_token_key=, :token_credential_key= ## # Returns the token credential secret for this client. # # @return [String] The token credential secret. def token_credential_secret return @token_credential_secret end alias_method :access_token_secret, :token_credential_secret ## # Sets the token credential secret for this client. # # @param [String, #to_str] new_token_credential_secret # The token credential secret. def token_credential_secret=(new_token_credential_secret) if new_token_credential_secret != nil if !new_token_credential_secret.respond_to?(:to_str) raise TypeError, "Can't convert #{new_token_credential_secret.class} " + "into String." end new_token_credential_secret = new_token_credential_secret.to_str @token_credential_secret = new_token_credential_secret else @token_credential_secret = nil end end alias_method :access_token_secret=, :token_credential_secret= ## # Returns the callback for this client. # # @return [String] The OAuth callback. def callback return @callback || ::Signet::OAuth1::OUT_OF_BAND end ## # Sets the callback for this client. # # @param [String, #to_str] new_callback # The OAuth callback. def callback=(new_callback) if new_callback != nil if !new_callback.respond_to?(:to_str) raise TypeError, "Can't convert #{new_callback.class} into String." end new_callback = new_callback.to_str @callback = new_callback else @callback = nil end end ## # Generates a request for temporary credentials. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters — # Non-standard additional parameters. # - :realm — # The Authorization realm. See RFC 2617. # # @return [Array] The request object. def generate_temporary_credential_request(options={}) verifications = { :temporary_credential_uri => 'Temporary credentials URI', :client_credential_key => 'Client credential key', :client_credential_secret => 'Client credential secret' } # Make sure all required state is set verifications.each do |(key, value)| unless self.send(key) raise ArgumentError, "#{key} was not set." end end options = { :signature_method => 'HMAC-SHA1', :additional_parameters => [], :realm => nil }.merge(options) method = 'POST' parameters = ::Signet::OAuth1.unsigned_temporary_credential_parameters( :client_credential_key => self.client_credential_key, :callback => self.callback, :signature_method => options[:signature_method], :additional_parameters => options[:additional_parameters] ) signature = ::Signet::OAuth1.sign_parameters( method, self.temporary_credential_uri, parameters, self.client_credential_secret ) parameters << ['oauth_signature', signature] authorization_header = [ 'Authorization', ::Signet::OAuth1.generate_authorization_header( parameters, options[:realm] ) ] headers = [authorization_header] if method == 'POST' headers << ['Content-Type', 'application/x-www-form-urlencoded'] end return [ method, self.temporary_credential_uri.to_str, headers, [''] ] end alias_method( :generate_request_token_request, :generate_temporary_credential_request ) ## # Transmits a request for a temporary credential. This method does not # have side-effects within the client. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters — # Non-standard additional parameters. # - :realm — # The Authorization realm. See RFC 2617. # - :adapter — # The HTTP adapter. # Defaults to HTTPAdapter::NetHTTPRequestAdapter. # - :connection — # An open, manually managed HTTP connection. # Must be of type HTTPAdapter::Connection and the # internal connection representation must match the HTTP adapter # being used. # # @return [Signet::OAuth1::Credential] The temporary credential. # # @example # temporary_credential = client.fetch_temporary_credential( # :additional_parameters => { # :scope => 'https://mail.google.com/mail/feed/atom' # } # ) def fetch_temporary_credential(options={}) adapter = options[:adapter] unless adapter require 'httpadapter' require 'httpadapter/adapters/net_http' adapter = HTTPAdapter::NetHTTPRequestAdapter end connection = options[:connection] request = self.generate_temporary_credential_request(options) response = HTTPAdapter.transmit(request, adapter, connection) status, headers, body = response merged_body = StringIO.new body.each do |chunk| merged_body.write(chunk) end body = merged_body.string if status.to_i == 200 return ::Signet::OAuth1.parse_form_encoded_credentials(body) elsif [400, 401, 403].include?(status.to_i) message = 'Authorization failed.' if body.strip.length > 0 message += " Server message:\n#{body.strip}" end error = ::Signet::AuthorizationError.new(message, request, response) raise error else message = "Unexpected status code: #{status}." if body.strip.length > 0 message += " Server message:\n#{body.strip}" end error = ::Signet::AuthorizationError.new(message, request, response) raise error end end alias_method( :fetch_request_token, :fetch_temporary_credential ) ## # Transmits a request for a temporary credential. This method updates # the client with the new temporary credential. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters — # Non-standard additional parameters. # - :realm — # The Authorization realm. See RFC 2617. # - :adapter — # The HTTP adapter. # Defaults to HTTPAdapter::NetHTTPRequestAdapter. # - :connection — # An open, manually managed HTTP connection. # Must be of type HTTPAdapter::Connection and the # internal connection representation must match the HTTP adapter # being used. # # @return [Signet::OAuth1::Credential] The temporary credential. # # @example # client.fetch_temporary_credential!(:additional_parameters => { # :scope => 'https://mail.google.com/mail/feed/atom' # }) def fetch_temporary_credential!(options={}) credential = self.fetch_temporary_credential(options) self.temporary_credential = credential end alias_method( :fetch_request_token!, :fetch_temporary_credential! ) ## # Generates a request for token credentials. # # @param [Hash] options # The configuration parameters for the request. # - :verifier — # The OAuth verifier provided by the server. Required. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :realm — # The Authorization realm. See RFC 2617. # # @return [Array] The request object. def generate_token_credential_request(options={}) verifications = { :token_credential_uri => 'Token credentials URI', :client_credential_key => 'Client credential key', :client_credential_secret => 'Client credential secret', :temporary_credential_key => 'Temporary credential key', :temporary_credential_secret => 'Temporary credential secret' } # Make sure all required state is set verifications.each do |(key, value)| unless self.send(key) raise ArgumentError, "#{key} was not set." end end options = { :signature_method => 'HMAC-SHA1', :realm => nil }.merge(options) method = 'POST' parameters = ::Signet::OAuth1.unsigned_token_credential_parameters( :client_credential_key => self.client_credential_key, :temporary_credential_key => self.temporary_credential_key, :signature_method => options[:signature_method], :verifier => options[:verifier] ) signature = ::Signet::OAuth1.sign_parameters( method, self.token_credential_uri, parameters, self.client_credential_secret, self.temporary_credential_secret ) parameters << ['oauth_signature', signature] authorization_header = [ 'Authorization', ::Signet::OAuth1.generate_authorization_header( parameters, options[:realm] ) ] headers = [authorization_header] if method == 'POST' headers << ['Content-Type', 'application/x-www-form-urlencoded'] end return [ method, self.token_credential_uri.to_str, headers, [''] ] end alias_method( :generate_access_token_request, :generate_token_credential_request ) ## # Transmits a request for a token credential. This method does not # have side-effects within the client. # # @param [Hash] options # The configuration parameters for the request. # - :verifier — # The OAuth verifier provided by the server. Required. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :realm — # The Authorization realm. See RFC 2617. # - :adapter — # The HTTP adapter. # Defaults to HTTPAdapter::NetHTTPRequestAdapter. # - :connection — # An open, manually managed HTTP connection. # Must be of type HTTPAdapter::Connection and the # internal connection representation must match the HTTP adapter # being used. # # @return [Signet::OAuth1::Credential] The token credential. # # @example # token_credential = client.fetch_token_credential( # :verifier => '12345' # ) def fetch_token_credential(options={}) adapter = options[:adapter] unless adapter require 'httpadapter' require 'httpadapter/adapters/net_http' adapter = HTTPAdapter::NetHTTPRequestAdapter end connection = options[:connection] request = self.generate_token_credential_request(options) response = HTTPAdapter.transmit(request, adapter, connection) status, headers, body = response merged_body = StringIO.new body.each do |chunk| merged_body.write(chunk) end body = merged_body.string if status.to_i == 200 return ::Signet::OAuth1.parse_form_encoded_credentials(body) elsif [400, 401, 403].include?(status.to_i) message = 'Authorization failed.' if body.strip.length > 0 message += " Server message:\n#{body.strip}" end error = ::Signet::AuthorizationError.new(message, request, response) raise error else message = "Unexpected status code: #{status}." if body.strip.length > 0 message += " Server message:\n#{body.strip}" end error = ::Signet::AuthorizationError.new(message, request, response) raise error end end alias_method( :fetch_access_token, :fetch_token_credential ) ## # Transmits a request for a token credential. This method updates # the client with the new token credential. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters — # Non-standard additional parameters. # - :realm — # The Authorization realm. See RFC 2617. # - :adapter — # The HTTP adapter. # Defaults to HTTPAdapter::NetHTTPRequestAdapter. # - :connection — # An open, manually managed HTTP connection. # Must be of type HTTPAdapter::Connection and the # internal connection representation must match the HTTP adapter # being used. # # @return [Signet::OAuth1::Credential] The token credential. # # @example # client.fetch_token_credential!(:verifier => '12345') def fetch_token_credential!(options={}) credential = self.fetch_token_credential(options) self.token_credential = credential end alias_method( :fetch_access_token!, :fetch_token_credential! ) ## # Generates an authenticated request for protected resources. # # @param [Hash] options # The configuration parameters for the request. # - :request — # A pre-constructed request to sign. # - :method — # The HTTP method for the request. Defaults to 'GET'. # - :uri — # The URI for the request. # - :headers — # The HTTP headers for the request. # - :body — # The HTTP body for the request. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :realm — # The Authorization realm. See RFC 2617. # # @return [Array] The request object. def generate_authenticated_request(options={}) verifications = { :client_credential_key => 'Client credential key', :client_credential_secret => 'Client credential secret', :token_credential_key => 'Token credential key', :token_credential_secret => 'Token credential secret' } # Make sure all required state is set verifications.each do |(key, value)| unless self.send(key) raise ArgumentError, "#{key} was not set." end end options = { :signature_method => 'HMAC-SHA1', :realm => nil }.merge(options) if options[:request] if options[:request].kind_of?(Array) request = options[:request] elsif options[:adapter] || options[:request].respond_to?(:to_ary) request = HTTPAdapter.adapt_request(options[:request], options[:adapter]) end method, uri, headers, body = request else method = options[:method] || 'GET' uri = options[:uri] headers = options[:headers] || [] body = options[:body] || '' end request_components = { :method => method, :uri => uri, :headers => headers, :body => body } # Verify that we have all pieces required to return an HTTP request request_components.each do |(key, value)| unless value raise ArgumentError, "Missing :#{key} parameter." end end if !body.kind_of?(String) && body.respond_to?(:each) # Just in case we get a chunked body merged_body = StringIO.new body.each do |chunk| merged_body.write(chunk) end body = merged_body.string end if !body.kind_of?(String) raise TypeError, "Expected String, got #{body.class}." end method = method.to_s.upcase parameters = ::Signet::OAuth1.unsigned_resource_parameters( :client_credential_key => self.client_credential_key, :token_credential_key => self.token_credential_key, :signature_method => options[:signature_method] ) media_type = nil headers.each do |(header, value)| if header.downcase == 'Content-Type'.downcase media_type = value.gsub(/^([^;]+)(;.*?)?$/, '\1') end end if method == 'POST' && media_type == 'application/x-www-form-urlencoded' post_parameters = Addressable::URI.form_unencode(body) else post_parameters = [] end parameters = parameters.concat(post_parameters) # No need to attach URI query parameters, the .sign_parameters # method takes care of that automatically. signature = ::Signet::OAuth1.sign_parameters( method, uri, parameters, self.client_credential_secret, self.token_credential_secret ) parameters << ['oauth_signature', signature] authorization_header = [ 'Authorization', ::Signet::OAuth1.generate_authorization_header( parameters, options[:realm] ) ] headers << authorization_header return [method, uri.to_str, headers, [body]] end ## # Transmits a request for a protected resource. # # @param [Hash] options # The configuration parameters for the request. # - :request — # A pre-constructed request to sign. # - :method — # The HTTP method for the request. Defaults to 'GET'. # - :uri — # The URI for the request. # - :headers — # The HTTP headers for the request. # - :body — # The HTTP body for the request. # - :signature_method — # The signature method. Defaults to 'HMAC-SHA1'. # - :realm — # The Authorization realm. See RFC 2617. # - :adapter — # The HTTP adapter. # Defaults to HTTPAdapter::NetHTTPRequestAdapter. # - :connection — # An open, manually managed HTTP connection. # Must be of type HTTPAdapter::Connection and the # internal connection representation must match the HTTP adapter # being used. # # @example # # Using Net::HTTP # response = client.fetch_protected_resource( # :uri => 'http://www.example.com/protected/resource' # ) # status, headers, body = response # # @example # # Using Typhoeus # response = client.fetch_protected_resource( # :request => Typhoeus::Request.new( # 'http://www.example.com/protected/resource' # ), # :adapter => HTTPAdapter::TyphoeusRequestAdapter, # :connection => connection # ) # status, headers, body = response # # @return [Array] The response object. def fetch_protected_resource(options={}) adapter = options[:adapter] unless adapter require 'httpadapter' require 'httpadapter/adapters/net_http' adapter = HTTPAdapter::NetHTTPRequestAdapter end connection = options[:connection] request = self.generate_authenticated_request(options) response = HTTPAdapter.transmit(request, adapter, connection) status, headers, body = response merged_body = StringIO.new body.each do |chunk| merged_body.write(chunk) end body = merged_body.string if status.to_i == 401 # When accessing a protected resource, we only want to raise an # error for 401 responses. message = 'Authorization failed.' if body.strip.length > 0 message += " Server message:\n#{body.strip}" end error = ::Signet::AuthorizationError.new(message, request, response) raise error else return response end end end end end