class Signet::OAuth1::Server

Attributes

client_credential[RW]

@return [Proc] lookup the value from this Proc.

nonce_timestamp[RW]

@return [Proc] lookup the value from this Proc.

temporary_credential[RW]

@return [Proc] lookup the value from this Proc.

token_credential[RW]

@return [Proc] lookup the value from this Proc.

verifier[RW]

@return [Proc] lookup the value from this Proc.

Public Class Methods

new(options={}) click to toggle source

Creates an OAuth 1.0 server. @overload initialize(options)

@param [Proc] nonce_timestamp verify a nonce/timestamp pair.
@param [Proc] client_credential find a client credential.
@param [Proc] token_credential find a token credential.
@param [Proc] temporary_credential find a temporary credential.
@param [Proc] verifier validate a verifier value.

@example

server = Signet::OAuth1::Server.new(
  :nonce_timestamp =>
    lambda { |n,t| OauthNonce.remember(n,t) },
  :client_credential =>
    lambda { |key| ClientCredential.find_by_key(key).to_hash },
  :token_credential =>
    lambda { |key| TokenCredential.find_by_key(key).to_hash },
  :temporary_credential =>
    lambda { |key| TemporaryCredential.find_by_key(key).to_hash },
  :verifier =>
    lambda {|verifier| Verifier.find_by_verifier(verifier).active? }
)
# File lib/signet/oauth_1/server.rb, line 53
def initialize(options={})
  [:nonce_timestamp, :client_credential, :token_credential,
   :temporary_credential, :verifier].each do |attr|
     instance_variable_set("@#{attr}", options[attr])
  end
end

Public Instance Methods

authenticate_resource_request(options={}) click to toggle source

Authenticates a request for a protected resource. @overload #authenticate_resource_request(options)

@param [Hash] request The configuration parameters for the request.
@param [String] method the HTTP method , defaults to <code>GET</code>
@param [Addressable::URI, String] uri the URI .
@param [Hash, Array] headers the HTTP headers.
@param [StringIO, String] body The HTTP body.
@param [Boolean] two_legged skip the token_credential lookup?
@param [HTTPAdapter] adapter The HTTP adapter(optional).

@return [Hash] A hash of the credentials and realm for a valid request,

or <code>nil</code> if not valid.
# File lib/signet/oauth_1/server.rb, line 395
def authenticate_resource_request(options={})
  verifications = {
    :client_credential =>
      lambda do |x|
        ::Signet::OAuth1::Credential.new('Client credential key',
                                         'Client credential secret')
      end
  }

  unless(options[:two_legged] == true)
    verifications.update(
      :token_credential =>
        lambda do |x|
          ::Signet::OAuth1::Credential.new('Token credential key',
                                           'Token credential secret')
        end
    )
  end
  # Make sure all required state is set
  verifications.each do |(key, _value)|
    unless self.send(key)
      raise ArgumentError, "#{key} was not set."
    end
  end

  if(options[:request])
    request_components = verify_request_components(
      :request=>options[:request],
      :adapter=>options[:adapter] )
  else
    request_components = verify_request_components(
      :method=>options[:method],
      :uri=>options[:uri],
      :headers=>options[:headers],
      :body=>options[:body] )
  end
  method = request_components[:method]
  uri = request_components[:uri]
  headers = request_components[:headers]
  body = request_components[:body]


  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

  media_type = nil
  headers.each do |(header, value)|
    if header.downcase == 'Content-Type'.downcase
      media_type = value.gsub(/^([^;]+)(;.*?)?$/, '\1')
    end
  end

  auth_hash = verify_auth_header_components(headers)

  auth_token = auth_hash['oauth_token']


  unless(options[:two_legged])
    return nil if(auth_token.nil?)
    return nil unless(token_credential = find_token_credential(auth_token))
    token_credential_secret = token_credential.secret if token_credential
  end

  return nil unless(client_credential =
                      find_client_credential(auth_hash['oauth_consumer_key']))

  return nil unless validate_nonce_timestamp(auth_hash['oauth_nonce'],
                                               auth_hash['oauth_timestamp'])

  if(method == ('POST' || 'PUT') &&
     media_type == 'application/x-www-form-urlencoded')
    request_components[:body] = body
    post_parameters = Addressable::URI.form_unencode(body)
    post_parameters.each {|param| param[1] = "" if param[1].nil?}
    # If the auth header doesn't have the same params as the body, it
    # can't have been signed correctly(5849#3.4.1.3)
    unless(post_parameters.sort == auth_hash.reject{|k,v| k.index('oauth_')}.to_a.sort)
      raise MalformedAuthorizationError.new(
        'Request is of type application/x-www-form-urlencoded ' +
        'but Authentication header did not include form values'
                                           )
    end
  end

  client_credential_secret = client_credential.secret if client_credential

  computed_signature = ::Signet::OAuth1.sign_parameters(
    method,
    uri,
    # Realm isn't used, and will throw the signature off.
    auth_hash.reject{|k,v| k=='realm'}.to_a,
    client_credential_secret,
    token_credential_secret
  )

  if safe_equals?(computed_signature, auth_hash['oauth_signature'])
    {:client_credential=>client_credential,
     :token_credential=>token_credential,
     :realm=>auth_hash['realm']
    }
  else
    nil
  end
