mastercard_oauth1_signer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []