kerryb-right_aws 1.7.6 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,11 +24,73 @@
24
24
  # Test
25
25
  module RightAws
26
26
  require 'md5'
27
+ require 'pp'
27
28
 
28
29
  class AwsUtils #:nodoc:
29
- @@digest = OpenSSL::Digest::Digest.new("sha1")
30
+ @@digest1 = OpenSSL::Digest::Digest.new("sha1")
31
+ @@digest256 = nil
32
+ if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
33
+ @@digest256 = OpenSSL::Digest::Digest.new("sha256") rescue nil # Some installation may not support sha256
34
+ end
35
+
30
36
  def self.sign(aws_secret_access_key, auth_string)
31
- Base64.encode64(OpenSSL::HMAC.digest(@@digest, aws_secret_access_key, auth_string)).strip
37
+ Base64.encode64(OpenSSL::HMAC.digest(@@digest1, aws_secret_access_key, auth_string)).strip
38
+ end
39
+
40
+ # Escape a string accordingly Amazon rulles
41
+ # http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
42
+ def self.amz_escape(param)
43
+ param.to_s.gsub(/([^a-zA-Z0-9._~-]+)/n) do
44
+ '%' + $1.unpack('H2' * $1.size).join('%').upcase
45
+ end
46
+ end
47
+
48
+ # Set a timestamp and a signature version
49
+ def self.fix_service_params(service_hash, signature)
50
+ # Removed hardcoded UTC timezone
51
+ service_hash["Timestamp"] ||= Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z") unless service_hash["Expires"]
52
+ service_hash["SignatureVersion"] = signature
53
+ service_hash
54
+ end
55
+
56
+ # Signature Version 0
57
+ # A deprecated guy (should work till septemper 2009)
58
+ def self.sign_request_v0(aws_secret_access_key, service_hash)
59
+ fix_service_params(service_hash, '0')
60
+ string_to_sign = "#{service_hash['Action']}#{service_hash['Timestamp'] || service_hash['Expires']}"
61
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
62
+ service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
63
+ end
64
+
65
+ # Signature Version 1
66
+ # Another deprecated guy (should work till septemper 2009)
67
+ def self.sign_request_v1(aws_secret_access_key, service_hash)
68
+ fix_service_params(service_hash, '1')
69
+ string_to_sign = service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
70
+ service_hash['Signature'] = AwsUtils::sign(aws_secret_access_key, string_to_sign)
71
+ service_hash.to_a.collect{|key,val| "#{amz_escape(key)}=#{amz_escape(val.to_s)}" }.join("&")
72
+ end
73
+
74
+ # Signature Version 2
75
+ # EC2, SQS and SDB requests must be signed by this guy.
76
+ # See: http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?REST_RESTAuth.html
77
+ # http://developer.amazonwebservices.com/connect/entry.jspa?externalID=1928
78
+ def self.sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, uri)
79
+ fix_service_params(service_hash, '2')
80
+ # select a signing method (make an old openssl working with sha1)
81
+ # make 'HmacSHA256' to be a default one
82
+ service_hash['SignatureMethod'] = 'HmacSHA256' unless ['HmacSHA256', 'HmacSHA1'].include?(service_hash['SignatureMethod'])
83
+ service_hash['SignatureMethod'] = 'HmacSHA1' unless @@digest256
84
+ # select a digest
85
+ digest = (service_hash['SignatureMethod'] == 'HmacSHA256' ? @@digest256 : @@digest1)
86
+ # form string to sign
87
+ canonical_string = service_hash.keys.sort.map do |key|
88
+ "#{amz_escape(key)}=#{amz_escape(service_hash[key])}"
89
+ end.join('&')
90
+ string_to_sign = "#{http_verb.to_s.upcase}\n#{host.downcase}\n#{uri}\n#{canonical_string}"
91
+ # sign the string
92
+ signature = amz_escape(Base64.encode64(OpenSSL::HMAC.digest(digest, aws_secret_access_key, string_to_sign)).strip)
93
+ "#{canonical_string}&Signature=#{signature}"
32
94
  end
33
95
 
34
96
  # From Amazon's SQS Dev Guide, a brief description of how to escape:
@@ -44,6 +106,23 @@ module RightAws
44
106
  e = URI.escape(raw)
45
107
  e.gsub(/\+/, "%2b")
46
108
  end
