aws-sdk-s3 1.30.0 → 1.114.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +903 -0
  3. data/LICENSE.txt +202 -0
  4. data/VERSION +1 -0
  5. data/lib/aws-sdk-s3/arn/access_point_arn.rb +69 -0
  6. data/lib/aws-sdk-s3/arn/multi_region_access_point_arn.rb +68 -0
  7. data/lib/aws-sdk-s3/arn/object_lambda_arn.rb +69 -0
  8. data/lib/aws-sdk-s3/arn/outpost_access_point_arn.rb +74 -0
  9. data/lib/aws-sdk-s3/bucket.rb +377 -77
  10. data/lib/aws-sdk-s3/bucket_acl.rb +57 -14
  11. data/lib/aws-sdk-s3/bucket_cors.rb +67 -13
  12. data/lib/aws-sdk-s3/bucket_lifecycle.rb +54 -15
  13. data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +56 -15
  14. data/lib/aws-sdk-s3/bucket_logging.rb +52 -15
  15. data/lib/aws-sdk-s3/bucket_notification.rb +44 -15
  16. data/lib/aws-sdk-s3/bucket_policy.rb +51 -13
  17. data/lib/aws-sdk-s3/bucket_region_cache.rb +2 -0
  18. data/lib/aws-sdk-s3/bucket_request_payment.rb +51 -12
  19. data/lib/aws-sdk-s3/bucket_tagging.rb +59 -13
  20. data/lib/aws-sdk-s3/bucket_versioning.rb +118 -12
  21. data/lib/aws-sdk-s3/bucket_website.rb +66 -13
  22. data/lib/aws-sdk-s3/client.rb +9016 -669
  23. data/lib/aws-sdk-s3/client_api.rb +786 -2
  24. data/lib/aws-sdk-s3/customizations/bucket.rb +59 -16
  25. data/lib/aws-sdk-s3/customizations/multipart_upload.rb +2 -0
  26. data/lib/aws-sdk-s3/customizations/object.rb +200 -62
  27. data/lib/aws-sdk-s3/customizations/object_summary.rb +5 -0
  28. data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +2 -0
  29. data/lib/aws-sdk-s3/customizations.rb +4 -1
  30. data/lib/aws-sdk-s3/encryption/client.rb +23 -6
  31. data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +71 -29
  32. data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +43 -5
  33. data/lib/aws-sdk-s3/encryption/default_key_provider.rb +2 -0
  34. data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +13 -2
  35. data/lib/aws-sdk-s3/encryption/errors.rb +2 -0
  36. data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +2 -0
  37. data/lib/aws-sdk-s3/encryption/io_decrypter.rb +11 -3
  38. data/lib/aws-sdk-s3/encryption/io_encrypter.rb +2 -0
  39. data/lib/aws-sdk-s3/encryption/key_provider.rb +2 -0
  40. data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +34 -3
  41. data/lib/aws-sdk-s3/encryption/materials.rb +8 -6
  42. data/lib/aws-sdk-s3/encryption/utils.rb +25 -0
  43. data/lib/aws-sdk-s3/encryption.rb +4 -0
  44. data/lib/aws-sdk-s3/encryptionV2/client.rb +566 -0
  45. data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +222 -0
  46. data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +170 -0
  47. data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +40 -0
  48. data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +65 -0
  49. data/lib/aws-sdk-s3/encryptionV2/errors.rb +37 -0
  50. data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +58 -0
  51. data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +37 -0
  52. data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +73 -0
  53. data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +31 -0
  54. data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +169 -0
  55. data/lib/aws-sdk-s3/encryptionV2/materials.rb +60 -0
  56. data/lib/aws-sdk-s3/encryptionV2/utils.rb +103 -0
  57. data/lib/aws-sdk-s3/encryption_v2.rb +23 -0
  58. data/lib/aws-sdk-s3/errors.rb +123 -1
  59. data/lib/aws-sdk-s3/event_streams.rb +20 -7
  60. data/lib/aws-sdk-s3/file_downloader.rb +17 -10
  61. data/lib/aws-sdk-s3/file_part.rb +11 -6
  62. data/lib/aws-sdk-s3/file_uploader.rb +33 -14
  63. data/lib/aws-sdk-s3/legacy_signer.rb +17 -25
  64. data/lib/aws-sdk-s3/multipart_file_uploader.rb +78 -19
  65. data/lib/aws-sdk-s3/multipart_stream_uploader.rb +20 -7
  66. data/lib/aws-sdk-s3/multipart_upload.rb +178 -28
  67. data/lib/aws-sdk-s3/multipart_upload_error.rb +2 -0
  68. data/lib/aws-sdk-s3/multipart_upload_part.rb +236 -43
  69. data/lib/aws-sdk-s3/object.rb +848 -162
  70. data/lib/aws-sdk-s3/object_acl.rb +81 -20
  71. data/lib/aws-sdk-s3/object_copier.rb +2 -0
  72. data/lib/aws-sdk-s3/object_multipart_copier.rb +2 -0
  73. data/lib/aws-sdk-s3/object_summary.rb +615 -144
  74. data/lib/aws-sdk-s3/object_version.rb +161 -68
  75. data/lib/aws-sdk-s3/plugins/accelerate.rb +38 -38
  76. data/lib/aws-sdk-s3/plugins/arn.rb +254 -0
  77. data/lib/aws-sdk-s3/plugins/bucket_dns.rb +8 -8
  78. data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +25 -3
  79. data/lib/aws-sdk-s3/plugins/dualstack.rb +38 -33
  80. data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +4 -4
  81. data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +3 -1
  82. data/lib/aws-sdk-s3/plugins/http_200_errors.rb +11 -3
  83. data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +73 -0
  84. data/lib/aws-sdk-s3/plugins/location_constraint.rb +2 -0
  85. data/lib/aws-sdk-s3/plugins/md5s.rb +34 -30
  86. data/lib/aws-sdk-s3/plugins/object_lambda_endpoint.rb +25 -0
  87. data/lib/aws-sdk-s3/plugins/redirects.rb +2 -0
  88. data/lib/aws-sdk-s3/plugins/s3_host_id.rb +2 -0
  89. data/lib/aws-sdk-s3/plugins/s3_signer.rb +95 -36
  90. data/lib/aws-sdk-s3/plugins/skip_whole_multipart_get_checksums.rb +31 -0
  91. data/lib/aws-sdk-s3/plugins/sse_cpk.rb +3 -1
  92. data/lib/aws-sdk-s3/plugins/streaming_retry.rb +139 -0
  93. data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +2 -0
  94. data/lib/aws-sdk-s3/presigned_post.rb +110 -51
  95. data/lib/aws-sdk-s3/presigner.rb +168 -66
  96. data/lib/aws-sdk-s3/resource.rb +41 -5
  97. data/lib/aws-sdk-s3/types.rb +8652 -1146
  98. data/lib/aws-sdk-s3/waiters.rb +67 -1
  99. data/lib/aws-sdk-s3.rb +12 -6
  100. metadata +38 -13
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'openssl'
2
4
  require 'base64'