end
authenticate_temporary_credential_request(options={}) click to toggle source

Authenticates a temporary credential request. If no oauth_callback is present in the request, oob will be returned.

@overload #authenticate_temporary_credential_request(options)

@param [Hash] request The configuration parameters for the request.
@param [String] method the HTTP method , defaults to <code>GET</code>
@param [Addressable::URI, String] uri the URI .
@param [Hash, Array] headers the HTTP headers.
@param [StringIO, String] body The HTTP body.
@param [HTTPAdapter] adapter The HTTP adapter(optional).

@return [String] The oauth_callback value, or false if not valid.

# File lib/signet/oauth_1/server.rb, line 251
def authenticate_temporary_credential_request(options={})
  verifications = {
    :client_credential =>
      lambda { |x| ::Signet::OAuth1::Credential.new('Client credential key',
                                                    'Client credential secret'
                                                   )
      }
  }
  verifications.each do |(key, _value)|
    raise ArgumentError, "#{key} was not set." unless self.send(key)
  end

  if(options[:request])
    request_components = verify_request_components(
      :request=>options[:request],
      :adapter=>options[:adapter] )
  else
    request_components = verify_request_components(
      :method=>options[:method],
      :uri=>options[:uri],
      :headers=>options[:headers] )
  end
  # body should be blank; we don't care in any case.
  method = request_components[:method]
  uri = request_components[:uri]
  headers = request_components[:headers]

  auth_hash = verify_auth_header_components(headers)
  return false unless(client_credential = find_client_credential(
                                            auth_hash['oauth_consumer_key']) )

  return false unless validate_nonce_timestamp(auth_hash['oauth_nonce'],
                                               auth_hash['oauth_timestamp'])
  client_credential_secret = client_credential.secret if client_credential

  computed_signature = ::Signet::OAuth1.sign_parameters(
    method,
    uri,
    # Realm isn't used, and will throw the signature off.
    auth_hash.reject{|k,v| k=='realm'}.to_a,
    client_credential_secret,
    nil
  )
  if safe_equals?(computed_signature, auth_hash['oauth_signature'])
    if(auth_hash.fetch('oauth_callback', 'oob').empty?)
      'oob'
    else
      auth_hash.fetch('oauth_callback')
    end
  else
    false
  end
end
authenticate_token_credential_request(options={}) click to toggle source

Authenticates a token credential request. @overload #authenticate_token_credential_request(options)

@param [Hash] request The configuration parameters for the request.
@param [String] method the HTTP method , defaults to <code>GET</code>
@param [Addressable::URI, String] uri the URI .
@param [Hash, Array] headers the HTTP headers.
@param [StringIO, String] body The HTTP body.
@param [HTTPAdapter] adapter The HTTP adapter(optional).

@return [Hash] A hash of credentials and realm for a valid request,

