Class: OAuthenticator::SignableRequest

Inherits:
Object
  • Object
show all
Defined in:
lib/oauthenticator/signable_request.rb

Overview

a request which may be signed with OAuth, generally in order to apply the signature to an outgoing request in the Authorization header.

primarily this is to be used like:

oauthenticator_signable_request = OAuthenticator::SignableRequest.new(
  :request_method => my_request_method,
  :uri => my_request_uri,
  :media_type => my_request_media_type,
  :body => my_request_body,
  :signature_method => my_oauth_signature_method,
  :consumer_key => my_oauth_consumer_key,
  :consumer_secret => my_oauth_consumer_secret,
  :token => my_oauth_token,
  :token_secret => my_oauth_token_secret,
  :realm => my_authorization_realm
)
my_http_request.headers['Authorization'] = oauthenticator_signable_request.authorization

Constant Summary collapse

PROTOCOL_PARAM_KEYS =

keys of OAuth protocol parameters which form the Authorization header (with an oauth_ prefix). signature is considered separately.

%w(consumer_key token signature_method timestamp nonce version).map(&:freeze).freeze

Instance Method Summary collapse

Constructor Details

#initialize(attributes) ⇒ SignableRequest

initialize a signable request with the following attributes (keys may be string or symbol):

  • request_method (required) - get, post, etc. may be string or symbol.
  • uri (required) - request URI. to_s is called so URI or Addressable::URI or whatever may be passed.
  • media_type (required) - the request media type (may be nil if there is no body). note that this may be different than the Content-Type header; other components of that such as encoding must not be included.
  • body (required) - the request body. may be a String or an IO, or nil if no body is present.
  • hash_body? - whether to add the oauth_body_hash parameter, per the OAuth Request Body Hash specification. defaults to true. not used if the 'authorization' parameter is used.
  • signature_method (required*) - oauth signature method (String)
  • consumer_key (required*) - oauth consumer key (String)
  • consumer_secret (required*) - oauth consumer secret (String)
  • token (optional*) - oauth token; may be omitted if only using a consumer key (two-legged)
  • token_secret (optional) - must be present if token is present. must be omitted if token is omitted.
  • timestamp (optional*) - if omitted, defaults to the current time. if nil is passed, no oauth_timestamp will be present in the generated authorization.
  • nonce (optional*) - if omitted, defaults to a random string. if nil is passed, no oauth_nonce will be present in the generated authorization.
  • version (optional*) - must be nil or '1.0'. defaults to '1.0' if omitted. if nil is passed, no oauth_version will be present in the generated authorization.
  • realm (optional) - authorization realm. if nil is passed, no realm will be present in the generated authorization.
  • authorization - a hash of a received Authorization header, the result of a call to OAuthenticator.parse_authorization. it is useful for calculating the signature of a received request, but for fully authenticating a received request it is generally preferable to use OAuthenticator::SignedRequest. specifying this precludes the requirement to specify any of PROTOCOL_PARAM_KEYS.

(*) attributes which are in PROTOCOL_PARAM_KEYS are unused (and not required) when the 'authorization' attribute is given for signature verification. normally, though, they are used and are required or optional as noted.

Raises:

  • (TypeError)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/oauthenticator/signable_request.rb', line 63