3
5
 
@@ -96,7 +98,7 @@ module Aws
96
98
  # or call the associated method.
97
99
  #
98
100
  # ```ruby
99
- # post = Aws::S3::PresignedPost.new(creds, region, bucket).
101
+ # post = Aws::S3::PresignedPost.new(creds, region, bucket)
100
102
  # post.content_type('text/plain')
101
103
  # ```
102
104
  #
@@ -174,44 +176,76 @@ module Aws
174
176
  # ```
175
177
  #
176
178
  class PresignedPost
179
+ @@allowed_fields = []
177
180
 
178
181
  # @param [Credentials] credentials Security credentials for signing
179
182
  # the post policy.
180
183
  # @param [String] bucket_region Region of the target bucket.
181
184
  # @param [String] bucket_name Name of the target bucket.
185
+ # @option options [Boolean] :use_accelerate_endpoint (false) When `true`,
186
+ # PresignedPost will attempt to use accelerated endpoint.
187
+ # @option options [String] :url See {PresignedPost#url}.
188
+ # @option options [Sting, Array<String>] :allow_any
189
+ # See {PresignedPost#allow_any}.
182
190
  # @option options [Time] :signature_expiration Specify when the signature on
183
191
  # the post will expire. Defaults to one hour from creation of the
184
192
  # presigned post. May not exceed one week from creation time.
185
193
  # @option options [String] :key See {PresignedPost#key}.
186
- # @option options [String] :key_starts_with See {PresignedPost#key_starts_with}.
194
+ # @option options [String] :key_starts_with
195
+ # See {PresignedPost#key_starts_with}.
187
196
  # @option options [String] :acl See {PresignedPost#acl}.