or <code>nil</code> if not valid.
# File lib/signet/oauth_1/server.rb, line 317
def authenticate_token_credential_request(options={})
  verifications = {
    :client_credential =>
      lambda {|x| ::Signet::OAuth1::Credential.new('Client credential key',
                                                   'Client credential secret')
             },
    :temporary_credential =>
      lambda {|x| ::Signet::OAuth1::Credential.new('Temporary credential key',
                                                   'Temporary credential secret')
             },
    :verifier =>
      lambda {|x| 'Verifier' }
  }
  verifications.each do |(key, _value)|
    unless self.send(key)
      raise ArgumentError, "#{key} was not set."
    end
  end
  if(options[:request])
    request_components = verify_request_components(
      :request=>options[:request],
      :adapter=>options[:adapter]
    )
  else
    request_components = verify_request_components(
      :method=>options[:method],
      :uri=>options[:uri],
      :headers=>options[:headers],
      :body=>options[:body]
    )
  end
  # body should be blank; we don't care in any case.
  method = request_components[:method]
  uri = request_components[:uri]
  headers = request_components[:headers]

  auth_hash = verify_auth_header_components(headers)
  return false unless(
    client_credential = find_client_credential(auth_hash['oauth_consumer_key'])
  )
  return false unless(
    temporary_credential = find_temporary_credential(auth_hash['oauth_token'])
  )
  return false unless validate_nonce_timestamp(
    auth_hash['oauth_nonce'], auth_hash['oauth_timestamp'])

  computed_signature = ::Signet::OAuth1.sign_parameters(
    method,
    uri,
    # Realm isn't used, and will throw the signature off.
    auth_hash.reject{|k,v| k=='realm'}.to_a,
    client_credential.secret,
    temporary_credential.secret
  )

  if safe_equals?(computed_signature, auth_hash['oauth_signature'])
    {:client_credential=>client_credential,
      :temporary_credential=>temporary_credential,
      :realm=>auth_hash['realm']
    }
  else
    nil
  end
end
call_credential_lookup(credential, key) click to toggle source

Call a credential lookup, and cast the result to a proper Credential.

@param [Proc] credential to call. @param [String] key provided to the Proc in credential @return [Signet::OAuth1::Credential] credential provided by

<code>credential</code> (if any).
# File lib/signet/oauth_1/server.rb, line 118
def call_credential_lookup(credential, key)
  cred = credential.call(key) if
          credential.respond_to?(:call)
  return nil if cred.nil?
  return nil unless (cred.respond_to?(:to_str) ||
                     cred.respond_to?(:to_ary) ||
                     cred.respond_to?(:to_hash) )
  if(cred.instance_of?(::Signet::OAuth1::Credential))
    cred
  else
    ::Signet::OAuth1::Credential.new(cred)
  end
end
find_client_credential(key) click to toggle source

