s33r 0.4.2 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. data/examples/cli/instant_download_server.rb +88 -0
  2. data/examples/cli/s3cli.rb +31 -52
  3. data/examples/cli/simple.rb +16 -6
  4. data/examples/fores33r/app/controllers/browser_controller.rb +12 -10
  5. data/examples/fores33r/app/helpers/application_helper.rb +2 -1
  6. data/examples/fores33r/app/views/browser/_upload.rhtml +1 -1
  7. data/examples/fores33r/app/views/browser/index.rhtml +4 -4
  8. data/examples/fores33r/config/environment.rb +5 -3
  9. data/examples/fores33r/log/development.log +2259 -0
  10. data/examples/fores33r/log/mongrel.log +59 -0
  11. data/examples/s3.yaml +2 -6
  12. data/lib/s33r/bucket.rb +103 -0
  13. data/lib/s33r/bucket_listing.rb +33 -76
  14. data/lib/s33r/client.rb +305 -446
  15. data/lib/s33r/networking.rb +197 -0
  16. data/lib/s33r/s33r_exception.rb +29 -18
  17. data/lib/s33r/s33r_http.rb +36 -18
  18. data/lib/s33r/s3_acl.rb +32 -52
  19. data/lib/s33r/s3_logging.rb +117 -0
  20. data/lib/s33r/s3_obj.rb +124 -69
  21. data/lib/s33r/utility.rb +447 -0
  22. data/test/cases/spec_acl.rb +10 -40
  23. data/test/cases/spec_bucket_listing.rb +12 -32
  24. data/test/cases/spec_logging.rb +47 -0
  25. data/test/cases/spec_networking.rb +11 -0
  26. data/test/cases/spec_s3_object.rb +44 -5
  27. data/test/cases/spec_utility.rb +264 -0
  28. data/test/files/acl.xml +0 -6
  29. data/test/files/config.yaml +5 -0
  30. data/test/files/logging_status_disabled.xml +3 -0
  31. data/test/files/logging_status_enabled.xml +7 -0
  32. data/test/test_setup.rb +7 -2
  33. metadata +16 -94
  34. data/examples/cli/acl_x.rb +0 -41
  35. data/examples/cli/logging_x.rb +0 -20
  36. data/examples/fores33r/README +0 -183
  37. data/html/classes/MIME.html +0 -120
  38. data/html/classes/MIME/InvalidContentType.html +0 -119
  39. data/html/classes/MIME/Type.html +0 -1173
  40. data/html/classes/MIME/Types.html +0 -566
  41. data/html/classes/Net.html +0 -108
  42. data/html/classes/Net/HTTPGenericRequest.html +0 -233
  43. data/html/classes/Net/HTTPResponse.html +0 -271
  44. data/html/classes/S33r.html +0 -986
  45. data/html/classes/S33r/BucketListing.html +0 -434
  46. data/html/classes/S33r/Client.html +0 -1575
  47. data/html/classes/S33r/LoggingResource.html +0 -222
  48. data/html/classes/S33r/NamedBucket.html +0 -693
  49. data/html/classes/S33r/OrderlyXmlMarkup.html +0 -165
  50. data/html/classes/S33r/S33rException.html +0 -124
  51. data/html/classes/S33r/S33rException/BucketListingMaxKeysError.html +0 -111
  52. data/html/classes/S33r/S33rException/BucketNotLogTargetable.html +0 -119
  53. data/html/classes/S33r/S33rException/InvalidBucketListing.html +0 -111
  54. data/html/classes/S33r/S33rException/InvalidPermission.html +0 -111
  55. data/html/classes/S33r/S33rException/InvalidS3GroupType.html +0 -111
  56. data/html/classes/S33r/S33rException/MalformedBucketName.html +0 -111
  57. data/html/classes/S33r/S33rException/MethodNotAvailable.html +0 -111
  58. data/html/classes/S33r/S33rException/MissingBucketName.html +0 -111
  59. data/html/classes/S33r/S33rException/MissingRequiredHeaders.html +0 -111
  60. data/html/classes/S33r/S33rException/MissingResource.html +0 -111
  61. data/html/classes/S33r/S33rException/S3FallenOver.html +0 -111
  62. data/html/classes/S33r/S33rException/TryingToPutEmptyResource.html +0 -117
  63. data/html/classes/S33r/S33rException/UnsupportedCannedACL.html +0 -111
  64. data/html/classes/S33r/S33rException/UnsupportedHTTPMethod.html +0 -111
  65. data/html/classes/S33r/S3ACL.html +0 -125
  66. data/html/classes/S33r/S3ACL/ACLDoc.html +0 -521
  67. data/html/classes/S33r/S3ACL/AmazonCustomer.html +0 -168
  68. data/html/classes/S33r/S3ACL/CanonicalUser.html +0 -212
  69. data/html/classes/S33r/S3ACL/Grant.html +0 -403
  70. data/html/classes/S33r/S3ACL/Grantee.html +0 -239
  71. data/html/classes/S33r/S3ACL/Group.html +0 -178
  72. data/html/classes/S33r/S3Object.html +0 -618
  73. data/html/classes/S33r/Sync.html +0 -152
  74. data/html/classes/XML.html +0 -202
  75. data/html/classes/XML/Document.html +0 -125
  76. data/html/classes/XML/Node.html +0 -124
  77. data/html/created.rid +0 -1
  78. data/html/files/CHANGELOG.html +0 -107
  79. data/html/files/MIT-LICENSE.html +0 -129
  80. data/html/files/README_txt.html +0 -259
  81. data/html/files/lib/s33r/bucket_listing_rb.html +0 -101
  82. data/html/files/lib/s33r/builder_rb.html +0 -108
  83. data/html/files/lib/s33r/client_rb.html +0 -111
  84. data/html/files/lib/s33r/core_rb.html +0 -113
  85. data/html/files/lib/s33r/libxml_extensions_rb.html +0 -101
  86. data/html/files/lib/s33r/libxml_loader_rb.html +0 -109
  87. data/html/files/lib/s33r/logging_rb.html +0 -108
  88. data/html/files/lib/s33r/mimetypes_rb.html +0 -120
  89. data/html/files/lib/s33r/named_bucket_rb.html +0 -101
  90. data/html/files/lib/s33r/s33r_exception_rb.html +0 -101
  91. data/html/files/lib/s33r/s33r_http_rb.html +0 -108
  92. data/html/files/lib/s33r/s3_acl_rb.html +0 -108
  93. data/html/files/lib/s33r/s3_obj_rb.html +0 -108
  94. data/html/files/lib/s33r/sync_rb.html +0 -101
  95. data/html/files/lib/s33r_rb.html +0 -101
  96. data/html/fr_class_index.html +0 -66
  97. data/html/fr_file_index.html +0 -44
  98. data/html/fr_method_index.html +0 -183
  99. data/html/index.html +0 -24
  100. data/html/rdoc-style.css +0 -208
  101. data/lib/s33r/core.rb +0 -296
  102. data/lib/s33r/logging.rb +0 -43
  103. data/lib/s33r/named_bucket.rb +0 -148
  104. data/lib/s33r/sync.rb +0 -13
  105. data/test/cases/spec_all_buckets.rb +0 -28
  106. data/test/cases/spec_client.rb +0 -101
  107. data/test/cases/spec_core.rb +0 -128
  108. data/test/cases/spec_namedbucket.rb +0 -46
  109. data/test/cases/spec_sync.rb +0 -34
  110. data/test/files/all_buckets.xml +0 -21
  111. data/test/files/client_config.yml +0 -5
  112. data/test/files/namedbucket_config.yml +0 -8
  113. data/test/files/namedbucket_config2.yml +0 -8
  114. data/test/test_bucket_setup.rb +0 -41
