class OAuth
Constants
- EMPTY_STRING
- SHA_BITS
Public Class Methods
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
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
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
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
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
@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
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
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
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
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