188
- # @option options [String] :acl_starts_with See {PresignedPost#acl_starts_with}.
189
- # @option options [String] :cache_control See {PresignedPost#cache_control}.
190
- # @option options [String] :cache_control_starts_with See {PresignedPost#cache_control_starts_with}.
197
+ # @option options [String] :acl_starts_with
198
+ # See {PresignedPost#acl_starts_with}.
199
+ # @option options [String] :cache_control
200
+ # See {PresignedPost#cache_control}.
201
+ # @option options [String] :cache_control_starts_with
202
+ # See {PresignedPost#cache_control_starts_with}.
191
203
  # @option options [String] :content_type See {PresignedPost#content_type}.
192
- # @option options [String] :content_type_starts_with See {PresignedPost#content_type_starts_with}.
193
- # @option options [String] :content_disposition See {PresignedPost#content_disposition}.
194
- # @option options [String] :content_disposition_starts_with See {PresignedPost#content_disposition_starts_with}.
195
- # @option options [String] :content_encoding See {PresignedPost#content_encoding}.
196
- # @option options [String] :content_encoding_starts_with See {PresignedPost#content_encoding_starts_with}.
197
- # @option options [String] :expires See {PresignedPost#expires}.
198
- # @option options [String] :expires_starts_with See {PresignedPost#expires_starts_with}.
199
- # @option options [Range<Integer>] :content_length_range See {PresignedPost#content_length_range}.
200
- # @option options [String] :success_action_redirect See {PresignedPost#success_action_redirect}.
201
- # @option options [String] :success_action_redirect_starts_with See {PresignedPost#success_action_redirect_starts_with}.
202
- # @option options [String] :success_action_status See {PresignedPost#success_action_status}.
203
- # @option options [String] :storage_class See {PresignedPost#storage_class}.
204
- # @option options [String] :website_redirect_location See {PresignedPost#website_redirect_location}.
205
- # @option options [Hash<String,String>] :metadata See {PresignedPost#metadata}.
206
- # @option options [Hash<String,String>] :metadata_starts_with See {PresignedPost#metadata_starts_with}.
207
- # @option options [String] :server_side_encryption See {PresignedPost#server_side_encryption}.
208
- # @option options [String] :server_side_encryption_aws_kms_key_id See {PresignedPost#server_side_encryption_aws_kms_key_id}.
209
- # @option options [String] :server_side_encryption_customer_algorithm See {PresignedPost#server_side_encryption_customer_algorithm}.
210
- # @option options [String] :server_side_encryption_customer_key See {PresignedPost#server_side_encryption_customer_key}.
204
+ # @option options [String] :content_type_starts_with
205
+ # See {PresignedPost#content_type_starts_with}.
206
+ # @option options [String] :content_disposition
207
+ # See {PresignedPost#content_disposition}.
208
+ # @option options [String] :content_disposition_starts_with
209
+ # See {PresignedPost#content_disposition_starts_with}.
210
+ # @option options [String] :content_encoding
211
+ # See {PresignedPost#content_encoding}.
212
+ # @option options [String] :content_encoding_starts_with
213
+ # See {PresignedPost#content_encoding_starts_with}.
214
+ # @option options [Time] :expires See {PresignedPost#expires}.
215
+ # @option options [String] :expires_starts_with
216
+ # See {PresignedPost#expires_starts_with}.
217
+ # @option options [Range<Integer>] :content_length_range
218
+ # See {PresignedPost#content_length_range}.
219
+ # @option options [String] :success_action_redirect
220
+ # See {PresignedPost#success_action_redirect}.
221
+ # @option options [String] :success_action_redirect_starts_with
222
+ # See {PresignedPost#success_action_redirect_starts_with}.
223
+ # @option options [String] :success_action_status
224
+ # See {PresignedPost#success_action_status}.
225
+ # @option options [String] :storage_class
226
+ # See {PresignedPost#storage_class}.
227
+ # @option options [String] :website_redirect_location
228
+ # See {PresignedPost#website_redirect_location}.
229
+ # @option options [Hash<String,String>] :metadata
230
+ # See {PresignedPost#metadata}.
231
+ # @option options [Hash<String,String>] :metadata_starts_with
232
+ # See {PresignedPost#metadata_starts_with}.
233
+ # @option options [String] :server_side_encryption
234
+ # See {PresignedPost#server_side_encryption}.
235
+ # @option options [String] :server_side_encryption_aws_kms_key_id
236
+ # See {PresignedPost#server_side_encryption_aws_kms_key_id}.
237
+ # @option options [String] :server_side_encryption_customer_algorithm
238
+ # See {PresignedPost#server_side_encryption_customer_algorithm}.
239
+ # @option options [String] :server_side_encryption_customer_key
240
+ # See {PresignedPost#server_side_encryption_customer_key}.
241
+ # @option options [String] :server_side_encryption_customer_key_starts_with
242
+ # See {PresignedPost#server_side_encryption_customer_key_starts_with}.
211
243
  def initialize(credentials, bucket_region, bucket_name, options = {})