@@ -0,0 +1,197 @@
1
+ require 'uri'
2
+ require 'openssl'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'stringio'
6
+
7
+ base = File.dirname(__FILE__)
8
+ require File.join(base, 's33r_exception')
9
+ require File.join(base, 's33r_http')
10
+ require File.join(base, 'utility')
11
+
12
+ # Core functionality for managing HTTP requests to S3.
13
+ module S33r
14
+ module Networking
15
+ include S3Exception
16
+ include Net
17
+
18
+ #-- These are specific to the mechanics of the HTTP request.
19
+
20
+ # Net::HTTP instance.
21
+ attr_accessor :client
22
+
23
+ # Are HTTP connections persistent?
24
+ attr_accessor :persistent
25
+
26
+ # Default chunk size for connections.
27
+ attr_accessor :chunk_size
28
+
29
+ # Should requests be dumped?
30
+ attr_accessor :dump_requests
31
+
32
+ # The last response received by the client.
33
+ attr_reader :last_response
34
+
35
+ # Get default options to use on every response.
36
+ def request_defaults
37
+ @request_defaults || {}
38
+ end
39
+
40
+ # Set the defaults.
41
+ def request_defaults=(options={})
42
+ @request_defaults = options
43
+ end
44
+
45
+ # Send a request over the wire.
46
+ #
47
+ # This method streams +data+ if it responds to the +stat+ method
48
+ # (as file handles do).
49
+ #
50
+ # Keys for +headers+ should be strings (e.g. 'Content-Type').
51
+ #
52
+ # +url_options+ is a standard set of options acceptable to s3_url;
53
+ # if any options aren't set, they are set using the +request_defaults+ method
54
+ # (options passed in here override defaults).
55
+ #
56
+ # You can also pass <tt>:authenticated => false</tt> if you want to visit a
57
+ # public URL here; otherwise, an Authorization header is generated and sent. If the client
58
+ # doesn't have an access key or secret access key specified, however, trying to create
59
+ # an access key will result in an error being raised.
60
+ #
61
+ # Returns a Net::HTTPResponse instance.
62
+ def do_request(method, url_options={}, data=nil, headers={})
63
+ # Use the default settings only if not specified in url_options.
64
+ url_options = request_defaults.merge(url_options)
65
+
66
+ # Get the URL.
67
+ url = s3_url(url_options)
68
+ uri = URI(url)
69
+
70
+ # Bar any except the allowed methods.
71
+ raise MethodNotAllowed, "The #{method} HTTP method is not supported" unless METHOD_VERBS.include?(method)
72
+
73
+ # Get a requester.
74
+ path = uri.path
75
+ path += "?" + uri.query if uri.query
76
+ req = eval("HTTP::" + method[0,1].upcase + method[1..-1].downcase + ".new('#{path}')")
77
+
78
+ req.chunk_size = chunk_size || DEFAULT_CHUNK_SIZE
79
+
80
+ # Add the S3 headers which are always required.
81
+ headers.merge!(default_headers(headers))
82
+
83
+ # Headers for canned ACL
84
+ headers.merge! canned_acl_header(url_options[:canned_acl]) if 'PUT' == method
85
+
86
+ # Generate the S3 authorization header if the client has
87
+ # the appropriate instance variable getters.
88
+ unless (false == url_options[:authenticated])
89
+ headers['Authorization'] = generate_auth_header_value(method, path, headers,
90
+ url_options[:access], url_options[:secret])
91
+ end
92
+
93
+ # Insert the headers into the request object.
94
+ headers.each do |key, value|
95
+ req[key] = value
96
+ end
97
+
98
+ # Add data to the request as a stream.
99
+ if req.request_body_permitted?
100
+ # For streaming files; NB Content-Length will be set by Net::HTTP
101
+ # for character-based data: this section of is only used
102
+ # when reading directly from a file.
103
+ if data.respond_to?(:stat)
104
+ length = data.stat.size
105
+ # Strings can be streamed too.
106
+ elsif data.is_a?(String)
107
+ length = data.length
108
+ data = StringIO.new(data)
109
+ else
110
+ length = 0
111
+ end
112
+
113
+ # Data can be streamed if it responds to the read method.
114
+ if data.respond_to?(:read)
115
+ req.body_stream = data
116
+ req['Content-Length'] = length.to_s
117
+ data = nil
118
+ end
119
+ else
120
+ data = nil
121
+ end
122
+
123
+ puts req.to_s if @dump_requests
124
+
125
+ # Set up the request.
126
+ ### <snip> start shameless stealing from Marcel Molina
127
+ request_runner = Proc.new do
128
+ response = @client.request(req, data)
129
+
130
+ # Add some nice messages to the response (like the S3 error
131
+ # message if it occurred).
132
+ response.conveniencify(method)
133
+ @last_response = response
134
+
135
+ return response
136
+ end
137
+
138
+ # Get a client instance.
139
+ init_client(uri)
140
+
141
+ # Run the request.
142
+ if persistent
143
+ @client.start unless @client.started?
144
+ response = request_runner.call
145
+ else
146
+ response = @client.start(&request_runner)
147
+ end
148
+
149
+ response
150
+ ### </snip> end shameless stealing from Marcel Molina
151
+ end
152
+
153
+ # Setup an HTTP client instance.
154
+ #
155
+ # Note that when you send your first request, the client is set
156
+ # up using whichever parameters for host and port you passed the first
157
+ # time. If you change the host or port, the client will be regenerated.
158
+ #
159
+ # +url+ is a URI instance generated from a full URL, including a host
160
+ # name and scheme.
161
+ def init_client(url)
162
+ host = url.host || HOST
163
+ port = url.port
164
+ if @client.nil? or @client.port != port or @client.address != host
165
+ @client = HTTP.new(host, port)
166
+ @client.use_ssl = false
167
+
168
+ # Check whether client needs to use SSL.
169
+ if port == PORT
170
+ # turn off SSL certificate verification
171
+ @client.verify_mode = OpenSSL::SSL::VERIFY_NONE
172
+ @client.use_ssl = true
173
+ end
174
+ end
175
+ end
176
+
177
+ # Perform a get request.
178
+ def do_get(options={}, headers={})
179
+ do_request('GET', options, nil, headers)
180
+ end
181
+
182
+ # Perform a put request.
183
+ def do_put(data, options={}, headers={})
184
+ do_request('PUT', options, data, headers)
185
+ end
186
+
187
+ # Perform a delete request.
188
+ def do_delete(options={}, headers={})
189
+ do_request('DELETE', options, headers)
190
+ end
191
+
192
+ # Perform a head request.
193
+ def do_head(options={}, headers={})
194
+ do_request('HEAD', options, headers)
195
+ end
196
+ end
197
+ end
@@ -1,25 +1,38 @@
1
1
  module S33r
