Module: Signet::OAuth1

Defined in:
lib/signet/oauth_1/credential.rb,
lib/signet/oauth_1.rb,
lib/signet/oauth_1/client.rb,
lib/signet/oauth_1/signature_methods/hmac_sha1.rb

Overview

:nodoc:

Defined Under Namespace

Modules: HMACSHA1 Classes: Client, Credential

Constant Summary collapse

OUT_OF_BAND =
'oob'

Class Method Summary collapse

Class Method Details

.encode(value) ⇒ String

Converts a value to a percent-encoded String according to the rules given in RFC 5849. All non-unreserved characters are percent-encoded.

Parameters:

  • value (Symbol, #to_str)

    The value to be encoded.

Returns:

  • (String)

    The percent-encoded value.



22
23
24
25
26
27
28
# File 'lib/signet/oauth_1.rb', line 22

def self.encode(value)
  value = value.to_s if value.kind_of?(Symbol)
  return Addressable::URI.encode_component(
    value,
    Addressable::URI::CharacterClasses::UNRESERVED
  )
end

.extract_credential_key_option(credential_type, options) ⇒ String

Processes an options Hash to find a credential key value. Allows for greater flexibility in configuration.

Parameters:

  • credential_type (Symbol)

    One of :client, :temporary, :token, :consumer, :request, or :access.

Returns:

  • (String)

    The credential key value.



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
# File 'lib/signet/oauth_1.rb', line 69

def self.extract_credential_key_option(credential_type, options)
  credential_key_symbol =
    ("#{credential_type}_credential_key").to_sym
  credential_symbol =
    ("#{credential_type}_credential").to_sym
  if options[credential_key_symbol]
    credential_key = options[credential_key_symbol]
  elsif options[credential_symbol]
    require 'signet/oauth_1/credential'
    if !options[credential_symbol].respond_to?(:key)
      raise TypeError,
        "Expected Signet::OAuth1::Credential, " +
        "got #{options[credential_symbol].class}."
    end
    credential_key = options[credential_symbol].key
  elsif options[:client]
    require 'signet/oauth_1/client'
    if !options[:client].kind_of?(::Signet::OAuth1::Client)
      raise TypeError,
        "Expected Signet::OAuth1::Client, got #{options[:client].class}."
    end
    credential_key = options[:client].send(credential_key_symbol)
  else
    credential_key = nil
  end
  if credential_key != nil && !credential_key.kind_of?(String)
    raise TypeError,
      "Expected String, got #{credential_key.class}."
  end
  return credential_key
end

.extract_credential_secret_option(credential_type, options) ⇒ String

Processes an options Hash to find a credential secret value. Allows for greater flexibility in configuration.

Parameters:

  • credential_type (Symbol)

    One of :client, :temporary, :token, :consumer, :request, or :access.

Returns:

  • (String)

    The credential secret value.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/signet/oauth_1.rb', line 111

def self.extract_credential_secret_option(credential_type, options)
  credential_secret_symbol =
    ("#{credential_type}_credential_secret").to_sym
  credential_symbol =
    ("#{credential_type}_credential").to_sym
  if options[credential_secret_symbol]
    credential_secret = options[credential_secret_symbol]
  elsif options[credential_symbol]
    require 'signet/oauth_1/credential'
    if !options[credential_symbol].respond_to?(:secret)
      raise TypeError,
        "Expected Signet::OAuth1::Credential, " +
        "got #{options[credential_symbol].class}."
    end
    credential_secret = options[credential_symbol].secret
  elsif options[:client]
    require 'signet/oauth_1/client'
    if !options[:client].kind_of?(::Signet::OAuth1::Client)
      raise TypeError,
        "Expected Signet::OAuth1::Client, got #{options[:client].class}."
    end
    credential_secret = options[:client].send(credential_secret_symbol)
  else
    credential_secret = nil
  end
  if credential_secret != nil && !credential_secret.kind_of?(String)
    raise TypeError,
      "Expected String, got #{credential_secret.class}."
  end
  return credential_secret
end

.generate_authorization_header(parameters, realm = nil) ⇒ String

Generates an Authorization header from a parameter list according to the rules given in RFC 5849.

Parameters:

  • parameters (Enumerable)

    The OAuth parameter list.

  • realm (String) (defaults to: nil)

    The Authorization realm. See RFC 2617.

Returns:

  • (String)

    The Authorization header.



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/signet/oauth_1.rb', line 208

def self.generate_authorization_header(parameters, realm=nil)
  if !parameters.kind_of?(Enumerable) || parameters.kind_of?(String)
    raise TypeError, "Expected Enumerable, got #{parameters.class}."
  end
  parameter_list = parameters.map do |k, v|
    if k == 'realm'
      raise ArgumentError,
        'The "realm" parameter must be specified as a separate argument.'
    end
    "#{self.encode(k)}=\"#{self.encode(v)}\""
  end
  if realm
    realm = realm.gsub('"', '\"')
    parameter_list.unshift("realm=\"#{realm}\"")
  end
  return 'OAuth ' + parameter_list.join(", ")
end

.generate_authorization_uri(authorization_uri, options = {}) ⇒ String

Appends the optional ‘oauth_token’ and ‘oauth_callback’ parameters to the base authorization URI.

Parameters:

  • authorization_uri (Addressable::URI, String, #to_str)

    The base authorization URI.

Returns:

  • (String)

    The authorization URI to redirect the user to.



353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
# File 'lib/signet/oauth_1.rb', line 353

def self.generate_authorization_uri(authorization_uri, options={})
  options = {
    :callback => nil,
    :additional_parameters => {}
  }.merge(options)
  temporary_credential_key =
    self.extract_credential_key_option(:temporary, options)
  parsed_uri = Addressable::URI.parse(authorization_uri).dup
  query_values = parsed_uri.query_values || {}
  if options[:additional_parameters]
    query_values = query_values.merge(
      options[:additional_parameters].inject({}) { |h,(k,v)| h[k]=v; h }
    )
  end
  if temporary_credential_key
    query_values['oauth_token'] = temporary_credential_key
  end
  if options[:callback]
    query_values['oauth_callback'] = options[:callback]
  end
  parsed_uri.query_values = query_values
  return parsed_uri.normalize.to_s
end

.generate_base_string(method, uri, parameters) ⇒ String

Generates a signature base string according to the algorithm given in RFC 5849. Joins the method, URI, and normalized parameter string with ‘&’ characters.

Parameters:

  • method (String)

    The HTTP method.

  • The (Addressable::URI, String, #to_str)

    URI.

  • parameters (Enumerable)

    The OAuth parameter list.

Returns:

  • (String)

    The signature base string.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/signet/oauth_1.rb', line 174

def self.generate_base_string(method, uri, parameters)
  if !parameters.kind_of?(Enumerable)
    raise TypeError, "Expected Enumerable, got #{parameters.class}."
  end
  method = method.to_s.upcase
  parsed_uri = Addressable::URI.parse(uri)
  uri = Addressable::URI.new(
    :scheme => parsed_uri.normalized_scheme,
    :authority => parsed_uri.normalized_authority,
    :path => parsed_uri.path,
    :query => parsed_uri.query,
    :fragment => parsed_uri.fragment
  )
  uri_parameters = uri.query_values.to_a
  uri = uri.omit(:query, :fragment).to_s
  merged_parameters =
    uri_parameters.concat(parameters.map { |k, v| [k, v] })
  parameter_string = self.normalize_parameters(merged_parameters)
  return [
    self.encode(method),
    self.encode(uri),
    self.encode(parameter_string)
  ].join('&')
end

.generate_nonceString

Returns a nonce suitable for use as an 'oauth_nonce' value.

Returns:

  • (String)

    A random nonce.



55
56
57
# File 'lib/signet/oauth_1.rb', line 55

def self.generate_nonce()
  return SecureRandom.random_bytes(16).unpack('H*').join('')
end

.generate_timestampString

Returns a timestamp suitable for use as an 'oauth_timestamp' value.

Returns:

  • (String)

    The current timestamp.



46
47
48
# File 'lib/signet/oauth_1.rb', line 46

def self.generate_timestamp()
  return Time.now.to_i.to_s
end

.normalize_parameters(parameters) ⇒ String

Normalizes a set of OAuth parameters according to the algorithm given in RFC 5849. Sorts key/value pairs lexically by byte order, first by key, then by value, joins key/value pairs with the ‘=’ character, then joins the entire parameter list with ‘&’ characters.

Parameters:

  • parameters (Enumerable)

    The OAuth parameter list.

Returns:

  • (String)

    The normalized parameter list.



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/signet/oauth_1.rb', line 152

def self.normalize_parameters(parameters)
  if !parameters.kind_of?(Enumerable)
    raise TypeError, "Expected Enumerable, got #{parameters.class}."
  end
  parameter_list = parameters.map do |k, v|
    next if k == "oauth_signature"
    # This is probably the wrong place to try to exclude the realm
    "#{self.encode(k)}=#{self.encode(v)}"
  end
  return parameter_list.compact.sort.join("&")
end

.parse_authorization_header(field_value) ⇒ Object

Parses an Authorization header into its component parameters. Parameter keys and values are decoded according to the rules given in RFC 5849.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/signet/oauth_1.rb', line 230

def self.parse_authorization_header(field_value)
  if !field_value.kind_of?(String)
    raise TypeError, "Expected String, got #{field_value.class}."
  end
  auth_scheme = field_value[/^([-._0-9a-zA-Z]+)/, 1]
  case auth_scheme
  when /^OAuth$/i
    # Other token types may be supported eventually
    pairs = Signet.parse_auth_param_list(field_value[/^OAuth\s+(.*)$/i, 1])
    return (pairs.inject([]) do |accu, (k, v)|
      if k != 'realm'
        k = self.unencode(k)
        v = self.unencode(v)
      end
      accu << [k, v]
      accu
    end)
  else
    raise ParseError,
      'Parsing non-OAuth Authorization headers is out of scope.'
  end
end

.parse_form_encoded_credentials(body) ⇒ Signet::OAuth1::Credential

Parses an application/x-www-form-urlencoded HTTP response body into an OAuth key/secret pair.

Parameters:

  • body (String)

    The response body.

Returns:



260
261
262
263
264
265
266
267
# File 'lib/signet/oauth_1.rb', line 260

def self.parse_form_encoded_credentials(body)
  if !body.kind_of?(String)
    raise TypeError, "Expected String, got #{body.class}."
  end
  return Signet::OAuth1::Credential.new(
    Addressable::URI.form_unencode(body)
  )
end

.sign_parameters(method, uri, parameters, client_credential_secret, token_credential_secret = nil) ⇒ String

Generates an OAuth signature using the signature method indicated in the parameter list. Unsupported signature methods will result in a NotImplementedError exception being raised.

Parameters:

  • method (String)

    The HTTP method.

  • The (Addressable::URI, String, #to_str)

    URI.

  • parameters (Enumerable)

    The OAuth parameter list.

  • client_credential_secret (String)

    The client credential secret.

  • token_credential_secret (String) (defaults to: nil)

    The token credential secret. Omitted when unavailable.

Returns:

  • (String)

    The signature.



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'lib/signet/oauth_1.rb', line 282

def self.sign_parameters(method, uri, parameters,
    client_credential_secret, token_credential_secret=nil)
  # Technically, the token_credential_secret parameter here may actually
  # be a temporary credential secret when obtaining a token credential
  # for the first time
  base_string = self.generate_base_string(method, uri, parameters)
  parameters = parameters.inject({}) { |h,(k,v)| h[k.to_s]=v; h }
  signature_method = parameters['oauth_signature_method']
  case signature_method
  when 'HMAC-SHA1'
    require 'signet/oauth_1/signature_methods/hmac_sha1'
    return Signet::OAuth1::HMACSHA1.generate_signature(
      base_string, client_credential_secret, token_credential_secret
    )
  else
    raise NotImplementedError,
      "Unsupported signature method: #{signature_method}"
  end
end

.unencode(value) ⇒ String

Converts a percent-encoded String to an unencoded value.

Parameters:

  • value (#to_str)

    The percent-encoded String to be unencoded.

Returns:

  • (String)

    The unencoded value.



37
38
39
# File 'lib/signet/oauth_1.rb', line 37

def self.unencode(value)
  return Addressable::URI.unencode_component(value)
end

.unsigned_resource_parameters(options = {}) ⇒ Array

Generates an OAuth parameter list to be used when requesting a protected resource.

Parameters:

  • options (Hash) (defaults to: {})

    The configuration parameters for the request.

    • :client_credential_key — The client credential key.

    • :token_credential_key — The token credential key.

    • :signature_method — The signature method. Defaults to 'HMAC-SHA1'.

    • :two_legged — A switch for two-legged OAuth. Defaults to false.

Returns:

  • (Array)

    The parameter list as an Array of key/value pairs.



442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'lib/signet/oauth_1.rb', line 442

def self.unsigned_resource_parameters(options={})
  options = {
    :signature_method => 'HMAC-SHA1',
    :two_legged => false
  }.merge(options)
  client_credential_key =
    self.extract_credential_key_option(:client, options)
  if client_credential_key == nil
    raise ArgumentError, "Missing :client_credential_key parameter."
  end
  unless options[:two_legged]
    token_credential_key =
      self.extract_credential_key_option(:token, options)
    if token_credential_key == nil
      raise ArgumentError, "Missing :token_credential_key parameter."
    end
  end
  parameters = [
    ["oauth_consumer_key", client_credential_key],
    ["oauth_signature_method", options[:signature_method]],
    ["oauth_timestamp", self.generate_timestamp()],
    ["oauth_nonce", self.generate_nonce()],
    ["oauth_version", "1.0"]
  ]
  unless options[:two_legged]
    parameters << ["oauth_token", token_credential_key]
  end
  # No additional parameters allowed here
  return parameters
end

.unsigned_temporary_credential_parameters(options = {}) ⇒ Array

Generates an OAuth parameter list to be used when obtaining a set of temporary credentials.

Parameters:

  • options (Hash) (defaults to: {})

    The configuration parameters for the request.

    • :client_credential_key — The client credential key.

    • :callback — The OAuth callback. Defaults to OUT_OF_BAND.

    • :signature_method — The signature method. Defaults to 'HMAC-SHA1'.

    • :additional_parameters — Non-standard additional parameters.

Returns:

  • (Array)

    The parameter list as an Array of key/value pairs.



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/signet/oauth_1.rb', line 319

def self.unsigned_temporary_credential_parameters(options={})
  options = {
    :callback => ::Signet::OAuth1::OUT_OF_BAND,
    :signature_method => 'HMAC-SHA1',
    :additional_parameters => []
  }.merge(options)
  client_credential_key =
    self.extract_credential_key_option(:client, options)
  if client_credential_key == nil
    raise ArgumentError, "Missing :client_credential_key parameter."
  end
  parameters = [
    ["oauth_consumer_key", client_credential_key],
    ["oauth_signature_method", options[:signature_method]],
    ["oauth_timestamp", self.generate_timestamp()],
    ["oauth_nonce", self.generate_nonce()],
    ["oauth_version", "1.0"],
    ["oauth_callback", options[:callback]]
  ]
  # Works for any Enumerable
  options[:additional_parameters].each do |key, value|
    parameters << [key, value]
  end
  return parameters
end

.unsigned_token_credential_parameters(options = {}) ⇒ Array

Generates an OAuth parameter list to be used when obtaining a set of token credentials.

Parameters:

  • options (Hash) (defaults to: {})

    The configuration parameters for the request.

    • :client_credential_key — The client credential key.

    • :temporary_credential_key — The temporary credential key.

    • :verifier — The OAuth verifier.

    • :signature_method — The signature method. Defaults to 'HMAC-SHA1'.

Returns:

  • (Array)

    The parameter list as an Array of key/value pairs.



394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/signet/oauth_1.rb', line 394

def self.unsigned_token_credential_parameters(options={})
  options = {
    :signature_method => 'HMAC-SHA1',
    :verifier => nil
  }.merge(options)
  client_credential_key =
    self.extract_credential_key_option(:client, options)
  temporary_credential_key =
    self.extract_credential_key_option(:temporary, options)
  if client_credential_key == nil
    raise ArgumentError, "Missing :client_credential_key parameter."
  end
  if temporary_credential_key == nil
    raise ArgumentError, "Missing :temporary_credential_key parameter."
  end
  if options[:verifier] == nil
    raise ArgumentError, "Missing :verifier parameter."
  end
  parameters = [
    ["oauth_consumer_key", client_credential_key],
    ["oauth_token", temporary_credential_key],
    ["oauth_signature_method", options[:signature_method]],
    ["oauth_timestamp", self.generate_timestamp()],
    ["oauth_nonce", self.generate_nonce()],
    ["oauth_verifier", options[:verifier]],
    ["oauth_version", "1.0"]
  ]
  # No additional parameters allowed here
  return parameters
end