212
244
  @credentials = credentials.credentials
213
245
  @bucket_region = bucket_region
214
246
  @bucket_name = bucket_name
247
+ @accelerate = !!options.delete(:use_accelerate_endpoint)
248
+ options.delete(:url) if @accelerate # resource methods pass url
215
249
  @url = options.delete(:url) || bucket_url
216
250
  @fields = {}
217
251
  @key_set = false
@@ -221,7 +255,12 @@ module Aws
221
255
  case option_name
222
256
  when :allow_any then allow_any(option_value)
223
257
  when :signature_expiration then @signature_expiration = option_value
224
- else send("#{option_name}", option_value)
258
+ else
259
+ if @@allowed_fields.include?(option_name)
260
+ send("#{option_name}", option_value)
261
+ else
262
+ raise ArgumentError, "Unsupported option: #{option_name}"
263
+ end
225
264
  end
226
265
  end
227
266
  end
@@ -234,7 +273,7 @@ module Aws
234
273
  # as hidden input fields.
235
274
  def fields
236
275
  check_required_values!
237
- datetime = Time.now.utc.strftime("%Y%m%dT%H%M%SZ")
276
+ datetime = Time.now.utc.strftime('%Y%m%dT%H%M%SZ')
238
277
  fields = @fields.dup
239
278
  fields.update('policy' => policy(datetime))
240
279
  fields.update(signature_fields(datetime))
@@ -253,24 +292,30 @@ module Aws
253
292
  end
254
293
 
255
294
  # @api private
256
- def self.define_field(field, *args)
295
+ def self.define_field(field, *args, &block)
296
+ @@allowed_fields << field
257
297
  options = args.last.is_a?(Hash) ? args.pop : {}
258
298
  field_name = args.last || field.to_s
259
299
 
260
- define_method("#{field}") do |value|
261
- with(field_name, value)
262
- end
300
+ if block_given?
301
+ define_method("#{field}", block)
302
+ else
303
+ define_method("#{field}") do |value|
304
+ with(field_name, value)
305
+ end
263
306
 
264
- if options[:starts_with]
265
- define_method("#{field}_starts_with") do |value|
266
- starts_with(field_name, value)
307
+ if options[:starts_with]
308
+ @@allowed_fields << "#{field}_starts_with".to_sym
309
+ define_method("#{field}_starts_with") do |value|
310
+ starts_with(field_name, value)
311
+ end
267
312
  end
268
313
  end
269
314
  end
270
315
 
271
316
  # @!group Fields
272
317
 
273
- # The key to use for the uploaded object. Use can use `${filename}`
318
+ # The key to use for the uploaded object. You can use `${filename}`
274
319
  # as a variable in the key. This will be replaced with the name
275
320
  # of the file as provided by the user.
276
321
  #
@@ -281,7 +326,7 @@ module Aws
281
326
  # @param [String] key
282
327
  # @see http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html)
283
328
  # @return [self]
284
- def key(key)
329
+ define_field(:key) do |key|
285
330
  @key_set = true
286
331
  with('key', key)
287
332
  end
@@ -290,7 +335,7 @@ module Aws
290
335
  # @param [String] prefix
291
336
  # @see #key
292
337
  # @return [self]
293
- def key_starts_with(prefix)
338
+ define_field(:key_starts_with) do |prefix|
294
339
  @key_set = true
295
340
  starts_with('key', prefix)
296
341
  end
@@ -373,21 +418,21 @@ module Aws
373
418
  # @param [Time] time
374
419
  # @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.21
375
420
  # @return [self]
376
- def expires(time)
421
+ define_field(:expires) do |time|
377
422
  with('Expires', time.httpdate)
378
423
  end
379
424
 
380
425
  # @param [String] prefix
381
426
  # @see #expires
382
427
  # @return [self]
383
- def expires_starts_with(prefix)
428
+ define_field(:expires_starts_with) do |prefix|
384
429
  starts_with('Expires', prefix)
385
430
  end
386
431
 
387
432
  # The minimum and maximum allowable size for the uploaded content.
388
433
  # @param [Range<Integer>] byte_range
389
434
  # @return [self]
390
- def content_length_range(byte_range)
435
+ define_field(:content_length_range) do |byte_range|
391
436
  min = byte_range.begin
392
437
  max = byte_range.end
393
438
  max -= 1 if byte_range.exclude_end?
