remit 0.0.5 → 0.0.6

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,143 @@
1
+ ###############################################################################
2
+ # Copyright 2008-2010 Amazon Technologies, Inc
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ #
5
+ # You may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at: http://aws.amazon.com/apache2.0
7
+ # This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8
+ # CONDITIONS OF ANY KIND, either express or implied. See the License for the
9
+ # specific language governing permissions and limitations under the License.
10
+ ##############################################################################
11
+ require 'base64'
12
+ require 'cgi'
13
+ require 'openssl'
14
+
15
+ module Amazon
16
+ module FPS
17
+
18
+ #
19
+ # Copyright:: Copyright (c) 2009 Amazon.com, Inc. or its affiliates. All Rights Reserved.
20
+ #
21
+ # RFC 2104-compliant HMAC signature for request parameters
22
+ # Implements AWS Signature, as per following spec:
23
+ #
24
+ # If Signature Version is 1, it performs the following:
25
+ #
26
+ # Sorts all parameters (including SignatureVersion and excluding Signature,
27
+ # the value of which is being created), ignoring case.
28
+ #
29
+ # Iterate over the sorted list and append the parameter name (in original case)
30
+ # and then its value. It will not URL-encode the parameter values before
31
+ # constructing this string. There are no separators.
32
+ #
33
+ # If Signature Version is 2, string to sign is based on following:
34
+ #
35
+ # 1. The HTTP Request Method followed by an ASCII newline (%0A)
36
+ # 2. The HTTP Host header in the form of lowercase host, followed by an ASCII newline.
37
+ # 3. The URL encoded HTTP absolute path component of the URI
38
+ # (up to but not including the query string parameters);
39
+ # if this is empty use a forward '/'. This parameter is followed by an ASCII newline.
40
+ # 4. The concatenation of all query string components (names and values)
41
+ # as UTF-8 characters which are URL encoded as per RFC 3986
42
+ # (hex characters MUST be uppercase), sorted using lexicographic byte ordering.
43
+ # Parameter names are separated from their values by the '=' character
44
+ # (ASCII character 61), even if the value is empty.
45
+ # Pairs of parameter and values are separated by the '&' character (ASCII code 38).
46
+ #
47
+ class SignatureUtils
48
+
49
+ SIGNATURE_KEYNAME = "Signature"
50
+ SIGNATURE_METHOD_KEYNAME = "SignatureMethod"
51
+ SIGNATURE_VERSION_KEYNAME = "SignatureVersion"
52
+
53
+ HMAC_SHA256_ALGORITHM = "HmacSHA256"
54
+ HMAC_SHA1_ALGORITHM = "HmacSHA1"
55
+
56
+ def self.sign_parameters(args)
57
+ signature_version = args[:parameters][SIGNATURE_VERSION_KEYNAME]
58
+ string_to_sign = "";
59
+ algorithm = 'sha1';
60
+ if (signature_version == '1') then
61
+ string_to_sign = calculate_string_to_sign_v1(args)
62
+ elsif (signature_version == '2') then
63
+ algorithm = get_algorithm(args[:parameters][SIGNATURE_METHOD_KEYNAME])
64
+ string_to_sign = calculate_string_to_sign_v2(args)
65
+ else
66
+ raise "Invalid Signature Version specified"
67
+ end
68
+ return compute_signature(string_to_sign, args[:aws_secret_key], algorithm)
69
+ end
70
+
71
+ # Convert a string into URL encoded form.
72
+ def self.urlencode(plaintext)
73
+ CGI.escape(plaintext.to_s).gsub("+", "%20").gsub("%7E", "~")
74
+ end
75
+
76
+ private # All the methods below are private
77
+
78
+ def self.calculate_string_to_sign_v1(args)
79
+ parameters = args[:parameters]
80
+
81
+ # exclude any existing Signature parameter from the canonical string
82
+ sorted = (parameters.reject { |k, v| k == SIGNATURE_KEYNAME }).sort { |a,b| a[0].downcase <=> b[0].downcase }
83
+
84
+ canonical = ''
85
+ sorted.each do |v|
86
+ canonical << v[0]
87
+ canonical << v[1] unless(v[1].nil?)
88
+ end
89
+
90
+ return canonical
91
+ end
92
+
93
+ def self.calculate_string_to_sign_v2(args)
94
+ parameters = args[:parameters]
95
+
96
+ uri = args[:uri]
97
+ uri = "/" if uri.nil? or uri.empty?
98
+ uri = urlencode(uri).gsub("%2F", "/")
99
+
100
+ verb = args[:verb]
101
+ host = args[:host].downcase
102
+
103
+ # exclude any existing Signature parameter from the canonical string
104
+ sorted = (parameters.reject { |k, v| k == SIGNATURE_KEYNAME })
105
+
106
+ # sort the parameters
107
+ sorted = sorted.sort{ |a, b| a.to_s <=> b.to_s }
108
+
109
+ canonical = "#{verb}\n#{host}\n#{uri}\n"
110
+ isFirst = true
111
+
112
+ sorted.each { |v|
113
+ if(isFirst) then
114
+ isFirst = false
115
+ else
116
+ canonical << '&'
117
+ end
118
+
119
+ canonical << urlencode(v[0])
120
+ unless(v[1].nil?) then
121
+ canonical << '='
122
+ canonical << urlencode(v[1])
123
+ end
124
+ }
125
+
126
+ return canonical
127
+ end
128
+
129
+ def self.get_algorithm(signature_method)
130
+ return 'sha256' if (signature_method == HMAC_SHA256_ALGORITHM);
131
+ return 'sha1'
132
+ end
133
+
134
+ def self.compute_signature(canonical, aws_secret_key, algorithm = 'sha1')
135
+ digest = OpenSSL::Digest::Digest.new(algorithm)
136
+ return Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_key, canonical)).chomp
137
+ end
138
+
139
+ end
140
+
141
+ end
142
+ end
143
+
@@ -0,0 +1,180 @@
1
+ ###############################################################################
2
+ # Copyright 2008-2010 Amazon Technologies, Inc
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ #
5
+ # You may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at: http://aws.amazon.com/apache2.0
7
+ # This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
8
+ # CONDITIONS OF ANY KIND, either express or implied. See the License for the
9
+ # specific language governing permissions and limitations under the License.
10
+ ##############################################################################
11
+
12
+ require 'base64'
13
+ require 'cgi'
14
+ require 'openssl'
15
+ require 'net/http'
16
+ require 'net/https'
17
+ require 'rexml/document'
18
+
19
+ module Amazon
20
+ module FPS
21
+
22
+
23
+ class SignatureUtilsForOutbound
24
+
25
+ SIGNATURE_KEYNAME = "signature"
26
+ SIGNATURE_METHOD_KEYNAME = "signatureMethod"
27
+ SIGNATURE_VERSION_KEYNAME = "signatureVersion"
28
+ CERTIFICATE_URL_KEYNAME = "certificateUrl"
29
+ CERTIFICATE_URL_ROOT = "https://fps.amazonaws.com/"
30
+ CERTIFICATE_URL_ROOT_SANDBOX = "https://fps.sandbox.amazonaws.com/"
31
+
32
+ FPS_PROD_ENDPOINT = CERTIFICATE_URL_ROOT
33
+ FPS_SANDBOX_ENDPOINT = CERTIFICATE_URL_ROOT_SANDBOX
34
+ ACTION_PARAM = "?Action=VerifySignature"
35
+ END_POINT_PARAM = "&UrlEndPoint="
36
+ HTTP_PARAMS_PARAM = "&HttpParameters="
37
+ VERSION_PARAM_VALUE = "&Version=2008-09-17"
38
+
39
+ USER_AGENT_STRING = "SigV2_MigrationSampleCode_Ruby-2010-09-13"
40
+
41
+ SIGNATURE_VERSION_1 = "1"
42
+ SIGNATURE_VERSION_2 = "2"
43
+ RSA_SHA1_ALGORITHM = "RSA-SHA1"
44
+
45
+ def initialize(aws_access_key, aws_secret_key)
46
+ @aws_secret_key = aws_secret_key
47
+ @aws_access_key = aws_access_key
48
+ end
49
+
50
+ def validate_request(args)
51
+ parameters = args[:parameters]
52
+ return validate_signature_v2(args) if (parameters[SIGNATURE_VERSION_KEYNAME] == SIGNATURE_VERSION_2)
53
+ return validate_signature_v1(args)
54
+ end
55
+
56
+ def validate_signature_v1(args)
57
+ parameters = args[:parameters]
58
+ signature = "";
59
+ if(parameters[SIGNATURE_KEYNAME] != nil) then
60
+ signature = parameters[SIGNATURE_KEYNAME];
61
+ else
62
+ raise "Signature is missing from parameters"
63
+ end
64
+
65
+ canonical = SignatureUtilsForOutbound::calculate_string_to_sign_v1(args)
66
+ digest = OpenSSL::Digest::Digest.new('sha1')
67
+ return signature == Base64.encode64(OpenSSL::HMAC.digest(digest, @aws_secret_key, canonical)).chomp
68
+ end
69
+
70
+ def validate_signature_v2(args)
71
+ [:parameters,
72
+ :http_method,
73
+ :url_end_point].each do |arg|
74
+ raise "#{arg.inspect} is missing from the arguments." unless args[arg]
75
+ end
76
+
77
+ url_end_point = args[:url_end_point]
78
+
79
+ parameters = args[:parameters]
80
+ raise ":parameters must be enumerable" unless args[:parameters].kind_of? Enumerable
81
+
82
+ signature = parameters[SIGNATURE_KEYNAME];
83
+ raise "'signature' is missing from the parameters." if (signature.nil? or signature.empty?)
84
+
85
+ signature_version = parameters[SIGNATURE_VERSION_KEYNAME];
86
+ raise "'signatureVersion' is missing from the parameters." if (signature_version.nil? or signature_version.empty?)
87
+ raise "'signatureVersion' present in parameters is invalid. Valid values are: 2" if (signature_version != SIGNATURE_VERSION_2)
88
+
89
+ signature_method = parameters[SIGNATURE_METHOD_KEYNAME]
90
+ raise "'signatureMethod' is missing from the parameters." if (signature_method.nil? or signature_method.empty?)
91
+ signature_algorithm = SignatureUtilsForOutbound::get_algorithm(signature_method)
92
+ raise "'signatureMethod' present in parameters is invalid. Valid values are: RSA-SHA1" if (signature_algorithm.nil?)
93
+
94
+ certificate_url = parameters[CERTIFICATE_URL_KEYNAME]
95
+ raise "'certificate_url' is missing from the parameters." if (certificate_url.nil? or certificate_url.empty?)
96
+
97
+ # Construct VerifySignatureAPI request
98
+ if(SignatureUtilsForOutbound::starts_with(certificate_url, FPS_SANDBOX_ENDPOINT) == true) then
99
+ verify_signature_request = FPS_SANDBOX_ENDPOINT
100
+ elsif(SignatureUtilsForOutbound::starts_with(certificate_url, FPS_PROD_ENDPOINT) == true) then
101
+ verify_signature_request = FPS_PROD_ENDPOINT
102
+ else
103
+ raise "'certificateUrl' received is not valid. Valid certificate urls start with " <<
104
+ CERTIFICATE_URL_ROOT << " or " << CERTIFICATE_URL_ROOT_SANDBOX << "."
105
+ end
106
+
107
+ verify_signature_request = verify_signature_request + ACTION_PARAM +
108
+ END_POINT_PARAM +
109
+ SignatureUtilsForOutbound::urlencode(url_end_point) +
110
+ VERSION_PARAM_VALUE +
111
+ HTTP_PARAMS_PARAM +
112
+ SignatureUtilsForOutbound::urlencode(SignatureUtilsForOutbound::get_http_params(parameters))
113
+
114
+ verify_signature_response = SignatureUtilsForOutbound::get_http_data(verify_signature_request)
115
+
116
+ # parse the response
117
+ document = REXML::Document.new(verify_signature_response)
118
+
119
+ status_el = document.elements['VerifySignatureResponse/VerifySignatureResult/VerificationStatus']
120
+ return (!status_el.nil? && status_el.text == "Success")
121
+ end
122
+
123
+ def self.calculate_string_to_sign_v1(args)
124
+ parameters = args[:parameters]
125
+
126
+ # exclude any existing Signature parameter from the canonical string
127
+ sorted = (parameters.reject { |k, v| ((k == SIGNATURE_KEYNAME)) }).sort { |a,b| a[0].downcase <=> b[0].downcase }
128
+
129
+ canonical = ''
130
+ sorted.each do |v|
131
+ canonical << v[0]
132
+ canonical << v[1] unless(v[1].nil?)
133
+ end
134
+
135
+ return canonical
136
+ end
137
+
138
+ def self.get_algorithm(signature_method)
139
+ return OpenSSL::Digest::SHA1.new if (signature_method == RSA_SHA1_ALGORITHM)
140
+ return nil
141
+ end
142
+
143
+ # Convert a string into URL encoded form.
144
+ def self.urlencode(plaintext)
145
+ CGI.escape(plaintext.to_s).gsub("+", "%20").gsub("%7E", "~")
146
+ end
147
+
148
+ def self.get_http_data(url)
149
+ #2. fetch certificate if not found in cache
150
+ uri = URI.parse(url)
151
+ http_session = Net::HTTP.new(uri.host, uri.port)
152
+ http_session.use_ssl = true
153
+ http_session.ca_file = File.dirname(__FILE__) + '/ca-bundle.crt'
154
+ http_session.verify_mode = OpenSSL::SSL::VERIFY_PEER
155
+ http_session.verify_depth = 5
156
+
157
+ res = http_session.start {|http_session|
158
+ req = Net::HTTP::Get.new(url, {"User-Agent" => USER_AGENT_STRING})
159
+ http_session.request(req)
160
+ }
161
+
162
+ return res.body
163
+ end
164
+
165
+ def self.starts_with(string, prefix)
166
+ prefix = prefix.to_s
167
+ string[0, prefix.length] == prefix
168
+ end
169
+
170
+ def self.get_http_params(params)
171
+ params.map do |(k, v)|
172
+ urlencode(k) + "=" + urlencode(v)
173
+ end.join("&")
174
+ end
175
+
176
+ end
177
+
178
+ end
179
+ end
180
+
@@ -85,7 +85,7 @@ module Remit
85
85
  PIPELINE_URL = 'https://authorize.payments.amazon.com/cobranded-ui/actions/start'.freeze