2
- module S33rException
3
-
4
- class MethodNotAvailable < Exception
2
+ module S3Exception
3
+
4
+ # For errors originated by S3
5
+ class S3OriginatedException < Exception
6
+ attr_reader :s3_code, :s3_message
7
+
8
+ def initialize(code=nil, message=nil)
9
+ @s3_code = code
10
+ @s3_message = message
11
+ end
12
+
13
+ def to_s
14
+ "S3 returned an error\nError code: #{@s3_code}; error message: #{@s3_message}"
15
+ end
16
+ end
17
+
18
+ def self.error(s3_code, s3_message=nil)
19
+ S3OriginatedException.new(s3_code, s3_message)
5
20
  end
6
21
 
22
+ # Errors originated by S33r.
23
+ class MethodNotAllowed < Exception
24
+ end
25
+
7
26
  class MissingRequiredHeaders < Exception
8
27
  end
9
28
 
10
29
  class UnsupportedCannedACL < Exception
11
30
  end
12
31
 
13
- class UnsupportedHTTPMethod < Exception
14
- end
15
-
16
32
  class MalformedBucketName < Exception
17
33
  end
18
34
 
19
- class MissingBucketName < Exception
20
- end
21
-
22
- class MissingResource < Exception
35
+ class InvalidBucket < Exception
23
36
  end
