aws-sdk-s3 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|