mss-sdk 1.0.0
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/.yardopts +9 -0
- data/LICENSE.txt +0 -0
- data/README.md +192 -0
- data/bin/mss-rb +178 -0
- data/ca-bundle.crt +3554 -0
- data/lib/mss/core/async_handle.rb +89 -0
- data/lib/mss/core/cacheable.rb +76 -0
- data/lib/mss/core/client.rb +786 -0
- data/lib/mss/core/collection/simple.rb +81 -0
- data/lib/mss/core/collection/with_limit_and_next_token.rb +70 -0
- data/lib/mss/core/collection/with_next_token.rb +96 -0
- data/lib/mss/core/collection.rb +262 -0
- data/lib/mss/core/configuration.rb +527 -0
- data/lib/mss/core/credential_providers.rb +653 -0
- data/lib/mss/core/data.rb +251 -0
- data/lib/mss/core/deprecations.rb +83 -0
- data/lib/mss/core/endpoints.rb +36 -0
- data/lib/mss/core/http/connection_pool.rb +374 -0
- data/lib/mss/core/http/curb_handler.rb +150 -0
- data/lib/mss/core/http/handler.rb +88 -0
- data/lib/mss/core/http/net_http_handler.rb +144 -0
- data/lib/mss/core/http/patch.rb +98 -0
- data/lib/mss/core/http/request.rb +258 -0
- data/lib/mss/core/http/response.rb +80 -0
- data/lib/mss/core/indifferent_hash.rb +87 -0
- data/lib/mss/core/inflection.rb +55 -0
- data/lib/mss/core/ini_parser.rb +41 -0
- data/lib/mss/core/json_client.rb +46 -0
- data/lib/mss/core/json_parser.rb +75 -0
- data/lib/mss/core/json_request_builder.rb +34 -0
- data/lib/mss/core/json_response_parser.rb +78 -0
- data/lib/mss/core/lazy_error_classes.rb +107 -0
- data/lib/mss/core/log_formatter.rb +426 -0
- data/lib/mss/core/managed_file.rb +31 -0
- data/lib/mss/core/meta_utils.rb +44 -0
- data/lib/mss/core/model.rb +61 -0
- data/lib/mss/core/naming.rb +29 -0
- data/lib/mss/core/option_grammar.rb +737 -0
- data/lib/mss/core/options/json_serializer.rb +81 -0
- data/lib/mss/core/options/validator.rb +154 -0
- data/lib/mss/core/options/xml_serializer.rb +117 -0
- data/lib/mss/core/page_result.rb +74 -0
- data/lib/mss/core/policy.rb +938 -0
- data/lib/mss/core/query_client.rb +40 -0
- data/lib/mss/core/query_error_parser.rb +23 -0
- data/lib/mss/core/query_request_builder.rb +46 -0
- data/lib/mss/core/query_response_parser.rb +34 -0
- data/lib/mss/core/region.rb +84 -0
- data/lib/mss/core/region_collection.rb +79 -0
- data/lib/mss/core/resource.rb +412 -0
- data/lib/mss/core/resource_cache.rb +39 -0
- data/lib/mss/core/response.rb +214 -0
- data/lib/mss/core/response_cache.rb +49 -0
- data/lib/mss/core/rest_error_parser.rb +23 -0
- data/lib/mss/core/rest_json_client.rb +39 -0
- data/lib/mss/core/rest_request_builder.rb +153 -0
- data/lib/mss/core/rest_response_parser.rb +65 -0
- data/lib/mss/core/rest_xml_client.rb +46 -0
- data/lib/mss/core/service_interface.rb +82 -0
- data/lib/mss/core/signers/base.rb +45 -0
- data/lib/mss/core/signers/cloud_front.rb +55 -0
- data/lib/mss/core/signers/s3.rb +158 -0
- data/lib/mss/core/signers/version_2.rb +71 -0
- data/lib/mss/core/signers/version_3.rb +85 -0
- data/lib/mss/core/signers/version_3_https.rb +60 -0
- data/lib/mss/core/signers/version_4/chunk_signed_stream.rb +190 -0
- data/lib/mss/core/signers/version_4.rb +227 -0
- data/lib/mss/core/uri_escape.rb +43 -0
- data/lib/mss/core/xml/frame.rb +245 -0
- data/lib/mss/core/xml/frame_stack.rb +84 -0
- data/lib/mss/core/xml/grammar.rb +306 -0
- data/lib/mss/core/xml/parser.rb +69 -0
- data/lib/mss/core/xml/root_frame.rb +64 -0
- data/lib/mss/core/xml/sax_handlers/libxml.rb +46 -0
- data/lib/mss/core/xml/sax_handlers/nokogiri.rb +55 -0
- data/lib/mss/core/xml/sax_handlers/ox.rb +40 -0
- data/lib/mss/core/xml/sax_handlers/rexml.rb +46 -0
- data/lib/mss/core/xml/stub.rb +122 -0
- data/lib/mss/core.rb +602 -0
- data/lib/mss/errors.rb +161 -0
- data/lib/mss/rails.rb +194 -0
- data/lib/mss/s3/access_control_list.rb +262 -0
- data/lib/mss/s3/acl_object.rb +263 -0
- data/lib/mss/s3/acl_options.rb +200 -0
- data/lib/mss/s3/bucket.rb +757 -0
- data/lib/mss/s3/bucket_collection.rb +161 -0
- data/lib/mss/s3/bucket_lifecycle_configuration.rb +472 -0
- data/lib/mss/s3/bucket_region_cache.rb +51 -0
- data/lib/mss/s3/bucket_tag_collection.rb +110 -0
- data/lib/mss/s3/bucket_version_collection.rb +78 -0
- data/lib/mss/s3/cipher_io.rb +119 -0
- data/lib/mss/s3/client/xml.rb +265 -0
- data/lib/mss/s3/client.rb +2076 -0
- data/lib/mss/s3/config.rb +60 -0
- data/lib/mss/s3/cors_rule.rb +107 -0
- data/lib/mss/s3/cors_rule_collection.rb +193 -0
- data/lib/mss/s3/data_options.rb +190 -0
- data/lib/mss/s3/encryption_utils.rb +145 -0
- data/lib/mss/s3/errors.rb +93 -0
- data/lib/mss/s3/multipart_upload.rb +353 -0
- data/lib/mss/s3/multipart_upload_collection.rb +75 -0
- data/lib/mss/s3/object_collection.rb +355 -0
- data/lib/mss/s3/object_metadata.rb +102 -0
- data/lib/mss/s3/object_upload_collection.rb +76 -0
- data/lib/mss/s3/object_version.rb +153 -0
- data/lib/mss/s3/object_version_collection.rb +88 -0
- data/lib/mss/s3/paginated_collection.rb +74 -0
- data/lib/mss/s3/policy.rb +73 -0
- data/lib/mss/s3/prefix_and_delimiter_collection.rb +46 -0
- data/lib/mss/s3/prefixed_collection.rb +84 -0
- data/lib/mss/s3/presign_v4.rb +135 -0
- data/lib/mss/s3/presigned_post.rb +574 -0
- data/lib/mss/s3/region_detection.rb +75 -0
- data/lib/mss/s3/request.rb +61 -0
- data/lib/mss/s3/s3_object.rb +1795 -0
- data/lib/mss/s3/tree/branch_node.rb +67 -0
- data/lib/mss/s3/tree/child_collection.rb +103 -0
- data/lib/mss/s3/tree/leaf_node.rb +93 -0
- data/lib/mss/s3/tree/node.rb +21 -0
- data/lib/mss/s3/tree/parent.rb +86 -0
- data/lib/mss/s3/tree.rb +115 -0
- data/lib/mss/s3/uploaded_part.rb +81 -0
- data/lib/mss/s3/uploaded_part_collection.rb +83 -0
- data/lib/mss/s3/website_configuration.rb +101 -0
- data/lib/mss/s3.rb +161 -0
- data/lib/mss/version.rb +16 -0
- data/lib/mss-sdk.rb +2 -0
- data/lib/mss.rb +14 -0
- data/rails/init.rb +14 -0
- metadata +201 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# or in the "license" file accompanying this file. This file is
|
9
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
10
|
+
# ANY KIND, either express or implied. See the License for the specific
|
11
|
+
# language governing permissions and limitations under the License.
|
12
|
+
|
13
|
+
MSS::Core::Configuration.module_eval do
|
14
|
+
|
15
|
+
add_service 'S3', 's3', 's3'
|
16
|
+
|
17
|
+
add_option :s3_force_path_style, false, :boolean => true
|
18
|
+
|
19
|
+
add_option :s3_multipart_threshold, 16 * 1024 * 1024
|
20
|
+
|
21
|
+
add_option :s3_multipart_min_part_size, 5 * 1024 * 1024
|
22
|
+
|
23
|
+
add_option :s3_multipart_max_parts, 10000
|
24
|
+
|
25
|
+
add_option :s3_server_side_encryption, nil
|
26
|
+
|
27
|
+
add_option :s3_encryption_key, nil
|
28
|
+
|
29
|
+
add_option :s3_encryption_materials_location, :metadata
|
30
|
+
|
31
|
+
add_option :s3_encryption_matdesc, '{}'
|
32
|
+
|
33
|
+
add_option :s3_storage_class, 'STANDARD'
|
34
|
+
|
35
|
+
add_option :s3_cache_object_attributes, false, :boolean => true
|
36
|
+
|
37
|
+
add_option :s3_signature_version do |config, value|
|
38
|
+
v3_regions = %w(
|
39
|
+
us-east-1
|
40
|
+
us-west-1
|
41
|
+
us-west-2
|
42
|
+
ap-northeast-1
|
43
|
+
ap-southeast-1
|
44
|
+
ap-southeast-2
|
45
|
+
sa-east-1
|
46
|
+
eu-west-1
|
47
|
+
us-gov-west-1
|
48
|
+
)
|
49
|
+
if value
|
50
|
+
value
|
51
|
+
elsif config.s3 && config.s3[:signature_version]
|
52
|
+
config.s3[:signature_version]
|
53
|
+
elsif v3_regions.include?(config.s3_region)
|
54
|
+
:v3
|
55
|
+
else
|
56
|
+
:v4
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# or in the "license" file accompanying this file. This file is
|
9
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
10
|
+
# ANY KIND, either express or implied. See the License for the specific
|
11
|
+
# language governing permissions and limitations under the License.
|
12
|
+
|
13
|
+
module MSS
|
14
|
+
class S3
|
15
|
+
|
16
|
+
# Represents a single CORS rule for an S3 {Bucket}.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
#
|
20
|
+
# rule = bucket.cors.first
|
21
|
+
# rule.allowed_methods #=> ['GET', 'HEAD']
|
22
|
+
# rule.allowed_origins #=> ['*']
|
23
|
+
#
|
24
|
+
# @see CORSRuleCollection
|
25
|
+
class CORSRule
|
26
|
+
|
27
|
+
# @param [Hash] options A hash of the rule details.
|
28
|
+
#
|
29
|
+
# @option options [String] :id A unique identifier for the rule. The ID
|
30
|
+
# value can be up to 255 characters long. The IDs help you find
|
31
|
+
# a rule in the configuration.
|
32
|
+
#
|
33
|
+
# @option options [required,Array<String>] :allowed_methods A list of HTTP
|
34
|
+
# methods that you want to allow the origin to execute.
|
35
|
+
# Each rule must identify at least one method.
|
36
|
+
#
|
37
|
+
# @option options [required,Array<String>] :allowed_origins A list of
|
38
|
+
# origins you want to allow cross-domain requests from. This can
|
39
|
+
# contain at most one * wild character.
|
40
|
+
#
|
41
|
+
# @option options [Array<String>] :allowed_headers A list of headers
|
42
|
+
# allowed in a pre-flight OPTIONS request via the
|
43
|
+
# Access-Control-Request-Headers header. Each header name
|
44
|
+
# specified in the Access-Control-Request-Headers header must
|
45
|
+
# have a corresponding entry in the rule.
|
46
|
+
#
|
47
|
+
# Amazon S3 will send only the allowed headers in a response
|
48
|
+
# that were requested. This can contain at most one '*' wild
|
49
|
+
# character.
|
50
|
+
#
|
51
|
+
# @option options [Array<String>] :max_age_seconds The time in
|
52
|
+
# seconds that your browser is to cache the pre-flight response for
|
53
|
+
# the specified resource.
|
54
|
+
#
|
55
|
+
# @option options [Array<String>] :expose_headers One or more headers in
|
56
|
+
# the response that you want customers to be able to access
|
57
|
+
# from their applications (for example, from a JavaScript
|
58
|
+
# XMLHttpRequest object).
|
59
|
+
#
|
60
|
+
def initialize options = {}
|
61
|
+
@id = options[:id]
|
62
|
+
@allowed_methods = options[:allowed_methods] || []
|
63
|
+
@allowed_origins = options[:allowed_origins] || []
|
64
|
+
@allowed_headers = options[:allowed_headers] || []
|
65
|
+
@max_age_seconds = options[:max_age_seconds]
|
66
|
+
@expose_headers = options[:expose_headers] || []
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [String,nil] A user supplied unique identifier for this role.
|
70
|
+
# Set this when you set or add roles via {CORSRuleCollection}.
|
71
|
+
attr_reader :id
|
72
|
+
|
73
|
+
# @return [Array<String>] A list of HTTP methods (GET, POST, etc) this
|
74
|
+
# role authorizes.
|
75
|
+
attr_reader :allowed_methods
|
76
|
+
|
77
|
+
# @return [Array<String>] The list of origins allowed to make
|
78
|
+
# cross-domain requests to the bucket.
|
79
|
+
attr_reader :allowed_origins
|
80
|
+
|
81
|
+
# @return [Array<String>] A list of headers allowed in the pre-flight
|
82
|
+
# OPTIONS request.
|
83
|
+
attr_reader :allowed_headers
|
84
|
+
|
85
|
+
# @return [Integer,nil] The time in seconds the browser may cache the
|
86
|
+
# pre-flight response.
|
87
|
+
attr_reader :max_age_seconds
|
88
|
+
|
89
|
+
# @return [Array<String>] The headers that may be accessed cross-domain.
|
90
|
+
attr_reader :expose_headers
|
91
|
+
|
92
|
+
# @return [Hash]
|
93
|
+
def to_h
|
94
|
+
h = {}
|
95
|
+
h[:id] = id if id
|
96
|
+
h[:allowed_methods] = allowed_methods
|
97
|
+
h[:allowed_origins] = allowed_origins
|
98
|
+
h[:allowed_headers] = allowed_headers unless allowed_headers.empty?
|
99
|
+
h[:max_age_seconds] = max_age_seconds if max_age_seconds
|
100
|
+
h[:expose_headers] = expose_headers unless expose_headers.empty?
|
101
|
+
h
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# or in the "license" file accompanying this file. This file is
|
9
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
10
|
+
# ANY KIND, either express or implied. See the License for the specific
|
11
|
+
# language governing permissions and limitations under the License.
|
12
|
+
|
13
|
+
module MSS
|
14
|
+
class S3
|
15
|
+
|
16
|
+
# Manages the CORS rules for a single bucket.
|
17
|
+
#
|
18
|
+
# ## Getting Rules
|
19
|
+
#
|
20
|
+
# To get the CORS rules for a bucket, use the {Bucket#cors} method. This
|
21
|
+
# returns a CORSRuleCollection for the bucket. The collection is
|
22
|
+
# enumerable.
|
23
|
+
#
|
24
|
+
# # enumerating all rules for a buck
|
25
|
+
# bucket.cors.each do |rule|
|
26
|
+
# # rule is a CORSRule object
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# ## Setting Rules
|
30
|
+
#
|
31
|
+
# You can set the rules for a bucket (replacing all existing rules) using
|
32
|
+
# the {#set} method.
|
33
|
+
#
|
34
|
+
# # accepts a list of one or more rules
|
35
|
+
# bucket.rules.set(rule1, rule2)
|
36
|
+
#
|
37
|
+
# # rules can be an array of rules
|
38
|
+
# bucket.rules.set(rules)
|
39
|
+
#
|
40
|
+
# # passing an empty list or array removes all rules
|
41
|
+
# bucket.rules.set([])
|
42
|
+
# bucket.rules.clear # does the same thing
|
43
|
+
#
|
44
|
+
# Each rule can be a Hash, a {CORSRule} or another {CORSRuleCollection}.
|
45
|
+
# See {Client#put_bucket_cors} for a list of keys for a rule hash.
|
46
|
+
#
|
47
|
+
# ## Adding Rules
|
48
|
+
#
|
49
|
+
# Adding rules is the same as setting rules. Rules you add will be
|
50
|
+
# appended to the end of the existing list of rules.
|
51
|
+
#
|
52
|
+
# # add one or more rules
|
53
|
+
# bucket.rules.add(rules)
|
54
|
+
#
|
55
|
+
# ## Deleting Rules
|
56
|
+
#
|
57
|
+
# To remove a rule, use the {#delete_if} method.
|
58
|
+
#
|
59
|
+
# # delete rules that allow access from any domain
|
60
|
+
# bucket.cors.delete_if{|rule| rule.allowed_origins.include?('*')
|
61
|
+
class CORSRuleCollection
|
62
|
+
|
63
|
+
include Core::Collection::Simple
|
64
|
+
|
65
|
+
# @param [Bucket] bucket
|
66
|
+
# @param [Hash] options
|
67
|
+
def initialize bucket, options = {}
|
68
|
+
@bucket = bucket
|
69
|
+
super
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Bucket]
|
73
|
+
attr_reader :bucket
|
74
|
+
|
75
|
+
# Replaces the CORS rules attached to this bucket. You can pass
|
76
|
+
# one or more rules as an array or a list.
|
77
|
+
#
|
78
|
+
# # replace all exisitng rules with a single rule
|
79
|
+
# bucket.cors.set(
|
80
|
+
# :allowed_methods => %w(GET),
|
81
|
+
# :allowed_origins => %w(http://*.mydomain.com),
|
82
|
+
# :max_age_seconds => 3600)
|
83
|
+
#
|
84
|
+
# If you pass an empty array, all of the rules will be removed from
|
85
|
+
# the bucket.
|
86
|
+
#
|
87
|
+
# # these two lines are equivilent
|
88
|
+
# bucket.cors.clear
|
89
|
+
# bucket.cors.set([])
|
90
|
+
#
|
91
|
+
# @param [Hash,CORSRule,CORSRuleCollection] rules A list or array
|
92
|
+
# of one or more rules to set. Each rule may be a Hash, a CORSRule
|
93
|
+
# or a CORSRuleCollection.
|
94
|
+
#
|
95
|
+
# @return [nil]
|
96
|
+
#
|
97
|
+
def set *rules
|
98
|
+
|
99
|
+
raise ArgumentError, 'expected one or more rules' if rules.empty?
|
100
|
+
|
101
|
+
if rules == [[]]
|
102
|
+
self.clear
|
103
|
+
else
|
104
|
+
rules = rule_hashes(rules)
|
105
|
+
client.put_bucket_cors(:bucket_name => bucket.name, :rules => rules)
|
106
|
+
end
|
107
|
+
|
108
|
+
nil
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
# Add one or more CORS rules to this bucket.
|
113
|
+
#
|
114
|
+
# # adding a single rule as a hash
|
115
|
+
# bucket.cors.add(
|
116
|
+
# :allowed_methods => %w(GET HEAD),
|
117
|
+
# :allowed_origins => %w(*),
|
118
|
+
# :max_age_seconds => 3600)
|
119
|
+
#
|
120
|
+
# You can add multiple rules in a single call:
|
121
|
+
#
|
122
|
+
# # each rule may be a hash, CORSRule or a CORSRuleCollection,
|
123
|
+
# bucket.cors.add(rules)
|
124
|
+
#
|
125
|
+
# # alternatively you can pass a list of rules
|
126
|
+
# bucket.cors.add(rule1, rule2, ...)
|
127
|
+
#
|
128
|
+
# @param (see #set)
|
129
|
+
# @return (see #set)
|
130
|
+
def add *rules
|
131
|
+
self.set(self, *rules)
|
132
|
+
end
|
133
|
+
alias_method :create, :add
|
134
|
+
|
135
|
+
# Deletes every rule for which the block evaluates to `true`.
|
136
|
+
#
|
137
|
+
# @example Remove all rules that are open to the 'world'
|
138
|
+
#
|
139
|
+
# bucket.cors.delete_if{|rule| rule.allowed_origins.include?('*') }
|
140
|
+
#
|
141
|
+
# @yield [rule]
|
142
|
+
# @yieldparam [CORSRule] rule
|
143
|
+
# @yieldreturn [Boolean] Return `true` for each rule you want to delete.
|
144
|
+
# @return (see #set)
|
145
|
+
def delete_if &block
|
146
|
+
rules = []
|
147
|
+
self.each do |rule|
|
148
|
+
rules << rule unless yield(rule)
|
149
|
+
end
|
150
|
+
self.set(rules)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Removes all CORS rules attached to this bucket.
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
#
|
157
|
+
# bucket.cors.count #=> 3
|
158
|
+
# bucket.cors.clear
|
159
|
+
# bucket.cors.count #=> 0
|
160
|
+
#
|
161
|
+
# @return [nil]
|
162
|
+
def clear
|
163
|
+
client.delete_bucket_cors(:bucket_name => bucket.name)
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
protected
|
168
|
+
|
169
|
+
def _each_item options
|
170
|
+
resp = client.get_bucket_cors(options.merge(:bucket_name => bucket.name))
|
171
|
+
resp.data[:rules].each do |rule|
|
172
|
+
yield(CORSRule.new(rule))
|
173
|
+
end
|
174
|
+
rescue MSS::S3::Errors::NoSuchCORSConfiguration
|
175
|
+
# no cors rules exist for this bucket, nothing to yield
|
176
|
+
end
|
177
|
+
|
178
|
+
def rule_hashes rule
|
179
|
+
case rule
|
180
|
+
when Hash then rule
|
181
|
+
when CORSRule then rule.to_h
|
182
|
+
when CORSRuleCollection then rule.map(&:to_h)
|
183
|
+
when Array then rule.map{|r| rule_hashes(r) }.flatten
|
184
|
+
else
|
185
|
+
msg = "Expected one or more CORSRule, CORSRuleCollection or hash"
|
186
|
+
msg << ", got #{rule.class.name}"
|
187
|
+
raise ArgumentError, msg
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# or in the "license" file accompanying this file. This file is
|
9
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
10
|
+
# ANY KIND, either express or implied. See the License for the specific
|
11
|
+
# language governing permissions and limitations under the License.
|
12
|
+
|
13
|
+
require 'pathname'
|
14
|
+
|
15
|
+
module MSS
|
16
|
+
class S3
|
17
|
+
|
18
|
+
# Used by S3#S3Object and S3::Client to accept options with
|
19
|
+
# data that should be uploaded (streamed).
|
20
|
+
# @api private
|
21
|
+
module DataOptions
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
# @return [Hash] Returns a hash of options with a :data option that
|
26
|
+
# responds to #read and #eof?.
|
27
|
+
def compute_write_options *args, &block
|
28
|
+
|
29
|
+
options = convert_args_to_options_hash(*args)
|
30
|
+
|
31
|
+
validate_data!(options, &block)
|
32
|
+
|
33
|
+
rename_file_to_data(options)
|
34
|
+
|
35
|
+
convert_data_to_io_obj(options, &block)
|
36
|
+
|
37
|
+
try_to_determine_content_length(options)
|
38
|
+
|
39
|
+
options
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# Converts an argument list into a single hash of options. Treats
|
44
|
+
# non-hash arguments in the first position as a data option.
|
45
|
+
def convert_args_to_options_hash *args
|
46
|
+
case args.count
|
47
|
+
when 0 then {}
|
48
|
+
when 1 then args[0].is_a?(Hash) ? args[0] : { :data => args[0] }
|
49
|
+
when 2 then args[1].merge(:data => args[0])
|
50
|
+
else
|
51
|
+
msg = "expected 0, 1 or 2 arguments, got #{args.count}"
|
52
|
+
raise ArgumentError, msg
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Moves options[:file] to options[:data]. If this option is a string
|
57
|
+
# then it is treated as a file path and is converted to an open file.
|
58
|
+
def rename_file_to_data options
|
59
|
+
if file = options.delete(:file)
|
60
|
+
options[:data] = file.is_a?(String) ? open_file(file) : file
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Converts the :data option to an IO-like object. This allows us
|
65
|
+
# to always perform streaming uploads.
|
66
|
+
def convert_data_to_io_obj options, &block
|
67
|
+
|
68
|
+
data = options.delete(:data)
|
69
|
+
|
70
|
+
if block_given?
|
71
|
+
options[:data] = IOProxy.new(block)
|
72
|
+
elsif data.is_a?(String)
|
73
|
+
data = data.dup if data.frozen?
|
74
|
+
data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
|
75
|
+
options[:data] = StringIO.new(data)
|
76
|
+
elsif data.is_a?(Pathname)
|
77
|
+
options[:data] = open_file(data.to_s)
|
78
|
+
elsif io_like?(data)
|
79
|
+
options[:data] = data
|
80
|
+
else
|
81
|
+
msg = "invalid :data option, expected a String, Pathname or "
|
82
|
+
msg << "an object that responds to #read and #eof?"
|
83
|
+
raise ArgumentError, msg
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
# Attempts to determine the content length of the :data option.
|
89
|
+
# This is only done when a content length is not already provided.
|
90
|
+
def try_to_determine_content_length options
|
91
|
+
unless options[:content_length]
|
92
|
+
|
93
|
+
data = options[:data]
|
94
|
+
|
95
|
+
length = case
|
96
|
+
when data.respond_to?(:path) && data.path then File.size(data.path)
|
97
|
+
when data.respond_to?(:bytesize) then data.bytesize
|
98
|
+
when data.respond_to?(:size) then data.size
|
99
|
+
when data.respond_to?(:length) then data.length
|
100
|
+
else nil
|
101
|
+
end
|
102
|
+
|
103
|
+
options[:content_length] = length if length
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def validate_data! options, &block
|
109
|
+
|
110
|
+
data = options[:data]
|
111
|
+
file = options[:file]
|
112
|
+
|
113
|
+
raise ArgumentError, 'Object data passed multiple ways.' if
|
114
|
+
[data, file, block].compact.count > 1
|
115
|
+
|
116
|
+
data = file if file
|
117
|
+
|
118
|
+
return if block_given?
|
119
|
+
return if data.kind_of?(String)
|
120
|
+
return if data.kind_of?(Pathname)
|
121
|
+
return if io_like?(data)
|
122
|
+
|
123
|
+
msg = ":data must be provided as a String, Pathname, File, or "
|
124
|
+
msg << "an object that responds to #read and #eof?"
|
125
|
+
raise ArgumentError, msg
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
# @return [Boolean] Returns `true` if the object responds to
|
130
|
+
# `#read` and `#eof?`.
|
131
|
+
def io_like? io
|
132
|
+
io.respond_to?(:read) and io.respond_to?(:eof?)
|
133
|
+
end
|
134
|
+
|
135
|
+
# @param [String] path Path to a file on disk.
|
136
|
+
# @return [File] Given a path string, returns an open File.
|
137
|
+
def open_file path
|
138
|
+
Core::ManagedFile.open(path)
|
139
|
+
end
|
140
|
+
|
141
|
+
# A utility class that turns a block (with 2 args) into an
|
142
|
+
# IO object that responds to #read and #eof.
|
143
|
+
# @api private
|
144
|
+
class IOProxy
|
145
|
+
|
146
|
+
def initialize write_block
|
147
|
+
unless write_block.arity == 2
|
148
|
+
msg = "a write block must accept 2 yield params: a buffer and "
|
149
|
+
msg << "a number of bytes to write"
|
150
|
+
raise ArgumentError, msg
|
151
|
+
end
|
152
|
+
@write_block = write_block
|
153
|
+
@eof = false
|
154
|
+
end
|
155
|
+
|
156
|
+
def read bytes = nil, output_buffer = nil
|
157
|
+
data = if bytes
|
158
|
+
(@eof) ? nil : read_chunk(bytes)
|
159
|
+
else
|
160
|
+
(@eof) ? "" : read_all
|
161
|
+
end
|
162
|
+
output_buffer ? output_buffer.replace(data || '') : data
|
163
|
+
end
|
164
|
+
|
165
|
+
def eof?
|
166
|
+
@eof
|
167
|
+
end
|
168
|
+
|
169
|
+
protected
|
170
|
+
|
171
|
+
def read_chunk bytes
|
172
|
+
buffer = StringIO.new
|
173
|
+
@write_block.call(buffer, bytes)
|
174
|
+
buffer.rewind
|
175
|
+
@eof = true if buffer.size < bytes
|
176
|
+
buffer.read
|
177
|
+
end
|
178
|
+
|
179
|
+
def read_all
|
180
|
+
buffer = StringIO.new
|
181
|
+
buffer << read_chunk(1024 * 1024 * 5) until @eof
|
182
|
+
buffer.rewind
|
183
|
+
buffer.read
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# or in the "license" file accompanying this file. This file is
|
9
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
10
|
+
# ANY KIND, either express or implied. See the License for the specific
|
11
|
+
# language governing permissions and limitations under the License.
|
12
|
+
|
13
|
+
require 'openssl'
|
14
|
+
|
15
|
+
module MSS
|
16
|
+
class S3
|
17
|
+
# @api private
|
18
|
+
module EncryptionUtils
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
UNSAFE_MSG = "Unsafe encryption, data is longer than key length"
|
23
|
+
|
24
|
+
# @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
|
25
|
+
#
|
26
|
+
# @param [String] data Data to be encrypted.
|
27
|
+
#
|
28
|
+
# @note Use check_encryption_materials before this method to check
|
29
|
+
# formatting of keys.
|
30
|
+
# @note This should not be used for data longer than the key length as
|
31
|
+
# it will not be cryptographically safe.
|
32
|
+
#
|
33
|
+
# @return [String] Returns the data encrypted with the key given.
|
34
|
+
def encrypt data, key
|
35
|
+
rsa = OpenSSL::PKey::RSA
|
36
|
+
data_cipher_size = get_cipher_size(data.length)
|
37
|
+
|
38
|
+
# Encrypting data key
|
39
|
+
case key
|
40
|
+
when rsa # Asymmetric encryption
|
41
|
+
warn UNSAFE_MSG if key.public_key.n.num_bits < data_cipher_size
|
42
|
+
key.public_encrypt(data)
|
43
|
+
when String # Symmetric encryption
|
44
|
+
warn UNSAFE_MSG if get_cipher_size(key.length) < data_cipher_size
|
45
|
+
cipher = get_aes_cipher(:encrypt, :ECB, key)
|
46
|
+
cipher.update(data) + cipher.final
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [OpenSSL::PKey::RSA, String] key Key used to encrypt.
|
51
|
+
#
|
52
|
+
# @param [String] data Data to be encrypted.
|
53
|
+
#
|
54
|
+
# @note Use check_encryption_materials before this method to check
|
55
|
+
# formatting of keys
|
56
|
+
#
|
57
|
+
# @return [String] Returns the data decrypted with the key given.
|
58
|
+
def decrypt data, key
|
59
|
+
rsa = OpenSSL::PKey::RSA
|
60
|
+
begin
|
61
|
+
case key
|
62
|
+
when rsa # Asymmetric Decryption
|
63
|
+
key.private_decrypt(data)
|
64
|
+
when String # Symmetric Decryption
|
65
|
+
cipher = get_aes_cipher(:decrypt, :ECB, key)
|
66
|
+
cipher.update(data) + cipher.final
|
67
|
+
end
|
68
|
+
rescue OpenSSL::Cipher::CipherError
|
69
|
+
raise RuntimeError, "decryption failed, incorrect key?"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Checks for any formatting problems for keys and initialization vectors
|
74
|
+
# supported with EncryptionUtils.
|
75
|
+
def check_encryption_materials mode, key
|
76
|
+
rsa = OpenSSL::PKey::RSA
|
77
|
+
case key
|
78
|
+
when rsa
|
79
|
+
unless key.private? or mode == :encrypt
|
80
|
+
msg = "invalid key, #{rsa} requires a private key"
|
81
|
+
raise ArgumentError, msg
|
82
|
+
end
|
83
|
+
when String # no problem
|
84
|
+
else
|
85
|
+
msg = "invalid key, must be an #{rsa} or a cipher key string"
|
86
|
+
raise ArgumentError, msg
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# @param [OpenSSL::Cipher] cipher The cipher with configured key and iv.
|
91
|
+
#
|
92
|
+
# @yield [String, String] key_iv_pair A randomly generated key, iv pair
|
93
|
+
# for use with the given cipher. Sets the key and iv on the cipher.
|
94
|
+
def generate_aes_key cipher, &block
|
95
|
+
key_iv_pair = [cipher.random_key, cipher.random_iv]
|
96
|
+
yield(key_iv_pair) if block_given?
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param [Symbol] mode The encryption/decryption mode. Valid inputs are
|
100
|
+
# :encrypt or :decrypt
|
101
|
+
#
|
102
|
+
# @param [String] key Key for the cipher.
|
103
|
+
#
|
104
|
+
# @param [String] iv IV for the cipher.
|
105
|
+
#
|
106
|
+
# @return [OpenSSL::Cipher] Will return a configured `OpenSSL::Cipher`.
|
107
|
+
def get_aes_cipher mode, block_mode, key = nil, iv = nil
|
108
|
+
|
109
|
+
# If no key given, default to 256 bit
|
110
|
+
cipher_size = (key) ? get_cipher_size(key.length) : 256
|
111
|
+
|
112
|
+
cipher = OpenSSL::Cipher.new("AES-#{cipher_size}-#{block_mode}")
|
113
|
+
|
114
|
+
(mode == :encrypt) ? cipher.encrypt : cipher.decrypt
|
115
|
+
cipher.key = key if key
|
116
|
+
cipher.iv = iv if iv
|
117
|
+
cipher
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param [Integer] size Size of data given.
|
121
|
+
# @return [Integer] Returns the AES encrypted size based on a given size.
|
122
|
+
def get_encrypted_size size
|
123
|
+
# The next multiple of 16
|
124
|
+
((size / 16) + 1) * 16
|
125
|
+
end
|
126
|
+
module_function :get_encrypted_size
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
# @param [Fixnum] key_length Length of the key given.
|
131
|
+
# @return [Fixnum] Returns the cipher size based on the key length.
|
132
|
+
def get_cipher_size(key_length)
|
133
|
+
case key_length
|
134
|
+
when 32 then 256
|
135
|
+
when 24 then 192
|
136
|
+
when 16 then 128
|
137
|
+
else
|
138
|
+
msg = "invalid key, symmetric key required to be 16, 24, or 32 bytes "
|
139
|
+
msg << "in length, saw length #{key_length}"
|
140
|
+
raise ArgumentError, msg
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|