@@ -466,7 +511,7 @@ module Aws
466
511
  # prefixed with "x-amz-meta-".
467
512
  # @param [Hash<String,String>] hash
468
513
  # @return [self]
469
- def metadata(hash)
514
+ define_field(:metadata) do |hash|
470
515
  hash.each do |key, value|
471
516
  with("x-amz-meta-#{key}", value)
472
517
  end
@@ -477,7 +522,7 @@ module Aws
477
522
  # @param [Hash<String,String>] hash
478
523
  # @see #metadata
479
524
  # @return [self]
480
- def metadata_starts_with(hash)
525
+ define_field(:metadata_starts_with) do |hash|
481
526
  hash.each do |key, value|
482
527
  starts_with("x-amz-meta-#{key}", value)
483
528
  end
@@ -505,7 +550,10 @@ module Aws
505
550
  # (KMS) master encryption key to use for the object.
506
551
  # @param [String] value
507
552
  # @return [self]
508
- define_field(:server_side_encryption_aws_kms_key_id, 'x-amz-server-side-encryption-aws-kms-key-id')
553
+ define_field(
554
+ :server_side_encryption_aws_kms_key_id,
555
+ 'x-amz-server-side-encryption-aws-kms-key-id'
556
+ )
509
557
 
510
558
  # @!endgroup
511
559
 
@@ -518,7 +566,10 @@ module Aws
518
566
  # @param [String] value
519
567
  # @see #server_side_encryption_customer_key
520
568
  # @return [self]
521
- define_field(:server_side_encryption_customer_algorithm, 'x-amz-server-side-encryption-customer-algorithm')
569
+ define_field(
570
+ :server_side_encryption_customer_algorithm,
571
+ 'x-amz-server-side-encryption-customer-algorithm'
572
+ )
522
573
 
523
574
  # Specifies the customer-provided encryption key for Amazon S3 to use
524
575
  # in encrypting data. This value is used to store the object and then
@@ -529,7 +580,7 @@ module Aws
529
580
  # @param [String] value
530
581
  # @see #server_side_encryption_customer_algorithm
531
582
  # @return [self]
532
- def server_side_encryption_customer_key(value)
583
+ define_field(:server_side_encryption_customer_key) do |value|
533
584
  field_name = 'x-amz-server-side-encryption-customer-key'
534
585
  with(field_name, base64(value))
535
586
  with(field_name + '-MD5', base64(OpenSSL::Digest::MD5.digest(value)))
@@ -538,7 +589,7 @@ module Aws
538
589
  # @param [String] prefix
539
590
  # @see #server_side_encryption_customer_key
540
591
  # @return [self]
541
- def server_side_encryption_customer_key_starts_with(prefix)
592
+ define_field(:server_side_encryption_customer_key_starts_with) do |prefix|
542
593
  field_name = 'x-amz-server-side-encryption-customer-key'
543
594
  starts_with(field_name, prefix)
544
595
  end
@@ -571,8 +622,8 @@ module Aws
571
622
 
572
623
  def check_required_values!
573
624
  unless @key_set
574
- msg = "key required; you must provide a key via :key, "
575
- msg << ":key_starts_with, or :allow_any => ['key']"
625
+ msg = 'key required; you must provide a key via :key, '\
626
+ ":key_starts_with, or :allow_any => ['key']"
576
627
  raise msg
577
628
  end
578
629
  end
@@ -580,10 +631,18 @@ module Aws
580
631
  def bucket_url
581
632
  url = Aws::Partitions::EndpointProvider.resolve(@bucket_region, 's3')
582
633
  url = URI.parse(url)
583
- if Plugins::BucketDns.dns_compatible?(@bucket_name, true)
584
- url.host = @bucket_name + '.' + url.host
634
+ if Plugins::BucketDns.dns_compatible?(@bucket_name, _ssl = true)
635
+ if @accelerate
636
+ url.host = "#{@bucket_name}.s3-accelerate.amazonaws.com"
637
+ else
638
+ url.host = "#{@bucket_name}.#{url.host}"
639
+ end
585
640
  else
586
- url.path = '/' + @bucket_name
641
+ url.path = "/#{@bucket_name}"
642
+ end
643
+ if @bucket_region == 'us-east-1'
644
+ # keep legacy behavior by default
645
+ url.host = Plugins::IADRegionalEndpoint.legacy_host(url.host)
587
646
  end
588
647
  url.to_s
589
648
  end
@@ -613,7 +672,7 @@ module Aws
613
672
 
614
673
  def signature(datetime, string_to_sign)
