mastercard_oauth1_signer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/oauth.rb +253 -0
  3. data/oauth1_signer_ruby.gemspec +16 -0
  4. metadata +75 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0556b0ec00889b34169f2e9df97f03fa310902a80f53a3e9b2e7a3c6b0f40a92
4
+ data.tar.gz: 06ac83fc0b6d244db8d0add6b0d0c62486cca6101d2b478edb01e3b5351a1c90
5
+ SHA512:
6
+ metadata.gz: c3b44697f8f1a541da01347b1e1cd262abd2addd84f8417115ed6876f7c58af0e4741aafaac98108706ac530492d165e092361bba76e42d26113465139901031
7
+ data.tar.gz: 06cbae148cd7c605545a910760a3554bba080afd4313a16cbcd49b822aa0d43ec33ba49f819d0e285c80b9a88cc347afa6918776229e0bbb18331845e4148b66
data/lib/oauth.rb ADDED
@@ -0,0 +1,253 @@
1
+ require 'base64'
2
+ require 'openssl'
3
+ require 'uri'
4
+
5
+ class OAuth
6
+ class << self
7
+
8
+ EMPTY_STRING = ''.freeze
9
+ SHA_BITS = '256'.freeze
10
+
11
+ # Creates a Mastercard API compliant OAuth Authorization header
12
+ #
13
+ # @param {String} uri Target URI for this request
14
+ # @param {String} method HTTP method of the request
15
+ # @param {Any} payload Payload (nullable)
16
+ # @param {String} consumerKey Consumer key set up in a Mastercard Developer Portal project
17
+ # @param {String} signingKey The private key that will be used for signing the request that corresponds to the consumerKey
18
+ # @return {String} Valid OAuth1.0a signature with a body hash when payload is present
19
+ #
20
+ def get_authorization_header(uri, method, payload, consumer_key, signing_key)
21
+ query_params = extract_query_params(uri)
22
+ oauth_params = get_oauth_params(consumer_key, payload)
23
+
24
+ # Combine query and oauth_ parameters into lexicographically sorted string
25
+ param_string = to_oauth_param_string(query_params, oauth_params)
26
+
27
+ # Normalized URI without query params and fragment
28
+ base_uri = get_base_uri_string(uri)
29
+
30
+ # Signature base string
31
+ sbs = get_signature_base_string(method, base_uri, param_string)
32
+
33
+ # Signature
34
+ signature = sign_signature_base_string(sbs, signing_key)
35
+ oauth_params['oauth_signature'] = encode_uri_component(signature)
36
+
37
+ # Return
38
+ get_authorization_string(oauth_params)
39
+ end
40
+
41
+ #
42
+ # Parse query parameters out of the URL.
43
+ # https://tools.ietf.org/html/rfc5849#section-3.4.1.3
44
+ #
45
+ # @param {String} uri URL containing all query parameters that need to be signed
46
+ # @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.
47
+ #
48
+ def extract_query_params(uri)
49
+ query_params = URI.parse(uri).query
50
+
51
+ return {} if query_params.eql?(nil)
52
+
53
+ query_pairs = {}
54
+ pairs = query_params.split('&').sort_by(&:downcase)
55
+
56
+ pairs.each { |pair|
57
+ idx = pair.index('=')
58
+ key = idx > 0 ? pair[0..(idx - 1)] : pair
59
+ query_pairs[key] = [] unless query_pairs.include?(key)
60
+ value = if idx > 0 && pair.length > idx + 1
61
+ pair[(idx + 1)..pair.length]
62
+ else
63
+ EMPTY_STRING
64
+ end
65
+ query_pairs[key].push(value)
66
+ }
67
+ query_pairs
68
+ end
69
+
70
+ #
71
+ # @param {String} consumerKey Consumer key set up in a Mastercard Developer Portal project
72
+ # @param {Any} payload Payload (nullable)
73
+ # @return {Map}
74
+ #
75
+ def get_oauth_params(consumer_key, payload = nil)
76
+ oauth_params = {}
77
+
78
+ unless payload.nil?
79
+ oauth_params['oauth_body_hash'] = get_body_hash(payload)
80
+ end
81
+ oauth_params['oauth_consumer_key'] = consumer_key
82
+ oauth_params['oauth_nonce'] = get_nonce
83
+ oauth_params['oauth_signature_method'] = "RSA-SHA#{SHA_BITS}"
84
+ oauth_params['oauth_timestamp'] = time_stamp
85
+ oauth_params['oauth_version'] = '1.0'
86
+
87
+ oauth_params
88
+ end
89
+
90
+ #
91
+ # Constructs a valid Authorization header as per
92
+ # https://tools.ietf.org/html/rfc5849#section-3.5.1
93
+ # @param {Map<String, String>} oauthParams Map of OAuth parameters to be included in the Authorization header
94
+ # @return {String} Correctly formatted header
95
+ #
96
+ def get_authorization_string(oauth_params)
97
+ header = 'OAuth '
98
+ oauth_params.each {|entry|
99
+ entry_key = entry[0]
100
+ entry_val = entry[1]
101
+ header = "#{header}#{entry_key} = '#{entry_val}',"
102
+ }
103
+ # Remove trailing ,
104
+ header.slice(0, header.length - 1)
105
+ end
106
+
107
+ #
108
+ # Normalizes the URL as per
109
+ # https://tools.ietf.org/html/rfc5849#section-3.4.1.2
110
+ #
111
+ # @param {String} uri URL that will be called as part of this request
112
+ # @return {String} Normalized URL
113
+ #
114
+ def get_base_uri_string(uri)
115
+ url = URI.parse(uri)
116
+ # Lowercase scheme and authority
117
+ # Remove query and fragment
118
+ "#{url.scheme.downcase}://#{url.host.downcase}#{url.path.downcase}"
119
+ end
120
+
121
+ #
122
+ # Lexicographically sort all parameters and concatenate them into a string as per
123
+ # https://tools.ietf.org/html/rfc5849#section-3.4.1.3.2
124
+ #
125
+ # @param {Map<String, Set<String>>} queryParamsMap Map of all oauth parameters that need to be signed
126
+ # @param {Map<String, String>} oauthParamsMap Map of OAuth parameters to be included in Authorization header
127
+ # @return {String} Correctly encoded and sorted OAuth parameter string
128
+ #
129
+
130
+ def to_oauth_param_string(query_params_map, oauth_param_map)
131
+ consolidated_params = {}.merge(query_params_map)
132
+
133
+ # Add OAuth params to consolidated params map
134
+ oauth_param_map.each {|entry|
135
+ entry_key = entry[0]
136
+ entry_val = entry[1]
137
+ consolidated_params[entry_key] =
138
+ if consolidated_params.include?(entry_key)
139
+ entry_val
140
+ else
141
+ [].push(entry_val)
142
+ end
143
+ }
144
+
145
+ oauth_params = ''
146
+
147
+ # Add all parameters to the parameter string for signing
148
+ consolidated_params.each do |entry|
149
+ entry_key = entry[0]
150
+ entry_value = entry[1]
151
+
152
+ # Keys with same name are sorted by their values
153
+ entry_value = entry_value.sort if entry_value.size > 1
154
+
155
+ entry_value.each do |value|
156
+ oauth_params += "#{entry_key}=#{value}&"
157
+ end
158
+ end
159
+
160
+ # Remove trailing ampersand
161
+ string_length = oauth_params.length - 1
162
+ if oauth_params.end_with?('&')
163
+ oauth_params = oauth_params.slice(0, string_length)
164
+ end
165
+
166
+ oauth_params
167
+ end
168
+
169
+ #
170
+ # Generate a valid signature base string as per
171
+ # https://tools.ietf.org/html/rfc5849#section-3.4.1
172
+ #
173
+ # @param {String} httpMethod HTTP method of the request
174
+ # @param {String} baseUri Base URI that conforms with https://tools.ietf.org/html/rfc5849#section-3.4.1.2
175
+ # @param {String} paramString OAuth parameter string that conforms with https://tools.ietf.org/html/rfc5849#section-3.4.1.3
176
+ # @return {String} A correctly constructed and escaped signature base string
177
+ #
178
+ def get_signature_base_string(http_method, base_uri, param_string)
179
+ sbs =
180
+ # Uppercase HTTP method
181
+ "#{http_method.upcase}&" +
182
+ # Base URI
183
+ "#{encode_uri_component(base_uri)}&" +
184
+ # OAuth parameter string
185
+ encode_uri_component(param_string).to_s
186
+
187
+ sbs.gsub(/!/, '%21')
188
+ end
189
+
190
+ #
191
+ # Signs the signature base string using an RSA private key. The methodology is described at
192
+ # https://tools.ietf.org/html/rfc5849#section-3.4.3 but Mastercard uses the stronger SHA-256 algorithm
193
+ # as a replacement for the described SHA1 which is no longer considered secure.
194
+ #
195
+ # @param {String} sbs Signature base string formatted as per https://tools.ietf.org/html/rfc5849#section-3.4.1
196
+ # @param {String} signingKey Private key of the RSA key pair that was established with the service provider
197
+ # @return {String} RSA signature matching the contents of signature base string
198
+ #
199
+ # noinspection RubyArgCount
200
+ def OAuth.sign_signature_base_string(sbs, signing_key)
201
+ digest = OpenSSL::Digest::SHA256.new
202
+ rsa_key = OpenSSL::PKey::RSA.new signing_key
203
+
204
+ signature = ''
205
+ begin
206
+ signature = rsa_key.sign(digest, sbs)
207
+ rescue
208
+ raise Exception, 'Unable to sign the signature base string.'
209
+ end
210
+
211
+ Base64.strict_encode64(signature).chomp.gsub(/\n/, '')
212
+ end
213
+
214
+ #
215
+ # Generates a hash based on request payload as per
216
+ # https://tools.ietf.org/id/draft-eaton-oauth-bodyhash-00.html
217
+ #
218
+ # @param {Any} payload Request payload
219
+ # @return {String} Base64 encoded cryptographic hash of the given payload
220
+ #
221
+ def get_body_hash(payload)
222
+ # Base 64 encodes the SHA1 digest of payload
223
+ Base64.strict_encode64(Digest::SHA256.digest(payload.nil? ? '' : payload))
224
+ end
225
+
226
+ #
227
+ # Encodes a text string as a valid component of a Uniform Resource Identifier (URI).
228
+ # @ param uri_component A value representing an encoded URI component.
229
+ #
230
+ def encode_uri_component(uri_component)
231
+ URI.encode_www_form_component(uri_component)
232
+ end
233
+
234
+ #
235
+ # Generates a random string for replay protection as per
236
+ # https://tools.ietf.org/html/rfc5849#section-3.3
237
+ # @return {String} UUID with dashes removed
238
+ #
239
+ def get_nonce(len = 32)
240
+ # Returns a random string of length=len
241
+ o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten
242
+ (0...len).map { o[rand(o.length)] }.join
243
+ end
244
+
245
+ # Returns UNIX Timestamp as required per
246
+ # https://tools.ietf.org/html/rfc5849#section-3.3
247
+ # @return {String} UNIX timestamp (UTC)
248
+ #
249
+ def time_stamp
250
+ Time.now.getutc.to_i
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,16 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = "mastercard_oauth1_signer"
3
+ gem.authors = ["Mastercard"]
4
+ gem.email = ["APISupport@mastercard.com"]
5
+ gem.summary = %q{OAuth signature SDK}
6
+ gem.description = %q{Zero dependency library for generating a Mastercard API compliant OAuth signature}
7
+ gem.version = "1.0.0"
8
+ gem.license = "MIT"
9
+ gem.homepage = "https://github.com/Mastercard/oauth1-signer-ruby"
10
+ gem.files = Dir["{bin,spec,lib}/**/*"]+ Dir["data/*"] + ["oauth1_signer_ruby.gemspec"]
11
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
13
+ gem.require_paths = ["lib"]
14
+ gem.add_development_dependency "bundler", "~> 1.5"
15
+ gem.add_development_dependency "rake", "~> 0"
16
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mastercard_oauth1_signer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mastercard
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-01-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Zero dependency library for generating a Mastercard API compliant OAuth
42
+ signature
43
+ email:
44
+ - APISupport@mastercard.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - lib/oauth.rb
50
+ - oauth1_signer_ruby.gemspec
51
+ homepage: https://github.com/Mastercard/oauth1-signer-ruby
52
+ licenses:
53
+ - MIT
54
+ metadata: {}
55
+ post_install_message:
56
+ rdoc_options: []
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 2.7.7
72
+ signing_key:
73
+ specification_version: 4
74
+ summary: OAuth signature SDK
75
+ test_files: []