remit 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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, {