615
674
  k_secret = @credentials.secret_access_key
616
- k_date = hmac("AWS4" + k_secret, datetime[0,8])
675
+ k_date = hmac('AWS4' + k_secret, datetime[0,8])
617
676
  k_region = hmac(k_date, @bucket_region)
618
677
  k_service = hmac(k_region, 's3')
619
678
  k_credentials = hmac(k_service, 'aws4_request')
@@ -1,27 +1,94 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Aws
2
4
  module S3
3
-
4
- # Allows you to create presigned URLs for S3 operations.
5
- #
6
- # Example Use:
7
- #
8
- # signer = Aws::S3::Presigner.new
9
- # url = signer.presigned_url(:get_object, bucket: "bucket", key: "key")
10
- #
11
5
  class Presigner
12
-
13
6
  # @api private
14
7
  ONE_WEEK = 60 * 60 * 24 * 7
15
8
 
16
9
  # @api private
17
10
  FIFTEEN_MINUTES = 60 * 15
18
11
 
12
+ # @api private
13
+ BLACKLISTED_HEADERS = [
14
+ 'accept',
15
+ 'amz-sdk-request',
16
+ 'cache-control',
17
+ 'content-length', # due to a ELB bug
18
+ 'expect',
19
+ 'from',
20
+ 'if-match',
21
+ 'if-none-match',
22
+ 'if-modified-since',
23
+ 'if-unmodified-since',
24
+ 'if-range',
25
+ 'max-forwards',
26
+ 'pragma',
27
+ 'proxy-authorization',
28
+ 'referer',
29
+ 'te',
30
+ 'user-agent'
31
+ ].freeze
32
+
19
33
  # @option options [Client] :client Optionally provide an existing
20
34
  # S3 client
21
35
  def initialize(options = {})
22
36
  @client = options[:client] || Aws::S3::Client.new
23
37
  end
24
38
 
39
+ # Create presigned URLs for S3 operations.
40
+ #
41
+ # @example
42
+ # signer = Aws::S3::Presigner.new
43
+ # url = signer.presigned_url(:get_object, bucket: "bucket", key: "key")
44
+ #
45
+ # @param [Symbol] method Symbolized method name of the operation you want
46
+ # to presign.
47
+ #
48
+ # @option params [Integer] :expires_in (900) The number of seconds
49
+ # before the presigned URL expires. Defaults to 15 minutes. As signature
50
+ # version 4 has a maximum expiry time of one week for presigned URLs,
51
+ # attempts to set this value to greater than one week (604800) will
52
+ # raise an exception.
53
+ #
54
+ # @option params [Time] :time (Time.now) The starting time for when the
55
+ # presigned url becomes active.
56
+ #
57
+ # @option params [Boolean] :secure (true) When `false`, a HTTP URL
58
+ # is returned instead of the default HTTPS URL.
59
+ #
60
+ # @option params [Boolean] :virtual_host (false) When `true`, the
61
+ # bucket name will be used as the hostname.
62
+ #
63
+ # @option params [Boolean] :use_accelerate_endpoint (false) When `true`,
64
+ # Presigner will attempt to use accelerated endpoint.
65
+ #
66
+ # @option params [Array<String>] :whitelist_headers ([]) Additional
67
+ # headers to be included for the signed request. Certain headers beyond
68
+ # the authorization header could, in theory, be changed for various
69
+ # reasons (including but not limited to proxies) while in transit and
70
+ # after signing. This would lead to signature errors being returned,
71
+ # despite no actual problems with signing. (see BLACKLISTED_HEADERS)
72
+ #
73
+ # @raise [ArgumentError] Raises an ArgumentError if `:expires_in`
74
+ # exceeds one week.
75
+ #
76
+ # @return [String] a presigned url
77
+ def presigned_url(method, params = {})
78
+ url, _headers = _presigned_request(method, params)
79
+ url
80
+ end
81
+
82
+ # Allows you to create presigned URL requests for S3 operations. This
83
+ # method returns a tuple containing the URL and the signed X-amz-* headers
84
+ # to be used with the presigned url.
85
+ #
86
+ # @example
87
+ # signer = Aws::S3::Presigner.new
88
+ # url, headers = signer.presigned_request(
89
+ # :get_object, bucket: "bucket", key: "key"
90
+ # )
91
+ #
25
92
  # @param [Symbol] method Symbolized method name of the operation you want
26
93
  # to presign.
27
94
  #
@@ -31,6 +98,9 @@ module Aws
31
98
  # attempts to set this value to greater than one week (604800) will