86
86
  PIPELINE_SANDBOX_URL = 'https://authorize.payments-sandbox.amazon.com/cobranded-ui/actions/start'.freeze
87
87
  API_VERSION = Date.new(2007, 1, 8).to_s.freeze
88
- SIGNATURE_VERSION = 1.freeze
88
+ SIGNATURE_VERSION = 2.freeze
89
89
 
90
90
  attr_reader :access_key
91
91
  attr_reader :secret_key
@@ -108,6 +108,7 @@ module Remit
108
108
  new_query({
109
109
  :AWSAccessKeyId => @access_key,
110
110
  :SignatureVersion => SIGNATURE_VERSION,
111
+ Amazon::FPS::SignatureUtils::SIGNATURE_METHOD_KEYNAME => Amazon::FPS::SignatureUtils::HMAC_SHA256_ALGORITHM,
111
112
  :Version => API_VERSION,
112
113
  :Timestamp => Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
113
114
  })
@@ -122,13 +123,13 @@ module Remit
122
123
  private :query
123
124
 
124
125
  def sign(values)
125
- keys = values.keys.sort { |a, b| a.to_s.downcase <=> b.to_s.downcase }
126
-
127
- signature = keys.inject('') do |signature, key|
128
- signature += key.to_s + values[key].to_s
129
- end
130
-
131
- Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, @secret_key, signature)).strip
126
+ signature = Amazon::FPS::SignatureUtils.sign_parameters({
127
+ :parameters => values,
128
+ :aws_secret_key => @secret_key,
129
+ :host => @endpoint.host,
130
+ :verb => "GET",
131
+ :uri => @endpoint.path })
132
+ return signature
132
133
  end
