aws-s3 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/COPYING +19 -0
- data/INSTALL +35 -0
- data/README +529 -0
- data/Rakefile +284 -0
- data/bin/s3sh +4 -0
- data/bin/setup.rb +10 -0
- data/lib/aws/s3.rb +64 -0
- data/lib/aws/s3/acl.rb +631 -0
- data/lib/aws/s3/authentication.rb +218 -0
- data/lib/aws/s3/base.rb +232 -0
- data/lib/aws/s3/bittorrent.rb +58 -0
- data/lib/aws/s3/bucket.rb +323 -0
- data/lib/aws/s3/connection.rb +212 -0
- data/lib/aws/s3/error.rb +69 -0
- data/lib/aws/s3/exceptions.rb +130 -0
- data/lib/aws/s3/extensions.rb +186 -0
- data/lib/aws/s3/logging.rb +163 -0
- data/lib/aws/s3/object.rb +565 -0
- data/lib/aws/s3/owner.rb +44 -0
- data/lib/aws/s3/parsing.rb +138 -0
- data/lib/aws/s3/response.rb +180 -0
- data/lib/aws/s3/service.rb +43 -0
- data/lib/aws/s3/version.rb +12 -0
- data/support/faster-xml-simple/lib/faster_xml_simple.rb +115 -0
- data/support/faster-xml-simple/test/regression_test.rb +16 -0
- data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +22 -0
- data/support/rdoc/code_info.rb +211 -0
- data/test/acl_test.rb +243 -0
- data/test/authentication_test.rb +96 -0
- data/test/base_test.rb +143 -0
- data/test/bucket_test.rb +48 -0
- data/test/connection_test.rb +120 -0
- data/test/error_test.rb +75 -0
- data/test/extensions_test.rb +282 -0
- data/test/fixtures.rb +89 -0
- data/test/fixtures/buckets.yml +102 -0
- data/test/fixtures/errors.yml +34 -0
- data/test/fixtures/headers.yml +3 -0
- data/test/fixtures/logging.yml +15 -0
- data/test/fixtures/policies.yml +16 -0
- data/test/logging_test.rb +36 -0
- data/test/mocks/base.rb +89 -0
- data/test/object_test.rb +177 -0
- data/test/parsing_test.rb +82 -0
- data/test/remote/acl_test.rb +117 -0
- data/test/remote/bittorrent_test.rb +45 -0
- data/test/remote/bucket_test.rb +127 -0
- data/test/remote/logging_test.rb +82 -0
- data/test/remote/object_test.rb +267 -0
- data/test/remote/test_file.data +0 -0
- data/test/remote/test_helper.rb +30 -0
- data/test/response_test.rb +70 -0
- data/test/service_test.rb +26 -0
- data/test/test_helper.rb +82 -0
- metadata +125 -0
data/Rakefile
ADDED
@@ -0,0 +1,284 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
require 'rake/rdoctask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
|
8
|
+
require File.dirname(__FILE__) + '/lib/aws/s3'
|
9
|
+
|
10
|
+
def library_root
|
11
|
+
File.dirname(__FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
task :default => :test
|
15
|
+
|
16
|
+
Rake::TestTask.new do |test|
|
17
|
+
test.pattern = 'test/*_test.rb'
|
18
|
+
test.verbose = true
|
19
|
+
end
|
20
|
+
|
21
|
+
namespace :doc do
|
22
|
+
Rake::RDocTask.new do |rdoc|
|
23
|
+
rdoc.rdoc_dir = 'doc'
|
24
|
+
rdoc.title = "AWS::S3 -- Support for Amazon S3's REST api"
|
25
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
26
|
+
rdoc.rdoc_files.include('README')
|
27
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
28
|
+
end
|
29
|
+
|
30
|
+
task :rdoc => 'doc:readme'
|
31
|
+
|
32
|
+
task :refresh => :rerdoc do
|
33
|
+
system 'open doc/index.html'
|
34
|
+
end
|
35
|
+
|
36
|
+
task :readme do
|
37
|
+
require 'support/rdoc/code_info'
|
38
|
+
RDoc::CodeInfo.parse('lib/**/*.rb')
|
39
|
+
|
40
|
+
strip_comments = lambda {|comment| comment.gsub(/^# ?/, '')}
|
41
|
+
docs_for = lambda do |location|
|
42
|
+
info = RDoc::CodeInfo.for(location)
|
43
|
+
raise RuntimeError, "Couldn't find documentation for `#{location}'" unless info
|
44
|
+
strip_comments[info.comment]
|
45
|
+
end
|
46
|
+
|
47
|
+
File.open('README', 'w') do |file|
|
48
|
+
file.write ERB.new(IO.read('README.erb')).result(binding)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
namespace :dist do
|
54
|
+
spec = Gem::Specification.new do |s|
|
55
|
+
s.name = 'aws-s3'
|
56
|
+
s.version = Gem::Version.new(AWS::S3::Version)
|
57
|
+
s.summary = "Client library for Amazon's Simple Storage Service's REST API"
|
58
|
+
s.description = s.summary
|
59
|
+
s.email = 'marcel@vernix.org'
|
60
|
+
s.author = 'Marcel Molina Jr.'
|
61
|
+
s.has_rdoc = true
|
62
|
+
s.extra_rdoc_files = %w(README COPYING INSTALL)
|
63
|
+
s.homepage = 'http://amazon.rubyforge.org'
|
64
|
+
s.rubyforge_project = 'amazon'
|
65
|
+
s.files = FileList['Rakefile', 'lib/**/*.rb', 'bin/*', 'support/**/*.rb']
|
66
|
+
s.executables << 's3sh'
|
67
|
+
s.test_files = Dir['test/**/*']
|
68
|
+
|
69
|
+
s.add_dependency 'xml-simple'
|
70
|
+
s.add_dependency 'builder'
|
71
|
+
s.rdoc_options = ['--title', "AWS::S3 -- Support for Amazon S3's REST api",
|
72
|
+
'--main', 'README',
|
73
|
+
'--line-numbers', '--inline-source']
|
74
|
+
end
|
75
|
+
|
76
|
+
# Regenerate README before packaging
|
77
|
+
task :package => 'doc:readme'
|
78
|
+
Rake::GemPackageTask.new(spec) do |pkg|
|
79
|
+
pkg.need_tar_gz = true
|
80
|
+
pkg.package_files.include('{lib,script,test,support}/**/*')
|
81
|
+
pkg.package_files.include('README')
|
82
|
+
pkg.package_files.include('COPYING')
|
83
|
+
pkg.package_files.include('INSTALL')
|
84
|
+
pkg.package_files.include('Rakefile')
|
85
|
+
end
|
86
|
+
|
87
|
+
desc 'Install with gems'
|
88
|
+
task :install => :repackage do
|
89
|
+
sh "sudo gem i pkg/#{spec.name}-#{spec.version}.gem"
|
90
|
+
end
|
91
|
+
|
92
|
+
desc 'Uninstall gem'
|
93
|
+
task :uninstall do
|
94
|
+
sh "sudo gem uninstall #{spec.name} -x"
|
95
|
+
end
|
96
|
+
|
97
|
+
desc 'Reinstall gem'
|
98
|
+
task :reinstall => [:uninstall, :install]
|
99
|
+
|
100
|
+
task :confirm_release do
|
101
|
+
print "Releasing version #{spec.version}. Are you sure you want to proceed? [Yn] "
|
102
|
+
abort if STDIN.getc == ?n
|
103
|
+
end
|
104
|
+
|
105
|
+
desc 'Tag release'
|
106
|
+
task :tag do
|
107
|
+
svn_root = 'svn+ssh://marcel@rubyforge.org/var/svn/amazon/s3'
|
108
|
+
sh %(svn cp #{svn_root}/trunk #{svn_root}/tags/rel-#{spec.version} -m "Tag #{spec.name} release #{spec.version}")
|
109
|
+
end
|
110
|
+
|
111
|
+
desc 'Update changelog to include a release marker'
|
112
|
+
task :add_release_marker_to_changelog do
|
113
|
+
changelog = IO.read('CHANGELOG')
|
114
|
+
changelog.sub!(/^trunk:/, "#{spec.version}:")
|
115
|
+
|
116
|
+
File.open('CHANGELOG', 'w') do |file|
|
117
|
+
file.write "trunk:\n\n#{changelog}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
task :commit_changelog do
|
122
|
+
sh %(svn ci CHANGELOG -m "Bump changelog version marker for release")
|
123
|
+
end
|
124
|
+
|
125
|
+
desc 'Push a release to rubyforge'
|
126
|
+
task :release => [:confirm_release, :clean, :add_release_marker_to_changelog, :package, :commit_changelog, :tag] do
|
127
|
+
require 'rubyforge'
|
128
|
+
package = File.join('pkg', "#{spec.name}-#{spec.version}")
|
129
|
+
|
130
|
+
rubyforge = RubyForge.new
|
131
|
+
rubyforge.login
|
132
|
+
|
133
|
+
version_already_released = lambda do
|
134
|
+
releases = rubyforge.config['rubyforge']['release_ids']
|
135
|
+
releases.has_key?(spec.name) && releases[spec.name][spec.version]
|
136
|
+
end
|
137
|
+
|
138
|
+
abort("Release #{spec.version} already exists!") if version_already_released.call
|
139
|
+
|
140
|
+
if release_id = rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version, "#{package}.tar.gz")
|
141
|
+
rubyforge.add_file(spec.rubyforge_project, spec.name, release_id, "#{package}.gem")
|
142
|
+
else
|
143
|
+
puts 'Release failed!'
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
task :spec do
|
148
|
+
puts spec.to_ruby
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
desc 'Check code to test ratio'
|
153
|
+
task :stats do
|
154
|
+
library_files = FileList["#{library_root}/lib/**/*.rb"]
|
155
|
+
test_files = FileList["#{library_root}/test/**/*_test.rb"]
|
156
|
+
count_code_lines = Proc.new do |lines|
|
157
|
+
lines.inject(0) do |code_lines, line|
|
158
|
+
next code_lines if [/^\s*$/, /^\s*#/].any? {|non_code_line| non_code_line === line}
|
159
|
+
code_lines + 1
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
count_code_lines_for_files = Proc.new do |files|
|
164
|
+
files.inject(0) {|code_lines, file| code_lines + count_code_lines[IO.read(file)]}
|
165
|
+
end
|
166
|
+
|
167
|
+
library_code_lines = count_code_lines_for_files[library_files]
|
168
|
+
test_code_lines = count_code_lines_for_files[test_files]
|
169
|
+
ratio = Proc.new { sprintf('%.2f', test_code_lines.to_f / library_code_lines)}
|
170
|
+
|
171
|
+
puts "Code LOC: #{library_code_lines} Test LOC: #{test_code_lines} Code to Test Ratio: 1:#{ratio.call}"
|
172
|
+
end
|
173
|
+
|
174
|
+
namespace :test do
|
175
|
+
desc 'Check test coverage'
|
176
|
+
task :coverage do
|
177
|
+
system("rcov --sort coverage #{File.join(library_root, 'test/*_test.rb')}")
|
178
|
+
show_test_coverage_results
|
179
|
+
end
|
180
|
+
|
181
|
+
Rake::TestTask.new(:remote) do |test|
|
182
|
+
test.pattern = 'test/remote/*_test.rb'
|
183
|
+
test.verbose = true
|
184
|
+
end
|
185
|
+
|
186
|
+
Rake::TestTask.new(:all) do |test|
|
187
|
+
test.pattern = 'test/**/*_test.rb'
|
188
|
+
test.verbose = true
|
189
|
+
end
|
190
|
+
|
191
|
+
desc 'Check test coverage of full stack remote tests'
|
192
|
+
task :full_coverage do
|
193
|
+
system("rcov --sort coverage #{File.join(library_root, 'test/remote/*_test.rb')} #{File.join(library_root, 'test/*_test.rb')}")
|
194
|
+
show_test_coverage_results
|
195
|
+
end
|
196
|
+
|
197
|
+
def show_test_coverage_results
|
198
|
+
system("open #{File.join(library_root, 'coverage/index.html')}") if PLATFORM['darwin']
|
199
|
+
end
|
200
|
+
|
201
|
+
desc 'Remove coverage products'
|
202
|
+
task :clobber_coverage do
|
203
|
+
rm_r 'coverage' rescue nil
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
namespace :todo do
|
208
|
+
class << TODOS = IO.read(File.join(library_root, 'TODO'))
|
209
|
+
def items
|
210
|
+
split("\n").grep(/^\[\s|X\]/)
|
211
|
+
end
|
212
|
+
|
213
|
+
def completed
|
214
|
+
find_items_matching(/^\[X\]/)
|
215
|
+
end
|
216
|
+
|
217
|
+
def uncompleted
|
218
|
+
find_items_matching(/^\[\s\]/)
|
219
|
+
end
|
220
|
+
|
221
|
+
def find_items_matching(regexp)
|
222
|
+
items.grep(regexp).instance_eval do
|
223
|
+
def display
|
224
|
+
puts map {|item| "* #{item.sub(/^\[[^\]]\]\s/, '')}"}
|
225
|
+
end
|
226
|
+
self
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
desc 'Completed todo items'
|
232
|
+
task :completed do
|
233
|
+
TODOS.completed.display
|
234
|
+
end
|
235
|
+
|
236
|
+
desc 'Incomplete todo items'
|
237
|
+
task :uncompleted do
|
238
|
+
TODOS.uncompleted.display
|
239
|
+
end
|
240
|
+
end if File.exists?(File.join(library_root, 'TODO'))
|
241
|
+
|
242
|
+
namespace :site do
|
243
|
+
require 'erb'
|
244
|
+
require 'rdoc/markup/simple_markup'
|
245
|
+
require 'rdoc/markup/simple_markup/to_html'
|
246
|
+
|
247
|
+
readme = lambda { IO.read('README')[/^== Getting started\n(.*)/m, 1] }
|
248
|
+
ruby_code = /^\s*\n(\s+\S+.*?)\n\s*\n/m
|
249
|
+
|
250
|
+
readme_to_html = lambda do
|
251
|
+
handler = SM::ToHtml.new
|
252
|
+
handler.instance_eval do
|
253
|
+
require 'syntax'
|
254
|
+
require 'syntax/convertors/html'
|
255
|
+
def accept_verbatim(am, fragment)
|
256
|
+
syntax = Syntax::Convertors::HTML.for_syntax('ruby')
|
257
|
+
@res << %(<div class="ruby">#{syntax.convert(fragment.txt, true)}</div>)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
SM::SimpleMarkup.new.convert(readme.call, handler)
|
261
|
+
end
|
262
|
+
|
263
|
+
desc 'Regenerate the public website page'
|
264
|
+
task :build => 'doc:readme' do
|
265
|
+
File.open('site/public/index.html', 'w') do |file|
|
266
|
+
erb_data = {}
|
267
|
+
erb_data[:readme] = readme_to_html.call
|
268
|
+
file.write ERB.new(IO.read('site/index.erb')).result(binding)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
task :refresh => :build do
|
273
|
+
system 'open site/public/index.html'
|
274
|
+
end
|
275
|
+
|
276
|
+
desc 'Update the live website'
|
277
|
+
task :deploy => :build do
|
278
|
+
site_files = FileList['site/public/*']
|
279
|
+
site_files.delete_if {|file| File.directory?(file)}
|
280
|
+
sh %(scp #{site_files.join ' '} marcel@rubyforge.org:/var/www/gforge-projects/amazon/)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
task :clean => ['dist:clobber_package', 'doc:clobber_rdoc', 'test:clobber_coverage']
|
data/bin/s3sh
ADDED
data/bin/setup.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
if ENV['AMAZON_ACCESS_KEY_ID'] && ENV['AMAZON_SECRET_ACCESS_KEY']
|
3
|
+
AWS::S3::Base.establish_connection!(
|
4
|
+
:access_key_id => ENV['AMAZON_ACCESS_KEY_ID'],
|
5
|
+
:secret_access_key => ENV['AMAZON_SECRET_ACCESS_KEY']
|
6
|
+
)
|
7
|
+
end
|
8
|
+
|
9
|
+
require File.dirname(__FILE__) + '/../test/fixtures'
|
10
|
+
include AWS::S3
|
data/lib/aws/s3.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'cgi'
|
3
|
+
require 'uri'
|
4
|
+
require 'openssl'
|
5
|
+
require 'digest/sha1'
|
6
|
+
require 'net/https'
|
7
|
+
require 'time'
|
8
|
+
require 'date'
|
9
|
+
require 'generator'
|
10
|
+
|
11
|
+
$:.unshift(File.dirname(__FILE__))
|
12
|
+
require 's3/extensions'
|
13
|
+
require_library_or_gem 'builder' unless defined? Builder
|
14
|
+
|
15
|
+
require 's3/base'
|
16
|
+
require 's3/version'
|
17
|
+
require 's3/parsing'
|
18
|
+
require 's3/acl'
|
19
|
+
require 's3/logging'
|
20
|
+
require 's3/bittorrent'
|
21
|
+
require 's3/service'
|
22
|
+
require 's3/owner'
|
23
|
+
require 's3/bucket'
|
24
|
+
require 's3/object'
|
25
|
+
require 's3/error'
|
26
|
+
require 's3/exceptions'
|
27
|
+
require 's3/connection'
|
28
|
+
require 's3/authentication'
|
29
|
+
require 's3/response'
|
30
|
+
|
31
|
+
AWS::S3::Base.class_eval do
|
32
|
+
include AWS::S3::Connection::Management
|
33
|
+
end
|
34
|
+
|
35
|
+
AWS::S3::Bucket.class_eval do
|
36
|
+
include AWS::S3::Logging::Management
|
37
|
+
include AWS::S3::ACL::Bucket
|
38
|
+
end
|
39
|
+
|
40
|
+
AWS::S3::S3Object.class_eval do
|
41
|
+
include AWS::S3::ACL::S3Object
|
42
|
+
include AWS::S3::BitTorrent
|
43
|
+
end
|
44
|
+
|
45
|
+
# XmlSimple is a very convenient way of dealing with the xml needs of this library, but unfortunately
|
46
|
+
# it is powered by REXML which is both very slow (even for me who does not privilege performance)
|
47
|
+
# and consumes way too much memory (beach balls a MacBookPro with 2GB of memory for 2 minutes while parsing 60KB of xml).
|
48
|
+
#
|
49
|
+
# So, if libxml is installed, we use the FasterXmlSimple library, that provides most of the functionality of XmlSimple
|
50
|
+
# except it uses the xml/libxml library for xml parsing. If libxml isn't installed, we just fall back on
|
51
|
+
# XmlSimple.
|
52
|
+
AWS::S3::Parsing.parser =
|
53
|
+
begin
|
54
|
+
# N.B. Push, don't shift
|
55
|
+
$:.push(File.join(File.dirname(__FILE__), '..', '..', 'support', 'faster-xml-simple', 'lib'))
|
56
|
+
require_library_or_gem 'faster_xml_simple'
|
57
|
+
# Older version of libxml aren't stable (bus error when requesting attributes that don't exist) so we
|
58
|
+
# have to use a version greater than '0.3.8.2'.
|
59
|
+
raise LoadError unless XML::Parser::VERSION > '0.3.8.2'
|
60
|
+
FasterXmlSimple
|
61
|
+
rescue LoadError
|
62
|
+
require_library_or_gem 'xmlsimple' unless defined? XmlSimple
|
63
|
+
XmlSimple
|
64
|
+
end
|
data/lib/aws/s3/acl.rb
ADDED
@@ -0,0 +1,631 @@
|
|
1
|
+
module AWS
|
2
|
+
module S3
|
3
|
+
# By default buckets are private. This means that only the owner has access rights to the bucket and its objects.
|
4
|
+
# Objects in that bucket inherit the permission of the bucket unless otherwise specified. When an object is private, the owner can
|
5
|
+
# generate a signed url that exposes the object to anyone who has that url. Alternatively, buckets and objects can be given other
|
6
|
+
# access levels. Several canned access levels are defined:
|
7
|
+
#
|
8
|
+
# * <tt>:private</tt> - Owner gets FULL_CONTROL. No one else has any access rights. This is the default.
|
9
|
+
# * <tt>:public_read</tt> - Owner gets FULL_CONTROL and the anonymous principal is granted READ access. If this policy is used on an object, it can be read from a browser with no authentication.
|
10
|
+
# * <tt>:public_read_write</tt> - Owner gets FULL_CONTROL, the anonymous principal is granted READ and WRITE access. This is a useful policy to apply to a bucket, if you intend for any anonymous user to PUT objects into the bucket.
|
11
|
+
# * <tt>:authenticated_read</tt> - Owner gets FULL_CONTROL, and any principal authenticated as a registered Amazon S3 user is granted READ access.
|
12
|
+
#
|
13
|
+
# You can set a canned access level when you create a bucket or an object by using the <tt>:access</tt> option:
|
14
|
+
#
|
15
|
+
# S3Object.store(
|
16
|
+
# 'kiss.jpg',
|
17
|
+
# data,
|
18
|
+
# 'marcel',
|
19
|
+
# :content_type => 'image/jpg', :access => :public_read
|
20
|
+
# )
|
21
|
+
#
|
22
|
+
# Since the image we created is publicly readable, we can access it directly from a browser by going to the corresponding bucket name
|
23
|
+
# and specifying the object's key without a special authenticated url:
|
24
|
+
#
|
25
|
+
# http://s3.amazonaws.com/marcel/kiss.jpg
|
26
|
+
#
|
27
|
+
# ==== Building costum access policies
|
28
|
+
#
|
29
|
+
# For both buckets and objects, you can use the <tt>acl</tt> method to see its access control policy:
|
30
|
+
#
|
31
|
+
# policy = S3Object.acl('kiss.jpg', 'marcel')
|
32
|
+
# pp policy.grants
|
33
|
+
# [#<AWS::S3::ACL::Grant FULL_CONTROL to noradio>,
|
34
|
+
# #<AWS::S3::ACL::Grant READ to AllUsers Group>]
|
35
|
+
#
|
36
|
+
# Policies are made up of one or more grants which grant a specific permission to some grantee. Here we see the default FULL_CONTROL grant
|
37
|
+
# to the owner of this object. There is also READ permission granted to the Allusers Group, which means anyone has read access for the object.
|
38
|
+
#
|
39
|
+
# Say we wanted to grant access to anyone to read the access policy of this object. The current READ permission only grants them permission to read
|
40
|
+
# the object itself (for example, from a browser) but it does not allow them to read the access policy. For that we will need to grant the AllUsers group the READ_ACP permission.
|
41
|
+
#
|
42
|
+
# First we'll create a new grant object:
|
43
|
+
#
|
44
|
+
# grant = ACL::Grant.new
|
45
|
+
# # => #<AWS::S3::ACL::Grant (permission) to (grantee)>
|
46
|
+
# grant.permission = 'READ_ACP'
|
47
|
+
#
|
48
|
+
# Now we need to indicate who this grant is for. In other words, who the grantee is:
|
49
|
+
#
|
50
|
+
# grantee = ACL::Grantee.new
|
51
|
+
# # => #<AWS::S3::ACL::Grantee (xsi not set yet)>
|
52
|
+
#
|
53
|
+
# There are three ways to specify a grantee: 1) by their internal amazon id, such as the one returned with an object's Owner,
|
54
|
+
# 2) by their Amazon account email address or 3) by specifying a group. As of this writing you can not create custom groups, but
|
55
|
+
# Amazon does provide three already: AllUsers, Authenticated and LogDelivery. In this case we want to provide the grant to all users.
|
56
|
+
# This effectively means "anyone".
|
57
|
+
#
|
58
|
+
# grantee.group = 'AllUsers'
|
59
|
+
#
|
60
|
+
# Now that our grantee is setup, we'll associate it with the grant:
|
61
|
+
#
|
62
|
+
# grant.grantee = grantee
|
63
|
+
# grant
|
64
|
+
# # => #<AWS::S3::ACL::Grant READ_ACP to AllUsers Group>
|
65
|
+
#
|
66
|
+
# Are grant has all the information we need. Now that it's ready, we'll add it on to the object's access control policy's list of grants:
|
67
|
+
#
|
68
|
+
# policy.grants << grant
|
69
|
+
# pp policy.grants
|
70
|
+
# [#<AWS::S3::ACL::Grant FULL_CONTROL to noradio>,
|
71
|
+
# #<AWS::S3::ACL::Grant READ to AllUsers Group>,
|
72
|
+
# #<AWS::S3::ACL::Grant READ_ACP to AllUsers Group>]
|
73
|
+
#
|
74
|
+
# Now that the policy has the new grant, we reuse the <tt>acl</tt> method to persist the policy change:
|
75
|
+
#
|
76
|
+
# S3Object.acl('kiss.jpg', 'marcel', policy)
|
77
|
+
#
|
78
|
+
# If we fetch the object's policy again, we see that the grant has been added:
|
79
|
+
#
|
80
|
+
# pp S3Object.acl('kiss.jpg', 'marcel').grants
|
81
|
+
# [#<AWS::S3::ACL::Grant FULL_CONTROL to noradio>,
|
82
|
+
# #<AWS::S3::ACL::Grant READ to AllUsers Group>,
|
83
|
+
# #<AWS::S3::ACL::Grant READ_ACP to AllUsers Group>]
|
84
|
+
#
|
85
|
+
# If we were to access this object's acl url from a browser:
|
86
|
+
#
|
87
|
+
# http://s3.amazonaws.com/marcel/kiss.jpg?acl
|
88
|
+
#
|
89
|
+
# we would be shown its access control policy.
|
90
|
+
#
|
91
|
+
# ==== Pre-prepared grants
|
92
|
+
#
|
93
|
+
# Alternatively, the ACL::Grant class defines a set of stock grant policies that you can fetch by name. In most cases, you can
|
94
|
+
# just use one of these pre-prepared grants rather than building grants by hand. Two of these stock policies are <tt>:public_read</tt>
|
95
|
+
# and <tt>:public_read_acp</tt>, which happen to be the two grants that we built by hand above. In this case we could have simply written:
|
96
|
+
#
|
97
|
+
# policy.grants << ACL::Grant.grant(:public_read)
|
98
|
+
# policy.grants << ACL::Grant.grant(:public_read_acp)
|
99
|
+
# S3Object.acl('kiss.jpg', 'marcel', policy)
|
100
|
+
#
|
101
|
+
# The full details can be found in ACL::Policy, ACL::Grant and ACL::Grantee.
|
102
|
+
module ACL
|
103
|
+
# The ACL::Policy class lets you inspect and modify access controls for buckets and objects.
|
104
|
+
# A policy is made up of one or more Grants which specify a permission and a Grantee to whom that permission is granted.
|
105
|
+
#
|
106
|
+
# Buckets and objects are given a default access policy which contains one grant permitting the owner of the bucket or object
|
107
|
+
# FULL_CONTROL over its contents. This means they can read the object, write to the object, as well as read and write its
|
108
|
+
# policy.
|
109
|
+
#
|
110
|
+
# The <tt>acl</tt> method for both buckets and objects returns the policy object for that entity:
|
111
|
+
#
|
112
|
+
# policy = Bucket.acl('some-bucket')
|
113
|
+
#
|
114
|
+
# The <tt>grants</tt> method of a policy exposes its grants. You can treat this collection as an array and push new grants onto it:
|
115
|
+
#
|
116
|
+
# policy.grants << grant
|
117
|
+
#
|
118
|
+
# Check the documentation for Grant and Grantee for more details on how to create new grants.
|
119
|
+
class Policy
|
120
|
+
include SelectiveAttributeProxy #:nodoc:
|
121
|
+
attr_accessor :owner, :grants
|
122
|
+
|
123
|
+
def initialize(attributes = {})
|
124
|
+
@attributes = attributes
|
125
|
+
@grants = [].extend(GrantListExtensions)
|
126
|
+
extract_owner! if owner?
|
127
|
+
extract_grants! if grants?
|
128
|
+
end
|
129
|
+
|
130
|
+
# The xml representation of the policy.
|
131
|
+
def to_xml
|
132
|
+
Builder.new(owner, grants).to_s
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def owner?
|
138
|
+
attributes.has_key?('owner') || !owner.nil?
|
139
|
+
end
|
140
|
+
|
141
|
+
def grants?
|
142
|
+
(attributes.has_key?('access_control_list') && attributes['access_control_list']['grant']) || !grants.empty?
|
143
|
+
end
|
144
|
+
|
145
|
+
def extract_owner!
|
146
|
+
@owner = Owner.new(attributes.delete('owner'))
|
147
|
+
end
|
148
|
+
|
149
|
+
def extract_grants!
|
150
|
+
attributes['access_control_list']['grant'].each do |grant|
|
151
|
+
grants << Grant.new(grant)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
module GrantListExtensions #:nodoc:
|
156
|
+
def include?(grant)
|
157
|
+
case grant
|
158
|
+
when Symbol
|
159
|
+
super(ACL::Grant.grant(grant))
|
160
|
+
else
|
161
|
+
super
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def delete(grant)
|
166
|
+
case grant
|
167
|
+
when Symbol
|
168
|
+
super(ACL::Grant.grant(grant))
|
169
|
+
else
|
170
|
+
super
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class Builder < XmlGenerator #:nodoc:
|
176
|
+
attr_reader :owner, :grants
|
177
|
+
def initialize(owner, grants)
|
178
|
+
@owner = owner
|
179
|
+
@grants = grants.uniq # There could be some duplicate grants
|
180
|
+
super()
|
181
|
+
end
|
182
|
+
|
183
|
+
def build
|
184
|
+
xml.tag!('AccessControlPolicy', 'xmlns' => 'http://s3.amazonaws.com/doc/2006-03-01/') do
|
185
|
+
xml.Owner do
|
186
|
+
xml.ID owner.id
|
187
|
+
xml.DisplayName owner.display_name
|
188
|
+
end
|
189
|
+
|
190
|
+
xml.AccessControlList do
|
191
|
+
xml << grants.map {|grant| grant.to_xml}.join("\n")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# A Policy is made up of one or more Grant objects. A grant sets a specific permission and grants it to the associated grantee.
|
199
|
+
#
|
200
|
+
# When creating a new grant to add to a policy, you need only set its permission and then associate with a Grantee.
|
201
|
+
#
|
202
|
+
# grant = ACL::Grant.new
|
203
|
+
# => #<AWS::S3::ACL::Grant (permission) to (grantee)>
|
204
|
+
#
|
205
|
+
# Here we see that neither the permission nor the grantee have been set. Let's make this grant provide the READ permission.
|
206
|
+
#
|
207
|
+
# grant.permission = 'READ'
|
208
|
+
# grant
|
209
|
+
# => #<AWS::S3::ACL::Grant READ to (grantee)>
|
210
|
+
#
|
211
|
+
# Now let's assume we have a grantee to the AllUsers group already set up. Just associate that grantee with our grant.
|
212
|
+
#
|
213
|
+
# grant.grantee = all_users_group_grantee
|
214
|
+
# grant
|
215
|
+
# => #<AWS::S3::ACL::Grant READ to AllUsers Group>
|
216
|
+
#
|
217
|
+
# And now are grant is complete. It provides READ permission to the AllUsers group, effectively making this object publicly readable
|
218
|
+
# without any authorization.
|
219
|
+
#
|
220
|
+
# Assuming we have some object's policy available in a local variable called <tt>policy</tt>, we can now add this grant onto its
|
221
|
+
# collection of grants.
|
222
|
+
#
|
223
|
+
# policy.grants << grant
|
224
|
+
#
|
225
|
+
# And then we send the updated policy to the S3 servers.
|
226
|
+
#
|
227
|
+
# some_s3object.acl(policy)
|
228
|
+
class Grant
|
229
|
+
include SelectiveAttributeProxy #:nodoc:
|
230
|
+
constant :VALID_PERMISSIONS, %w(READ WRITE READ_ACP WRITE_ACP FULL_CONTROL)
|
231
|
+
attr_accessor :grantee
|
232
|
+
|
233
|
+
class << self
|
234
|
+
# Returns stock grants with name <tt>type</tt>.
|
235
|
+
#
|
236
|
+
# public_read_grant = ACL::Grant.grant :public_read
|
237
|
+
# => #<AWS::S3::ACL::Grant READ to AllUsers Group>
|
238
|
+
#
|
239
|
+
# Valid stock grant types are:
|
240
|
+
#
|
241
|
+
# * <tt>:authenticated_read</tt>
|
242
|
+
# * <tt>:authenticated_read_acp</tt>
|
243
|
+
# * <tt>:authenticated_write</tt>
|
244
|
+
# * <tt>:authenticated_write_acp</tt>
|
245
|
+
# * <tt>:logging_read</tt>
|
246
|
+
# * <tt>:logging_read_acp</tt>
|
247
|
+
# * <tt>:logging_write</tt>
|
248
|
+
# * <tt>:logging_write_acp</tt>
|
249
|
+
# * <tt>:public_read</tt>
|
250
|
+
# * <tt>:public_read_acp</tt>
|
251
|
+
# * <tt>:public_write</tt>
|
252
|
+
# * <tt>:public_write_acp</tt>
|
253
|
+
def grant(type)
|
254
|
+
case type
|
255
|
+
when *stock_grant_map.keys
|
256
|
+
build_stock_grant_for type
|
257
|
+
else
|
258
|
+
raise ArgumentError, "Unknown grant type `#{type}'"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
private
|
263
|
+
def stock_grant_map
|
264
|
+
grant = lambda {|permission, group| {:permission => permission, :group => group}}
|
265
|
+
groups = {:public => 'AllUsers', :authenticated => 'Authenticated', :logging => 'LogDelivery'}
|
266
|
+
permissions = %w(READ WRITE READ_ACP WRITE_ACP)
|
267
|
+
stock_grants = {}
|
268
|
+
groups.each do |grant_group_name, group_name|
|
269
|
+
permissions.each do |permission|
|
270
|
+
stock_grants["#{grant_group_name}_#{permission.downcase}".to_sym] = grant[permission, group_name]
|
271
|
+
end
|
272
|
+
end
|
273
|
+
stock_grants
|
274
|
+
end
|
275
|
+
memoized :stock_grant_map
|
276
|
+
|
277
|
+
def build_stock_grant_for(type)
|
278
|
+
stock_grant = stock_grant_map[type]
|
279
|
+
grant = new do |g|
|
280
|
+
g.permission = stock_grant[:permission]
|
281
|
+
end
|
282
|
+
grant.grantee = Grantee.new do |gr|
|
283
|
+
gr.group = stock_grant[:group]
|
284
|
+
end
|
285
|
+
grant
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def initialize(attributes = {})
|
290
|
+
attributes = {'permission' => nil}.merge(attributes)
|
291
|
+
@attributes = attributes
|
292
|
+
extract_grantee!
|
293
|
+
yield self if block_given?
|
294
|
+
end
|
295
|
+
|
296
|
+
# Set the permission for this grant.
|
297
|
+
#
|
298
|
+
# grant.permission = 'READ'
|
299
|
+
# grant
|
300
|
+
# => #<AWS::S3::ACL::Grant READ to (grantee)>
|
301
|
+
#
|
302
|
+
# If the specified permisison level is not valid, an <tt>InvalidAccessControlLevel</tt> exception will be raised.
|
303
|
+
def permission=(permission_level)
|
304
|
+
unless self.class.valid_permissions.include?(permission_level)
|
305
|
+
raise InvalidAccessControlLevel.new(self.class.valid_permissions, permission_level)
|
306
|
+
end
|
307
|
+
attributes['permission'] = permission_level
|
308
|
+
end
|
309
|
+
|
310
|
+
# The xml representation of this grant.
|
311
|
+
def to_xml
|
312
|
+
Builder.new(permission, grantee).to_s
|
313
|
+
end
|
314
|
+
|
315
|
+
def inspect #:nodoc:
|
316
|
+
"#<#{self.class}:##{object_id} #{self}>"
|
317
|
+
end
|
318
|
+
|
319
|
+
def to_s #:nodoc:
|
320
|
+
[permission || '(permission)', 'to', grantee ? grantee.type_representation : '(grantee)'].join ' '
|
321
|
+
end
|
322
|
+
|
323
|
+
def eql?(grant) #:nodoc:
|
324
|
+
# This won't work for an unposted AmazonCustomerByEmail because of the normalization
|
325
|
+
# to CanonicalUser but it will work for groups.
|
326
|
+
to_s == grant.to_s
|
327
|
+
end
|
328
|
+
alias_method :==, :eql?
|
329
|
+
|
330
|
+
def hash #:nodoc:
|
331
|
+
to_s.hash
|
332
|
+
end
|
333
|
+
|
334
|
+
private
|
335
|
+
|
336
|
+
def extract_grantee!
|
337
|
+
@grantee = Grantee.new(attributes['grantee']) if attributes['grantee']
|
338
|
+
end
|
339
|
+
|
340
|
+
class Builder < XmlGenerator #:nodoc:
|
341
|
+
attr_reader :grantee, :permission
|
342
|
+
|
343
|
+
def initialize(permission, grantee)
|
344
|
+
@permission = permission
|
345
|
+
@grantee = grantee
|
346
|
+
super()
|
347
|
+
end
|
348
|
+
|
349
|
+
def build
|
350
|
+
xml.Grant do
|
351
|
+
xml << grantee.to_xml
|
352
|
+
xml.Permission permission
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Grants bestow a access permission to grantees. Each grant of some access control list Policy is associated with a grantee.
|
359
|
+
# There are three ways of specifying a grantee at the time of this writing.
|
360
|
+
#
|
361
|
+
# * By canonical user - This format uses the <tt>id</tt> of a given Amazon account. The id value for a given account is available in the
|
362
|
+
# Owner object of a bucket, object or policy.
|
363
|
+
#
|
364
|
+
# grantee.id = 'bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1'
|
365
|
+
#
|
366
|
+
# Often the id will just be fetched from some owner object.
|
367
|
+
#
|
368
|
+
# grantee.id = some_object.owner.id
|
369
|
+
#
|
370
|
+
# * By amazon email address - You can specify an email address for any Amazon account. The Amazon account need not be signed up with the S3 service.
|
371
|
+
# though it must be unique across the entire Amazon system. This email address is normalized into a canonical user representation once the grant
|
372
|
+
# has been sent back up to the S3 servers.
|
373
|
+
#
|
374
|
+
# grantee.email_address = 'joe@example.org'
|
375
|
+
#
|
376
|
+
# * By group - As of this writing you can not create custom groups, but Amazon provides three group that you can use. See the documentation for the
|
377
|
+
# Grantee.group= method for details.
|
378
|
+
#
|
379
|
+
# grantee.group = 'Authenticated'
|
380
|
+
class Grantee
|
381
|
+
include SelectiveAttributeProxy #:nodoc:
|
382
|
+
|
383
|
+
undef_method :id # Get rid of Object#id
|
384
|
+
|
385
|
+
def initialize(attributes = {})
|
386
|
+
# Set default values for attributes that may not be passed in but we still want the object
|
387
|
+
# to respond to
|
388
|
+
attributes = {'id' => nil, 'display_name' => nil, 'email_address' => nil, 'uri' => nil}.merge(attributes)
|
389
|
+
@attributes = attributes
|
390
|
+
extract_type!
|
391
|
+
yield self if block_given?
|
392
|
+
end
|
393
|
+
|
394
|
+
# The xml representation of the current grantee object.
|
395
|
+
def to_xml
|
396
|
+
Builder.new(self).to_s
|
397
|
+
end
|
398
|
+
|
399
|
+
# Returns the type of grantee. Will be one of <tt>CanonicalUser</tt>, <tt>AmazonCustomerByEmail</tt> or <tt>Group</tt>.
|
400
|
+
def type
|
401
|
+
return attributes['type'] if attributes['type']
|
402
|
+
|
403
|
+
# Lookups are in order of preference so if, for example, you set the uri but display_name and id are also
|
404
|
+
# set, we'd rather go with the canonical representation.
|
405
|
+
if display_name && id
|
406
|
+
'CanonicalUser'
|
407
|
+
elsif email_address
|
408
|
+
'AmazonCustomerByEmail'
|
409
|
+
elsif uri
|
410
|
+
'Group'
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Sets the grantee's group by name.
|
415
|
+
#
|
416
|
+
# grantee.group = 'AllUsers'
|
417
|
+
#
|
418
|
+
# Currently, valid groups defined by S3 are:
|
419
|
+
#
|
420
|
+
# * <tt>AllUsers</tt>: This group represents anyone. In other words, an anonymous request.
|
421
|
+
# * <tt>Authenticated</tt>: Any authenticated account on the S3 service.
|
422
|
+
# * <tt>LogDelivery</tt>: The entity that delivers bucket access logs.
|
423
|
+
def group=(group_name)
|
424
|
+
section = %w(AllUsers Authenticated).include?(group_name) ? 'global' : 's3'
|
425
|
+
self.uri = "http://acs.amazonaws.com/groups/#{section}/#{group_name}"
|
426
|
+
end
|
427
|
+
|
428
|
+
# Returns the grantee's group. If the grantee is not a group, <tt>nil</tt> is returned.
|
429
|
+
def group
|
430
|
+
return unless uri
|
431
|
+
uri[%r([^/]+$)]
|
432
|
+
end
|
433
|
+
|
434
|
+
def type_representation #:nodoc:
|
435
|
+
case type
|
436
|
+
when 'CanonicalUser' then display_name || id
|
437
|
+
when 'AmazonCustomerByEmail' then email_address
|
438
|
+
when 'Group' then "#{group} Group"
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
def inspect #:nodoc:
|
443
|
+
"#<#{self.class}:##{object_id} #{type_representation || '(xsi not set yet)'}>"
|
444
|
+
end
|
445
|
+
|
446
|
+
private
|
447
|
+
def extract_type!
|
448
|
+
attributes['type'] = attributes.delete('xsi:type')
|
449
|
+
end
|
450
|
+
|
451
|
+
class Builder < XmlGenerator #:nodoc:
|
452
|
+
|
453
|
+
def initialize(grantee)
|
454
|
+
@grantee = grantee
|
455
|
+
super()
|
456
|
+
end
|
457
|
+
|
458
|
+
def build
|
459
|
+
xml.tag!('Grantee', attributes) do
|
460
|
+
representation
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
private
|
465
|
+
attr_reader :grantee
|
466
|
+
|
467
|
+
def representation
|
468
|
+
case grantee.type
|
469
|
+
when 'CanonicalUser'
|
470
|
+
xml.ID grantee.id
|
471
|
+
xml.DisplayName grantee.display_name
|
472
|
+
when 'AmazonCustomerByEmail'
|
473
|
+
xml.EmailAddress grantee.email_address
|
474
|
+
when 'Group'
|
475
|
+
xml.URI grantee.uri
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
def attributes
|
480
|
+
{'xsi:type' => grantee.type, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'}
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
module Bucket
|
486
|
+
def self.included(klass) #:nodoc:
|
487
|
+
klass.extend(ClassMethods)
|
488
|
+
end
|
489
|
+
|
490
|
+
module ClassMethods
|
491
|
+
# The acl method is the single point of entry for reading and writing access control list policies for a given bucket.
|
492
|
+
#
|
493
|
+
# # Fetch the acl for the 'marcel' bucket
|
494
|
+
# policy = Bucket.acl 'marcel'
|
495
|
+
#
|
496
|
+
# # Modify the policy ...
|
497
|
+
# # ...
|
498
|
+
#
|
499
|
+
# # Send updated policy back to the S3 servers
|
500
|
+
# Bucket.acl 'marcel', policy
|
501
|
+
def acl(name = nil, policy = nil)
|
502
|
+
if name.is_a?(ACL::Policy)
|
503
|
+
policy = name
|
504
|
+
name = nil
|
505
|
+
end
|
506
|
+
|
507
|
+
path = path(name) << '?acl'
|
508
|
+
respond_with ACL::Policy::Response do
|
509
|
+
policy ? put(path, {}, policy.to_xml) : ACL::Policy.new(get(path(name) << '?acl').policy)
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
# The acl method returns and updates the acl for a given bucket.
|
515
|
+
#
|
516
|
+
# # Fetch a bucket
|
517
|
+
# bucket = Bucket.find 'marcel'
|
518
|
+
#
|
519
|
+
# # Add a grant to the bucket's policy
|
520
|
+
# bucket.acl.grants << some_grant
|
521
|
+
#
|
522
|
+
# # Write the changes to the policy
|
523
|
+
# bucket.acl(bucket.acl)
|
524
|
+
def acl(reload = false)
|
525
|
+
policy = reload.is_a?(ACL::Policy) ? reload : nil
|
526
|
+
memoize(reload) do
|
527
|
+
self.class.acl(name, policy) if policy
|
528
|
+
self.class.acl(name)
|
529
|
+
end
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
module S3Object
|
534
|
+
def self.included(klass) #:nodoc:
|
535
|
+
klass.extend(ClassMethods)
|
536
|
+
end
|
537
|
+
|
538
|
+
module ClassMethods
|
539
|
+
# The acl method is the single point of entry for reading and writing access control list policies for a given object.
|
540
|
+
#
|
541
|
+
# # Fetch the acl for the 'kiss.jpg' object in the 'marcel' bucket
|
542
|
+
# policy = S3Object.acl 'kiss.jpg', 'marcel'
|
543
|
+
#
|
544
|
+
# # Modify the policy ...
|
545
|
+
# # ...
|
546
|
+
#
|
547
|
+
# # Send updated policy back to the S3 servers
|
548
|
+
# S3Object.acl 'kiss.jpg', 'marcel', policy
|
549
|
+
def acl(name, bucket = nil, policy = nil)
|
550
|
+
# We're using the second argument as the ACL::Policy
|
551
|
+
if bucket.is_a?(ACL::Policy)
|
552
|
+
policy = bucket
|
553
|
+
bucket = nil
|
554
|
+
end
|
555
|
+
|
556
|
+
bucket = bucket_name(bucket)
|
557
|
+
path = path!(bucket, name) << '?acl'
|
558
|
+
|
559
|
+
respond_with ACL::Policy::Response do
|
560
|
+
policy ? put(path, {}, policy.to_xml) : ACL::Policy.new(get(path).policy)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
# The acl method returns and updates the acl for a given s3 object.
|
566
|
+
#
|
567
|
+
# # Fetch a the object
|
568
|
+
# object = S3Object.find 'kiss.jpg', 'marcel'
|
569
|
+
#
|
570
|
+
# # Add a grant to the object's
|
571
|
+
# object.acl.grants << some_grant
|
572
|
+
#
|
573
|
+
# # Write the changes to the policy
|
574
|
+
# object.acl(object.acl)
|
575
|
+
def acl(reload = false)
|
576
|
+
policy = reload.is_a?(ACL::Policy) ? reload : nil
|
577
|
+
memoize(reload) do
|
578
|
+
self.class.acl(key, bucket.name, policy) if policy
|
579
|
+
self.class.acl(key, bucket.name)
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
class OptionProcessor #:nodoc:
|
585
|
+
attr_reader :options
|
586
|
+
class << self
|
587
|
+
def process!(options)
|
588
|
+
new(options).process!
|
589
|
+
end
|
590
|
+
end
|
591
|
+
|
592
|
+
def initialize(options)
|
593
|
+
options.to_normalized_options!
|
594
|
+
@options = options
|
595
|
+
@access_level = extract_access_level
|
596
|
+
end
|
597
|
+
|
598
|
+
def process!
|
599
|
+
return unless access_level_specified?
|
600
|
+
validate!
|
601
|
+
options['x-amz-acl'] = access_level
|
602
|
+
end
|
603
|
+
|
604
|
+
private
|
605
|
+
def extract_access_level
|
606
|
+
options.delete('access') || options.delete('x-amz-acl')
|
607
|
+
end
|
608
|
+
|
609
|
+
def validate!
|
610
|
+
raise InvalidAccessControlLevel.new(valid_levels, access_level) unless valid?
|
611
|
+
end
|
612
|
+
|
613
|
+
def valid?
|
614
|
+
valid_levels.include?(access_level)
|
615
|
+
end
|
616
|
+
|
617
|
+
def access_level_specified?
|
618
|
+
!@access_level.nil?
|
619
|
+
end
|
620
|
+
|
621
|
+
def valid_levels
|
622
|
+
%w(private public-read public-read-write authenticated-read)
|
623
|
+
end
|
624
|
+
|
625
|
+
def access_level
|
626
|
+
@normalized_access_level ||= @access_level.to_header
|
627
|
+
end
|
628
|
+
end
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|