24
37
 
25
38
  class BucketListingMaxKeysError < Exception
@@ -28,23 +41,21 @@ module S33r
28
41
  class InvalidBucketListing < Exception
29
42
  end
30
43
 
31
- class S3FallenOver < Exception
44
+ class InvalidGroupType < Exception
32
45
  end
33
46
 
34
- class InvalidS3GroupType < Exception
47
+ class InvalidPermission < Exception
35
48
  end
36
49
 
37
- class InvalidPermission < Exception
50
+ # Raised if an attempt is made to generate an authenticated URL
51
+ # with one of the required AWS keys missing.
52
+ class KeysIncomplete < Exception
38
53
  end
39
54
 
40
55
  # Raised if a bucket cannot be used as a log target
41
- # (i.e. if LogDelivery permissions not set - see S33r::S3ACL::ACLDoc.add_log_target_grants)
56
+ # (i.e. if LogDelivery permissions not set - see S33r::S3ACL::Policy.add_log_target_grants)
42
57
  class BucketNotLogTargetable < Exception
43
58
  end
44
-
45
- # Raised if you try to do a put with empty body
46
- class TryingToPutEmptyResource < Exception
47
- end
48
59
 
49
60
  end
50
61
  end
@@ -1,31 +1,48 @@
1
1
  require 'net/http'
2
- require File.join(File.dirname(__FILE__), 's33r_exception')
2
+ base = File.dirname(__FILE__)
3
+ require File.join(base, 's33r_exception')
4
+ require File.join(base, 'utility')
5
+ require File.join(base, 'libxml_loader')
6
+ require File.join(base, 'libxml_extensions')
3
7
 