Find the appropriate client credential by calling the {#client_credential} Proc.

@param [String] key provided to the {#client_credential} Proc. @return [Signet::OAuth1::Credential] The client credential.

# File lib/signet/oauth_1/server.rb, line 87
def find_client_credential(key)
  call_credential_lookup(@client_credential, key)
end
find_temporary_credential(key) click to toggle source

Find the appropriate client credential by calling the {#temporary_credential} Proc.

@param [String] key provided to the {#temporary_credential} Proc. @return [Signet::OAuth1::Credential] if the credential is found.

# File lib/signet/oauth_1/server.rb, line 107
def find_temporary_credential(key)
  call_credential_lookup(@temporary_credential, key)
end
find_token_credential(key) click to toggle source

Find the appropriate client credential by calling the {#token_credential} Proc.

@param [String] key provided to the {#token_credential} Proc. @return [Signet::OAuth1::Credential] if the credential is found.

# File lib/signet/oauth_1/server.rb, line 97
def find_token_credential(key)
  call_credential_lookup(@token_credential, key)
end
find_verifier(verifier) click to toggle source
Determine if the verifier is valid by calling the Proc in {#verifier}.

@param [String] verifier Key provided to the {#verifier} Proc. @return [Boolean] if the verifier Proc returns anything other than

<code>nil</code> or <code>false</code>.
# File lib/signet/oauth_1/server.rb, line 138
def find_verifier(verifier)
  verified = @verifier.call(verifier) if @verifier.respond_to?(:call)
  verified ? true : false
end
request_realm(options={}) click to toggle source

@overload #request_realm(options)

@param [Hash] request A pre-constructed request to verify.
@param [String] method the HTTP method , defaults to <code>GET</code>
@param [Addressable::URI, String] uri the URI .
@param [Hash, Array] headers the HTTP headers.
@param [StringIO, String] body The HTTP body.
@param [HTTPAdapter] adapter The HTTP adapter(optional).

@return [String] The Authorization realm(see RFC 2617) of the request.

# File lib/signet/oauth_1/server.rb, line 217
def request_realm(options={})
  if(options[:request])
    request_components = verify_request_components(
      :request=>options[:request],
      :adapter=>options[:adapter] )
  else
    request_components = verify_request_components(
      :method=>options[:method],
      :uri=>options[:uri],
      :headers=>options[:headers],
      :body=>options[:body] )
  end

  auth_header = request_components[:headers].find{|x| x[0] == 'Authorization'}
  if(auth_header.nil? || auth_header[1] == '')
    raise MalformedAuthorizationError.new('Authorization header is missing')
  end
  auth_hash = ::Signet::OAuth1.parse_authorization_header(
    auth_header[1] ).inject({}) {|acc, (key,val)| acc[key.downcase] = val; acc}
  auth_hash['realm']
end
safe_equals?(a, b) click to toggle source

Constant time string comparison.

# File lib/signet/oauth_1/server.rb, line 61
def safe_equals?(a, b)
  check = a.bytesize ^ b.bytesize
  a.bytes.zip(b.bytes) { |x, y| check |= x ^ y.to_i }
  check == 0
end
validate_nonce_timestamp(nonce, timestamp) click to toggle source

Determine if the supplied nonce/timestamp pair is valid by calling the {#nonce_timestamp} Proc.

@param [String, to_str] nonce value from the request @param [String, to_str] timestamp value from the request @return [Boolean] if the nonce/timestamp pair is valid.

# File lib/signet/oauth_1/server.rb, line 74
def validate_nonce_timestamp(nonce, timestamp)
  nonce =
    @nonce_timestamp.call(nonce, timestamp) if
      @nonce_timestamp.respond_to?(:call)
  nonce ? true : false
end
verify_auth_header_components(headers) click to toggle source

Validate and normalize the HTTP Authorization header.

@param [Array] headers from HTTP request. @return [Hash] Hash of Authorization header.

# File lib/signet/oauth_1/server.rb, line 196
def verify_auth_header_components(headers)
  auth_header = headers.find{|x| x[0] == 'Authorization'}
  if(auth_header.nil? || auth_header[1] == '')
    raise MalformedAuthorizationError.new('Authorization header is missing')
  end
  auth_hash = ::Signet::OAuth1.parse_authorization_header(
    auth_header[1] ).inject({}) {|acc, (key,val)| acc[key.downcase] = val; acc}

  auth_hash
end
verify_request_components(options={}) click to toggle source

Validate and normalize the components from an HTTP request. @overload #verify_request_components(options)

@param [Faraday::Request] request A pre-constructed request to verify.
@param [String] method the HTTP method , defaults to <code>GET</code>
@param [Addressable::URI, String] uri the URI .
@param [Hash, Array] headers the HTTP headers.
@param [StringIO, String] body The HTTP body.
@param [HTTPAdapter] adapter The HTTP adapter(optional).

@return [Hash] normalized request components

# File lib/signet/oauth_1/server.rb, line 154
def verify_request_components(options={})
  if options[:request]
    if options[:request].kind_of?(Faraday::Request) || options[:request].kind_of?(Array)
      request = options[:request]
    elsif options[:adapter]
      request = options[:adapter].adapt_request(options[:request])
    end
    method = request.method
    uri = request.path
    headers = request.headers
    body = request.body
  else
    method = options[:method] || :get
    uri = options[:uri]
    headers = options[:headers] || []
    body = options[:body] || ''
  end

  headers = headers.to_a if headers.kind_of?(Hash)
  method = method.to_s.upcase

  request_components = {
    :method => method,
    :uri => uri,
    :headers => headers
  }

  # Verify that we have all the pieces required to validate the HTTP request
  request_components.each do |(key, value)|
    unless value
      raise ArgumentError, "Missing :#{key} parameter."
    end
  end
  request_components[:body] = body
  request_components
end