133
134
  private :sign
134
135
  end
@@ -5,6 +5,9 @@ require 'uri'
5
5
  require 'rubygems'
6
6
  require 'relax'
7
7
 
8
+ require File.dirname(__FILE__) + '/../amazon/fps/signatureutils'
9
+ require File.dirname(__FILE__) + '/../amazon/fps/signatureutilsforoutbound'
10
+
8
11
  module Remit
9
12
  class Request < Relax::Request
10
13
  def self.action(name)
@@ -63,8 +66,17 @@ module Remit
63
66
  end
64
67
 
65
68
  def sign
66
- delete(:awsSignature)
67
- store(:awsSignature, Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, @secret_key, "#{@uri.path}?#{to_s(false)}".gsub('%20', '+'))).strip)
69
+ self[Amazon::FPS::SignatureUtils::SIGNATURE_VERSION_KEYNAME] = Remit::API::SIGNATURE_VERSION
70
+ self[Amazon::FPS::SignatureUtils::SIGNATURE_METHOD_KEYNAME] = Amazon::FPS::SignatureUtils::HMAC_SHA256_ALGORITHM
71
+
72
+ signature = Amazon::FPS::SignatureUtils.sign_parameters({
73
+ :parameters => self,
74
+ :aws_secret_key => @secret_key,
75
+ :host => @uri.host,
76
+ :verb => "GET",
77
+ :uri => @uri.path })
78
+
79
+ store(Amazon::FPS::SignatureUtilsForOutbound::SIGNATURE_KEYNAME.to_sym, signature)
68
80
  end
