aws-sdk-s3 1.0.0.rc1
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.
- checksums.yaml +7 -0
- data/lib/aws-sdk-s3.rb +66 -0
- data/lib/aws-sdk-s3/bucket.rb +595 -0
- data/lib/aws-sdk-s3/bucket_acl.rb +168 -0
- data/lib/aws-sdk-s3/bucket_cors.rb +146 -0
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +164 -0
- data/lib/aws-sdk-s3/bucket_logging.rb +142 -0
- data/lib/aws-sdk-s3/bucket_notification.rb +187 -0
- data/lib/aws-sdk-s3/bucket_policy.rb +138 -0
- data/lib/aws-sdk-s3/bucket_region_cache.rb +79 -0
- data/lib/aws-sdk-s3/bucket_request_payment.rb +128 -0
- data/lib/aws-sdk-s3/bucket_tagging.rb +143 -0
- data/lib/aws-sdk-s3/bucket_versioning.rb +188 -0
- data/lib/aws-sdk-s3/bucket_website.rb +177 -0
- data/lib/aws-sdk-s3/client.rb +3171 -0
- data/lib/aws-sdk-s3/client_api.rb +1991 -0
- data/lib/aws-sdk-s3/customizations.rb +29 -0
- data/lib/aws-sdk-s3/customizations/bucket.rb +127 -0
- data/lib/aws-sdk-s3/customizations/multipart_upload.rb +42 -0
- data/lib/aws-sdk-s3/customizations/object.rb +257 -0
- data/lib/aws-sdk-s3/customizations/object_summary.rb +65 -0
- data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +11 -0
- data/lib/aws-sdk-s3/encryption.rb +19 -0
- data/lib/aws-sdk-s3/encryption/client.rb +369 -0
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +178 -0
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +63 -0
- data/lib/aws-sdk-s3/encryption/default_key_provider.rb +38 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +50 -0
- data/lib/aws-sdk-s3/encryption/errors.rb +13 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +50 -0
- data/lib/aws-sdk-s3/encryption/io_decrypter.rb +29 -0
- data/lib/aws-sdk-s3/encryption/io_encrypter.rb +69 -0
- data/lib/aws-sdk-s3/encryption/key_provider.rb +29 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +71 -0
- data/lib/aws-sdk-s3/encryption/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryption/utils.rb +79 -0
- data/lib/aws-sdk-s3/errors.rb +23 -0
- data/lib/aws-sdk-s3/file_part.rb +75 -0
- data/lib/aws-sdk-s3/file_uploader.rb +58 -0
- data/lib/aws-sdk-s3/legacy_signer.rb +186 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +187 -0
- data/lib/aws-sdk-s3/multipart_upload.rb +287 -0
- data/lib/aws-sdk-s3/multipart_upload_error.rb +16 -0
- data/lib/aws-sdk-s3/multipart_upload_part.rb +314 -0
- data/lib/aws-sdk-s3/object.rb +942 -0
- data/lib/aws-sdk-s3/object_acl.rb +214 -0
- data/lib/aws-sdk-s3/object_copier.rb +99 -0
- data/lib/aws-sdk-s3/object_multipart_copier.rb +179 -0
- data/lib/aws-sdk-s3/object_summary.rb +794 -0
- data/lib/aws-sdk-s3/object_version.rb +406 -0
- data/lib/aws-sdk-s3/plugins/accelerate.rb +92 -0
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +89 -0
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +23 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +70 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +29 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +23 -0
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +47 -0
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +33 -0
- data/lib/aws-sdk-s3/plugins/md5s.rb +79 -0
- data/lib/aws-sdk-s3/plugins/redirects.rb +41 -0
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +208 -0
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +68 -0
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +94 -0
- data/lib/aws-sdk-s3/presigned_post.rb +647 -0
- data/lib/aws-sdk-s3/presigner.rb +160 -0
- data/lib/aws-sdk-s3/resource.rb +96 -0
- data/lib/aws-sdk-s3/types.rb +5750 -0
- data/lib/aws-sdk-s3/waiters.rb +178 -0
- metadata +154 -0
@@ -0,0 +1,29 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
module Encryption
|
4
|
+
|
5
|
+
# This module defines the interface required for a {Client#key_provider}.
|
6
|
+
# A key provider is any object that:
|
7
|
+
#
|
8
|
+
# * Responds to {#encryption_materials} with an {Materials} object.
|
9
|
+
#
|
10
|
+
# * Responds to {#key_for}, receiving a JSON document String,
|
11
|
+
# returning an encryption key. The returned encryption key
|
12
|
+
# must be one of:
|
13
|
+
#
|
14
|
+
# * `OpenSSL::PKey::RSA` - for asymmetric encryption
|
15
|
+
# * `String` - 32, 24, or 16 bytes long, for symmetric encryption
|
16
|
+
#
|
17
|
+
module KeyProvider
|
18
|
+
|
19
|
+
# @return [Materials]
|
20
|
+
def encryption_materials; end
|
21
|
+
|
22
|
+
# @param [String<JSON>] materials_description
|
23
|
+
# @return [OpenSSL::PKey::RSA, String] encryption_key
|
24
|
+
def key_for(materials_description); end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
module Encryption
|
6
|
+
# @api private
|
7
|
+
class KmsCipherProvider
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@kms_key_id = options[:kms_key_id]
|
11
|
+
@kms_client = options[:kms_client]
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Array<Hash,Cipher>] Creates an returns a new encryption
|
15
|
+
# envelope and encryption cipher.
|
16
|
+
def encryption_cipher
|
17
|
+
encryption_context = { "kms_cmk_id" => @kms_key_id }
|
18
|
+
key_data = @kms_client.generate_data_key(
|
19
|
+
key_id: @kms_key_id,
|
20
|
+
encryption_context: encryption_context,
|
21
|
+
key_spec: 'AES_256',
|
22
|
+
)
|
23
|
+
cipher = Utils.aes_encryption_cipher(:CBC)
|
24
|
+
cipher.key = key_data.plaintext
|
25
|
+
envelope = {
|
26
|
+
'x-amz-key-v2' => encode64(key_data.ciphertext_blob),
|
27
|
+
'x-amz-iv' => encode64(cipher.iv = cipher.random_iv),
|
28
|
+
'x-amz-cek-alg' => 'AES/CBC/PKCS5Padding',
|
29
|
+
'x-amz-wrap-alg' => 'kms',
|
30
|
+
'x-amz-matdesc' => Json.dump(encryption_context)
|
31
|
+
}
|
32
|
+
[envelope, cipher]
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Cipher] Given an encryption envelope, returns a
|
36
|
+
# decryption cipher.
|
37
|
+
def decryption_cipher(envelope)
|
38
|
+
encryption_context = Json.load(envelope['x-amz-matdesc'])
|
39
|
+
key = @kms_client.decrypt(
|
40
|
+
ciphertext_blob: decode64(envelope['x-amz-key-v2']),
|
41
|
+
encryption_context: encryption_context,
|
42
|
+
).plaintext
|
43
|
+
iv = decode64(envelope['x-amz-iv'])
|
44
|
+
block_mode =
|
45
|
+
case envelope['x-amz-cek-alg']
|
46
|
+
when 'AES/CBC/PKCS5Padding'
|
47
|
+
:CBC
|
48
|
+
when 'AES/GCM/NoPadding'
|
49
|
+
:GCM
|
50
|
+
else
|
51
|
+
type = envelope['x-amz-cek-alg'].inspect
|
52
|
+
msg = "unsupported content encrypting key (cek) format: #{type}"
|
53
|
+
raise Errors::DecryptionError, msg
|
54
|
+
end
|
55
|
+
Utils.aes_decryption_cipher(block_mode, key, iv)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def encode64(str)
|
61
|
+
Base64.encode64(str).split("\n") * ""
|
62
|
+
end
|
63
|
+
|
64
|
+
def decode64(str)
|
65
|
+
Base64.decode64(str)
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
module Encryption
|
6
|
+
class Materials
|
7
|
+
|
8
|
+
# @option options [required, OpenSSL::PKey::RSA, String] :key
|
9
|
+
# The master key to use for encrypting/decrypting all objects.
|
10
|
+
#
|
11
|
+
# @option options [String<JSON>] :description ('{}')
|
12
|
+
# The encryption materials description. This is must be
|
13
|
+
# a JSON document string.
|
14
|
+
#
|
15
|
+
def initialize(options = {})
|
16
|
+
@key = validate_key(options[:key])
|
17
|
+
@description = validate_desc(options[:description])
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [OpenSSL::PKey::RSA, String]
|
21
|
+
attr_reader :key
|
22
|
+
|
23
|
+
# @return [String<JSON>]
|
24
|
+
attr_reader :description
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def validate_key(key)
|
29
|
+
case key
|
30
|
+
when OpenSSL::PKey::RSA then key
|
31
|
+
when String
|
32
|
+
if [32, 24, 16].include?(key.bytesize)
|
33
|
+
key
|
34
|
+
else
|
35
|
+
msg = "invalid key, symmetric key required to be 16, 24, or "
|
36
|
+
msg << "32 bytes in length, saw length " + key.bytesize.to_s
|
37
|
+
raise ArgumentError, msg
|
38
|
+
end
|
39
|
+
else
|
40
|
+
msg = "invalid encryption key, expected an OpenSSL::PKey::RSA key "
|
41
|
+
msg << "(for asymmetric encryption) or a String (for symmetric "
|
42
|
+
msg << "encryption)."
|
43
|
+
raise ArgumentError, msg
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_desc(description)
|
48
|
+
Json.load(description)
|
49
|
+
description
|
50
|
+
rescue Json::ParseError
|
51
|
+
msg = "expected description to be a valid JSON document string"
|
52
|
+
raise ArgumentError, msg
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
module Encryption
|
6
|
+
# @api private
|
7
|
+
module Utils
|
8
|
+
|
9
|
+
UNSAFE_MSG = "unsafe encryption, data is longer than key length"
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def encrypt(key, data)
|
14
|
+
case key
|
15
|
+
when OpenSSL::PKey::RSA # asymmetric encryption
|
16
|
+
warn(UNSAFE_MSG) if key.public_key.n.num_bits < cipher_size(data)
|
17
|
+
key.public_encrypt(data)
|
18
|
+
when String # symmetric encryption
|
19
|
+
warn(UNSAFE_MSG) if cipher_size(key) < cipher_size(data)
|
20
|
+
cipher = aes_encryption_cipher(:ECB, key)
|
21
|
+
cipher.update(data) + cipher.final
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def decrypt(key, data)
|
26
|
+
begin
|
27
|
+
case key
|
28
|
+
when OpenSSL::PKey::RSA # asymmetric decryption
|
29
|
+
key.private_decrypt(data)
|
30
|
+
when String # symmetric Decryption
|
31
|
+
cipher = aes_cipher(:decrypt, :ECB, key, nil)
|
32
|
+
cipher.update(data) + cipher.final
|
33
|
+
end
|
34
|
+
rescue OpenSSL::Cipher::CipherError
|
35
|
+
msg = 'decryption failed, possible incorrect key'
|
36
|
+
raise Errors::DecryptionError, msg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# @param [String] block_mode "CBC" or "ECB"
|
41
|
+
# @param [OpenSSL::PKey::RSA, String, nil] key
|
42
|
+
# @param [String, nil] iv The initialization vector
|
43
|
+
def aes_encryption_cipher(block_mode, key = nil, iv = nil)
|
44
|
+
aes_cipher(:encrypt, block_mode, key, iv)
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [String] block_mode "CBC" or "ECB"
|
48
|
+
# @param [OpenSSL::PKey::RSA, String, nil] key
|
49
|
+
# @param [String, nil] iv The initialization vector
|
50
|
+
def aes_decryption_cipher(block_mode, key = nil, iv = nil)
|
51
|
+
aes_cipher(:decrypt, block_mode, key, iv)
|
52
|
+
end
|
53
|
+
|
54
|
+
# @param [String] mode "encrypt" or "decrypt"
|
55
|
+
# @param [String] block_mode "CBC" or "ECB"
|
56
|
+
# @param [OpenSSL::PKey::RSA, String, nil] key
|
57
|
+
# @param [String, nil] iv The initialization vector
|
58
|
+
def aes_cipher(mode, block_mode, key, iv)
|
59
|
+
cipher = key ?
|
60
|
+
OpenSSL::Cipher.new("aes-#{cipher_size(key)}-#{block_mode.downcase}") :
|
61
|
+
OpenSSL::Cipher.new("aes-256-#{block_mode.downcase}")
|
62
|
+
cipher.send(mode) # encrypt or decrypt
|
63
|
+
cipher.key = key if key
|
64
|
+
cipher.iv = iv if iv
|
65
|
+
cipher
|
66
|
+
end
|
67
|
+
|
68
|
+
# @param [String] key
|
69
|
+
# @return [Integer]
|
70
|
+
# @raise ArgumentError
|
71
|
+
def cipher_size(key)
|
72
|
+
key.bytesize * 8
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# WARNING ABOUT GENERATED CODE
|
2
|
+
#
|
3
|
+
# This file is generated. See the contributing for info on making contributions:
|
4
|
+
# https://github.com/aws/aws-sdk-ruby/blob/master/CONTRIBUTING.md
|
5
|
+
#
|
6
|
+
# WARNING ABOUT GENERATED CODE
|
7
|
+
|
8
|
+
module Aws
|
9
|
+
module S3
|
10
|
+
module Errors
|
11
|
+
|
12
|
+
extend Aws::Errors::DynamicErrors
|
13
|
+
|
14
|
+
# Raised when calling #load or #data on a resource class that can not be
|
15
|
+
# loaded. This can happen when:
|
16
|
+
#
|
17
|
+
# * A resource class has identifiers, but no data attributes.
|
18
|
+
# * Resource data is only available when making an API call that
|
19
|
+
# enumerates all resources of that type.
|
20
|
+
class ResourceNotLoadable < RuntimeError; end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Aws
|
2
|
+
module S3
|
3
|
+
|
4
|
+
# A utility class that provides an IO-like interface to a portion of
|
5
|
+
# a file on disk.
|
6
|
+
# @api private
|
7
|
+
class FilePart
|
8
|
+
|
9
|
+
# @option options [required,String,Pathname,File,Tempfile] :source
|
10
|
+
# @option options [required,Integer] :offset The file part will read
|
11
|
+
# starting at this byte offset.
|
12
|
+
# @option options [required,Integer] :size The maximum number of bytes to
|
13
|
+
# read from the `:offset`.
|
14
|
+
def initialize(options = {})
|
15
|
+
@source = options[:source]
|
16
|
+
@first_byte = options[:offset]
|
17
|
+
@last_byte = @first_byte + options[:size]
|
18
|
+
@size = options[:size]
|
19
|
+
@file = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String,Pathname,File,Tempfile]
|
23
|
+
attr_reader :source
|
24
|
+
|
25
|
+
# @return [Integer]
|
26
|
+
attr_reader :first_byte
|
27
|
+
|
28
|
+
# @return [Integer]
|
29
|
+
attr_reader :last_byte
|
30
|
+
|
31
|
+
# @return [Integer]
|
32
|
+
attr_reader :size
|
33
|
+
|
34
|
+
def read(bytes = nil, output_buffer = nil)
|
35
|
+
open_file unless @file
|
36
|
+
read_from_file(bytes, output_buffer)
|
37
|
+
end
|
38
|
+
|
39
|
+
def rewind
|
40
|
+
if @file
|
41
|
+
@file.seek(@first_byte)
|
42
|
+
@position = @first_byte
|
43
|
+
end
|
44
|
+
0
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
@file.close if @file
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def open_file
|
54
|
+
@file = File.open(@source, 'rb')
|
55
|
+
rewind
|
56
|
+
end
|
57
|
+
|
58
|
+
def read_from_file(bytes, output_buffer)
|
59
|
+
if bytes
|
60
|
+
data = @file.read([remaining_bytes, bytes].min)
|
61
|
+
data = nil if data == ''
|
62
|
+
else
|
63
|
+
data = @file.read(remaining_bytes)
|
64
|
+
end
|
65
|
+
@position += data ? data.bytesize : 0
|
66
|
+
output_buffer ? output_buffer.replace(data || '') : data
|
67
|
+
end
|
68
|
+
|
69
|
+
def remaining_bytes
|
70
|
+
@last_byte - @position
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
# @api private
|
6
|
+
class FileUploader
|
7
|
+
|
8
|
+
FIFTEEN_MEGABYTES = 15 * 1024 * 1024
|
9
|
+
|
10
|
+
# @option options [Client] :client
|
11
|
+
# @option options [Integer] :multipart_threshold Files greater than
|
12
|
+
# `:multipart_threshold` bytes are uploaded using S3 multipart APIs.
|
13
|
+
def initialize(options = {})
|
14
|
+
@options = options
|
15
|
+
@client = options[:client] || Client.new
|
16
|
+
@multipart_threshold = options[:multipart_threshold] || FIFTEEN_MEGABYTES
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Client]
|
20
|
+
attr_reader :client
|
21
|
+
|
22
|
+
# @return [Integer] Files larger than this in bytes are uploaded
|
23
|
+
# using a {MultipartFileUploader}.
|
24
|
+
attr_reader :multipart_threshold
|
25
|
+
|
26
|
+
# @param [String,Pathname,File,Tempfile] source
|
27
|
+
# @option options [required,String] :bucket
|
28
|
+
# @option options [required,String] :key
|
29
|
+
# @return [void]
|
30
|
+
def upload(source, options = {})
|
31
|
+
if File.size(source) >= multipart_threshold
|
32
|
+
MultipartFileUploader.new(@options).upload(source, options)
|
33
|
+
else
|
34
|
+
put_object(source, options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def put_object(source, options)
|
41
|
+
open_file(source) do |file|
|
42
|
+
@client.put_object(options.merge(body: file))
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def open_file(source)
|
47
|
+
if String === source || Pathname === source
|
48
|
+
file = File.open(source, 'rb')
|
49
|
+
yield(file)
|
50
|
+
file.close
|
51
|
+
else
|
52
|
+
yield(source)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'time'
|
3
|
+
require 'openssl'
|
4
|
+
require 'webrick/httputils'
|
5
|
+
require 'aws-sdk-core/query'
|
6
|
+
|
7
|
+
module Aws
|
8
|
+
module S3
|
9
|
+
# @api private
|
10
|
+
class LegacySigner
|
11
|
+
|
12
|
+
SIGNED_QUERYSTRING_PARAMS = Set.new(%w(
|
13
|
+
|
14
|
+
acl delete cors lifecycle location logging notification partNumber
|
15
|
+
policy requestPayment restore tagging torrent uploadId uploads
|
16
|
+
versionId versioning versions website replication requestPayment
|
17
|
+
accelerate
|
18
|
+
|
19
|
+
response-content-type response-content-language
|
20
|
+
response-expires response-cache-control
|
21
|
+
response-content-disposition response-content-encoding
|
22
|
+
|
23
|
+
))
|
24
|
+
|
25
|
+
def self.sign(context)
|
26
|
+
new(
|
27
|
+
context.config.credentials,
|
28
|
+
context.params,
|
29
|
+
context.config.force_path_style
|
30
|
+
).sign(context.http_request)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [CredentialProvider] credentials
|
34
|
+
def initialize(credentials, params, force_path_style)
|
35
|
+
@credentials = credentials.credentials
|
36
|
+
@params = Query::ParamList.new
|
37
|
+
params.each_pair do |param_name, param_value|
|
38
|
+
@params.set(param_name, param_value)
|
39
|
+
end
|
40
|
+
@force_path_style = force_path_style
|
41
|
+
end
|
42
|
+
|
43
|
+
attr_reader :credentials, :params
|
44
|
+
|
45
|
+
def sign(request)
|
46
|
+
if token = credentials.session_token
|
47
|
+
request.headers["X-Amz-Security-Token"] = token
|
48
|
+
end
|
49
|
+
request.headers['Authorization'] = authorization(request)
|
50
|
+
end
|
51
|
+
|
52
|
+
def authorization(request)
|
53
|
+
"AWS #{credentials.access_key_id}:#{signature(request)}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def signature(request)
|
57
|
+
string_to_sign = string_to_sign(request)
|
58
|
+
signature = digest(credentials.secret_access_key, string_to_sign)
|
59
|
+
uri_escape(signature)
|
60
|
+
end
|
61
|
+
|
62
|
+
def digest(secret, string_to_sign)
|
63
|
+
Base64.encode64(hmac(secret, string_to_sign)).strip
|
64
|
+
end
|
65
|
+
|
66
|
+
def hmac(key, value)
|
67
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), key, value)
|
68
|
+
end
|
69
|
+
|
70
|
+
# From the S3 developer guide:
|
71
|
+
#
|
72
|
+
# StringToSign =
|
73
|
+
# HTTP-Verb ` "\n" `
|
74
|
+
# content-md5 ` "\n" `
|
75
|
+
# content-type ` "\n" `
|
76
|
+
# date ` "\n" `
|
77
|
+
# CanonicalizedAmzHeaders + CanonicalizedResource;
|
78
|
+
#
|
79
|
+
def string_to_sign(request)
|
80
|
+
[
|
81
|
+
request.http_method,
|
82
|
+
request.headers.values_at('Content-Md5', 'Content-Type').join("\n"),
|
83
|
+
signing_string_date(request),
|
84
|
+
canonicalized_headers(request),
|
85
|
+
canonicalized_resource(request.endpoint),
|
86
|
+
].flatten.compact.join("\n")
|
87
|
+
end
|
88
|
+
|
89
|
+
def signing_string_date(request)
|
90
|
+
# if a date is provided via x-amz-date then we should omit the
|
91
|
+
# Date header from the signing string (should appear as a blank line)
|
92
|
+
if request.headers.detect{|k,v| k.to_s =~ /^x-amz-date$/i }
|
93
|
+
''
|
94
|
+
else
|
95
|
+
request.headers['Date'] = Time.now.httpdate
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# CanonicalizedAmzHeaders
|
100
|
+
#
|
101
|
+
# See the developer guide for more information on how this element
|
102
|
+
# is generated.
|
103
|
+
#
|
104
|
+
def canonicalized_headers(request)
|
105
|
+
x_amz = request.headers.select{|k, v| k =~ /^x-amz-/i }
|
106
|
+
x_amz = x_amz.collect{|k, v| [k.downcase, v] }
|
107
|
+
x_amz = x_amz.sort_by{|k, v| k }
|
108
|
+
x_amz = x_amz.collect{|k, v| "#{k}:#{v.to_s.strip}" }.join("\n")
|
109
|
+
x_amz == '' ? nil : x_amz
|
110
|
+
end
|
111
|
+
|
112
|
+
# From the S3 developer guide
|
113
|
+
#
|
114
|
+
# CanonicalizedResource =
|
115
|
+
# [ "/" ` Bucket ] `
|
116
|
+
# <HTTP-Request-URI, protocol name up to the querystring> +
|
117
|
+
# [ sub-resource, if present. e.g. "?acl", "?location",
|
118
|
+
# "?logging", or "?torrent"];
|
119
|
+
#
|
120
|
+
# @api private
|
121
|
+
def canonicalized_resource(endpoint)
|
122
|
+
|
123
|
+
parts = []
|
124
|
+
|
125
|
+
# virtual hosted-style requests require the hostname to appear
|
126
|
+
# in the canonicalized resource prefixed by a forward slash.
|
127
|
+
if bucket = params[:bucket]
|
128
|
+
bucket = bucket.value
|
129
|
+
ssl = endpoint.scheme == 'https'
|
130
|
+
if Plugins::BucketDns.dns_compatible?(bucket, ssl) && !@force_path_style
|
131
|
+
parts << "/#{bucket}"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# append the path name (no querystring)
|
136
|
+
parts << endpoint.path
|
137
|
+
|
138
|
+
# lastly any sub resource querystring params need to be appened
|
139
|
+
# in lexigraphical ordered joined by '&' and prefixed by '?'
|
140
|
+
params = signed_querystring_params(endpoint)
|
141
|
+
|
142
|
+
unless params.empty?
|
143
|
+
parts << '?'
|
144
|
+
parts << params.sort.collect{|p| p.to_s }.join('&')
|
145
|
+
end
|
146
|
+
|
147
|
+
parts.join
|
148
|
+
end
|
149
|
+
|
150
|
+
def signed_querystring_params(endpoint)
|
151
|
+
endpoint.query.to_s.split('&').select do |p|
|
152
|
+
SIGNED_QUERYSTRING_PARAMS.include?(p.split('=')[0])
|
153
|
+
end.map { |p| CGI.unescape(p) }
|
154
|
+
end
|
155
|
+
|
156
|
+
def uri_escape(s)
|
157
|
+
|
158
|
+
#URI.escape(s)
|
159
|
+
|
160
|
+
# URI.escape is deprecated, replacing it with escape from webrick
|
161
|
+
# to squelch the massive number of warnings generated from Ruby.
|
162
|
+
# The following script was used to determine the differences
|
163
|
+
# between the various escape methods available. The webrick
|
164
|
+
# escape only had two differences and it is available in the
|
165
|
+
# standard lib.
|
166
|
+
#
|
167
|
+
# (0..255).each {|c|
|
168
|
+
# s = [c].pack("C")
|
169
|
+
# e = [
|
170
|
+
# CGI.escape(s),
|
171
|
+
# ERB::Util.url_encode(s),
|
172
|
+
# URI.encode_www_form_component(s),
|
173
|
+
# WEBrick::HTTPUtils.escape_form(s),
|
174
|
+
# WEBrick::HTTPUtils.escape(s),
|
175
|
+
# URI.escape(s),
|
176
|
+
# ]
|
177
|
+
# next if e.uniq.length == 1
|
178
|
+
# puts("%5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
|
179
|
+
# }
|
180
|
+
#
|
181
|
+
WEBrick::HTTPUtils.escape(s).gsub('%5B', '[').gsub('%5D', ']')
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|