109
+
110
+ def self.allow_only(allowed_keys, params)
111
+ bogus_args = []
112
+ params.keys.each {|p| bogus_args.push(p) unless allowed_keys.include?(p) }
113
+ raise AwsError.new("The following arguments were given but are not legal for the function call #{caller_method}: #{bogus_args.inspect}") if bogus_args.length > 0
114
+ end
115
+
116
+ def self.mandatory_arguments(required_args, params)
117
+ rargs = required_args.dup
118
+ params.keys.each {|p| rargs.delete(p)}
119
+ raise AwsError.new("The following mandatory arguments were not provided to #{caller_method}: #{rargs.inspect}") if rargs.length > 0
120
+ end
121
+
122
+ def self.caller_method
123
+ caller[1]=~/`(.*?)'/
124
+ $1
125
+ end
47
126
 
48
127
  end
49
128
 
@@ -93,7 +172,7 @@ module RightAws
93
172
  end
94
173
 
95
174
  module RightAwsBaseInterface
96
- DEFAULT_SIGNATURE_VERSION = '1'
175
+ DEFAULT_SIGNATURE_VERSION = '2'
97
176
 
98
177
  @@caching = false
99
178
  def self.caching
@@ -102,7 +181,7 @@ module RightAws
102
181
  def self.caching=(caching)
103
182
  @@caching = caching
104
183
  end
105
-
184
+
106
185
  # Current aws_access_key_id
107
186
  attr_reader :aws_access_key_id
108
187
  # Last HTTP request object
@@ -130,10 +209,20 @@ module RightAws
130
209
  if aws_access_key_id.blank? || aws_secret_access_key.blank?
131
210
  @aws_access_key_id = aws_access_key_id
132
211
  @aws_secret_access_key = aws_secret_access_key
133
- @params[:server] ||= service_info[:default_host]
134
- @params[:port] ||= service_info[:default_port]
135
- @params[:service] ||= service_info[:default_service]
136
- @params[:protocol] ||= service_info[:default_protocol]
212
+ # if the endpoint was explicitly defined - then use it
213
+ if @params[:endpoint_url]
214
+ @params[:server] = URI.parse(@params[:endpoint_url]).host
215
+ @params[:port] = URI.parse(@params[:endpoint_url]).port
216
+ @params[:service] = URI.parse(@params[:endpoint_url]).path
217
+ @params[:protocol] = URI.parse(@params[:endpoint_url]).scheme
218
+ @params[:region] = nil
219
+ else
220
+ @params[:server] ||= service_info[:default_host]
221
+ @params[:server] = "#{@params[:region]}.#{@params[:server]}" if @params[:region]
222
+ @params[:port] ||= service_info[:default_port]
223
+ @params[:service] ||= service_info[:default_service]
224
+ @params[:protocol] ||= service_info[:default_protocol]
225
+ end
137
226
  @params[:multi_thread] ||= defined?(AWS_DAEMON)
138
227
  @logger = @params[:logger]
139
228
  @logger = RAILS_DEFAULT_LOGGER if !@logger && defined?(RAILS_DEFAULT_LOGGER)
@@ -144,6 +233,15 @@ module RightAws
144
233
  @signature_version = (params[:signature_version] || DEFAULT_SIGNATURE_VERSION).to_s
145
234
  end
146
235
 
236
+ def signed_service_params(aws_secret_access_key, service_hash, http_verb=nil, host=nil, service=nil )
237
+ case signature_version.to_s
238
+ when '0' then AwsUtils::sign_request_v0(aws_secret_access_key, service_hash)
239
+ when '1' then AwsUtils::sign_request_v1(aws_secret_access_key, service_hash)
240
+ when '2' then AwsUtils::sign_request_v2(aws_secret_access_key, service_hash, http_verb, host, service)
241
+ else raise AwsError.new("Unknown signature version (#{signature_version.to_s}) requested")
242
+ end
243
+ end
244
+
147
245
  # Returns +true+ if the describe_xxx responses are being cached
148
246
  def caching?
149
247
  @params.key?(:cache) ? @params[:cache] : @@caching
@@ -157,10 +255,13 @@ module RightAws
157
255
  def cache_hits?(function, response, do_raise=:raise)
158
256
  result = false
159
257
  if caching?
160
- function = function.to_sym
258
+ function = function.to_sym
259
+ # get rid of requestId (this bad boy was added for API 2008-08-08+ and it is uniq for every response)
260
+ response = response.sub(%r{<requestId>.+?</requestId>}, '')
161
261
  response_md5 = MD5.md5(response).to_s
162
- # well, the response is new, reset cache data
262
+ # check for changes
163
263
  unless @cache[function] && @cache[function][:response_md5] == response_md5
264
+ # well, the response is new, reset cache data
164
265
  update_cache(function, {:response_md5 => response_md5,
165
266
  :timestamp => Time.now,
166
267
  :hits => 0,
@@ -264,6 +365,27 @@ module RightAws
264
365
  raise
265
366
  end
266
367
 
368
+ def request_cache_or_info(method, link, parser_class, benchblock, use_cache=true) #:nodoc:
369
+ # We do not want to break the logic of parsing hence will use a dummy parser to process all the standard
370
+ # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
371
+ # If the caching is enabled and hit then throw AwsNoChange.
372
+ # P.S. caching works for the whole images list only! (when the list param is blank)
373
+ # check cache
374
+ response, params = request_info(link, RightDummyParser.new)
375
+ cache_hits?(method.to_sym, response.body) if use_cache
376
+ parser = parser_class.new(:logger => @logger)
377
+ benchblock.xml.add!{ parser.parse(response, params) }
378
+ result = block_given? ? yield(parser) : parser.result
379
+ # update parsed data
380
+ update_cache(method.to_sym, :parsed => result) if use_cache
381
+ result
382
+ end
383
+
384
+ # Returns Amazons request ID for the latest request
385
+ def last_request_id
386
+ @last_response && @last_response.body.to_s[%r{<requestId>(.+?)</requestId>}] && $1
387
+ end
388
+
267
389
  end
268
390
 
269
391
 
@@ -550,8 +672,9 @@ module RightAws
550
672
  @xmlpath += @xmlpath.empty? ? name : "/#{name}"
551
673
  end
552
674
  def tag_end(name)
553
- @xmlpath[/^(.*?)\/?#{name}$/]
554
- @xmlpath = $1
675
+ if @xmlpath =~ /^(.*?)\/?#{name}$/
676
+ @xmlpath = $1
677
+ end
555
678
  tagend(name)
556
679
  end
557
680
  def text(text)
@@ -656,5 +779,20 @@ module RightAws
656
779
  end
657
780
  end
658
781
 
782
+ # Dummy parser - does nothing
783
+ # Returns the original params back
784
+ class RightDummyParser # :nodoc:
785
+ attr_accessor :result
786
+ def parse(response, params={})
787
+ @result = [response, params]
788
+ end
789
+ end
790
+
791
+ class RightHttp2xxParser < RightAWSParser # :nodoc:
792
+ def parse(response)
793
+ @result = response.is_a?(Net::HTTPSuccess)
794
+ end
795
+ end
796
+
659
797
  end
660
798