69
81
 
70
82
  def to_s(signed=true)
@@ -7,35 +7,36 @@ module Remit
7
7
  class Pipeline
8
8
  @parameters = []
9
9
  attr_reader :parameters
10
-
10
+
11
11
  class << self
12
12
  # Create the parameters hash for the subclass.
13
13
  def inherited(subclass) #:nodoc:
14
14
  subclass.instance_variable_set('@parameters', [])
15
15
  end
16
-
16
+
17
17
  def parameter(name)
18
18
  attr_accessor name
19
19
  @parameters << name
20
20
  end
21
-
21
+
22
22
  def convert_key(key)
23
23
  key.to_s.gsub(/_(.)/) { $1.upcase }.to_sym
24
24
  end
25
-
25
+
26
26
  # Returns a hash of all of the parameters for this request, including
27
27
  # those that are inherited.
28
28
  def parameters #:nodoc:
29
29
  (superclass.respond_to?(:parameters) ? superclass.parameters : []) + @parameters
30
30
  end
31
31
  end
32
-
32
+
33
33
  attr_reader :api
34
-
34
+
35
35
  parameter :pipeline_name
36
36
  parameter :return_url
37
37
  parameter :caller_key
38
38
  parameter :version
39
+ parameter :signature_version
39
40
  parameter :address_name