def initialize(attributes)
  raise TypeError, "attributes must be a hash" unless attributes.is_a?(Hash)
  # stringify symbol keys
  @attributes = attributes.map { |k,v| {k.is_a?(Symbol) ? k.to_s : k => v} }.inject({}, &:update)

  # validation - presence
  required = %w(request_method uri media_type body)
  required += %w(signature_method consumer_key) unless @attributes['authorization']
  missing = required - @attributes.keys
  raise ArgumentError, "missing required attributes: #{missing.inspect}" if missing.any?
  other_recognized = PROTOCOL_PARAM_KEYS + %w(authorization consumer_secret token_secret realm hash_body?)
  extra = @attributes.keys - (required + other_recognized)
  raise ArgumentError, "received unrecognized attributes: #{extra.inspect}" if extra.any?

  if @attributes['authorization']
    # this means we are signing an existing request to validate the received signature. don't use defaults.
    unless @attributes['authorization'].is_a?(Hash)
      raise TypeError, "authorization must be a Hash"
    end

    # if authorization is specified, protocol params should not be specified in the regular attributes 
    given_protocol_params = @attributes.reject { |k,v| !(PROTOCOL_PARAM_KEYS.include?(k) && v) }
    if given_protocol_params.any?
      raise ArgumentError, "an existing authorization was given, but protocol parameters were also " +
        "given. protocol parameters should not be specified when verifying an existing authorization. " +
        "given protocol parameters were: #{given_protocol_params.inspect}"
    end
  else
    # defaults
    defaults = {
      'version' => '1.0',
    }
    if @attributes['signature_method'] != 'PLAINTEXT'
      defaults.update({
        'nonce' => OpenSSL::Random.random_bytes(16).unpack('H*')[0],
        'timestamp' => Time.now.to_i.to_s,
      })
    end
    @attributes['authorization'] = PROTOCOL_PARAM_KEYS.map do |key|
      {"oauth_#{key}" => @attributes.key?(key) ? @attributes[key] : defaults[key]}
    end.inject({}, &:update).reject {|k,v| v.nil? }
    @attributes['authorization']['realm'] = @attributes['realm'] unless @attributes['realm'].nil?

    hash_body
  end
end

Instance Method Details

#authorizationString

returns the Authorization header generated for this request.

Returns:

  • (String)

    Authorization header



113
114
115
# File 'lib/oauthenticator/signable_request.rb', line 113

def authorization
  "OAuth #{normalized_protocol_params_string}"
end

#body_hashString?

the oauth_body_hash calculated for this request, if applicable, per the OAuth Request Body Hash specification.

Returns:

  • (String, nil)

    oauth body hash



130
131
132
# File 'lib/oauthenticator/signable_request.rb', line 130

def body_hash
  BODY_HASH_METHODS[signature_method] ? BODY_HASH_METHODS[signature_method].bind(self).call : nil
end

#form_encoded?Boolean

is the media type application/x-www-form-urlencoded

Returns:

  • (Boolean)


158
159
160
161
162
163
# File 'lib/oauthenticator/signable_request.rb', line 158

def form_encoded?
  media_type = @attributes['media_type']
  # media tye is case insensitive per http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7
  media_type = media_type.downcase if media_type.is_a?(String)
  media_type == "application/x-www-form-urlencoded"
end

#protocol_paramsHash<String, String>

protocol params for this request as described in section 3.4.1.3

signature is not calculated for this - use #signed_protocol_params to get protocol params including a signature.

note that if this is a previously-signed request, the oauth_signature attribute returned is the received value, NOT the value calculated by us.

Returns:

  • (Hash<String, String>)

    protocol params



143
144
145
# File 'lib/oauthenticator/signable_request.rb', line 143

def protocol_params
  @attributes['authorization'].dup
end

#signatureString

the oauth_signature calculated for this request.

Returns:

  • (String)

    oauth signature



120
121
122
123
124
# File 'lib/oauthenticator/signable_request.rb', line 120

def signature
  rbmethod = SIGNATURE_METHODS[signature_method] ||
    raise(ArgumentError, "invalid signature method: #{signature_method}")
  rbmethod.bind(self).call
end

#signed_protocol_paramsHash<String, String>

protocol params for this request as described in section 3.4.1.3, including our calculated oauth_signature.

Returns:

  • (Hash<String, String>)

    signed protocol params



151
152
153
# File 'lib/oauthenticator/signable_request.rb', line 151

def signed_protocol_params
  protocol_params.merge('oauth_signature' => signature)
end