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,938 @@
|
|
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 'date'
|
14
|
+
require 'json'
|
15
|
+
|
16
|
+
module MSS
|
17
|
+
module Core
|
18
|
+
|
19
|
+
# Represents an access policy for MSS operations and resources. For example:
|
20
|
+
#
|
21
|
+
# policy = Policy.new
|
22
|
+
# policy.allow(
|
23
|
+
# :actions => ['s3:PutObject'],
|
24
|
+
# :resources => "arn:mss:s3:::mybucket/mykey/*",
|
25
|
+
# :principals => :any
|
26
|
+
# ).where(:acl).is("public-read")
|
27
|
+
#
|
28
|
+
# policy.to_json # => '{ "Version":"2008-10-17", ...'
|
29
|
+
#
|
30
|
+
# @see #initialize More ways to construct a policy.
|
31
|
+
class Policy
|
32
|
+
|
33
|
+
# @see Statement
|
34
|
+
# @return [Array] An array of policy statements.
|
35
|
+
attr_reader :statements
|
36
|
+
|
37
|
+
# @return [String] The version of the policy language used in this
|
38
|
+
# policy object.
|
39
|
+
attr_reader :version
|
40
|
+
|
41
|
+
# @return [String] A unique ID for the policy.
|
42
|
+
attr_reader :id
|
43
|
+
|
44
|
+
class Statement; end
|
45
|
+
|
46
|
+
# Constructs a policy. There are a few different ways to
|
47
|
+
# build a policy:
|
48
|
+
#
|
49
|
+
# * With hash arguments:
|
50
|
+
#
|
51
|
+
# Policy.new(:statements => [
|
52
|
+
# {
|
53
|
+
# :effect => :allow,
|
54
|
+
# :actions => :all,
|
55
|
+
# :principals => ["abc123"],
|
56
|
+
# :resources => "mybucket/mykey"
|
57
|
+
# }
|
58
|
+
# ])
|
59
|
+
#
|
60
|
+
# * From a JSON policy document:
|
61
|
+
#
|
62
|
+
# Policy.from_json(policy_json_string)
|
63
|
+
#
|
64
|
+
# * With a block:
|
65
|
+
#
|
66
|
+
# Policy.new do |policy|
|
67
|
+
# policy.allow(
|
68
|
+
# :actions => ['s3:PutObject'],
|
69
|
+
# :resources => "arn:mss:s3:::mybucket/mykey/*",
|
70
|
+
# :principals => :any
|
71
|
+
# ).where(:acl).is("public-read")
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
def initialize(opts = {})
|
75
|
+
@statements = opts.values_at(:statements, "Statement").select do |a|
|
76
|
+
a.kind_of?(Array)
|
77
|
+
end.flatten.map do |stmt|
|
78
|
+
self.class::Statement.new(stmt)
|
79
|
+
end
|
80
|
+
|
81
|
+
if opts.has_key?(:id) or opts.has_key?("Id")
|
82
|
+
@id = opts[:id] || opts["Id"]
|
83
|
+
else
|
84
|
+
@id = SecureRandom.uuid.tr('-','')
|
85
|
+
end
|
86
|
+
if opts.has_key?(:version) or opts.has_key?("Version")
|
87
|
+
@version = opts[:version] || opts["Version"]
|
88
|
+
else
|
89
|
+
@version = "2008-10-17"
|
90
|
+
end
|
91
|
+
|
92
|
+
yield(self) if block_given?
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Boolean] Returns true if the two policies are the same.
|
96
|
+
def ==(other)
|
97
|
+
if other.kind_of?(Core::Policy)
|
98
|
+
self.hash_without_ids == other.hash_without_ids
|
99
|
+
else
|
100
|
+
false
|
101
|
+
end
|
102
|
+
end
|
103
|
+
alias_method :eql?, :==
|
104
|
+
|
105
|
+
# Removes the ids from the policy and its statements for the purpose
|
106
|
+
# of comparing two policies for equivilence.
|
107
|
+
# @return [Hash] Returns the policy as a hash with no ids
|
108
|
+
# @api private
|
109
|
+
def hash_without_ids
|
110
|
+
hash = self.to_h
|
111
|
+
hash.delete('Id')
|
112
|
+
hash['Statement'].each do |statement|
|
113
|
+
statement.delete('Sid')
|
114
|
+
end
|
115
|
+
hash
|
116
|
+
end
|
117
|
+
protected :hash_without_ids
|
118
|
+
|
119
|
+
# Returns a hash representation of the policy. The following
|
120
|
+
# statements are equivalent:
|
121
|
+
#
|
122
|
+
# policy.to_h.to_json
|
123
|
+
# policy.to_json
|
124
|
+
#
|
125
|
+
# @return [Hash]
|
126
|
+
def to_h
|
127
|
+
{
|
128
|
+
"Version" => version,
|
129
|
+
"Id" => id,
|
130
|
+
"Statement" => statements.map { |st| st.to_h }
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# @return [String] a JSON representation of the policy.
|
135
|
+
def to_json
|
136
|
+
to_h.to_json
|
137
|
+
end
|
138
|
+
|
139
|
+
# Constructs a policy from a JSON representation.
|
140
|
+
# @see #initialize
|
141
|
+
# @return [Policy] Returns a Policy object constructed by parsing
|
142
|
+
# the passed JSON policy.
|
143
|
+
def self.from_json(json)
|
144
|
+
new(JSON.parse(json))
|
145
|
+
end
|
146
|
+
|
147
|
+
# Convenient syntax for expressing operators in statement
|
148
|
+
# condition blocks. For example, the following:
|
149
|
+
#
|
150
|
+
# policy.allow.where(:s3_prefix).not("forbidden").
|
151
|
+
# where(:current_time).lte(Date.today+1)
|
152
|
+
#
|
153
|
+
# is equivalent to:
|
154
|
+
#
|
155
|
+
# conditions = Policy::ConditionBlock.new
|
156
|
+
# conditions.add(:not, :s3_prefix, "forbidden")
|
157
|
+
# conditions.add(:lte, :current_time, Date.today+1)
|
158
|
+
# policy.allow(:conditions => conditions)
|
159
|
+
#
|
160
|
+
# @see ConditionBlock#add
|
161
|
+
class OperatorBuilder
|
162
|
+
|
163
|
+
# @api private
|
164
|
+
def initialize(condition_builder, key)
|
165
|
+
@condition_builder = condition_builder
|
166
|
+
@key = key
|
167
|
+
end
|
168
|
+
|
169
|
+
def method_missing(m, *values)
|
170
|
+
@condition_builder.conditions.add(m, @key, *values)
|
171
|
+
@condition_builder
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
# Convenient syntax for adding conditions to a statement.
|
177
|
+
# @see Policy#allow
|
178
|
+
# @see Policy#deny
|
179
|
+
class ConditionBuilder
|
180
|
+
|
181
|
+
# @return [Array] Returns an array of policy conditions.
|
182
|
+
attr_reader :conditions
|
183
|
+
|
184
|
+
# @api private
|
185
|
+
def initialize(conditions)
|
186
|
+
@conditions = conditions
|
187
|
+
end
|
188
|
+
|
189
|
+
# Adds a condition for the given key. For example:
|
190
|
+
#
|
191
|
+
# policy.allow(...).where(:current_time).lte(Date.today + 1)
|
192
|
+
#
|
193
|
+
# @return [OperatorBuilder]
|
194
|
+
def where(key, operator = nil, *values)
|
195
|
+
if operator
|
196
|
+
@conditions.add(operator, key, *values)
|
197
|
+
self
|
198
|
+
else
|
199
|
+
OperatorBuilder.new(self, key)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
# Convenience method for constructing a new statement with the
|
206
|
+
# "Allow" effect and adding it to the policy. For example:
|
207
|
+
#
|
208
|
+
# policy.allow(
|
209
|
+
# :actions => [:put_object],
|
210
|
+
# :principals => :any,
|
211
|
+
# :resources => "mybucket/mykey/*").
|
212
|
+
# where(:acl).is("public-read")
|
213
|
+
#
|
214
|
+
# @option (see Statement#initialize)
|
215
|
+
# @see Statement#initialize
|
216
|
+
# @return [ConditionBuilder]
|
217
|
+
def allow(opts = {})
|
218
|
+
stmt = self.class::Statement.new(opts.merge(:effect => :allow))
|
219
|
+
statements << stmt
|
220
|
+
ConditionBuilder.new(stmt.conditions)
|
221
|
+
end
|
222
|
+
|
223
|
+
# Convenience method for constructing a new statement with the
|
224
|
+
# "Deny" effect and adding it to the policy. For example:
|
225
|
+
#
|
226
|
+
# policy.deny(
|
227
|
+
# :actions => [:put_object],
|
228
|
+
# :principals => :any,
|
229
|
+
# :resources => "mybucket/mykey/*"
|
230
|
+
# ).where(:acl).is("public-read")
|
231
|
+
#
|
232
|
+
# @param (see Statement#initialize)
|
233
|
+
# @see Statement#initialize
|
234
|
+
# @return [ConditionBuilder]
|
235
|
+
def deny(opts = {})
|
236
|
+
stmt = self.class::Statement.new(opts.merge(:effect => :deny))
|
237
|
+
statements << stmt
|
238
|
+
ConditionBuilder.new(stmt.conditions)
|
239
|
+
end
|
240
|
+
|
241
|
+
# Represents the condition block of a policy. In JSON,
|
242
|
+
# condition blocks look like this:
|
243
|
+
#
|
244
|
+
# { "StringLike": { "s3:prefix": ["photos/*", "photos.html"] } }
|
245
|
+
#
|
246
|
+
# ConditionBlock lets you specify conditions like the above
|
247
|
+
# example using the add method, for example:
|
248
|
+
#
|
249
|
+
# conditions.add(:like, :s3_prefix, "photos/*", "photos.html")
|
250
|
+
#
|
251
|
+
# See the add method documentation for more details about how
|
252
|
+
# to specify keys and operators.
|
253
|
+
#
|
254
|
+
# This class also provides a convenient way to query a
|
255
|
+
# condition block to see what operators, keys, and values it
|
256
|
+
# has. For example, consider the following condition block
|
257
|
+
# (in JSON):
|
258
|
+
#
|
259
|
+
# {
|
260
|
+
# "StringEquals": {
|
261
|
+
# "s3:prefix": "photos/index.html"
|
262
|
+
# },
|
263
|
+
# "DateEquals": {
|
264
|
+
# "mss:CurrentTime": ["2010-10-12", "2011-01-02"]
|
265
|
+
# },
|
266
|
+
# "NumericEquals": {
|
267
|
+
# "s3:max-keys": 10
|
268
|
+
# }
|
269
|
+
# }
|
270
|
+
#
|
271
|
+
# You can get access to the condition data using #[], #keys,
|
272
|
+
# #operators, and #values -- for example:
|
273
|
+
#
|
274
|
+
# conditions["DateEquals"]["mss:CurrentTime"].values
|
275
|
+
# # => ["2010-10-12", "2011-01-02"]
|
276
|
+
#
|
277
|
+
# You can also perform more sophisticated queries, like this
|
278
|
+
# one:
|
279
|
+
#
|
280
|
+
# conditions[:is].each do |equality_conditions|
|
281
|
+
# equality_conditions.keys.each do |key|
|
282
|
+
# puts("#{key} may be any of: " +
|
283
|
+
# equality_conditions[key].values.join(" ")
|
284
|
+
# end
|
285
|
+
# end
|
286
|
+
#
|
287
|
+
# This would print the following lines:
|
288
|
+
#
|
289
|
+
# s3:prefix may be any of: photos/index.html
|
290
|
+
# mss:CurrentTime may be any of: 2010-10-12 2011-01-02
|
291
|
+
# s3:max-keys may be any of: 10
|
292
|
+
#
|
293
|
+
class ConditionBlock
|
294
|
+
|
295
|
+
# @api private
|
296
|
+
def initialize(conditions = {})
|
297
|
+
# filter makes a copy
|
298
|
+
@conditions = filter_conditions(conditions)
|
299
|
+
end
|
300
|
+
|
301
|
+
# Adds a condition to the block. This method defines a
|
302
|
+
# convenient set of abbreviations for operators based on the
|
303
|
+
# type of value passed in. For example:
|
304
|
+
#
|
305
|
+
# conditions.add(:is, :secure_transport, true)
|
306
|
+
#
|
307
|
+
# Maps to:
|
308
|
+
#
|
309
|
+
# { "Bool": { "mss:SecureTransport": true } }
|
310
|
+
#
|
311
|
+
# While:
|
312
|
+
#
|
313
|
+
# conditions.add(:is, :s3_prefix, "photos/")
|
314
|
+
#
|
315
|
+
# Maps to:
|
316
|
+
#
|
317
|
+
# { "StringEquals": { "s3:prefix": "photos/" } }
|
318
|
+
#
|
319
|
+
# The following list shows which operators are accepted as
|
320
|
+
# symbols and how they are represented in the JSON policy:
|
321
|
+
#
|
322
|
+
# * `:is` (StringEquals, NumericEquals, DateEquals, or Bool)
|
323
|
+
# * `:like` (StringLike)
|
324
|
+
# * `:not_like` (StringNotLike)
|
325
|
+
# * `:not` (StringNotEquals, NumericNotEquals, or DateNotEquals)
|
326
|
+
# * `:greater_than`, `:gt` (NumericGreaterThan or DateGreaterThan)
|
327
|
+
# * `:greater_than_equals`, `:gte`
|
328
|
+
# (NumericGreaterThanEquals or DateGreaterThanEquals)
|
329
|
+
# * `:less_than`, `:lt` (NumericLessThan or DateLessThan)
|
330
|
+
# * `:less_than_equals`, `:lte`
|
331
|
+
# (NumericLessThanEquals or DateLessThanEquals)
|
332
|
+
# * `:is_ip_address` (IpAddress)
|
333
|
+
# * `:not_ip_address` (NotIpAddress)
|
334
|
+
# * `:is_arn` (ArnEquals)
|
335
|
+
# * `:not_arn` (ArnNotEquals)
|
336
|
+
# * `:is_arn_like` (ArnLike)
|
337
|
+
# * `:not_arn_like` (ArnNotLike)
|
338
|
+
#
|
339
|
+
# @param [Symbol or String] operator The operator used to
|
340
|
+
# compare the key with the value. See above for valid
|
341
|
+
# values and their interpretations.
|
342
|
+
#
|
343
|
+
# @param [Symbol or String] key The key to compare. Symbol
|
344
|
+
# keys are inflected to match MSS conventions. By
|
345
|
+
# default, the key is assumed to be in the "mss"
|
346
|
+
# namespace, but if you prefix the symbol name with "s3_"
|
347
|
+
# it will be sent in the "s3" namespace. For example,
|
348
|
+
# `:s3_prefix` is sent as "s3:prefix" while
|
349
|
+
# `:secure_transport` is sent as "mss:SecureTransport".
|
350
|
+
# See
|
351
|
+
# for a list of the available keys for each action in S3.
|
352
|
+
#
|
353
|
+
# @param [Mixed] values The value to compare against.
|
354
|
+
# This can be:
|
355
|
+
# * a String
|
356
|
+
# * a number
|
357
|
+
# * a Date, DateTime, or Time
|
358
|
+
# * a boolean value
|
359
|
+
# This method does not attempt to validate that the values
|
360
|
+
# are valid for the operators or keys they are used with.
|
361
|
+
#
|
362
|
+
def add(operator, key, *values)
|
363
|
+
if operator.kind_of?(Symbol)
|
364
|
+
converted_values = values.map { |v| convert_value(v) }
|
365
|
+
else
|
366
|
+
converted_values = values
|
367
|
+
end
|
368
|
+
operator = translate_operator(operator, values.first)
|
369
|
+
op = (@conditions[operator] ||= {})
|
370
|
+
raise "duplicate #{operator} conditions for #{key}" if op[key]
|
371
|
+
op[translate_key(key)] = converted_values
|
372
|
+
end
|
373
|
+
|
374
|
+
# @api private
|
375
|
+
def to_h
|
376
|
+
@conditions
|
377
|
+
end
|
378
|
+
|
379
|
+
# Filters the conditions described in the block, returning a
|
380
|
+
# new ConditionBlock that contains only the matching
|
381
|
+
# conditions. Each argument is matched against either the
|
382
|
+
# keys or the operators in the block, and you can specify
|
383
|
+
# the key or operator in any way that's valid for the #add
|
384
|
+
# method. Some examples:
|
385
|
+
#
|
386
|
+
# # all conditions using the StringLike operator
|
387
|
+
# conditions["StringLike"]
|
388
|
+
#
|
389
|
+
# # all conditions using StringEquals, DateEquals, NumericEquals, or Bool
|
390
|
+
# conditions[:is]
|
391
|
+
#
|
392
|
+
# # all conditions on the s3:prefix key
|
393
|
+
# conditions["s3:prefix"]
|
394
|
+
#
|
395
|
+
# # all conditions on the mss:CurrentTime key
|
396
|
+
# conditions[:current_time]
|
397
|
+
#
|
398
|
+
# Multiple conditions are ANDed together, so the following
|
399
|
+
# are equivalent:
|
400
|
+
#
|
401
|
+
# conditions[:s3_prefix][:is]
|
402
|
+
# conditions[:is][:s3_prefix]
|
403
|
+
# conditions[:s3_prefix, :is]
|
404
|
+
#
|
405
|
+
# @see #add
|
406
|
+
# @return [ConditionBlock] A new set of conditions filtered by the
|
407
|
+
# given conditions.
|
408
|
+
def [](*args)
|
409
|
+
filtered = @conditions
|
410
|
+
args.each do |filter|
|
411
|
+
type = valid_operator?(filter) ? nil : :key
|
412
|
+
filtered = filter_conditions(filtered) do |op, key, value|
|
413
|
+
(match, type) = match_triple(filter, type, op, key, value)
|
414
|
+
match
|
415
|
+
end
|
416
|
+
end
|
417
|
+
self.class.new(filtered)
|
418
|
+
end
|
419
|
+
|
420
|
+
# @return [Array] Returns an array of operators used in this block.
|
421
|
+
def operators
|
422
|
+
@conditions.keys
|
423
|
+
end
|
424
|
+
|
425
|
+
# @return [Array] Returns an array of unique keys used in the block.
|
426
|
+
def keys
|
427
|
+
@conditions.values.map do |keys|
|
428
|
+
keys.keys if keys
|
429
|
+
end.compact.flatten.uniq
|
430
|
+
end
|
431
|
+
|
432
|
+
# Returns all values used in the block. Note that the
|
433
|
+
# values may not all be from the same condition; for example:
|
434
|
+
#
|
435
|
+
# conditions.add(:like, :user_agent, "mozilla", "explorer")
|
436
|
+
# conditions.add(:lt, :s3_max_keys, 12)
|
437
|
+
# conditions.values # => ["mozilla", "explorer", 12]
|
438
|
+
#
|
439
|
+
# @return [Array] Returns an array of values used in this condition block.
|
440
|
+
def values
|
441
|
+
@conditions.values.map do |keys|
|
442
|
+
keys.values
|
443
|
+
end.compact.flatten
|
444
|
+
end
|
445
|
+
|
446
|
+
# @api private
|
447
|
+
protected
|
448
|
+
def match_triple(filter, type, op, key, value)
|
449
|
+
value = [value].flatten.first
|
450
|
+
if type
|
451
|
+
target = (type == :operator ? op : key)
|
452
|
+
match = send("match_#{type}", filter, target, value)
|
453
|
+
else
|
454
|
+
if match_operator(filter, op, value)
|
455
|
+
match = true
|
456
|
+
type = :operator
|
457
|
+
elsif match_key(filter, key)
|
458
|
+
match = true
|
459
|
+
type = :key
|
460
|
+
else
|
461
|
+
match = false
|
462
|
+
end
|
463
|
+
end
|
464
|
+
[match, type]
|
465
|
+
end
|
466
|
+
|
467
|
+
# @api private
|
468
|
+
protected
|
469
|
+
def match_operator(filter, op, value)
|
470
|
+
# dates are the only values that don't come back as native types in JSON
|
471
|
+
# but where we use the type as a cue to the operator translation
|
472
|
+
value = Date.today if op =~ /^Date/
|
473
|
+
translate_operator(filter, value) == op
|
474
|
+
end
|
475
|
+
|
476
|
+
# @api private
|
477
|
+
protected
|
478
|
+
def match_key(filter, key, value = nil)
|
479
|
+
translate_key(filter) == key
|
480
|
+
end
|
481
|
+
|
482
|
+
# @api private
|
483
|
+
protected
|
484
|
+
def filter_conditions(conditions = @conditions)
|
485
|
+
conditions.inject({}) do |m, (op, keys)|
|
486
|
+
m[op] = keys.inject({}) do |m2, (key, value)|
|
487
|
+
m2[key] = value if !block_given? or yield(op, key, value)
|
488
|
+
m2
|
489
|
+
end
|
490
|
+
m.delete(op) if m[op].empty?
|
491
|
+
m
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
# @api private
|
496
|
+
protected
|
497
|
+
def translate_key(key)
|
498
|
+
if key.kind_of?(Symbol)
|
499
|
+
if key.to_s =~ /^s3_(.*)$/
|
500
|
+
s3_name = $1
|
501
|
+
if s3_name == "version_id" or
|
502
|
+
s3_name == "location_constraint"
|
503
|
+
s3_name = Inflection.class_name(s3_name)
|
504
|
+
else
|
505
|
+
s3_name.tr!('_', '-')
|
506
|
+
end
|
507
|
+
"s3:#{s3_name}"
|
508
|
+
else
|
509
|
+
"mss:#{Inflection.class_name(key.to_s)}"
|
510
|
+
end
|
511
|
+
else
|
512
|
+
key
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
# @api private
|
517
|
+
MODIFIERS = {
|
518
|
+
/_ignoring_case$/ => "IgnoreCase",
|
519
|
+
/_equals$/ => "Equals"
|
520
|
+
}
|
521
|
+
|
522
|
+
# @api private
|
523
|
+
protected
|
524
|
+
def valid_operator?(operator)
|
525
|
+
translate_operator(operator, "")
|
526
|
+
true
|
527
|
+
rescue ArgumentError => e
|
528
|
+
false
|
529
|
+
end
|
530
|
+
|
531
|
+
# @api private
|
532
|
+
protected
|
533
|
+
def translate_operator(operator, example_value)
|
534
|
+
return operator if operator.kind_of?(String)
|
535
|
+
|
536
|
+
original_operator = operator
|
537
|
+
(operator, opts) = strip_modifiers(operator)
|
538
|
+
|
539
|
+
raise ArgumentError.new("unrecognized operator #{original_operator}") unless
|
540
|
+
respond_to?("translate_#{operator}", true)
|
541
|
+
send("translate_#{operator}", example_value, opts)
|
542
|
+
end
|
543
|
+
|
544
|
+
# @api private
|
545
|
+
protected
|
546
|
+
def translate_is(example, opts)
|
547
|
+
return "Bool" if type_notation(example) == "Bool"
|
548
|
+
base_translate(example, "Equals", opts[:ignore_case])
|
549
|
+
end
|
550
|
+
|
551
|
+
# @api private
|
552
|
+
protected
|
553
|
+
def translate_not(example, opts)
|
554
|
+
base_translate(example, "NotEquals", opts[:ignore_case])
|
555
|
+
end
|
556
|
+
|
557
|
+
# @api private
|
558
|
+
protected
|
559
|
+
def translate_like(example, opts)
|
560
|
+
base_translate(example, "Like")
|
561
|
+
end
|
562
|
+
|
563
|
+
# @api private
|
564
|
+
protected
|
565
|
+
def translate_not_like(example, opts)
|
566
|
+
base_translate(example, "NotLike")
|
567
|
+
end
|
568
|
+
|
569
|
+
# @api private
|
570
|
+
protected
|
571
|
+
def translate_less_than(example, opts)
|
572
|
+
base_translate(example, "LessThan", opts[:equals])
|
573
|
+
end
|
574
|
+
alias_method :translate_lt, :translate_less_than
|
575
|
+
|
576
|
+
# @api private
|
577
|
+
protected
|
578
|
+
def translate_lte(example, opts)
|
579
|
+
translate_less_than(example, { :equals => "Equals" })
|
580
|
+
end
|
581
|
+
|
582
|
+
# @api private
|
583
|
+
protected
|
584
|
+
def translate_greater_than(example, opts)
|
585
|
+
base_translate(example, "GreaterThan", opts[:equals])
|
586
|
+
end
|
587
|
+
alias_method :translate_gt, :translate_greater_than
|
588
|
+
|
589
|
+
# @api private
|
590
|
+
protected
|
591
|
+
def translate_gte(example, opts)
|
592
|
+
translate_greater_than(example, { :equals => "Equals" })
|
593
|
+
end
|
594
|
+
|
595
|
+
# @api private
|
596
|
+
protected
|
597
|
+
def translate_is_ip_address(example, opts)
|
598
|
+
"IpAddress"
|
599
|
+
end
|
600
|
+
|
601
|
+
# @api private
|
602
|
+
protected
|
603
|
+
def translate_not_ip_address(example, opts)
|
604
|
+
"NotIpAddress"
|
605
|
+
end
|
606
|
+
|
607
|
+
# @api private
|
608
|
+
protected
|
609
|
+
def translate_is_arn(example, opts)
|
610
|
+
"ArnEquals"
|
611
|
+
end
|
612
|
+
|
613
|
+
# @api private
|
614
|
+
protected
|
615
|
+
def translate_not_arn(example, opts)
|
616
|
+
"ArnNotEquals"
|
617
|
+
end
|
618
|
+
|
619
|
+
# @api private
|
620
|
+
protected
|
621
|
+
def translate_is_arn_like(example, opts)
|
622
|
+
"ArnLike"
|
623
|
+
end
|
624
|
+
|
625
|
+
# @api private
|
626
|
+
protected
|
627
|
+
def translate_not_arn_like(example, opts)
|
628
|
+
"ArnNotLike"
|
629
|
+
end
|
630
|
+
|
631
|
+
# @api private
|
632
|
+
protected
|
633
|
+
def base_translate(example, base_operator, *modifiers)
|
634
|
+
"#{type_notation(example)}#{base_operator}#{modifiers.join}"
|
635
|
+
end
|
636
|
+
|
637
|
+
# @api private
|
638
|
+
protected
|
639
|
+
def type_notation(example)
|
640
|
+
case example
|
641
|
+
when String
|
642
|
+
"String"
|
643
|
+
when Numeric
|
644
|
+
"Numeric"
|
645
|
+
when Time, Date
|
646
|
+
"Date"
|
647
|
+
when true, false
|
648
|
+
"Bool"
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
# @api private
|
653
|
+
protected
|
654
|
+
def convert_value(value)
|
655
|
+
case value
|
656
|
+
when DateTime, Time
|
657
|
+
Time.parse(value.to_s).iso8601
|
658
|
+
when Date
|
659
|
+
value.strftime("%Y-%m-%d")
|
660
|
+
else
|
661
|
+
value
|
662
|
+
end
|
663
|
+
end
|
664
|
+
|
665
|
+
# @api private
|
666
|
+
protected
|
667
|
+
def strip_modifiers(operator)
|
668
|
+
opts = {}
|
669
|
+
MODIFIERS.each do |(regex, mod)|
|
670
|
+
ruby_name = Inflection.ruby_name(mod).to_sym
|
671
|
+
opts[ruby_name] = ""
|
672
|
+
if operator.to_s =~ regex
|
673
|
+
opts[ruby_name] = mod
|
674
|
+
operator = operator.to_s.sub(regex, '').to_sym
|
675
|
+
end
|
676
|
+
end
|
677
|
+
[operator, opts]
|
678
|
+
end
|
679
|
+
|
680
|
+
end
|
681
|
+
|
682
|
+
# Represents a statement in a policy.
|
683
|
+
#
|
684
|
+
# @see Policy#allow
|
685
|
+
# @see Policy#deny
|
686
|
+
class Statement
|
687
|
+
|
688
|
+
# @return [String] Returns the statement id
|
689
|
+
attr_accessor :sid
|
690
|
+
|
691
|
+
# @return [String] Returns the statement effect, either "Allow" or
|
692
|
+
# "Deny"
|
693
|
+
attr_accessor :effect
|
694
|
+
|
695
|
+
# @return [Array] Returns an array of principals.
|
696
|
+
attr_accessor :principals
|
697
|
+
|
698
|
+
# @return [Array] Returns an array of statement actions included
|
699
|
+
# by this policy statement.
|
700
|
+
attr_accessor :actions
|
701
|
+
|
702
|
+
# @return [Array] Returns an array of actions excluded by this
|
703
|
+
# policy statement.
|
704
|
+
attr_accessor :excluded_actions
|
705
|
+
|
706
|
+
# @return [Array] Returns an array of resources affected by this
|
707
|
+
# policy statement.
|
708
|
+
attr_accessor :resources
|
709
|
+
|
710
|
+
# @return [Array] Returns an array of conditions for this policy.
|
711
|
+
attr_accessor :conditions
|
712
|
+
|
713
|
+
attr_accessor :excluded_resources
|
714
|
+
|
715
|
+
# Constructs a new statement.
|
716
|
+
#
|
717
|
+
# @option opts [String] :sid The statement ID. This is optional; if
|
718
|
+
# omitted, a UUID will be generated for the statement.
|
719
|
+
# @option opts [String] :effect The statement effect, which must be either
|
720
|
+
# "Allow" or "Deny".
|
721
|
+
# @see Policy#allow
|
722
|
+
# @see Policy#deny
|
723
|
+
# @option opts [String or array of strings] :principals The account(s)
|
724
|
+
# affected by the statement. These should be MSS account IDs.
|
725
|
+
# @option opts :actions The action or actions affected by
|
726
|
+
# the statement. These can be symbols or strings. If
|
727
|
+
# they are strings, you can use wildcard character "*"
|
728
|
+
# to match zero or more characters in the action name.
|
729
|
+
# Symbols are expected to match methods of S3::Client.
|
730
|
+
# @option opts :excluded_actions Action or actions which are
|
731
|
+
# explicitly not affected by this statement. As with
|
732
|
+
# `:actions`, these may be symbols or strings.
|
733
|
+
# @option opts [String or array of strings] :resources The
|
734
|
+
# resource(s) affected by the statement. These can be
|
735
|
+
# expressed as ARNs (e.g. `arn:mss:s3:::mybucket/mykey`)
|
736
|
+
# or you may omit the `arn:mss:s3:::` prefix and just give
|
737
|
+
# the path as `bucket_name/key`. You may use the wildcard
|
738
|
+
# character "*" to match zero or more characters in the
|
739
|
+
# resource name.
|
740
|
+
# @option opts [ConditionBlock or Hash] :conditions
|
741
|
+
# Additional conditions that narrow the effect of the
|
742
|
+
# statement. It's typically more convenient to use the
|
743
|
+
# ConditionBuilder instance returned from Policy#allow or
|
744
|
+
# Policy#deny to add conditions to a statement.
|
745
|
+
# @see S3::Client
|
746
|
+
def initialize(opts = {})
|
747
|
+
self.sid = SecureRandom.uuid.tr('-','')
|
748
|
+
self.conditions = ConditionBlock.new
|
749
|
+
|
750
|
+
parse_options(opts)
|
751
|
+
|
752
|
+
yield(self) if block_given?
|
753
|
+
end
|
754
|
+
|
755
|
+
# Convenience method to add to the list of actions affected
|
756
|
+
# by this statement.
|
757
|
+
def include_actions(*actions)
|
758
|
+
self.actions ||= []
|
759
|
+
self.actions.push(*actions)
|
760
|
+
end
|
761
|
+
alias_method :include_action, :include_actions
|
762
|
+
|
763
|
+
# Convenience method to add to the list of actions
|
764
|
+
# explicitly not affected by this statement.
|
765
|
+
def exclude_actions(*actions)
|
766
|
+
self.excluded_actions ||= []
|
767
|
+
self.excluded_actions.push(*actions)
|
768
|
+
end
|
769
|
+
alias_method :exclude_action, :exclude_actions
|
770
|
+
|
771
|
+
# @api private
|
772
|
+
def to_h
|
773
|
+
stmt = {
|
774
|
+
"Sid" => sid,
|
775
|
+
"Effect" => Inflection.class_name(effect.to_s),
|
776
|
+
"Principal" => principals_hash,
|
777
|
+
"Resource" => (resource_arns if resource_arns),
|
778
|
+
"NotResource" => (excluded_resource_arns if excluded_resource_arns),
|
779
|
+
"Condition" => (conditions.to_h if conditions)
|
780
|
+
}
|
781
|
+
stmt.delete("Condition") if !conditions || conditions.to_h.empty?
|
782
|
+
stmt.delete("Principal") unless principals_hash
|
783
|
+
stmt.delete("Resource") unless resource_arns
|
784
|
+
stmt.delete("NotResource") unless excluded_resource_arns
|
785
|
+
if !translated_actions || translated_actions.empty?
|
786
|
+
stmt["NotAction"] = translated_excluded_actions
|
787
|
+
else
|
788
|
+
stmt["Action"] = translated_actions
|
789
|
+
end
|
790
|
+
stmt
|
791
|
+
end
|
792
|
+
|
793
|
+
protected
|
794
|
+
def parse_options(options)
|
795
|
+
options.each do |name, value|
|
796
|
+
name = Inflection.ruby_name(name.to_s)
|
797
|
+
name.sub!(/s$/,'')
|
798
|
+
send("parse_#{name}_option", value) if
|
799
|
+
respond_to?("parse_#{name}_option", true)
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
803
|
+
protected
|
804
|
+
def parse_effect_option(value)
|
805
|
+
self.effect = value
|
806
|
+
end
|
807
|
+
|
808
|
+
protected
|
809
|
+
def parse_sid_option(value)
|
810
|
+
self.sid = value
|
811
|
+
end
|
812
|
+
|
813
|
+
protected
|
814
|
+
def parse_action_option(value)
|
815
|
+
coerce_array_option(:actions, value)
|
816
|
+
end
|
817
|
+
|
818
|
+
protected
|
819
|
+
def parse_not_action_option(value)
|
820
|
+
coerce_array_option(:excluded_actions, value)
|
821
|
+
end
|
822
|
+
alias_method :parse_excluded_action_option, :parse_not_action_option
|
823
|
+
|
824
|
+
protected
|
825
|
+
def parse_principal_option(value)
|
826
|
+
if value and value.kind_of?(Hash)
|
827
|
+
value = value["MSS"] || []
|
828
|
+
end
|
829
|
+
|
830
|
+
coerce_array_option(:principals, value)
|
831
|
+
end
|
832
|
+
|
833
|
+
protected
|
834
|
+
def parse_resource_option(value)
|
835
|
+
coerce_array_option(:resources, value)
|
836
|
+
end
|
837
|
+
|
838
|
+
def parse_not_resource_option(value)
|
839
|
+
coerce_array_option(:excluded_resources, value)
|
840
|
+
end
|
841
|
+
alias_method :parse_excluded_resource_option, :parse_not_resource_option
|
842
|
+
|
843
|
+
protected
|
844
|
+
def parse_condition_option(value)
|
845
|
+
self.conditions = ConditionBlock.new(value)
|
846
|
+
end
|
847
|
+
|
848
|
+
protected
|
849
|
+
def coerce_array_option(attr, value)
|
850
|
+
if value.kind_of?(Array)
|
851
|
+
send("#{attr}=", value)
|
852
|
+
else
|
853
|
+
send("#{attr}=", [value])
|
854
|
+
end
|
855
|
+
end
|
856
|
+
|
857
|
+
protected
|
858
|
+
def principals_hash
|
859
|
+
return nil unless principals
|
860
|
+
{ "MSS" =>
|
861
|
+
principals.map do |principal|
|
862
|
+
principal == :any ? "*" : principal
|
863
|
+
end }
|
864
|
+
end
|
865
|
+
|
866
|
+
protected
|
867
|
+
def translate_action(action)
|
868
|
+
case action
|
869
|
+
when String then action
|
870
|
+
when :any then '*'
|
871
|
+
when Symbol
|
872
|
+
|
873
|
+
if self.class == Core::Policy::Statement
|
874
|
+
msg = 'symbolized action names are only accepted by service ' +
|
875
|
+
'specific policies (e.g. MSS::S3::Policy)'
|
876
|
+
raise ArgumentError, msg
|
877
|
+
end
|
878
|
+
|
879
|
+
unless self.class::ACTION_MAPPING.has_key?(action)
|
880
|
+
raise ArgumentError, "unrecognized action: #{action}"
|
881
|
+
end
|
882
|
+
|
883
|
+
self.class::ACTION_MAPPING[action]
|
884
|
+
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
protected
|
889
|
+
def translated_actions
|
890
|
+
return nil unless actions
|
891
|
+
actions.map do |action|
|
892
|
+
translate_action(action)
|
893
|
+
end
|
894
|
+
end
|
895
|
+
|
896
|
+
protected
|
897
|
+
def translated_excluded_actions
|
898
|
+
return nil unless excluded_actions
|
899
|
+
excluded_actions.map { |a| translate_action(a) }
|
900
|
+
end
|
901
|
+
|
902
|
+
protected
|
903
|
+
def resource_arns
|
904
|
+
return nil unless resources
|
905
|
+
resources.map do |resource|
|
906
|
+
case resource
|
907
|
+
when :any then "*"
|
908
|
+
else resource_arn(resource)
|
909
|
+
end
|
910
|
+
end
|
911
|
+
end
|
912
|
+
|
913
|
+
protected
|
914
|
+
def resource_arn resource
|
915
|
+
resource.to_s
|
916
|
+
end
|
917
|
+
|
918
|
+
protected
|
919
|
+
def excluded_resource_arns
|
920
|
+
return nil unless excluded_resources
|
921
|
+
excluded_resources.map do |excluded_resource|
|
922
|
+
case excluded_resource
|
923
|
+
when :any then "*"
|
924
|
+
else excluded_resource_arn(excluded_resource)
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
protected
|
930
|
+
def excluded_resource_arn excluded_resource
|
931
|
+
excluded_resource.to_s
|
932
|
+
end
|
933
|
+
|
934
|
+
end
|
935
|
+
|
936
|
+
end
|
937
|
+
end
|
938
|
+
end
|