OLD | NEW |
(Empty) | |
| 1 # Copyright 2010 Google Inc. |
| 2 # |
| 3 # Permission is hereby granted, free of charge, to any person obtaining a |
| 4 # copy of this software and associated documentation files (the |
| 5 # "Software"), to deal in the Software without restriction, including |
| 6 # without limitation the rights to use, copy, modify, merge, publish, dis- |
| 7 # tribute, sublicense, and/or sell copies of the Software, and to permit |
| 8 # persons to whom the Software is furnished to do so, subject to the fol- |
| 9 # lowing conditions: |
| 10 # |
| 11 # The above copyright notice and this permission notice shall be included |
| 12 # in all copies or substantial portions of the Software. |
| 13 # |
| 14 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 15 # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- |
| 16 # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
| 17 # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
| 18 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
| 20 # IN THE SOFTWARE. |
| 21 |
| 22 from boto.gs.user import User |
| 23 from boto.exception import InvalidAclError |
| 24 |
| 25 ACCESS_CONTROL_LIST = 'AccessControlList' |
| 26 ALL_AUTHENTICATED_USERS = 'AllAuthenticatedUsers' |
| 27 ALL_USERS = 'AllUsers' |
| 28 DISPLAY_NAME = 'DisplayName' |
| 29 DOMAIN = 'Domain' |
| 30 EMAIL_ADDRESS = 'EmailAddress' |
| 31 ENTRY = 'Entry' |
| 32 ENTRIES = 'Entries' |
| 33 GROUP_BY_DOMAIN = 'GroupByDomain' |
| 34 GROUP_BY_EMAIL = 'GroupByEmail' |
| 35 GROUP_BY_ID = 'GroupById' |
| 36 ID = 'ID' |
| 37 NAME = 'Name' |
| 38 OWNER = 'Owner' |
| 39 PERMISSION = 'Permission' |
| 40 SCOPE = 'Scope' |
| 41 TYPE = 'type' |
| 42 USER_BY_EMAIL = 'UserByEmail' |
| 43 USER_BY_ID = 'UserById' |
| 44 |
| 45 |
| 46 CannedACLStrings = ['private', 'public-read', 'project-private', |
| 47 'public-read-write', 'authenticated-read', |
| 48 'bucket-owner-read', 'bucket-owner-full-control'] |
| 49 """A list of Google Cloud Storage predefined (canned) ACL strings.""" |
| 50 |
| 51 SupportedPermissions = ['READ', 'WRITE', 'FULL_CONTROL'] |
| 52 """A list of supported ACL permissions.""" |
| 53 |
| 54 class ACL: |
| 55 |
| 56 def __init__(self, parent=None): |
| 57 self.parent = parent |
| 58 self.entries = [] |
| 59 |
| 60 @property |
| 61 def acl(self): |
| 62 return self |
| 63 |
| 64 def __repr__(self): |
| 65 # Owner is optional in GS ACLs. |
| 66 if hasattr(self, 'owner'): |
| 67 entries_repr = ['Owner:%s' % self.owner.__repr__()] |
| 68 else: |
| 69 entries_repr = [''] |
| 70 acl_entries = self.entries |
| 71 if acl_entries: |
| 72 for e in acl_entries.entry_list: |
| 73 entries_repr.append(e.__repr__()) |
| 74 return '<%s>' % ', '.join(entries_repr) |
| 75 |
| 76 # Method with same signature as boto.s3.acl.ACL.add_email_grant(), to allow |
| 77 # polymorphic treatment at application layer. |
| 78 def add_email_grant(self, permission, email_address): |
| 79 entry = Entry(type=USER_BY_EMAIL, email_address=email_address, |
| 80 permission=permission) |
| 81 self.entries.entry_list.append(entry) |
| 82 |
| 83 # Method with same signature as boto.s3.acl.ACL.add_user_grant(), to allow |
| 84 # polymorphic treatment at application layer. |
| 85 def add_user_grant(self, permission, user_id): |
| 86 entry = Entry(permission=permission, type=USER_BY_ID, id=user_id) |
| 87 self.entries.entry_list.append(entry) |
| 88 |
| 89 def add_group_email_grant(self, permission, email_address): |
| 90 entry = Entry(type=GROUP_BY_EMAIL, email_address=email_address, |
| 91 permission=permission) |
| 92 self.entries.entry_list.append(entry) |
| 93 |
| 94 def add_group_grant(self, permission, group_id): |
| 95 entry = Entry(type=GROUP_BY_ID, id=group_id, permission=permission) |
| 96 self.entries.entry_list.append(entry) |
| 97 |
| 98 def startElement(self, name, attrs, connection): |
| 99 if name.lower() == OWNER.lower(): |
| 100 self.owner = User(self) |
| 101 return self.owner |
| 102 elif name.lower() == ENTRIES.lower(): |
| 103 self.entries = Entries(self) |
| 104 return self.entries |
| 105 else: |
| 106 return None |
| 107 |
| 108 def endElement(self, name, value, connection): |
| 109 if name.lower() == OWNER.lower(): |
| 110 pass |
| 111 elif name.lower() == ENTRIES.lower(): |
| 112 pass |
| 113 else: |
| 114 setattr(self, name, value) |
| 115 |
| 116 def to_xml(self): |
| 117 s = '<%s>' % ACCESS_CONTROL_LIST |
| 118 # Owner is optional in GS ACLs. |
| 119 if hasattr(self, 'owner'): |
| 120 s += self.owner.to_xml() |
| 121 acl_entries = self.entries |
| 122 if acl_entries: |
| 123 s += acl_entries.to_xml() |
| 124 s += '</%s>' % ACCESS_CONTROL_LIST |
| 125 return s |
| 126 |
| 127 |
| 128 class Entries: |
| 129 |
| 130 def __init__(self, parent=None): |
| 131 self.parent = parent |
| 132 # Entries is the class that represents the same-named XML |
| 133 # element. entry_list is the list within this class that holds the data. |
| 134 self.entry_list = [] |
| 135 |
| 136 def __repr__(self): |
| 137 entries_repr = [] |
| 138 for e in self.entry_list: |
| 139 entries_repr.append(e.__repr__()) |
| 140 return '<Entries: %s>' % ', '.join(entries_repr) |
| 141 |
| 142 def startElement(self, name, attrs, connection): |
| 143 if name.lower() == ENTRY.lower(): |
| 144 entry = Entry(self) |
| 145 self.entry_list.append(entry) |
| 146 return entry |
| 147 else: |
| 148 return None |
| 149 |
| 150 def endElement(self, name, value, connection): |
| 151 if name.lower() == ENTRY.lower(): |
| 152 pass |
| 153 else: |
| 154 setattr(self, name, value) |
| 155 |
| 156 def to_xml(self): |
| 157 s = '<%s>' % ENTRIES |
| 158 for entry in self.entry_list: |
| 159 s += entry.to_xml() |
| 160 s += '</%s>' % ENTRIES |
| 161 return s |
| 162 |
| 163 |
| 164 # Class that represents a single (Scope, Permission) entry in an ACL. |
| 165 class Entry: |
| 166 |
| 167 def __init__(self, scope=None, type=None, id=None, name=None, |
| 168 email_address=None, domain=None, permission=None): |
| 169 if not scope: |
| 170 scope = Scope(self, type, id, name, email_address, domain) |
| 171 self.scope = scope |
| 172 self.permission = permission |
| 173 |
| 174 def __repr__(self): |
| 175 return '<%s: %s>' % (self.scope.__repr__(), self.permission.__repr__()) |
| 176 |
| 177 def startElement(self, name, attrs, connection): |
| 178 if name.lower() == SCOPE.lower(): |
| 179 # The following if statement used to look like this: |
| 180 # if not TYPE in attrs: |
| 181 # which caused problems because older versions of the |
| 182 # AttributesImpl class in the xml.sax library neglected to include |
| 183 # a __contains__() method (which Python calls to implement the |
| 184 # 'in' operator). So when you use the in operator, like the if |
| 185 # statement above, Python invokes the __getiter__() method with |
| 186 # index 0, which raises an exception. More recent versions of |
| 187 # xml.sax include the __contains__() method, rendering the in |
| 188 # operator functional. The work-around here is to formulate the |
| 189 # if statement as below, which is the legal way to query |
| 190 # AttributesImpl for containment (and is also how the added |
| 191 # __contains__() method works). At one time gsutil disallowed |
| 192 # xmlplus-based parsers, until this more specific problem was |
| 193 # determined. |
| 194 if TYPE not in attrs: |
| 195 raise InvalidAclError('Missing "%s" in "%s" part of ACL' % |
| 196 (TYPE, SCOPE)) |
| 197 self.scope = Scope(self, attrs[TYPE]) |
| 198 return self.scope |
| 199 elif name.lower() == PERMISSION.lower(): |
| 200 pass |
| 201 else: |
| 202 return None |
| 203 |
| 204 def endElement(self, name, value, connection): |
| 205 if name.lower() == SCOPE.lower(): |
| 206 pass |
| 207 elif name.lower() == PERMISSION.lower(): |
| 208 value = value.strip() |
| 209 if not value in SupportedPermissions: |
| 210 raise InvalidAclError('Invalid Permission "%s"' % value) |
| 211 self.permission = value |
| 212 else: |
| 213 setattr(self, name, value) |
| 214 |
| 215 def to_xml(self): |
| 216 s = '<%s>' % ENTRY |
| 217 s += self.scope.to_xml() |
| 218 s += '<%s>%s</%s>' % (PERMISSION, self.permission, PERMISSION) |
| 219 s += '</%s>' % ENTRY |
| 220 return s |
| 221 |
| 222 class Scope: |
| 223 |
| 224 # Map from Scope type.lower() to lower-cased list of allowed sub-elems. |
| 225 ALLOWED_SCOPE_TYPE_SUB_ELEMS = { |
| 226 ALL_AUTHENTICATED_USERS.lower() : [], |
| 227 ALL_USERS.lower() : [], |
| 228 GROUP_BY_DOMAIN.lower() : [DOMAIN.lower()], |
| 229 GROUP_BY_EMAIL.lower() : [ |
| 230 DISPLAY_NAME.lower(), EMAIL_ADDRESS.lower(), NAME.lower()], |
| 231 GROUP_BY_ID.lower() : [DISPLAY_NAME.lower(), ID.lower(), NAME.lower()], |
| 232 USER_BY_EMAIL.lower() : [ |
| 233 DISPLAY_NAME.lower(), EMAIL_ADDRESS.lower(), NAME.lower()], |
| 234 USER_BY_ID.lower() : [DISPLAY_NAME.lower(), ID.lower(), NAME.lower()] |
| 235 } |
| 236 |
| 237 def __init__(self, parent, type=None, id=None, name=None, |
| 238 email_address=None, domain=None): |
| 239 self.parent = parent |
| 240 self.type = type |
| 241 self.name = name |
| 242 self.id = id |
| 243 self.domain = domain |
| 244 self.email_address = email_address |
| 245 if self.type.lower() not in self.ALLOWED_SCOPE_TYPE_SUB_ELEMS: |
| 246 raise InvalidAclError('Invalid %s %s "%s" ' % |
| 247 (SCOPE, TYPE, self.type)) |
| 248 |
| 249 def __repr__(self): |
| 250 named_entity = None |
| 251 if self.id: |
| 252 named_entity = self.id |
| 253 elif self.email_address: |
| 254 named_entity = self.email_address |
| 255 elif self.domain: |
| 256 named_entity = self.domain |
| 257 if named_entity: |
| 258 return '<%s: %s>' % (self.type, named_entity) |
| 259 else: |
| 260 return '<%s>' % self.type |
| 261 |
| 262 def startElement(self, name, attrs, connection): |
| 263 if (not name.lower() in |
| 264 self.ALLOWED_SCOPE_TYPE_SUB_ELEMS[self.type.lower()]): |
| 265 raise InvalidAclError('Element "%s" not allowed in %s %s "%s" ' % |
| 266 (name, SCOPE, TYPE, self.type)) |
| 267 return None |
| 268 |
| 269 def endElement(self, name, value, connection): |
| 270 value = value.strip() |
| 271 if name.lower() == DOMAIN.lower(): |
| 272 self.domain = value |
| 273 elif name.lower() == EMAIL_ADDRESS.lower(): |
| 274 self.email_address = value |
| 275 elif name.lower() == ID.lower(): |
| 276 self.id = value |
| 277 elif name.lower() == NAME.lower(): |
| 278 self.name = value |
| 279 else: |
| 280 setattr(self, name, value) |
| 281 |
| 282 def to_xml(self): |
| 283 s = '<%s type="%s">' % (SCOPE, self.type) |
| 284 if (self.type.lower() == ALL_AUTHENTICATED_USERS.lower() |
| 285 or self.type.lower() == ALL_USERS.lower()): |
| 286 pass |
| 287 elif self.type.lower() == GROUP_BY_DOMAIN.lower(): |
| 288 s += '<%s>%s</%s>' % (DOMAIN, self.domain, DOMAIN) |
| 289 elif (self.type.lower() == GROUP_BY_EMAIL.lower() |
| 290 or self.type.lower() == USER_BY_EMAIL.lower()): |
| 291 s += '<%s>%s</%s>' % (EMAIL_ADDRESS, self.email_address, |
| 292 EMAIL_ADDRESS) |
| 293 if self.name: |
| 294 s += '<%s>%s</%s>' % (NAME, self.name, NAME) |
| 295 elif (self.type.lower() == GROUP_BY_ID.lower() |
| 296 or self.type.lower() == USER_BY_ID.lower()): |
| 297 s += '<%s>%s</%s>' % (ID, self.id, ID) |
| 298 if self.name: |
| 299 s += '<%s>%s</%s>' % (NAME, self.name, NAME) |
| 300 else: |
| 301 raise InvalidAclError('Invalid scope type "%s" ', self.type) |
| 302 |
| 303 s += '</%s>' % SCOPE |
| 304 return s |
OLD | NEW |