fmalamitsas-aws-s3 0.6.2.1254423625

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. data/CHANGELOG +107 -0
  2. data/COPYING +19 -0
  3. data/INSTALL +55 -0
  4. data/README.erb +58 -0
  5. data/Rakefile +334 -0
  6. data/TODO +26 -0
  7. data/aws-s3.gemspec +42 -0
  8. data/bin/s3sh +6 -0
  9. data/bin/setup.rb +10 -0
  10. data/lib/aws/s3.rb +60 -0
  11. data/lib/aws/s3/acl.rb +636 -0
  12. data/lib/aws/s3/authentication.rb +222 -0
  13. data/lib/aws/s3/base.rb +270 -0
  14. data/lib/aws/s3/bittorrent.rb +58 -0
  15. data/lib/aws/s3/bucket.rb +372 -0
  16. data/lib/aws/s3/connection.rb +288 -0
  17. data/lib/aws/s3/error.rb +69 -0
  18. data/lib/aws/s3/exceptions.rb +133 -0
  19. data/lib/aws/s3/extensions.rb +342 -0
  20. data/lib/aws/s3/logging.rb +317 -0
  21. data/lib/aws/s3/object.rb +626 -0
  22. data/lib/aws/s3/owner.rb +46 -0
  23. data/lib/aws/s3/parsing.rb +99 -0
  24. data/lib/aws/s3/response.rb +180 -0
  25. data/lib/aws/s3/service.rb +51 -0
  26. data/lib/aws/s3/version.rb +12 -0
  27. data/site/index.erb +41 -0
  28. data/site/public/images/box-and-gem.gif +0 -0
  29. data/site/public/images/favicon.ico +0 -0
  30. data/site/public/ruby.css +18 -0
  31. data/site/public/screen.css +99 -0
  32. data/support/faster-xml-simple/COPYING +18 -0
  33. data/support/faster-xml-simple/README +8 -0
  34. data/support/faster-xml-simple/Rakefile +54 -0
  35. data/support/faster-xml-simple/lib/faster_xml_simple.rb +190 -0
  36. data/support/faster-xml-simple/test/fixtures/test-1.rails.yml +4 -0
  37. data/support/faster-xml-simple/test/fixtures/test-1.xml +3 -0
  38. data/support/faster-xml-simple/test/fixtures/test-1.yml +4 -0
  39. data/support/faster-xml-simple/test/fixtures/test-2.rails.yml +6 -0
  40. data/support/faster-xml-simple/test/fixtures/test-2.xml +3 -0
  41. data/support/faster-xml-simple/test/fixtures/test-2.yml +6 -0
  42. data/support/faster-xml-simple/test/fixtures/test-3.rails.yml +6 -0
  43. data/support/faster-xml-simple/test/fixtures/test-3.xml +5 -0
  44. data/support/faster-xml-simple/test/fixtures/test-3.yml +6 -0
  45. data/support/faster-xml-simple/test/fixtures/test-4.rails.yml +5 -0
  46. data/support/faster-xml-simple/test/fixtures/test-4.xml +7 -0
  47. data/support/faster-xml-simple/test/fixtures/test-4.yml +5 -0
  48. data/support/faster-xml-simple/test/fixtures/test-5.rails.yml +8 -0
  49. data/support/faster-xml-simple/test/fixtures/test-5.xml +7 -0
  50. data/support/faster-xml-simple/test/fixtures/test-5.yml +8 -0
  51. data/support/faster-xml-simple/test/fixtures/test-6.rails.yml +43 -0
  52. data/support/faster-xml-simple/test/fixtures/test-6.xml +29 -0
  53. data/support/faster-xml-simple/test/fixtures/test-6.yml +41 -0
  54. data/support/faster-xml-simple/test/fixtures/test-7.rails.yml +23 -0
  55. data/support/faster-xml-simple/test/fixtures/test-7.xml +22 -0
  56. data/support/faster-xml-simple/test/fixtures/test-7.yml +22 -0
  57. data/support/faster-xml-simple/test/fixtures/test-8.rails.yml +14 -0
  58. data/support/faster-xml-simple/test/fixtures/test-8.xml +8 -0
  59. data/support/faster-xml-simple/test/fixtures/test-8.yml +11 -0
  60. data/support/faster-xml-simple/test/regression_test.rb +47 -0
  61. data/support/faster-xml-simple/test/test_helper.rb +17 -0
  62. data/support/faster-xml-simple/test/xml_simple_comparison_test.rb +46 -0
  63. data/support/rdoc/code_info.rb +211 -0
  64. data/test/acl_test.rb +254 -0
  65. data/test/authentication_test.rb +118 -0
  66. data/test/base_test.rb +136 -0
  67. data/test/bucket_test.rb +74 -0
  68. data/test/connection_test.rb +216 -0
  69. data/test/error_test.rb +70 -0
  70. data/test/extensions_test.rb +340 -0
  71. data/test/fixtures.rb +89 -0
  72. data/test/fixtures/buckets.yml +133 -0
  73. data/test/fixtures/errors.yml +34 -0
  74. data/test/fixtures/headers.yml +3 -0
  75. data/test/fixtures/logging.yml +15 -0
  76. data/test/fixtures/loglines.yml +5 -0
  77. data/test/fixtures/logs.yml +7 -0
  78. data/test/fixtures/policies.yml +16 -0
  79. data/test/logging_test.rb +89 -0
  80. data/test/mocks/fake_response.rb +26 -0
  81. data/test/object_test.rb +205 -0
  82. data/test/parsing_test.rb +66 -0
  83. data/test/remote/acl_test.rb +116 -0
  84. data/test/remote/bittorrent_test.rb +45 -0
  85. data/test/remote/bucket_test.rb +146 -0
  86. data/test/remote/logging_test.rb +82 -0
  87. data/test/remote/object_test.rb +379 -0
  88. data/test/remote/test_file.data +0 -0
  89. data/test/remote/test_helper.rb +33 -0
  90. data/test/response_test.rb +68 -0
  91. data/test/service_test.rb +23 -0
  92. data/test/test_helper.rb +118 -0
  93. metadata +241 -0