4
- # Addtions to the Net::HTTP classes
5
- #
6
- # Adds some convenience functions for checking response status.
8
+ # Additions to the Net::HTTP classes
9
+
10
+ # Adds some convenience functions for checking response status, parsing
11
+ # error messages from
7
12
  class Net::HTTPResponse
8
- attr_accessor :success
9
13
  attr_writer :body
10
14
 
11
- def check_s3_availability
12
- if (500..503).include? code.to_i
13
- raise S33r::S33rException::S3FallenOver, "S3 has fallen over! (got a #{code} code in response)"
15
+ # An exception wrapped around the response from S3
16
+ attr_reader :s3_error
17
+
18
+ # Wrap responses properly so the S3 error codes don't have to be read.
19
+ def conveniencify(method)
20
+ if '200' == code
21
+ @ok = true
22
+ @s3_message = 'Request successful'
23
+ # 204 returned for successful bucket or item deletes
24
+ elsif '204' == code and 'DELETE' == method
25
+ @s3_message = 'Successfully deleted'
26
+ @ok = true
27
+ else
28
+ @ok = false
29
+ if body.nil?
30
+ @s3_error = S33r::S3Exception.error(code)
31
+ else
32
+ # Get the S3 error message and code
33
+ error_response = XML.get_xml_doc(body)
34
+ s3_code = error_response.xget('//Code')
35
+ s3_message = error_response.xget('//Message')
36
+
37
+ @s3_error = S33r::S3Exception.error(s3_code, s3_message)
38
+ end
14
39
  end
15
40
  end
16
41
 
17
42
  def ok?
18
- success
19
- end
20
-
21
- def success
22
- code == '200'
23
- end
24
-
25
- def not_found
26
- code = '404'
43
+ @ok || false
27
44
  end
28
-
45
+
29
46
  def to_s
30
47
  body
31
48
  end
@@ -52,6 +69,7 @@ class Net::HTTPGenericRequest
52
69
  self.each_capitalized do |key, value|
53
70
  str += "#{key}: #{value}\n"
54
71
  end
72
+ str += "*******\n" + body + "\n" unless body.nil?
55
73
  str += "*******\n\n"
56
74
  str
57
75
  end
data/lib/s33r/s3_acl.rb CHANGED
@@ -15,9 +15,9 @@ module S33r
15
15
  #
16
16
  # Represents both retrieved ACL XML or can be built up
17
17
  # using objects and converted to XML.
18
- # NB the ACLDoc is oblivious to the resource it is going
18
+ # NB the Policy is oblivious to the resource it is going
19
19
  # to be applied to.
20
- class ACLDoc
20
+ class Policy
21
21
  # List of grants to be applied.
22
22
  attr_accessor :grants, :owner
23
23
 
@@ -27,7 +27,7 @@ module S33r
27
27
  @owner = owner
28
28
  end
29
29
 
30
- # Create an ACLDoc instance from a raw Access Control Policy XML document.
30
+ # Create an Policy instance from a raw Access Control Policy XML document.
31
31
  #
32
32
  # +acl_xml+ is a raw Access Control Policy XML string (NOT libxml Document or Node).
33
33
  #
@@ -50,7 +50,7 @@ module S33r
50
50
  grants << Grant.new(grantee, permission)
51
51
  end
52
52
 
53
- ACLDoc.new(owner, grants)
53
+ Policy.new(owner, grants)
54
54
  end
55
55
 
56
56
  # Generate AccessControlPolicy XML document.
@@ -81,12 +81,10 @@ module S33r
81
81
  # Returns true if grant was added;
82
82
  # false otherwise (grant already exists).
83
83
  def add_grant(grant)
84
- if @grants.include?(grant)
85
- return false
86
- else
84
+ unless @grants.include?(grant)
87
85
  @grants << grant
88
- return true
89
86
  end
87
+ self
90
88
  end
91
89
 
92
90
  # Remove a grant from the ACL document. Note that if you
@@ -100,6 +98,7 @@ module S33r
100
98
  # false if it wasn't in the document.
101
99
  def remove_grant(grant)
102
100
  @grants.delete_if { |g| grant == g }
101
+ self
103
102
  end
104
103
 
105
104
  # Does the ACL contain a grant for public reads?
@@ -117,42 +116,15 @@ module S33r
117
116
  add_grant(Grant.public_read_grant)
118
117
  end
119
118
 
