class OAuth

Constants

EMPTY_STRING
SHA_BITS

Public Class Methods

encode_uri_component(uri_component) click to toggle source

Encodes a text string as a valid component of a Uniform Resource Identifier (URI). @ param uri_component A value representing an encoded URI component.

# File lib/oauth.rb, line 230
def encode_uri_component(uri_component)
  URI.encode_www_form_component(uri_component)
end
extract_query_params(uri) click to toggle source

Parse query parameters out of the URL. tools.ietf.org/html/rfc5849#section-3.4.1.3

@param {String} uri URL containing all query parameters that need to be signed @return {Map<String, Set<String>} Sorted map of query parameter key/value pairs. Values for parameters with the same name are added into a list.

# File lib/oauth.rb, line 49
def extract_query_params(uri)
  query_params = URI.parse(uri).query

  return {} if query_params.eql?(nil)

  query_pairs = {}
  pairs = query_params.split('&').sort_by(&:downcase)

  pairs.each do |pair|
    idx = pair.index('=')
    key = idx.positive? ? pair[0..(idx - 1)] : pair
    query_pairs[key] = [] unless query_pairs.include?(key)
    value = if idx.positive? && pair.length > idx + 1
              pair[(idx + 1)..pair.length]
            else
              EMPTY_STRING
            end
    query_pairs[key].push(value)
  end
  query_pairs
end
get_authorization_header(uri, method, payload, consumer_key, signing_key) click to toggle source

Creates a Mastercard API compliant OAuth Authorization header

@param {String} uri Target URI for this request @param {String} method HTTP method of the request @param {Any} payload Payload (nullable) @param {String} consumerKey Consumer key set up in a Mastercard Developer Portal project @param {String} signingKey The private key that will be used for signing the request that corresponds to the consumerKey @return {String} Valid OAuth1.0a signature with a body hash when payload is present

# File lib/oauth.rb, line 21
def get_authorization_header(uri, method, payload, consumer_key, signing_key)
  query_params = extract_query_params(uri)
  oauth_params = get_oauth_params(consumer_key, payload)

  # Combine query and oauth_ parameters into lexicographically sorted string
  param_string = to_oauth_param_string(query_params, oauth_params)

  # Normalized URI without query params and fragment
  base_uri = get_base_uri_string(uri)

  # Signature base string
  sbs = get_signature_base_string(method, base_uri, param_string)

  # Signature
  signature = sign_signature_base_string(sbs, signing_key)
  oauth_params['oauth_signature'] = encode_uri_component(signature)

  # Return
  get_authorization_string(oauth_params)
end
get_authorization_string(oauth_params) click to toggle source

Constructs a valid Authorization header as per tools.ietf.org/html/rfc5849#section-3.5.1 @param {Map<String, String>} oauthParams Map of OAuth parameters to be included in the Authorization header @return {String} Correctly formatted header

# File lib/oauth.rb, line 95
def get_authorization_string(oauth_params)
  header = 'OAuth '
  oauth_params.each do |entry|
    entry_key = entry[0]
    entry_val = entry[1]
    header = "#{header}#{entry_key}=\"#{entry_val}\","
  end
  # Remove trailing ,
  header.slice(0, header.length - 1)
end
get_base_uri_string(uri) click to toggle source

Normalizes the URL as per tools.ietf.org/html/rfc5849#section-3.4.1.2

@param {String} uri URL that will be called as part of this request @return {String} Normalized URL

# File lib/oauth.rb, line 113
def get_base_uri_string(uri)
  url = URI.parse(uri)
  # Lowercase scheme and authority
  # Remove redundant port, query, and fragment
  base_uri = "#{url.scheme.downcase}://#{url.host.downcase}"
  base_uri += ":#{url.port}" if (url.scheme.downcase == 'https') && (url.port != 443)
  base_uri += ":#{url.port}" if (url.scheme.downcase == 'http') && (url.port != 80)
  base_uri += "/#{url.path[1..-1]}"
end
get_body_hash(payload) click to toggle source

Generates a hash based on request payload as per tools.ietf.org/id/draft-eaton-oauth-bodyhash-00.html

@param {Any} payload Request payload @return {String} Base64 encoded cryptographic hash of the given payload

# File lib/oauth.rb, line 221
def get_body_hash(payload)
  # Base 64 encodes the SHA1 digest of payload
  Base64.strict_encode64(Digest::SHA256.digest(payload.nil? ? '' : payload))
end
get_nonce(len = 32) click to toggle source

Generates a random string for replay protection as per tools.ietf.org/html/rfc5849#section-3.3 @return {String} UUID with dashes removed

