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/server.rb,
lib/signet/oauth_1/signature_methods/rsa_sha1.rb,
lib/signet/oauth_1/signature_methods/hmac_sha1.rb,
lib/signet/oauth_1/signature_methods/plaintext.rb

Overview

:nodoc:

Defined Under Namespace

Modules: HMACSHA1, PLAINTEXT, RSASHA1 Classes: Client, Credential, Server

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.



18
19
20
21
22
23
24
# File 'lib/signet/oauth_1.rb', line 18

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.



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

def self.extract_credential_key_option(credential_type, options)
  # Normalize key to String to allow indifferent access.
  options = options.inject({}) { |accu, (k, v)| accu[k.to_s] = v; accu }
  credential_key = "#{credential_type}_credential_key"
  credential = "#{credential_type}_credential"
  if options[credential_key]
    credential_key = options[credential_key]
  elsif options[credential]
    require 'signet/oauth_1/credential'
    if !options[credential].respond_to?(:key)
      raise TypeError,
        "Expected Signet::OAuth1::Credential, " +
        "got #{options[credential].class}."
    end
    credential_key = options[credential].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)
  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.



107
108
109
110
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
# File 'lib/signet/oauth_1.rb', line 107

def self.extract_credential_secret_option(credential_type, options)
  # Normalize key to String to allow indifferent access.
  options = options.inject({}) { |accu, (k, v)| accu[k.to_s] = v; accu }
  credential_secret = "#{credential_type}_credential_secret"
  credential = "#{credential_type}_credential"
  if options[credential_secret]
    credential_secret = options[credential_secret]
  elsif options[credential]
    require 'signet/oauth_1/credential'
    if !options[credential].respond_to?(:secret)
      raise TypeError,
        "Expected Signet::OAuth1::Credential, " +
        "got #{options[credential].class}."
    end
    credential_secret = options[credential].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)
  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.



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/signet/oauth_1.rb', line 204

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.



359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/signet/oauth_1.rb', line 359

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.

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

    The URI.

  • parameters (Enumerable)

    The OAuth parameter list.

Returns:

  • (String)

    The signature base string.



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/signet/oauth_1.rb', line 170

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.



51
52
53
# File 'lib/signet/oauth_1.rb', line 51

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.



42
43
44
# File 'lib/signet/oauth_1.rb', line 42

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.



148
149
150
151
152
153
154
155
156
157
158
# File 'lib/signet/oauth_1.rb', line 148

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.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/signet/oauth_1.rb', line 226

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:



256
257
258
259
260
261
262
263
# File 'lib/signet/oauth_1.rb', line 256

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.

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

    The 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.



278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/signet/oauth_1.rb', line 278

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
    )
  when 'RSA-SHA1'
    require 'signet/oauth_1/signature_methods/rsa_sha1'
    return Signet::OAuth1::RSASHA1.generate_signature(
      base_string, client_credential_secret, token_credential_secret
    )
  when 'PLAINTEXT'
    require 'signet/oauth_1/signature_methods/plaintext'
    return Signet::OAuth1::PLAINTEXT.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.



33
34
35
# File 'lib/signet/oauth_1.rb', line 33

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.



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
# File 'lib/signet/oauth_1.rb', line 448

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.



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/signet/oauth_1.rb', line 325

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.



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/signet/oauth_1.rb', line 400

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