120
- # Does the ACL make the associated resource available as a log target?
121
- def log_targetable?
122
- log_target_grants = Grant.log_target_grants
123
- log_target_grants.each { |g| return false if !grants.include?(g) }
124
- return true
125
- end
126
-
127
- # Add permissions to an instances which give READ_ACL
128
- # and WRITE permissions to the LogDelivery group. Used
129
- # to enable a bucket as a logging destination.
130
- #
131
- # Returns true if grants added, false otherwise
132
- # (if already a log target).
133
- def add_log_target_grants
134
- if log_targetable?
135
- return false
136
- else
137
- Grant.log_target_grants.each { |g| add_grant(g) }
138
- return true
139
- end
119
+ # Remove the public READ permission from this instance.
120
+ def remove_public_read_grant
121
+ remove_grant(Grant.public_read_grant)
140
122
  end
141
123
 
142
- # Remove log target ACLs from the document.
143
- #
144
- # Returns true if all log target grants were removed;
145
- # false otherwise.
146
- #
147
- # NB even if this method returns false, that doesn't mean
148
- # the bucket is still a log target. Use log_targetable? to check
149
- # whether a bucket can be used as a log target.
150
- def remove_log_target_grants
151
- ok = true
152
- Grant.log_target_grants.each { |g| ok = ok and remove_grant(g) }
153
- ok
124
+ # String representation of the policy.
125
+ def to_s
126
+ @grants.inject('') {|acc, grant| acc += "* " + grant.to_s + "\n"}
154
127
  end
155
-
156
128
  end
157
129
 
158
130
  # Representation of an S3 Grant
@@ -171,7 +143,8 @@ module S33r
171
143
  else
172
144
  @permission = PERMISSIONS[permission]
173
145
  end
174
- raise InvalidPermission, "Permission #{permission.to_s} is not a valid permission specifier" if @permission.nil?
146
+ raise S3Exception::InvalidPermission, \
147
+ "Permission #{permission.to_s} is not a valid permission specifier" if @permission.nil?
175
148
  end
176
149
 
177
150
  # Note that setting a grant for an Amazon customer is the
@@ -196,15 +169,6 @@ module S33r
196
169
  Grant.new(Group.new(:all_users), :read)
197
170
  end
198
171
 
199
- # Generator for a grant which gives the LogDelivery group
200
- # write and read_acl permissions on a bucket.
201
- #
202
- # Returns an array with the two required Grant instances.
203
- def Grant.log_target_grants
204
- log_delivery_group = Group.new(:log_delivery)
205
- [Grant.new(log_delivery_group, :read_acl), Grant.new(log_delivery_group, :write)]
206
- end
207
-
208
172
  # Convert a Grant object into an XML fragment.
209
173
  def to_xml
210
174
  xml_str = ""
@@ -239,6 +203,10 @@ module S33r
239
203
  return true
240
204
  end
241
205
 
206
+ def to_s
207
+ "#{@grantee.to_s} has permission #{@permission}"
208
+ end
209
+
242
210
  end
243
211
 
244
212
  # Abstract representation of an S3 Grantee.
@@ -291,6 +259,10 @@ module S33r
291
259
  @grantee_type = GRANTEE_TYPES[:amazon_customer]
292
260
  @email_address = email_address
293
261
  end
262
+
263
+ def to_s
264
+ "Amazon customer with address '#{@email_address}"
265
+ end
294
266
  end
295
267
 
296
268
  # An S3 user.
@@ -315,6 +287,10 @@ module S33r
315
287
  display_name = user_xml_doc.xget('DisplayName')
316
288
  new(user_id, display_name)
317
289
  end
290
+
291
+ def to_s
292
+ "Canonical user '#{@display_name}' (with user ID '#{@user_id}')"
293
+ end
318
294
  end
319
295
 
320
296
  # One of the predefined S3 groups.
@@ -327,11 +303,15 @@ module S33r
327
303
  # one of the pre-defined Amazon group types.
328
304
  def initialize(group_type)
329
305
  unless S3_GROUP_TYPES.has_key?(group_type)
330
- raise InvalidS3GroupType, 'No such group type #{group_type}'
306
+ raise S3Exception::InvalidGroupType, 'No such group type #{group_type}'
331
307
  end
332
308
  @group_type = S3_GROUP_TYPES[group_type]
333
309
  @grantee_type = GRANTEE_TYPES[:group]
334
310
  end
311
+
312
+ def to_s
313
+ "Group '#{@group_type}'"
314
+ end
335
315
  end
336
316
 
337
317
  end