# File lib/oauth.rb, line 239
def get_nonce(len = 32)
  # Returns a random string of length=len
  o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
  (0...len).map { o[rand(o.length)] }.join
end
get_oauth_params(consumer_key, payload = nil) click to toggle source

@param {String} consumerKey Consumer key set up in a Mastercard Developer Portal project @param {Any} payload Payload (nullable) @return {Map}

# File lib/oauth.rb, line 76
def get_oauth_params(consumer_key, payload = nil)
  oauth_params = {}

  oauth_params['oauth_body_hash'] = get_body_hash(payload)
  oauth_params['oauth_consumer_key'] = consumer_key
  oauth_params['oauth_nonce'] = get_nonce
  oauth_params['oauth_signature_method'] = "RSA-SHA#{SHA_BITS}"
  oauth_params['oauth_timestamp'] = time_stamp
  oauth_params['oauth_version'] = '1.0'

  oauth_params
end
get_signature_base_string(http_method, base_uri, param_string) click to toggle source

Generate a valid signature base string as per tools.ietf.org/html/rfc5849#section-3.4.1

@param {String} httpMethod HTTP method of the request @param {String} baseUri Base URI that conforms with tools.ietf.org/html/rfc5849#section-3.4.1.2 @param {String} paramString OAuth parameter string that conforms with tools.ietf.org/html/rfc5849#section-3.4.1.3 @return {String} A correctly constructed and escaped signature base string

# File lib/oauth.rb, line 178
def get_signature_base_string(http_method, base_uri, param_string)
  sbs =
    # Uppercase HTTP method
    "#{http_method.upcase}&" +
    # Base URI
    "#{encode_uri_component(base_uri)}&" +
    # OAuth parameter string
    encode_uri_component(param_string).to_s

  sbs.gsub(/!/, '%21')
end
sign_signature_base_string(sbs, signing_key) click to toggle source

Signs the signature base string using an RSA private key. The methodology is described at tools.ietf.org/html/rfc5849#section-3.4.3 but Mastercard uses the stronger SHA-256 algorithm as a replacement for the described SHA1 which is no longer considered secure.

@param {String} sbs Signature base string formatted as per tools.ietf.org/html/rfc5849#section-3.4.1 @param {String} signingKey Private key of the RSA key pair that was established with the service provider @return {String} RSA signature matching the contents of signature base string

noinspection RubyArgCount

# File lib/oauth.rb, line 200
def OAuth.sign_signature_base_string(sbs, signing_key)
  digest = OpenSSL::Digest.new('SHA256')
  rsa_key = OpenSSL::PKey::RSA.new signing_key

  signature = ''
  begin
    signature = rsa_key.sign(digest, sbs)
  rescue
    raise Exception, 'Unable to sign the signature base string.'
  end

  Base64.strict_encode64(signature).chomp.gsub(/\n/, '')
end
time_stamp() click to toggle source

Returns UNIX Timestamp as required per tools.ietf.org/html/rfc5849#section-3.3 @return {String} UNIX timestamp (UTC)

# File lib/oauth.rb, line 249
def time_stamp
  Time.now.getutc.to_i
end
to_oauth_param_string(query_params_map, oauth_param_map) click to toggle source

Lexicographically sort all parameters and concatenate them into a string as per tools.ietf.org/html/rfc5849#section-3.4.1.3.2

@param {Map<String, Set<String>>} queryParamsMap Map of all oauth parameters that need to be signed @param {Map<String, String>} oauthParamsMap Map of OAuth parameters to be included in Authorization header @return {String} Correctly encoded and sorted OAuth parameter string

# File lib/oauth.rb, line 131
def to_oauth_param_string(query_params_map, oauth_param_map)
  consolidated_params = {}.merge(query_params_map)

  # Add OAuth params to consolidated params map
  oauth_param_map.each do |entry|
    entry_key = entry[0]
    entry_val = entry[1]
    consolidated_params[entry_key] =
      if consolidated_params.include?(entry_key)
        entry_val
      else
        [].push(entry_val)
      end
  end

  consolidated_params = consolidated_params.sort_by { |k, _| k }.to_h
  oauth_params = ''

  # Add all parameters to the parameter string for signing
  consolidated_params.each do |entry|
    entry_key = entry[0]
    entry_value = entry[1]

    # Keys with same name are sorted by their values
    entry_value = entry_value.sort if entry_value.size > 1

    entry_value.each do |value|
      oauth_params += "#{entry_key}=#{value}&"
    end
  end

  # Remove trailing ampersand
  string_length = oauth_params.length - 1
  oauth_params = oauth_params.slice(0, string_length) if oauth_params.end_with?('&')

  oauth_params
end