data/TODO ADDED
@@ -0,0 +1,26 @@
1
+ 0.3.0
2
+
3
+ [ ] Alias make alias for establish_connection! that is non-bang
4
+
5
+ [ ] Pass filter criteria like :max_keys onto methods like logs_for and logs which return logs.
6
+ [ ] Add high level support to custom logging information as documented in the "Adding Custom Information..." here http://docs.amazonwebservices.com/AmazonS3/2006-03-01/LogFormat.html
7
+
8
+ [ ] Bucket.delete(:force => true) needs to fetch all objects in the bucket until there are no more, taking into account the max-keys limit of 1000 objects at a time and it needs to do so in a very efficient manner so it can handle very large buckets (using :prefix and :marker)
9
+ [ ] Ability to set content_type on S3Object that has not been stored yet
10
+ [ ] Allow symbol and abbreviated version of logging options ('target_prefix' => :prefix, 'target_bucket' => :bucket)
11
+ [ ] Allow symbol options for grant's constructor ('permission' => :permission)
12
+ [ ] Reconsider save method to Policies returned by Bucket and S3Object's acl instance method so you can do some_object.acl.save after modifying it rather than some_object.acl(some_object.acl)
13
+
14
+ [X] S3Object.copy and S3Object.move should preserve the acl
15
+ [X] Consider opening up Net::HTTPGenericRequest to replace hardcoded chunk_size to something greater than 1k (maybe 500k since the files are presumed to be quite large)
16
+ [X] Add S3Object.exists?
17
+ [X] See about replacing XmlSimple with libxml if it's installed since XmlSimple can be rather slow (due to wrapping REXML)
18
+ [X] Ability to build up the README from internal docs so documentation for various classes and the README can feed from a single source
19
+ [X] Bittorrent documentation
20
+ [X] Document logging methods
21
+ [X] Bittorrent
22
+ [X] ACL documentation
23
+ [X] Log management ([de]activation & retrieval)
24
+ [X] Remote ACL tests
25
+ [X] ACL requesting and parsing
26
+ [X] ACL updating for already stored objects which merges with existing ACL
@@ -0,0 +1,42 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = 'fmalamitsas-aws-s3'
5
+ s.version = "0.6.2.1254423625"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["marcel@vernix.org"]
9
+ s.date = %q{2009-10-01}
10
+ s.description = %q{Client library for Amazon's Simple Storage Service's REST API.}
11
+ s.email = %q{Marcel Molina Jr.}
12
+ s.executables = ["s3sh", "setup.rb"]
13
+ s.extra_rdoc_files = ["CHANGELOG", "COPYING", "README.erb", "TODO", "bin/s3sh", "bin/setup.rb", "lib/aws/s3.rb", "lib/aws/s3/acl.rb", "lib/aws/s3/authentication.rb", "lib/aws/s3/base.rb", "lib/aws/s3/bittorrent.rb", "lib/aws/s3/bucket.rb", "lib/aws/s3/connection.rb", "lib/aws/s3/error.rb", "lib/aws/s3/exceptions.rb", "lib/aws/s3/extensions.rb", "lib/aws/s3/logging.rb", "lib/aws/s3/object.rb", "lib/aws/s3/owner.rb", "lib/aws/s3/parsing.rb", "lib/aws/s3/response.rb", "lib/aws/s3/service.rb", "lib/aws/s3/version.rb"]
14
+ s.files = ["CHANGELOG", "COPYING", "INSTALL", "README.erb", "Rakefile", "TODO", "aws-s3.gemspec", "bin/s3sh", "bin/setup.rb", "lib/aws/s3.rb", "lib/aws/s3/acl.rb", "lib/aws/s3/authentication.rb", "lib/aws/s3/base.rb", "lib/aws/s3/bittorrent.rb", "lib/aws/s3/bucket.rb", "lib/aws/s3/connection.rb", "lib/aws/s3/error.rb", "lib/aws/s3/exceptions.rb", "lib/aws/s3/extensions.rb", "lib/aws/s3/logging.rb", "lib/aws/s3/object.rb", "lib/aws/s3/owner.rb", "lib/aws/s3/parsing.rb", "lib/aws/s3/response.rb", "lib/aws/s3/service.rb", "lib/aws/s3/version.rb", "site/index.erb", "site/public/images/box-and-gem.gif", "site/public/images/favicon.ico", "site/public/ruby.css", "site/public/screen.css", "support/faster-xml-simple/COPYING", "support/faster-xml-simple/README", "support/faster-xml-simple/Rakefile", "support/faster-xml-simple/lib/faster_xml_simple.rb", "support/faster-xml-simple/test/fixtures/test-1.rails.yml", "support/faster-xml-simple/test/fixtures/test-1.xml", "support/faster-xml-simple/test/fixtures/test-1.yml", "support/faster-xml-simple/test/fixtures/test-2.rails.yml", "support/faster-xml-simple/test/fixtures/test-2.xml", "support/faster-xml-simple/test/fixtures/test-2.yml", "support/faster-xml-simple/test/fixtures/test-3.rails.yml", "support/faster-xml-simple/test/fixtures/test-3.xml", "support/faster-xml-simple/test/fixtures/test-3.yml", "support/faster-xml-simple/test/fixtures/test-4.rails.yml", "support/faster-xml-simple/test/fixtures/test-4.xml", "support/faster-xml-simple/test/fixtures/test-4.yml", "support/faster-xml-simple/test/fixtures/test-5.rails.yml", "support/faster-xml-simple/test/fixtures/test-5.xml", "support/faster-xml-simple/test/fixtures/test-5.yml", "support/faster-xml-simple/test/fixtures/test-6.rails.yml", "support/faster-xml-simple/test/fixtures/test-6.xml", "support/faster-xml-simple/test/fixtures/test-6.yml", "support/faster-xml-simple/test/fixtures/test-7.rails.yml", "support/faster-xml-simple/test/fixtures/test-7.xml", "support/faster-xml-simple/test/fixtures/test-7.yml", "support/faster-xml-simple/test/fixtures/test-8.rails.yml", "support/faster-xml-simple/test/fixtures/test-8.xml", "support/faster-xml-simple/test/fixtures/test-8.yml", "support/faster-xml-simple/test/regression_test.rb", "support/faster-xml-simple/test/test_helper.rb", "support/faster-xml-simple/test/xml_simple_comparison_test.rb", "support/rdoc/code_info.rb", "test/acl_test.rb", "test/authentication_test.rb", "test/base_test.rb", "test/bucket_test.rb", "test/connection_test.rb", "test/error_test.rb", "test/extensions_test.rb", "test/fixtures.rb", "test/fixtures/buckets.yml", "test/fixtures/errors.yml", "test/fixtures/headers.yml", "test/fixtures/logging.yml", "test/fixtures/loglines.yml", "test/fixtures/logs.yml", "test/fixtures/policies.yml", "test/logging_test.rb", "test/mocks/fake_response.rb", "test/object_test.rb", "test/parsing_test.rb", "test/remote/acl_test.rb", "test/remote/bittorrent_test.rb", "test/remote/bucket_test.rb", "test/remote/logging_test.rb", "test/remote/object_test.rb", "test/remote/test_file.data", "test/remote/test_helper.rb", "test/response_test.rb", "test/service_test.rb", "test/test_helper.rb"]
15
+ s.has_rdoc = true
16
+ s.homepage = %q{http://amazon.rubyforge.org}
17
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Aws-s3", "--main", "README.erb"]
18
+ s.require_paths = ["lib"]
19
+ s.rubyforge_project = %q{aws-s3}
20
+ s.rubygems_version = %q{1.3.1}
21
+ s.summary = %q{Client library for Amazon's Simple Storage Service's REST API.}
22
+ s.test_files = ["test/acl_test.rb", "test/authentication_test.rb", "test/base_test.rb", "test/bucket_test.rb", "test/connection_test.rb", "test/error_test.rb", "test/extensions_test.rb", "test/logging_test.rb", "test/object_test.rb", "test/parsing_test.rb", "test/remote/acl_test.rb", "test/remote/bittorrent_test.rb", "test/remote/bucket_test.rb", "test/remote/logging_test.rb", "test/remote/object_test.rb", "test/remote/test_helper.rb", "test/response_test.rb", "test/service_test.rb", "test/test_helper.rb"]
23
+
24
+ if s.respond_to? :specification_version then
25
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
26
+ s.specification_version = 2
27
+
28
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
29
+ s.add_runtime_dependency(%q<xml-simple>, [">= 0"])
30
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
31
+ s.add_runtime_dependency(%q<mime-types>, [">= 0"])
32
+ else
33
+ s.add_dependency(%q<xml-simple>, [">= 0"])
34
+ s.add_dependency(%q<builder>, [">= 0"])
35
+ s.add_dependency(%q<mime-types>, [">= 0"])
36
+ end
37
+ else
38
+ s.add_dependency(%q<xml-simple>, [">= 0"])
39
+ s.add_dependency(%q<builder>, [">= 0"])
40
+ s.add_dependency(%q<mime-types>, [">= 0"])
41
+ end
42
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ s3_lib = File.dirname(__FILE__) + '/../lib/aws/s3'
3
+ setup = File.dirname(__FILE__) + '/setup'
4
+ irb_name = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb'
5
+
6
+ exec "#{irb_name} -r #{s3_lib} -r #{setup} --simple-prompt"
@@ -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
@@ -0,0 +1,60 @@
1
+ require 'cgi'
2
+ require 'uri'
3
+ require 'openssl'
4
+ require 'digest/sha1'
5
+ require 'net/https'
6
+ require 'time'
7
+ require 'date'
8
+ require 'open-uri'
9
+
10
+ $:.unshift(File.dirname(__FILE__))
11
+ require 's3/extensions'
12
+ require_library_or_gem 'builder' unless defined? Builder
13
+ require_library_or_gem 'mime/types', 'mime-types' unless defined? MIME::Types
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
+ require_library_or_gem 'xmlsimple', 'xml-simple' unless defined? XmlSimple
46
+ # If libxml is installed, we use the FasterXmlSimple library, that provides most of the functionality of XmlSimple
47
+ # except it uses the xml/libxml library for xml parsing (rather than REXML). If libxml isn't installed, we just fall back on
48
+ # XmlSimple.
49
+ AWS::S3::Parsing.parser =
50
+ begin
51
+ require_library_or_gem 'xml/libxml'
52
+ # Older version of libxml aren't stable (bus error when requesting attributes that don't exist) so we
53
+ # have to use a version greater than '0.3.8.2'.
54
+ raise LoadError unless XML::Parser::VERSION > '0.3.8.2'
55
+ $:.push(File.join(File.dirname(__FILE__), '..', '..', 'support', 'faster-xml-simple', 'lib'))
56
+ require 'faster_xml_simple'
57
+ FasterXmlSimple
58
+ rescue LoadError
59
+ XmlSimple
60
+ end
@@ -0,0 +1,636 @@
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
+ # :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 custum 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
+
174
+ # Two grant lists are equal if they have identical grants both in terms of permission and grantee.
175
+ def ==(grants)
176
+ size == grants.size && all? {|grant| grants.include?(grant)}
177
+ end
178
+ end
179
+
180
+ class Builder < XmlGenerator #:nodoc:
181
+ attr_reader :owner, :grants
182
+ def initialize(owner, grants)
183
+ @owner = owner
184
+ @grants = grants.uniq # There could be some duplicate grants
185
+ super()
186
+ end
187
+
188
+ def build
189
+ xml.tag!('AccessControlPolicy', 'xmlns' => 'http://s3.amazonaws.com/doc/2006-03-01/') do
190
+ xml.Owner do
191
+ xml.ID owner.id
192
+ xml.DisplayName owner.display_name
193
+ end
194
+
195
+ xml.AccessControlList do
196
+ xml << grants.map {|grant| grant.to_xml}.join("\n")
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # A Policy is made up of one or more Grant objects. A grant sets a specific permission and grants it to the associated grantee.
204
+ #
205
+ # When creating a new grant to add to a policy, you need only set its permission and then associate with a Grantee.
206
+ #
207
+ # grant = ACL::Grant.new
208
+ # => #<AWS::S3::ACL::Grant (permission) to (grantee)>
209
+ #
210
+ # Here we see that neither the permission nor the grantee have been set. Let's make this grant provide the READ permission.
211
+ #
212
+ # grant.permission = 'READ'
213
+ # grant
214
+ # => #<AWS::S3::ACL::Grant READ to (grantee)>
215
+ #
216
+ # Now let's assume we have a grantee to the AllUsers group already set up. Just associate that grantee with our grant.
217
+ #
218
+ # grant.grantee = all_users_group_grantee
219
+ # grant
220
+ # => #<AWS::S3::ACL::Grant READ to AllUsers Group>
221
+ #
222
+ # And now are grant is complete. It provides READ permission to the AllUsers group, effectively making this object publicly readable
223
+ # without any authorization.
224
+ #
225
+ # Assuming we have some object's policy available in a local variable called <tt>policy</tt>, we can now add this grant onto its
226
+ # collection of grants.
227
+ #
228
+ # policy.grants << grant
229
+ #
230
+ # And then we send the updated policy to the S3 servers.
231
+ #
232
+ # some_s3object.acl(policy)
233
+ class Grant
234
+ include SelectiveAttributeProxy #:nodoc:
235
+ constant :VALID_PERMISSIONS, %w(READ WRITE READ_ACP WRITE_ACP FULL_CONTROL)
236
+ attr_accessor :grantee
237
+
238
+ class << self
239
+ # Returns stock grants with name <tt>type</tt>.
240
+ #
241
+ # public_read_grant = ACL::Grant.grant :public_read
242
+ # => #<AWS::S3::ACL::Grant READ to AllUsers Group>
243
+ #
244
+ # Valid stock grant types are:
245
+ #
246
+ # * <tt>:authenticated_read</tt>
247
+ # * <tt>:authenticated_read_acp</tt>
248
+ # * <tt>:authenticated_write</tt>
249
+ # * <tt>:authenticated_write_acp</tt>
250
+ # * <tt>:logging_read</tt>
251
+ # * <tt>:logging_read_acp</tt>
252
+ # * <tt>:logging_write</tt>
253
+ # * <tt>:logging_write_acp</tt>
254
+ # * <tt>:public_read</tt>
255
+ # * <tt>:public_read_acp</tt>
256
+ # * <tt>:public_write</tt>
257
+ # * <tt>:public_write_acp</tt>
258
+ def grant(type)
259
+ case type
260
+ when *stock_grant_map.keys
261
+ build_stock_grant_for type
262
+ else
263
+ raise ArgumentError, "Unknown grant type `#{type}'"
264
+ end
265
+ end
266
+
267
+ private
268
+ def stock_grant_map
269
+ grant = lambda {|permission, group| {:permission => permission, :group => group}}
270
+ groups = {:public => 'AllUsers', :authenticated => 'Authenticated', :logging => 'LogDelivery'}
271
+ permissions = %w(READ WRITE READ_ACP WRITE_ACP)
272
+ stock_grants = {}
273
+ groups.each do |grant_group_name, group_name|
274
+ permissions.each do |permission|
275
+ stock_grants["#{grant_group_name}_#{permission.downcase}".to_sym] = grant[permission, group_name]
276
+ end
277
+ end
278
+ stock_grants
279
+ end
280
+ memoized :stock_grant_map
281
+
282
+ def build_stock_grant_for(type)
283
+ stock_grant = stock_grant_map[type]
284
+ grant = new do |g|
285
+ g.permission = stock_grant[:permission]
286
+ end
287
+ grant.grantee = Grantee.new do |gr|
288
+ gr.group = stock_grant[:group]
289
+ end
290
+ grant
291
+ end
292
+ end
293
+
294
+ def initialize(attributes = {})
295
+ attributes = {'permission' => nil}.merge(attributes)
296
+ @attributes = attributes
297
+ extract_grantee!
298
+ yield self if block_given?
299
+ end
300
+
301
+ # Set the permission for this grant.
302
+ #
303
+ # grant.permission = 'READ'
304
+ # grant
305
+ # => #<AWS::S3::ACL::Grant READ to (grantee)>
306
+ #
307
+ # If the specified permisison level is not valid, an <tt>InvalidAccessControlLevel</tt> exception will be raised.
308
+ def permission=(permission_level)
309
+ unless self.class.valid_permissions.include?(permission_level)
310
+ raise InvalidAccessControlLevel.new(self.class.valid_permissions, permission_level)
311
+ end
312
+ attributes['permission'] = permission_level
313
+ end
314
+
315
+ # The xml representation of this grant.
316
+ def to_xml
317
+ Builder.new(permission, grantee).to_s
318
+ end
319
+
320
+ def inspect #:nodoc:
321
+ "#<%s:0x%s %s>" % [self.class, object_id, self]
322
+ end
323
+
324
+ def to_s #:nodoc:
325
+ [permission || '(permission)', 'to', grantee ? grantee.type_representation : '(grantee)'].join ' '
326
+ end
327
+
328
+ def eql?(grant) #:nodoc:
329
+ # This won't work for an unposted AmazonCustomerByEmail because of the normalization
330
+ # to CanonicalUser but it will work for groups.
331
+ to_s == grant.to_s
332
+ end
333
+ alias_method :==, :eql?
334
+
335
+ def hash #:nodoc:
336
+ to_s.hash
337
+ end
338
+
339
+ private
340
+
341
+ def extract_grantee!
342
+ @grantee = Grantee.new(attributes['grantee']) if attributes['grantee']
343
+ end
344
+
345
+ class Builder < XmlGenerator #:nodoc:
346
+ attr_reader :grantee, :permission
347
+
348
+ def initialize(permission, grantee)
349
+ @permission = permission
350
+ @grantee = grantee
351
+ super()
352
+ end
353
+
354
+ def build
355
+ xml.Grant do
356
+ xml << grantee.to_xml
357
+ xml.Permission permission
358
+ end
359
+ end
360
+ end
361
+ end
362
+
363
+ # Grants bestow a access permission to grantees. Each grant of some access control list Policy is associated with a grantee.
364
+ # There are three ways of specifying a grantee at the time of this writing.
365
+ #
366
+ # * 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
367
+ # Owner object of a bucket, object or policy.
368
+ #
369
+ # grantee.id = 'bb2041a25975c3d4ce9775fe9e93e5b77a6a9fad97dc7e00686191f3790b13f1'
370
+ #
371
+ # Often the id will just be fetched from some owner object.
372
+ #
373
+ # grantee.id = some_object.owner.id
374
+ #
375
+ # * 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.
376
+ # though it must be unique across the entire Amazon system. This email address is normalized into a canonical user representation once the grant
377
+ # has been sent back up to the S3 servers.
378
+ #
379
+ # grantee.email_address = 'joe@example.org'
380
+ #
381
+ # * 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
382
+ # Grantee.group= method for details.
383
+ #
384
+ # grantee.group = 'Authenticated'
385
+ class Grantee
386
+ include SelectiveAttributeProxy #:nodoc:
387
+
388
+ undef_method :id if method_defined?(:id) # Get rid of Object#id
389
+
390
+ def initialize(attributes = {})
391
+ # Set default values for attributes that may not be passed in but we still want the object
392
+ # to respond to
393
+ attributes = {'id' => nil, 'display_name' => nil, 'email_address' => nil, 'uri' => nil}.merge(attributes)
394
+ @attributes = attributes
395
+ extract_type!
396
+ yield self if block_given?
397
+ end
398
+
399
+ # The xml representation of the current grantee object.
400
+ def to_xml
401
+ Builder.new(self).to_s
402
+ end
403
+
404
+ # Returns the type of grantee. Will be one of <tt>CanonicalUser</tt>, <tt>AmazonCustomerByEmail</tt> or <tt>Group</tt>.
405
+ def type
406
+ return attributes['type'] if attributes['type']
407
+
408
+ # Lookups are in order of preference so if, for example, you set the uri but display_name and id are also
409
+ # set, we'd rather go with the canonical representation.
410
+ if display_name && id
411
+ 'CanonicalUser'
412
+ elsif email_address
413
+ 'AmazonCustomerByEmail'
414
+ elsif uri
415
+ 'Group'
416
+ end
417
+ end
418
+
419
+ # Sets the grantee's group by name.
420
+ #
421
+ # grantee.group = 'AllUsers'
422
+ #
423
+ # Currently, valid groups defined by S3 are:
424
+ #
425
+ # * <tt>AllUsers</tt>: This group represents anyone. In other words, an anonymous request.
426
+ # * <tt>Authenticated</tt>: Any authenticated account on the S3 service.
427
+ # * <tt>LogDelivery</tt>: The entity that delivers bucket access logs.
428
+ def group=(group_name)
429
+ section = %w(AllUsers Authenticated).include?(group_name) ? 'global' : 's3'
430
+ self.uri = "http://acs.amazonaws.com/groups/#{section}/#{group_name}"
431
+ end
432
+
433
+ # Returns the grantee's group. If the grantee is not a group, <tt>nil</tt> is returned.
434
+ def group
435
+ return unless uri
436
+ uri[%r([^/]+$)]
437
+ end
438
+
439
+ def type_representation #:nodoc:
440
+ case type
441
+ when 'CanonicalUser' then display_name || id
442
+ when 'AmazonCustomerByEmail' then email_address
443
+ when 'Group' then "#{group} Group"
444
+ end
445
+ end
446
+
447
+ def inspect #:nodoc:
448
+ "#<%s:0x%s %s>" % [self.class, object_id, type_representation || '(type not set yet)']
449
+ end
450
+
451
+ private
452
+ def extract_type!
453
+ attributes['type'] = attributes.delete('xsi:type')
454
+ end
455
+
456
+ class Builder < XmlGenerator #:nodoc:
457
+
458
+ def initialize(grantee)
459
+ @grantee = grantee
460
+ super()
461
+ end
462
+
463
+ def build
464
+ xml.tag!('Grantee', attributes) do
465
+ representation
466
+ end
467
+ end
468
+
469
+ private
470
+ attr_reader :grantee
471
+
472
+ def representation
473
+ case grantee.type
474
+ when 'CanonicalUser'
475
+ xml.ID grantee.id
476
+ xml.DisplayName grantee.display_name
477
+ when 'AmazonCustomerByEmail'
478
+ xml.EmailAddress grantee.email_address
479
+ when 'Group'
480
+ xml.URI grantee.uri
481
+ end
482
+ end
483
+
484
+ def attributes
485
+ {'xsi:type' => grantee.type, 'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance'}
486
+ end
487
+ end
488
+ end
489
+
490
+ module Bucket
491
+ def self.included(klass) #:nodoc:
492
+ klass.extend(ClassMethods)
493
+ end
494
+
495
+ module ClassMethods
496
+ # The acl method is the single point of entry for reading and writing access control list policies for a given bucket.
497
+ #
498
+ # # Fetch the acl for the 'marcel' bucket
499
+ # policy = Bucket.acl 'marcel'
500
+ #
501
+ # # Modify the policy ...
502
+ # # ...
503
+ #
504
+ # # Send updated policy back to the S3 servers
505
+ # Bucket.acl 'marcel', policy
506
+ def acl(name = nil, policy = nil)
507
+ if name.is_a?(ACL::Policy)
508
+ policy = name
509
+ name = nil
510
+ end
511
+
512
+ path = path(name) << '?acl'
513
+ respond_with ACL::Policy::Response do
514
+ policy ? put(path, {}, policy.to_xml) : ACL::Policy.new(get(path(name) << '?acl').policy)
515
+ end
516
+ end
517
+ end
518
+
519
+ # The acl method returns and updates the acl for a given bucket.
520
+ #
521
+ # # Fetch a bucket
522
+ # bucket = Bucket.find 'marcel'
523
+ #
524
+ # # Add a grant to the bucket's policy
525
+ # bucket.acl.grants << some_grant
526
+ #
527
+ # # Write the changes to the policy
528
+ # bucket.acl(bucket.acl)
529
+ def acl(reload = false)
530
+ policy = reload.is_a?(ACL::Policy) ? reload : nil
531
+ expirable_memoize(reload) do
532
+ self.class.acl(name, policy) if policy
533
+ self.class.acl(name)
534
+ end
535
+ end
536
+ end
537
+
538
+ module S3Object
539
+ def self.included(klass) #:nodoc:
540
+ klass.extend(ClassMethods)
541
+ end
542
+
543
+ module ClassMethods
544
+ # The acl method is the single point of entry for reading and writing access control list policies for a given object.
545
+ #
546
+ # # Fetch the acl for the 'kiss.jpg' object in the 'marcel' bucket
547
+ # policy = S3Object.acl 'kiss.jpg', 'marcel'
548
+ #
549
+ # # Modify the policy ...
550
+ # # ...
551
+ #
552
+ # # Send updated policy back to the S3 servers
553
+ # S3Object.acl 'kiss.jpg', 'marcel', policy
554
+ def acl(name, bucket = nil, policy = nil)
555
+ # We're using the second argument as the ACL::Policy
556
+ if bucket.is_a?(ACL::Policy)
557
+ policy = bucket
558
+ bucket = nil
559
+ end
560
+
561
+ bucket = bucket_name(bucket)
562
+ path = path!(bucket, name) << '?acl'
563
+
564
+ respond_with ACL::Policy::Response do
565
+ policy ? put(path, {}, policy.to_xml) : ACL::Policy.new(get(path).policy)
566
+ end
567
+ end
568
+ end
569
+
570
+ # The acl method returns and updates the acl for a given s3 object.
571
+ #
572
+ # # Fetch a the object
573
+ # object = S3Object.find 'kiss.jpg', 'marcel'
574
+ #
575
+ # # Add a grant to the object's
576
+ # object.acl.grants << some_grant
577
+ #
578
+ # # Write the changes to the policy
579
+ # object.acl(object.acl)
580
+ def acl(reload = false)
581
+ policy = reload.is_a?(ACL::Policy) ? reload : nil
582
+ expirable_memoize(reload) do
583
+ self.class.acl(key, bucket.name, policy) if policy
584
+ self.class.acl(key, bucket.name)
585
+ end
586
+ end
587
+ end
588
+
589
+ class OptionProcessor #:nodoc:
590
+ attr_reader :options
591
+ class << self
592
+ def process!(options)
593
+ new(options).process!
594
+ end
595
+ end
596
+
597
+ def initialize(options)
598
+ options.to_normalized_options!
599
+ @options = options
600
+ @access_level = extract_access_level
601
+ end
602
+
603
+ def process!
604
+ return unless access_level_specified?
605
+ validate!
606
+ options['x-amz-acl'] = access_level
607
+ end
608
+
609
+ private
610
+ def extract_access_level
611
+ options.delete('access') || options.delete('x-amz-acl')
612
+ end
613
+
614
+ def validate!
615
+ raise InvalidAccessControlLevel.new(valid_levels, access_level) unless valid?
616
+ end
617
+
618
+ def valid?
619
+ valid_levels.include?(access_level)
620
+ end
621
+
622
+ def access_level_specified?
623
+ !@access_level.nil?
624
+ end
625
+
626
+ def valid_levels
627
+ %w(private public-read public-read-write authenticated-read)
628
+ end
629
+
630
+ def access_level
631
+ @normalized_access_level ||= @access_level.to_header
632
+ end
633
+ end
634
+ end
635
+ end
636
+ end