kerryb-right_aws 1.7.6 → 1.10.1

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.
@@ -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