lib/pesapal/oauth.rb in pesapal-1.5.4 vs lib/pesapal/oauth.rb in pesapal-1.5.5
- old
+ new
@@ -1,151 +1,184 @@
module Pesapal
-
+ # Supporting oAuth 1.0 methods. See [oAuth 1.0 spec][1] for details.
+ #
+ # [1]: http://oauth.net/core/1.0/
module Oauth
-
- # generate query string from parameters hash
- def Oauth.generate_encoded_params_query_string(params = {})
-
- # 1) percent encode every key and value that will be signed
- # 2) sort the list of parameters alphabetically by encoded key
- # 3) for each key/value pair
- # - append the encoded key to the output string
- # - append the '=' character to the output string
- # - append the encoded value to the output string
- # 4) if there are more key/value pairs remaining, append a '&' character
- # to the output string
-
- # the oauth spec says to sort lexigraphically, which is the default
- # alphabetical sort for many libraries. in case of two parameters with
- # the same encoded key, the oauth spec says to continue sorting based on
- # value
-
+ # Generate query string from a Hash.
+ #
+ # 1. Percent encode every key and value that will be signed
+ # 2. Sort the list of parameters alphabetically by encoded key
+ # 3. For each key/value pair
+ # * append the encoded key to the output string
+ # * append the '=' character to the output string
+ # * append the encoded value to the output string
+ # 4. If there are more key/value pairs remaining, append a '&' character
+ # to the output string
+ #
+ # The oauth spec says to sort lexicographically, which is the default
+ # alphabetical sort for many libraries. In case of two parameters with the
+ # same encoded key, the oauth spec says to continue sorting based on value.
+ #
+ # @param params [Hash] Hash of parameters.
+ #
+ # @return [String] valid valid parameter query string.
+ def self.generate_encoded_params_query_string(params = {})
queries = []
- params.each do |k,v| queries.push "#{self.parameter_encode(k.to_s)}=#{self.parameter_encode(v.to_s)}" end
-
- # parameters are sorted by name, using lexicographical byte value
- # ordering
+ params.each { |k, v| queries.push "#{parameter_encode(k.to_s)}=#{parameter_encode(v.to_s)}" }
queries.sort!
-
queries.join('&')
end
- # generate oauth nonce
- def Oauth.generate_nonce(length)
-
- # the consumer shall then generate a nonce value that is unique for all
- # requests with that timestamp. a nonce is a random string, uniquely
- # generated for each request. the nonce allows the service provider to
- # verify that a request has never been made before and helps prevent
- # replay attacks when requests are made over a non- secure channel (such
- # as http).
-
+ # Generate an nonce
+ #
+ # > _The Consumer SHALL then generate a Nonce value that is unique for all
+ # > requests with that timestamp. A nonce is a random string, uniquely
+ # > generated for each request. The nonce allows the Service Provider to
+ # > verify that a request has never been made before and helps prevent
+ # > replay attacks when requests are made over a non-secure channel (such as
+ # > HTTP)._
+ #
+ # See [section 8 of the oAuth 1.0 spec][1]
+ #
+ # [1]: http://oauth.net/core/1.0/#nonce
+ #
+ # @param length [Integer] number of characters of the resulting nonce.
+ #
+ # @return [String] generated random nonce.
+ def self.generate_nonce(length)
chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ0123456789'
nonce = ''
length.times { nonce << chars[rand(chars.size)] }
-
"#{nonce}"
end
- # generate the oauth signature using hmac-sha1 algorithm
- def Oauth.generate_oauth_signature(http_method, absolute_url, params, consumer_secret, token_secret = nil)
-
- # the signature is calculated by passing the signature base string and
- # signing key to the hmac-sha1 hashing algorithm. the output of the hmac
- # signing function is a binary string. this needs to be base64 encoded to
- # produce the signature string.
-
- # for pesapal flow we don't have a token secret to we will set as nil and
- # the appropriate action will be taken as per the oauth spec. see notes in
- # the method that creates signing keys
-
- # prepare the values we need
+ # Generate the oAuth signature using HMAC-SHA1 algorithm.
+ #
+ # The signature is calculated by passing the signature base string and
+ # signing key to the HMAC-SHA1 hashing algorithm. The output of the HMAC
+ # signing function is a binary string. this needs to be Base64 encoded to
+ # produce the signature string.
+ #
+ # For pesapal flow we don't have a token secret to we will set as nil and
+ # the appropriate action will be taken as per the oAuth spec. See
+ # {generate_signing_key} for details.
+ #
+ # @param http_method [String] the HTTP method.
+ #
+ # @param absolute_url [String] the absolute URL.
+ #
+ # @param params [Hash] URL parameters.
+ #
+ # @param consumer_secret [String] the consumer secret.
+ #
+ # @param token_secret [String] the token secret.
+ #
+ # @return [String] valid oAuth signature.
+ def self.generate_oauth_signature(http_method, absolute_url, params, consumer_secret, token_secret = nil)
digest = OpenSSL::Digest::Digest.new('sha1')
- signature_base_string = self.generate_signature_base_string(http_method, absolute_url, params)
- signing_key = self.generate_signing_key(consumer_secret, token_secret)
-
+ signature_base_string = generate_signature_base_string(http_method, absolute_url, params)
+ signing_key = generate_signing_key(consumer_secret, token_secret)
hmac = OpenSSL::HMAC.digest(digest, signing_key, signature_base_string)
Base64.encode64(hmac).chomp
end
- # generate query string from signable parameters hash
- def Oauth.generate_signable_encoded_params_query_string(params = {})
-
- # oauth_signature parameter MUST be excluded, assumes it was already
- # initialized by calling set_parameters
+ # Generate query string from signable parameters Hash
+ #
+ # Same as {generate_encoded_params_query_string} but without
+ # `:oauth_signature` included in the parameters.
+ #
+ # @param params [Hash] Hash of parameters.
+ #
+ # @return [String] valid valid parameter query string.
+ def self.generate_signable_encoded_params_query_string(params = {})
params.delete(:oauth_signature)
-
- self.generate_encoded_params_query_string params
+ generate_encoded_params_query_string params
end
- # generate the oauth signature
- def Oauth.generate_signature_base_string(http_method, absolute_url, params)
+ # Generate an oAuth 1.0 signature base string.
+ #
+ # Three values collected so far must be joined to make a single string, from
+ # which the signature will be generated. This is called the signature base
+ # string. The signature base string should contain exactly 2 ampersand '&'
+ # characters. The percent '%' characters in the parameter string should be
+ # encoded as %25 in the signature base string.
+ #
+ # See [appendix A.5.1 of the oAuth 1.0 spec][1] for an example.
+ #
+ # [1]: http://oauth.net/core/1.0/#sig_base_example
+ #
+ # @param http_method [String] the HTTP method.
+ #
+ # @param absolute_url [String] the absolute URL.
+ #
+ # @param params [Hash] URL parameters.
+ #
+ # @return [String] valid signature base string.
+ def self.generate_signature_base_string(http_method, absolute_url, params)
- # three values collected so far must be joined to make a single string,
- # from which the signature will be generated. This is called the
- # signature base string by the OAuth specification
-
# step 1: convert the http method to uppercase
http_method = http_method.upcase
# step 2: percent encode the url
- url_encoded = self.parameter_encode(self.normalized_request_uri(absolute_url))
+ url_encoded = parameter_encode(normalized_request_uri(absolute_url))
# step 3: percent encode the parameter string
- parameter_string_encoded = self.parameter_encode(self.generate_signable_encoded_params_query_string params)
+ parameter_string_encoded = parameter_encode(generate_signable_encoded_params_query_string params)
- # the signature base string should contain exactly 2 ampersand '&'
- # characters. The percent '%' characters in the parameter string should be
- # encoded as %25 in the signature base string
-
"#{http_method}&#{url_encoded}&#{parameter_string_encoded}"
end
- # generate signing key
- def Oauth.generate_signing_key(consumer_secret, token_secret = nil)
+ # Generate signing key
+ #
+ # The signing key is simply the percent encoded consumer secret, followed by
+ # an ampersand character '&', followed by the percent encoded token secret.
+ # Note that there are some flows, such as when obtaining a request token,
+ # where the token secret is not yet known. In this case, the signing key
+ # should consist of the percent encoded consumer secret followed by an
+ # ampersand character '&'.
+ #
+ # @param consumer_secret [String] the consumer secret.
+ #
+ # @param token_secret [String] the token secret.
+ #
+ # @return [String] valid signing key.
+ def self.generate_signing_key(consumer_secret, token_secret = nil)
+ consumer_secret_encoded = parameter_encode(consumer_secret)
+ token_secret_encoded = ''
+ token_secret_encoded = parameter_encode(token_secret) unless token_secret.nil?
- # the signing key is simply the percent encoded consumer secret, followed
- # by an ampersand character '&', followed by the percent encoded token
- # secret
-
- # note that there are some flows, such as when obtaining a request token,
- # where the token secret is not yet known. In this case, the signing key
- # should consist of the percent encoded consumer secret followed by an
- # ampersand character '&'
-
- # "#{@credentials[:consumer_secret]}"
- consumer_secret_encoded = self.parameter_encode(consumer_secret)
-
- token_secret_encoded = ""
- unless token_secret.nil?
- token_secret_encoded = self.parameter_encode(token_secret)
- end
-
"#{consumer_secret_encoded}&#{token_secret_encoded}"
end
- # normalize request absolute URL
- def Oauth.normalized_request_uri(absolute_url)
-
- # the signature base string includes the request absolute url, tying the
- # signature to a specific endpoint. the url used in the signature base
- # string must include the scheme, authority, and path, and must exclude
- # the query and fragment as defined by [rfc3986] section 3.
-
- # if the absolute request url is not available to the service provider (it
- # is always available to the consumer), it can be constructed by combining
- # the scheme being used, the http host header, and the relative http
- # request url. if the host header is not available, the service provider
- # should use the host name communicated to the consumer in the
- # documentation or other means.
-
- # the service provider should document the form of url used in the
- # signature base string to avoid ambiguity due to url normalization.
- # unless specified, url scheme and authority must be lowercase and include
- # the port number; http default port 80 and https default port 443 must be
- # excluded.
-
+ # Construct normalized request absolute URL.
+ #
+ # > _The Signature Base String includes the request absolute URL, tying the
+ # > signature to a specific endpoint. The URL used in the Signature Base
+ # > String MUST include the scheme, authority, and path, and MUST exclude
+ # > the query and fragment as defined by [RFC3986] section 3._
+ #
+ # > _If the absolute request URL is not available to the Service Provider (it
+ # > is always available to the Consumer), it can be constructed by combining
+ # > the scheme being used, the HTTP Host header, and the relative HTTP
+ # > request URL. If the Host header is not available, the Service Provider
+ # > SHOULD use the host name communicated to the Consumer in the
+ # > documentation or other means._
+ #
+ # > _The Service Provider SHOULD document the form of URL used in the
+ # > Signature Base String to avoid ambiguity due to URL normalization.
+ # > Unless specified, URL scheme and authority MUST be lowercase and include
+ # > the port number; http default port 80 and https default port 443 MUST be
+ # > excluded._
+ #
+ # See [section 9.1.2 of the oAuth 1.0 spec][1]
+ #
+ # [1]: http://oauth.net/core/1.0/#anchor14
+ #
+ # @param absolute_url [String] URL to be normalized.
+ #
+ # @return [String] valid constructed URL as per the spec.
+ def self.normalized_request_uri(absolute_url)
u = URI.parse(absolute_url)
scheme = u.scheme.downcase
host = u.host.downcase
path = u.path
@@ -155,24 +188,30 @@
path = (path && path != '') ? path : '/'
"#{scheme}://#{host}#{port}#{path}"
end
- # percentage encode value as per the oauth spec
- def Oauth.parameter_encode(string)
-
- # all parameter names and values are escaped using the [rfc3986] percent-
- # encoding (%xx) mechanism. characters not in the unreserved character set
- # ([rfc3986] section 2.3) must be encoded. characters in the unreserved
- # character set must not be encoded. hexadecimal characters in encodings
- # must be upper case. text names and values must be encoded as utf-8
- # octets before percent-encoding them per [rfc3629].
-
+ # Encodes parameter name or values.
+ #
+ # > _All parameter names and values are escaped using the [RFC3986] percent-
+ # > encoding (%xx) mechanism. Characters not in the unreserved character set
+ # > ([RFC3986] section 2.3) MUST be encoded. Characters in the unreserved
+ # > character set MUST NOT be encoded. Hexadecimal characters in encodings
+ # > MUST be upper case. Text names and values MUST be encoded as UTF-8
+ # > octets before percent-encoding them per [RFC3629]._
+ #
+ # See [section 5.1 of the oAuth 1.0 spec][1]
+ #
+ # [1]: http://oauth.net/core/1.0/#encoding_parameters
+ #
+ # @param str [String] parameter name or value.
+ #
+ # @return [String] valid encoded result as per the spec.
+ def self.parameter_encode(str)
# reserved character regexp, per section 5.1
reserved_characters = /[^a-zA-Z0-9\-\.\_\~]/
-
# Apparently we can't force_encoding on a frozen string since that would modify it.
# What we can do is work with a copy
- URI::escape(string.dup.to_s.force_encoding(Encoding::UTF_8), reserved_characters)
+ URI.escape(str.dup.to_s.force_encoding(Encoding::UTF_8), reserved_characters)
end
end
end