pay_with_amazon 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.
@@ -0,0 +1,225 @@
1
+ require 'base64'
2
+ require 'json'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'openssl'
6
+ require 'uri'
7
+
8
+ module PayWithAmazon
9
+
10
+ class IpnWasNotAuthenticError < StandardError
11
+ end
12
+
13
+ # Pay with Amazon Ipn Handler
14
+ #
15
+ # This class authenticates an sns message sent from Amazon. It
16
+ # will validate the header, subject, and certificate. After validation
17
+ # there are many helper methods in place to extract information received
18
+ # from the ipn notification.
19
+ class IpnHandler
20
+
21
+ SIGNABLE_KEYS = [
22
+ 'Message',
23
+ 'MessageId',
24
+ 'Timestamp',
25
+ 'TopicArn',
26
+ 'Type',
27
+ ].freeze
28
+
29
+ COMMON_NAME = 'sns.amazonaws.com'
30
+
31
+ attr_reader(:headers, :body)
32
+ attr_accessor(:proxy_addr, :proxy_port, :proxy_user, :proxy_pass)
33
+
34
+ # To initialize this class you will be required to
35
+ # pass in the request header and body. The body can be
36
+ # passed in parsed by json or without.
37
+ # @param header [request.headers]
38
+ # @param body [request.body.read]
39
+ # @optional proxy_addr [String]
40
+ # @optional proxy_port [String]
41
+ # @optional proxy_user [String]
42
+ # @optional proxy_pass [String]
43
+ def initialize(
44
+ headers,
45
+ body,
46
+ proxy_addr: :ENV,
47
+ proxy_port: nil,
48
+ proxy_user: nil,
49
+ proxy_pass: nil)
50
+
51
+ @body = body
52
+ @raw = parse_from(@body)
53
+ @headers = headers
54
+ @proxy_addr = proxy_addr
55
+ @proxy_port = proxy_port
56
+ @proxy_user = proxy_user
57
+ @proxy_pass = proxy_pass
58
+ end
59
+
60
+ # This method will authenticate the ipn message sent from Amazon.
61
+ # It will return true if everything is verified. It will display an
62
+ # error message if verification fails.
63
+ def authentic?
64
+ begin
65
+ decoded_from_base64 = Base64.decode64(signature)
66
+ validate_header
67
+ validate_subject(get_certificate.subject)
68
+ public_key = get_public_key_from(get_certificate)
69
+ verify_public_key(public_key, decoded_from_base64, canonical_string)
70
+
71
+ return true
72
+ rescue IpnWasNotAuthenticError => e
73
+ raise e.message
74
+ end
75
+ end
76
+
77
+ def type
78
+ @raw['Type']
79
+ end
80
+
81
+ def message_id
82
+ @raw['MessageId']
83
+ end
84
+
85
+ def topic_arn
86
+ @raw['TopicArn']
87
+ end
88
+
89
+ def message
90
+ @raw['Message']
91
+ end
92
+
93
+ def timestamp
94
+ @raw['Timestamp']
95
+ end
96
+
97
+ def signature
98
+ @raw['Signature']
99
+ end
100
+
101
+ def signature_version
102
+ @raw['SignatureVersion']
103
+ end
104
+
105
+ def signing_cert_url
106
+ @raw['SigningCertURL']
107
+ end
108
+
109
+ def unsubscribe_url
110
+ @raw['UnsubscribeURL']
111
+ end
112
+
113
+ def notification_type
114
+ parse_from(@raw['Message'])["NotificationType"]
115
+ end
116
+
117
+ def seller_id
118
+ parse_from(@raw['Message'])["SellerId"]
119
+ end
120
+
121
+ def environment
122
+ parse_from(@raw['Message'])["ReleaseEnvironment"]
123
+ end
124
+
125
+ def version
126
+ parse_from(@raw['Message'])["Version"]
127
+ end
128
+
129
+ def notification_data
130
+ parse_from(@raw['Message'])["NotificationData"]
131
+ end
132
+
133
+ def message_timestamp
134
+ parse_from(@raw['Message'])["Timestamp"]
135
+ end
136
+
137
+ def parse_from(json)
138
+ JSON.parse(json)
139
+ end
140
+
141
+ protected
142
+
143
+ def get_certificate
144
+ cert_pem = download_cert(signing_cert_url)
145
+ OpenSSL::X509::Certificate.new(cert_pem)
146
+ end
147
+
148
+ def get_public_key_from(certificate)
149
+ OpenSSL::PKey::RSA.new(certificate.public_key)
150
+ end
151
+
152
+ def canonical_string
153
+ text = ''
154
+ SIGNABLE_KEYS.each do |key|
155
+ value = @raw[key]
156
+ next if value.nil? or value.empty?
157
+ text << key << "\n"
158
+ text << value << "\n"
159
+ end
160
+ text
161
+ end
162
+
163
+ def download_cert(url)
164
+ uri = URI.parse(url)
165
+ unless
166
+ uri.scheme == 'https' &&
167
+ uri.host.match(/^sns\.[a-zA-Z0-9\-]{3,}\.amazonaws\.com(\.cn)?$/) &&
168
+ File.extname(uri.path) == '.pem'
169
+ then
170
+ msg = "Error - certificate is not hosted at AWS URL (https): #{url}"
171
+ raise IpnWasNotAuthenticError, msg
172
+ end
173
+ tries = 0
174
+ begin
175
+ resp = https_get(url)
176
+ resp.body
177
+ rescue => error
178
+ tries += 1
179
+ retry if tries < 3
180
+ raise error
181
+ end
182
+ end
183
+
184
+ def https_get(url)
185
+ uri = URI.parse(url)
186
+ http = Net::HTTP.new(uri.host, uri.port, @proxy_addr, @proxy_port, @proxy_user, @proxy_pass)
187
+ http.use_ssl = true
188
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
189
+ http.start
190
+ resp = http.request(Net::HTTP::Get.new(uri.request_uri))
191
+ http.finish
192
+ resp
193
+ end
194
+
195
+ def validate_header
196
+ unless
197
+ @headers['x-amz-sns-message-type'] == 'Notification'
198
+ then
199
+ msg = "Error - Header does not contain x-amz-sns-message-type header"
200
+ raise IpnWasNotAuthenticError, msg
201
+ end
202
+ end
203
+
204
+ def validate_subject(certificate_subject)
205
+ subject = certificate_subject.to_a
206
+ unless
207
+ subject[4][1] == COMMON_NAME
208
+ then
209
+ msg = "Error - Unable to verify certificate subject issued by Amazon"
210
+ raise IpnWasNotAuthenticError, msg
211
+ end
212
+ end
213
+
214
+ def verify_public_key(public_key, decoded_signature, signed_string)
215
+ unless
216
+ public_key.verify(OpenSSL::Digest::SHA1.new, decoded_signature, signed_string)
217
+ then
218
+ msg = "Error - Unable to verify public key with signature and signed string"
219
+ raise IpnWasNotAuthenticError, msg
220
+ end
221
+ end
222
+
223
+ end
224
+
225
+ end
@@ -0,0 +1,43 @@
1
+ require 'rexml/document'
2
+
3
+ module PayWithAmazon
4
+
5
+ # This class provides helpers to parse the response
6
+ class Response
7
+
8
+ # @param response [String]
9
+ def initialize(response)
10
+ @response = response
11
+ end
12
+
13
+ def body
14
+ @response.body
15
+ end
16
+
17
+ def to_xml
18
+ REXML::Document.new(body)
19
+ end
20
+
21
+ def get_element(xpath, xml_element)
22
+ xml = self.to_xml
23
+ xml.elements.each(xpath) do |element|
24
+ @value = element.elements[xml_element].text
25
+ end
26
+ return @value
27
+ end
28
+
29
+ def code
30
+ @response.code
31
+ end
32
+
33
+ def success
34
+ if @response.code.eql? '200'
35
+ return true
36
+ else
37
+ return false
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,4 @@
1
+ module PayWithAmazon
2
+ VERSION = "1.0.0"
3
+ API_VERSION = "2013-01-01"
4
+ end
@@ -0,0 +1,4 @@
1
+ require 'pay_with_amazon/client'
2
+ require 'pay_with_amazon/ipn_handler'
3
+ require 'pay_with_amazon/response'
4
+ require 'pay_with_amazon/version'
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pay_with_amazon
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Amazon Payments
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Login and Pay with Amazon Ruby SDK
14
+ email:
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - LICENSE
20
+ - NOTICE
21
+ - README.md
22
+ - lib/pay_with_amazon.rb
23
+ - lib/pay_with_amazon/client.rb
24
+ - lib/pay_with_amazon/ipn_handler.rb
25
+ - lib/pay_with_amazon/response.rb
26
+ - lib/pay_with_amazon/version.rb
27
+ homepage: https://github.com/amzn/login-and-pay-with-amazon-sdk-ruby
28
+ licenses:
29
+ - Apache License, Version 2.0
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 2.0.0
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.4.5
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: Login and Pay with Amazon Ruby SDK
51
+ test_files: []
52
+ has_rdoc: