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.
Files changed (131) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +9 -0
  3. data/LICENSE.txt +0 -0
  4. data/README.md +192 -0
  5. data/bin/mss-rb +178 -0
  6. data/ca-bundle.crt +3554 -0
  7. data/lib/mss/core/async_handle.rb +89 -0
  8. data/lib/mss/core/cacheable.rb +76 -0
  9. data/lib/mss/core/client.rb +786 -0
  10. data/lib/mss/core/collection/simple.rb +81 -0
  11. data/lib/mss/core/collection/with_limit_and_next_token.rb +70 -0
  12. data/lib/mss/core/collection/with_next_token.rb +96 -0
  13. data/lib/mss/core/collection.rb +262 -0
  14. data/lib/mss/core/configuration.rb +527 -0
  15. data/lib/mss/core/credential_providers.rb +653 -0
  16. data/lib/mss/core/data.rb +251 -0
  17. data/lib/mss/core/deprecations.rb +83 -0
  18. data/lib/mss/core/endpoints.rb +36 -0
  19. data/lib/mss/core/http/connection_pool.rb +374 -0
  20. data/lib/mss/core/http/curb_handler.rb +150 -0
  21. data/lib/mss/core/http/handler.rb +88 -0
  22. data/lib/mss/core/http/net_http_handler.rb +144 -0
  23. data/lib/mss/core/http/patch.rb +98 -0
  24. data/lib/mss/core/http/request.rb +258 -0
  25. data/lib/mss/core/http/response.rb +80 -0
  26. data/lib/mss/core/indifferent_hash.rb +87 -0
  27. data/lib/mss/core/inflection.rb +55 -0
  28. data/lib/mss/core/ini_parser.rb +41 -0
  29. data/lib/mss/core/json_client.rb +46 -0
  30. data/lib/mss/core/json_parser.rb +75 -0
  31. data/lib/mss/core/json_request_builder.rb +34 -0
  32. data/lib/mss/core/json_response_parser.rb +78 -0
  33. data/lib/mss/core/lazy_error_classes.rb +107 -0
  34. data/lib/mss/core/log_formatter.rb +426 -0
  35. data/lib/mss/core/managed_file.rb +31 -0
  36. data/lib/mss/core/meta_utils.rb +44 -0
  37. data/lib/mss/core/model.rb +61 -0
  38. data/lib/mss/core/naming.rb +29 -0
  39. data/lib/mss/core/option_grammar.rb +737 -0
  40. data/lib/mss/core/options/json_serializer.rb +81 -0
  41. data/lib/mss/core/options/validator.rb +154 -0
  42. data/lib/mss/core/options/xml_serializer.rb +117 -0
  43. data/lib/mss/core/page_result.rb +74 -0
  44. data/lib/mss/core/policy.rb +938 -0
  45. data/lib/mss/core/query_client.rb +40 -0
  46. data/lib/mss/core/query_error_parser.rb +23 -0
  47. data/lib/mss/core/query_request_builder.rb +46 -0
  48. data/lib/mss/core/query_response_parser.rb +34 -0
  49. data/lib/mss/core/region.rb +84 -0
  50. data/lib/mss/core/region_collection.rb +79 -0
  51. data/lib/mss/core/resource.rb +412 -0
  52. data/lib/mss/core/resource_cache.rb +39 -0
  53. data/lib/mss/core/response.rb +214 -0
  54. data/lib/mss/core/response_cache.rb +49 -0
  55. data/lib/mss/core/rest_error_parser.rb +23 -0
  56. data/lib/mss/core/rest_json_client.rb +39 -0
  57. data/lib/mss/core/rest_request_builder.rb +153 -0
  58. data/lib/mss/core/rest_response_parser.rb +65 -0
  59. data/lib/mss/core/rest_xml_client.rb +46 -0
  60. data/lib/mss/core/service_interface.rb +82 -0
  61. data/lib/mss/core/signers/base.rb +45 -0
  62. data/lib/mss/core/signers/cloud_front.rb +55 -0
  63. data/lib/mss/core/signers/s3.rb +158 -0
  64. data/lib/mss/core/signers/version_2.rb +71 -0
  65. data/lib/mss/core/signers/version_3.rb +85 -0
  66. data/lib/mss/core/signers/version_3_https.rb +60 -0
  67. data/lib/mss/core/signers/version_4/chunk_signed_stream.rb +190 -0
  68. data/lib/mss/core/signers/version_4.rb +227 -0
  69. data/lib/mss/core/uri_escape.rb +43 -0
  70. data/lib/mss/core/xml/frame.rb +245 -0
  71. data/lib/mss/core/xml/frame_stack.rb +84 -0
  72. data/lib/mss/core/xml/grammar.rb +306 -0
  73. data/lib/mss/core/xml/parser.rb +69 -0
  74. data/lib/mss/core/xml/root_frame.rb +64 -0
  75. data/lib/mss/core/xml/sax_handlers/libxml.rb +46 -0
  76. data/lib/mss/core/xml/sax_handlers/nokogiri.rb +55 -0
  77. data/lib/mss/core/xml/sax_handlers/ox.rb +40 -0
  78. data/lib/mss/core/xml/sax_handlers/rexml.rb +46 -0
  79. data/lib/mss/core/xml/stub.rb +122 -0
  80. data/lib/mss/core.rb +602 -0
  81. data/lib/mss/errors.rb +161 -0
  82. data/lib/mss/rails.rb +194 -0
  83. data/lib/mss/s3/access_control_list.rb +262 -0
  84. data/lib/mss/s3/acl_object.rb +263 -0
  85. data/lib/mss/s3/acl_options.rb +200 -0
  86. data/lib/mss/s3/bucket.rb +757 -0
  87. data/lib/mss/s3/bucket_collection.rb +161 -0
  88. data/lib/mss/s3/bucket_lifecycle_configuration.rb +472 -0
  89. data/lib/mss/s3/bucket_region_cache.rb +51 -0
  90. data/lib/mss/s3/bucket_tag_collection.rb +110 -0
  91. data/lib/mss/s3/bucket_version_collection.rb +78 -0
  92. data/lib/mss/s3/cipher_io.rb +119 -0
  93. data/lib/mss/s3/client/xml.rb +265 -0
  94. data/lib/mss/s3/client.rb +2076 -0
  95. data/lib/mss/s3/config.rb +60 -0
  96. data/lib/mss/s3/cors_rule.rb +107 -0
  97. data/lib/mss/s3/cors_rule_collection.rb +193 -0
  98. data/lib/mss/s3/data_options.rb +190 -0
  99. data/lib/mss/s3/encryption_utils.rb +145 -0
  100. data/lib/mss/s3/errors.rb +93 -0
  101. data/lib/mss/s3/multipart_upload.rb +353 -0
  102. data/lib/mss/s3/multipart_upload_collection.rb +75 -0
  103. data/lib/mss/s3/object_collection.rb +355 -0
  104. data/lib/mss/s3/object_metadata.rb +102 -0
  105. data/lib/mss/s3/object_upload_collection.rb +76 -0
  106. data/lib/mss/s3/object_version.rb +153 -0
  107. data/lib/mss/s3/object_version_collection.rb +88 -0
  108. data/lib/mss/s3/paginated_collection.rb +74 -0
  109. data/lib/mss/s3/policy.rb +73 -0
  110. data/lib/mss/s3/prefix_and_delimiter_collection.rb +46 -0
  111. data/lib/mss/s3/prefixed_collection.rb +84 -0
  112. data/lib/mss/s3/presign_v4.rb +135 -0
  113. data/lib/mss/s3/presigned_post.rb +574 -0
  114. data/lib/mss/s3/region_detection.rb +75 -0
  115. data/lib/mss/s3/request.rb +61 -0
  116. data/lib/mss/s3/s3_object.rb +1795 -0
  117. data/lib/mss/s3/tree/branch_node.rb +67 -0
  118. data/lib/mss/s3/tree/child_collection.rb +103 -0
  119. data/lib/mss/s3/tree/leaf_node.rb +93 -0
  120. data/lib/mss/s3/tree/node.rb +21 -0
  121. data/lib/mss/s3/tree/parent.rb +86 -0
  122. data/lib/mss/s3/tree.rb +115 -0
  123. data/lib/mss/s3/uploaded_part.rb +81 -0
  124. data/lib/mss/s3/uploaded_part_collection.rb +83 -0
  125. data/lib/mss/s3/website_configuration.rb +101 -0
  126. data/lib/mss/s3.rb +161 -0
  127. data/lib/mss/version.rb +16 -0
  128. data/lib/mss-sdk.rb +2 -0
  129. data/lib/mss.rb +14 -0
  130. data/rails/init.rb +14 -0
  131. 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