40
41
  parameter :address_line_1
41
42
  parameter :address_line_2
@@ -47,7 +48,7 @@ module Remit
47
48
 
48
49
  def initialize(api, options)
49
50
  @api = api
50
-
51
+
51
52
  options.each do |k,v|
52
53
  self.send("#{k}=", v)
53
54
  end
@@ -55,14 +56,14 @@ module Remit
55
56
 
56
57
  def url
57
58
  uri = URI.parse(@api.pipeline_url)
58
-
59
+
59
60
  query = {}
60
61
  self.class.parameters.each do |p|
61
62
  val = self.send(p)
62
-
63
+
63
64
  # Convert Time values to seconds from Epoch
64
65
  val = val.to_i if val.is_a?(Time)
65
-
66
+
66
67
  query[self.class.convert_key(p.to_s)] = val
67
68
  end
68
69
 
@@ -73,19 +74,19 @@ module Remit
73
74
  uri.to_s
74
75
  end
75
76
  end
76
-
77
+
77
78
  class SingleUsePipeline < Pipeline
78
79
  parameter :caller_reference
79
80
  parameter :payment_reason
80
81
  parameter :payment_method
81
82
  parameter :transaction_amount
82
83
  parameter :recipient_token
83
-
84
+
84
85
  def pipeline_name