32
99
  # raise an exception.
33
100
  #
101
+ # @option params [Time] :time (Time.now) The starting time for when the
102
+ # presigned url becomes active.
103
+ #
34
104
  # @option params [Boolean] :secure (true) When `false`, a HTTP URL
35
105
  # is returned instead of the default HTTPS URL.
36
106
  #
@@ -38,29 +108,51 @@ module Aws
38
108
  # bucket name will be used as the hostname. This will cause
39
109
  # the returned URL to be 'http' and not 'https'.
40
110
  #
41
- # @option params [Boolean] :use_accelerate_endpoint (false) When `true`, Presigner
42
- # will attempt to use accelerated endpoint
111
+ # @option params [Boolean] :use_accelerate_endpoint (false) When `true`,
112
+ # Presigner will attempt to use accelerated endpoint.
113
+ #
114
+ # @option params [Array<String>] :whitelist_headers ([]) Additional
115
+ # headers to be included for the signed request. Certain headers beyond
116
+ # the authorization header could, in theory, be changed for various
117
+ # reasons (including but not limited to proxies) while in transit and
118
+ # after signing. This would lead to signature errors being returned,
119
+ # despite no actual problems with signing. (see BLACKLISTED_HEADERS)
43
120
  #
44
121
  # @raise [ArgumentError] Raises an ArgumentError if `:expires_in`
45
122
  # exceeds one week.
46
123
  #
47
- def presigned_url(method, params = {})
48
- if params[:key].nil? or params[:key] == ''
49
- raise ArgumentError, ":key must not be blank"
50
- end
51
- virtual_host = !!params.delete(:virtual_host)
52
- scheme = http_scheme(params, virtual_host)
124
+ # @return [String, Hash] A tuple with a presigned URL and headers that
125
+ # should be included with the request.
126
+ def presigned_request(method, params = {})
127
+ _presigned_request(method, params, false)
128
+ end
129
+
130
+ private
131
+
132
+ def _presigned_request(method, params, hoist = true)
133
+ virtual_host = params.delete(:virtual_host)
134
+ time = params.delete(:time)
135
+ unsigned_headers = unsigned_headers(params)
136
+ scheme = http_scheme(params)
137
+ expires_in = expires_in(params)
53
138
 
54
139
  req = @client.build_request(method, params)
55
140
  use_bucket_as_hostname(req) if virtual_host
56
- sign_but_dont_send(req, expires_in(params), scheme)
57
- req.send_request.data
141
+ handle_presigned_url_context(req)
142
+
143
+ x_amz_headers = sign_but_dont_send(
144
+ req, expires_in, scheme, time, unsigned_headers, hoist
145
+ )
146
+ [req.send_request.data, x_amz_headers]
58
147
  end
59
148
 
60
- private
149
+ def unsigned_headers(params)
150
+ whitelist_headers = params.delete(:whitelist_headers) || []
151
+ BLACKLISTED_HEADERS - whitelist_headers
152
+ end
61
153
 
62
- def http_scheme(params, virtual_host)
63
- if params.delete(:secure) == false || virtual_host
154
+ def http_scheme(params)
155
+ if params.delete(:secure) == false
64
156
  'http'
65
157
  else
66
158
  @client.config.endpoint.scheme
@@ -68,10 +160,13 @@ module Aws
68
160
  end
69
161
 
70
162
  def expires_in(params)
71
- if expires_in = params.delete(:expires_in)
163
+ if (expires_in = params.delete(:expires_in))
72
164
  if expires_in > ONE_WEEK
73
- msg = "expires_in value of #{expires_in} exceeds one-week maximum"
74
- raise ArgumentError, msg
165
+ raise ArgumentError,
166
+ "expires_in value of #{expires_in} exceeds one-week maximum."
167
+ elsif expires_in <= 0
168
+ raise ArgumentError,
169
+ "expires_in value of #{expires_in} cannot be 0 or less."
75
170
  end
76
171
  expires_in
77
172
  else
@@ -85,14 +180,26 @@ module Aws
85
180
  uri = context.http_request.endpoint
86
181
  uri.host = context.params[:bucket]
87
182
  uri.path.sub!("/#{context.params[:bucket]}", '')
88
- uri.scheme = 'http'
89
- uri.port = 80
183
+ @handler.call(context)
184
+ end
185
+ end
186
+
187
+ # Used for excluding presigned_urls from API request count.
188
+ #
189
+ # Store context information as early as possible, to allow
190
+ # handlers to perform decisions based on this flag if need.
191
+ def handle_presigned_url_context(req)
192
+ req.handle(step: :initialize, priority: 98) do |context|
193
+ context[:presigned_url] = true
90
194
  @handler.call(context)