85
86
  Remit::PipelineName::SINGLE_USE
86
87
  end
87
88
  end
88
-
89
+
89
90
  class MultiUsePipeline < Pipeline
90
91
  parameter :caller_reference
91
92
  parameter :payment_reason
@@ -103,12 +104,12 @@ module Remit
103
104
  parameter :usage_limit_period_2
104
105
  parameter :usage_limit_value_2
105
106
  parameter :is_recipient_cobranding
106
-
107
+
107
108
  def pipeline_name
108
109
  Remit::PipelineName::MULTI_USE
109
110
  end
110
111
  end
111
-
112
+
112
113
  class RecipientPipeline < Pipeline
113
114
  parameter :caller_reference
114
115
  parameter :validity_start # Time or seconds from Epoch
@@ -118,7 +119,7 @@ module Remit
118
119
  parameter :caller_reference_refund
119
120
  parameter :max_variable_fee
120
121
  parameter :max_fixed_fee
121
-
122
+
122
123
  def pipeline_name
123
124
  Remit::PipelineName::RECIPIENT
124
125
  end
@@ -132,13 +133,13 @@ module Remit
132
133
  parameter :validity_start # Time or seconds from Epoch
133
134
  parameter :validity_expiry # Time or seconds from Epoch
134
135
  parameter :payment_method
135
- parameter :recurring_period
136
-
136
+ parameter :recurring_period
137
+
137
138
  def pipeline_name
138
139
  Remit::PipelineName::RECURRING
139
- end
140
+ end
140
141
  end
141
-
142
+
142
143
  class PostpaidPipeline < Pipeline
143
144
  parameter :caller_reference_sender
144
145
  parameter :caller_reference_settlement
@@ -154,12 +155,25 @@ module Remit
154
155
  parameter :usage_limit_type2
155
156
  parameter :usage_limit_period2
156
157
  parameter :usage_limit_value2
157
-
158
+
158
159
  def pipeline_name
159
160
  Remit::PipelineName::SETUP_POSTPAID
160
161
  end
161
162
  end
162
163
 
164
+ class PrepaidPipeline < Pipeline
165
+ parameter :caller_reference_sender
166
+ parameter :caller_reference_funding
167
+ parameter :payment_reason
168
+ parameter :payment_method
169
+ parameter :validity_start # Time or seconds from Epoch
170
+ parameter :validity_expiry # Time or seconds from Epoch
171
+ parameter :funding_amount
172
+ def pipeline_name
173
+ Remit::PipelineName::SETUP_PREPAID
174
+ end
175
+ end
176
+
163
177
  def get_single_use_pipeline(options)
164
178
  self.get_pipeline(SingleUsePipeline, options)
165
179
  end
@@ -171,14 +185,18 @@ module Remit
171
185
  def get_recipient_pipeline(options)
172
186
  self.get_pipeline(RecipientPipeline, options)
173
187
  end
174
-
188
+
175
189
  def get_recurring_use_pipeline(options)
176
190
  self.get_pipeline(RecurringUsePipeline, options)
177
191
  end
178
-
192
+
179
193
  def get_postpaid_pipeline(options)
180
194
  self.get_pipeline(PostpaidPipeline, options)
181
195
  end
196
+
197
+ def get_prepaid_pipeline(options)
198
+ self.get_pipeline(PrepaidPipeline, options)
199
+ end
182
200
 
183
201
  def get_pipeline(pipeline_subclass, options)
184
202
  pipeline = pipeline_subclass.new(self, {