91
195
  end
92
196
  end
93
197
 
94
198
  # @param [Seahorse::Client::Request] req
95
- def sign_but_dont_send(req, expires_in, scheme)
199
+ def sign_but_dont_send(
200
+ req, expires_in, scheme, time, unsigned_headers, hoist = true
201
+ )
202
+ x_amz_headers = {}
96
203
 
97
204
  http_req = req.context.http_request
98
205
 
@@ -100,11 +207,7 @@ module Aws
100
207
  req.handlers.remove(Aws::S3::Plugins::S3Signer::V4Handler)
101
208
  req.handlers.remove(Seahorse::Client::Plugins::ContentLength::Handler)
102
209
 
103
- signer = build_signer(req.context.config)
104
- req.context[:presigned_url] = true
105
-
106
210
  req.handle(step: :send) do |context|
107
-
108
211
  if scheme != http_req.endpoint.scheme
109
212
  endpoint = http_req.endpoint.dup
110
213
  endpoint.scheme = scheme
@@ -112,57 +215,56 @@ module Aws
112
215
  http_req.endpoint = URI.parse(endpoint.to_s)
113
216
  end
114
217
 
115
- # hoist x-amz-* headers to the querystring
116
218
  query = http_req.endpoint.query ? http_req.endpoint.query.split('&') : []
117
- http_req.headers.keys.each do |key|
118
- if key.match(/^x-amz/i)
119
- value = Aws::Sigv4::Signer.uri_escape(http_req.headers.delete(key))
219
+ http_req.headers.each do |key, value|
220
+ next unless key =~ /^x-amz/i
221
+
222
+ if hoist
223
+ value = Aws::Sigv4::Signer.uri_escape(value)
120
224
  key = Aws::Sigv4::Signer.uri_escape(key)
225
+ # hoist x-amz-* headers to the querystring
226
+ http_req.headers.delete(key)
121
227
  query << "#{key}=#{value}"
228
+ else
229
+ x_amz_headers[key] = value
122
230
  end
123
231
  end
124
232
  http_req.endpoint.query = query.join('&') unless query.empty?
125
233
 
234
+ signing_algorithm = :sigv4
235
+
236
+ # If it's an ARN, get the resolved region and service
237
+ if (arn = context.metadata[:s3_arn])
238
+ region = arn[:resolved_region]
239
+ service = arn[:arn].service
240
+ region = arn[:arn].is_a?(MultiRegionAccessPointARN) ? '*': arn[:resolved_region]
241
+ signing_algorithm = arn[:arn].is_a?(MultiRegionAccessPointARN) ? :sigv4a : :sigv4
242
+ end
243
+
244
+ signer = Aws::Sigv4::Signer.new(
245
+ service: service || 's3',
246
+ region: region || context.config.region,
247
+ signing_algorithm: signing_algorithm,
248
+ credentials_provider: context.config.credentials,
249
+ unsigned_headers: unsigned_headers,
250
+ apply_checksum_header: false,
251
+ uri_escape_path: false
252
+ )
253
+
126
254
  url = signer.presign_url(
127
255
  http_method: http_req.http_method,
128
256
  url: http_req.endpoint,
129
257
  headers: http_req.headers,
130
258
  body_digest: 'UNSIGNED-PAYLOAD',
131
- expires_in: expires_in
259
+ expires_in: expires_in,
260
+ time: time
132
261
  ).to_s
133
262
 
134
263
  Seahorse::Client::Response.new(context: context, data: url)
135
264
  end
265
+ # Return the headers
266
+ x_amz_headers
136
267
  end
137
-
138
- def build_signer(cfg)
139
- Aws::Sigv4::Signer.new(
140
- service: 's3',
141
- region: cfg.region,
142
- credentials_provider: cfg.credentials,
143
- unsigned_headers: [
144
- 'cache-control',
145
- 'content-length', # due to a ELB bug
146
- 'expect',
147
- 'max-forwards',
148
- 'pragma',
149
- 'te',
150
- 'if-match',
151
- 'if-none-match',
152
- 'if-modified-since',
153
- 'if-unmodified-since',
154
- 'if-range',
155
- 'accept',
156
- 'proxy-authorization',
157
- 'from',
158
- 'referer',
159
- 'user-agent',
160
- 'x-amzn-trace-id'
161
- ],
162
- uri_escape_path: false
163
- )
164
- end
165
-
166
268
  end
167
269
  end
168
270
  end