| OLD | NEW |
| 1 # -*- coding: utf-8 -*- |
| 1 # Copyright 2013 Google Inc. All Rights Reserved. | 2 # Copyright 2013 Google Inc. All Rights Reserved. |
| 2 # | 3 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); | 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. | 5 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at | 6 # You may obtain a copy of the License at |
| 6 # | 7 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 | 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # | 9 # |
| 9 # Unless required by applicable law or agreed to in writing, software | 10 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, | 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and | 13 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. | 14 # limitations under the License. |
| 14 """Contains helper objects for changing and deleting ACLs.""" | 15 """Contains helper objects for changing and deleting ACLs.""" |
| 15 | 16 |
| 17 from __future__ import absolute_import |
| 18 |
| 16 import re | 19 import re |
| 17 from xml.dom import minidom | |
| 18 | |
| 19 from boto.gs import acl | |
| 20 | 20 |
| 21 from gslib.exception import CommandException | 21 from gslib.exception import CommandException |
| 22 | 22 |
| 23 | 23 |
| 24 class ChangeType(object): | 24 class ChangeType(object): |
| 25 USER = 'User' | 25 USER = 'User' |
| 26 GROUP = 'Group' | 26 GROUP = 'Group' |
| 27 | 27 |
| 28 | 28 |
| 29 class AclChange(object): | 29 class AclChange(object): |
| 30 """Represents a logical change to an access control list.""" | 30 """Represents a logical change to an access control list.""" |
| 31 public_scopes = ['AllAuthenticatedUsers', 'AllUsers'] | 31 public_scopes = ['AllAuthenticatedUsers', 'AllUsers'] |
| 32 id_scopes = ['UserById', 'GroupById'] | 32 id_scopes = ['UserById', 'GroupById'] |
| 33 email_scopes = ['UserByEmail', 'GroupByEmail'] | 33 email_scopes = ['UserByEmail', 'GroupByEmail'] |
| 34 domain_scopes = ['GroupByDomain'] | 34 domain_scopes = ['GroupByDomain'] |
| 35 scope_types = public_scopes + id_scopes + email_scopes + domain_scopes | 35 scope_types = public_scopes + id_scopes + email_scopes + domain_scopes |
| 36 | 36 |
| 37 public_entity_all_users = 'allUsers' |
| 38 public_entity_all_auth_users = 'allAuthenticatedUsers' |
| 39 public_entity_types = (public_entity_all_users, public_entity_all_auth_users) |
| 40 project_entity_prefixes = ('project-editors-', 'project-owners-', |
| 41 'project-viewers-') |
| 42 group_entity_prefix = 'group-' |
| 43 user_entity_prefix = 'user-' |
| 44 domain_entity_prefix = 'domain-' |
| 45 |
| 37 permission_shorthand_mapping = { | 46 permission_shorthand_mapping = { |
| 38 'R': 'READ', | 47 'R': 'READER', |
| 39 'W': 'WRITE', | 48 'W': 'WRITER', |
| 40 'FC': 'FULL_CONTROL', | 49 'FC': 'OWNER', |
| 50 'O': 'OWNER', |
| 51 'READ': 'READER', |
| 52 'WRITE': 'WRITER', |
| 53 'FULL_CONTROL': 'OWNER' |
| 41 } | 54 } |
| 42 | 55 |
| 43 def __init__(self, acl_change_descriptor, scope_type): | 56 def __init__(self, acl_change_descriptor, scope_type): |
| 44 """Creates an AclChange object. | 57 """Creates an AclChange object. |
| 45 | 58 |
| 46 Args: | 59 Args: |
| 47 acl_change_descriptor: An acl change as described in the "ch" section of | 60 acl_change_descriptor: An acl change as described in the "ch" section of |
| 48 the "acl" command's help. | 61 the "acl" command's help. |
| 49 scope_type: Either ChangeType.USER or ChangeType.GROUP, specifying the | 62 scope_type: Either ChangeType.USER or ChangeType.GROUP, specifying the |
| 50 extent of the scope. | 63 extent of the scope. |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 85 self.perm = self.permission_shorthand_mapping[perm_token] | 98 self.perm = self.permission_shorthand_mapping[perm_token] |
| 86 else: | 99 else: |
| 87 self.perm = perm_token | 100 self.perm = perm_token |
| 88 | 101 |
| 89 scope_class = _ClassifyScopeIdentifier(scope_string) | 102 scope_class = _ClassifyScopeIdentifier(scope_string) |
| 90 if scope_class == 'Domain': | 103 if scope_class == 'Domain': |
| 91 # This may produce an invalid UserByDomain scope, | 104 # This may produce an invalid UserByDomain scope, |
| 92 # which is good because then validate can complain. | 105 # which is good because then validate can complain. |
| 93 self.scope_type = '{0}ByDomain'.format(scope_type) | 106 self.scope_type = '{0}ByDomain'.format(scope_type) |
| 94 self.identifier = scope_string | 107 self.identifier = scope_string |
| 95 elif scope_class in ['Email', 'Id']: | 108 elif scope_class in ('Email', 'Id'): |
| 96 self.scope_type = '{0}By{1}'.format(scope_type, scope_class) | 109 self.scope_type = '{0}By{1}'.format(scope_type, scope_class) |
| 97 self.identifier = scope_string | 110 self.identifier = scope_string |
| 98 elif scope_class == 'AllAuthenticatedUsers': | 111 elif scope_class == 'AllAuthenticatedUsers': |
| 99 self.scope_type = 'AllAuthenticatedUsers' | 112 self.scope_type = 'AllAuthenticatedUsers' |
| 100 elif scope_class == 'AllUsers': | 113 elif scope_class == 'AllUsers': |
| 101 self.scope_type = 'AllUsers' | 114 self.scope_type = 'AllUsers' |
| 102 else: | 115 else: |
| 103 # This is just a fallback, so we set it to something | 116 # This is just a fallback, so we set it to something |
| 104 # and the validate step has something to go on. | 117 # and the validate step has something to go on. |
| 105 self.scope_type = scope_string | 118 self.scope_type = scope_string |
| (...skipping 21 matching lines...) Expand all Loading... |
| 127 _ThrowError('{0} requires domain'.format(self.scope_type)) | 140 _ThrowError('{0} requires domain'.format(self.scope_type)) |
| 128 | 141 |
| 129 if self.perm not in self.permission_shorthand_mapping.values(): | 142 if self.perm not in self.permission_shorthand_mapping.values(): |
| 130 perms = ', '.join(self.permission_shorthand_mapping.values()) | 143 perms = ', '.join(self.permission_shorthand_mapping.values()) |
| 131 _ThrowError('Allowed permissions are {0}'.format(perms)) | 144 _ThrowError('Allowed permissions are {0}'.format(perms)) |
| 132 | 145 |
| 133 def _YieldMatchingEntries(self, current_acl): | 146 def _YieldMatchingEntries(self, current_acl): |
| 134 """Generator that yields entries that match the change descriptor. | 147 """Generator that yields entries that match the change descriptor. |
| 135 | 148 |
| 136 Args: | 149 Args: |
| 137 current_acl: An instance of bogo.gs.acl.ACL which will be searched | 150 current_acl: A list of apitools_messages.BucketAccessControls or |
| 138 for matching entries. | 151 ObjectAccessControls which will be searched for matching |
| 152 entries. |
| 139 | 153 |
| 140 Yields: | 154 Yields: |
| 141 An instance of boto.gs.acl.Entry. | 155 An apitools_messages.BucketAccessControl or ObjectAccessControl. |
| 142 """ | 156 """ |
| 143 for entry in current_acl.entries.entry_list: | 157 for entry in current_acl: |
| 144 if entry.scope.type == self.scope_type: | 158 if (self.scope_type in ('UserById', 'GroupById') and |
| 145 if self.scope_type in ['UserById', 'GroupById']: | 159 entry.entityId and self.identifier == entry.entityId): |
| 146 if self.identifier == entry.scope.id: | 160 yield entry |
| 147 yield entry | 161 elif (self.scope_type in ('UserByEmail', 'GroupByEmail') |
| 148 elif self.scope_type in ['UserByEmail', 'GroupByEmail']: | 162 and entry.email and self.identifier == entry.email): |
| 149 if self.identifier == entry.scope.email_address: | 163 yield entry |
| 150 yield entry | 164 elif (self.scope_type == 'GroupByDomain' and |
| 151 elif self.scope_type == 'GroupByDomain': | 165 entry.domain and self.identifier == entry.domain): |
| 152 if self.identifier == entry.scope.domain: | 166 yield entry |
| 153 yield entry | 167 elif (self.scope_type == 'AllUsers' and |
| 154 elif self.scope_type in ['AllUsers', 'AllAuthenticatedUsers']: | 168 entry.entity.lower() == self.public_entity_all_users.lower()): |
| 155 yield entry | 169 yield entry |
| 156 else: | 170 elif (self.scope_type == 'AllAuthenticatedUsers' and |
| 157 raise CommandException('Found an unrecognized ACL ' | 171 entry.entity.lower() == self.public_entity_all_auth_users.lower()): |
| 158 'entry type, aborting.') | 172 yield entry |
| 159 | 173 |
| 160 def _AddEntry(self, current_acl): | 174 def _AddEntry(self, current_acl, entry_class): |
| 161 """Adds an entry to an ACL.""" | 175 """Adds an entry to current_acl.""" |
| 162 if self.scope_type in ['UserById', 'UserById', 'GroupById']: | 176 if self.scope_type == 'UserById': |
| 163 entry = acl.Entry(type=self.scope_type, permission=self.perm, | 177 entry = entry_class(entityId=self.identifier, role=self.perm, |
| 164 id=self.identifier) | 178 entity=self.user_entity_prefix + self.identifier) |
| 165 elif self.scope_type in ['UserByEmail', 'GroupByEmail']: | 179 elif self.scope_type == 'GroupById': |
| 166 entry = acl.Entry(type=self.scope_type, permission=self.perm, | 180 entry = entry_class(entityId=self.identifier, role=self.perm, |
| 167 email_address=self.identifier) | 181 entity=self.group_entity_prefix + self.identifier) |
| 182 elif self.scope_type == 'UserByEmail': |
| 183 entry = entry_class(email=self.identifier, role=self.perm, |
| 184 entity=self.user_entity_prefix + self.identifier) |
| 185 elif self.scope_type == 'GroupByEmail': |
| 186 entry = entry_class(email=self.identifier, role=self.perm, |
| 187 entity=self.group_entity_prefix + self.identifier) |
| 168 elif self.scope_type == 'GroupByDomain': | 188 elif self.scope_type == 'GroupByDomain': |
| 169 entry = acl.Entry(type=self.scope_type, permission=self.perm, | 189 entry = entry_class(domain=self.identifier, role=self.perm, |
| 170 domain=self.identifier) | 190 entity=self.domain_entity_prefix + self.identifier) |
| 191 elif self.scope_type == 'AllAuthenticatedUsers': |
| 192 entry = entry_class(entity=self.public_entity_all_auth_users, |
| 193 role=self.perm) |
| 194 elif self.scope_type == 'AllUsers': |
| 195 entry = entry_class(entity=self.public_entity_all_users, role=self.perm) |
| 171 else: | 196 else: |
| 172 entry = acl.Entry(type=self.scope_type, permission=self.perm) | 197 raise CommandException('Add entry to ACL got unexpected scope type %s.' % |
| 198 self.scope_type) |
| 199 current_acl.append(entry) |
| 173 | 200 |
| 174 current_acl.entries.entry_list.append(entry) | 201 def _GetEntriesClass(self, current_acl): |
| 202 # Entries will share the same class, so just return the first one. |
| 203 for acl_entry in current_acl: |
| 204 return acl_entry.__class__ |
| 175 | 205 |
| 176 def Execute(self, uri, current_acl, logger): | 206 def Execute(self, storage_url, current_acl, command_name, logger): |
| 177 """Executes the described change on an ACL. | 207 """Executes the described change on an ACL. |
| 178 | 208 |
| 179 Args: | 209 Args: |
| 180 uri: The URI object to change. | 210 storage_url: StorageUrl representing the object to change. |
| 181 current_acl: An instance of boto.gs.acl.ACL to permute. | 211 current_acl: A list of ObjectAccessControls or |
| 212 BucketAccessControls to permute. |
| 213 command_name: String name of comamnd being run (e.g., 'acl'). |
| 182 logger: An instance of logging.Logger. | 214 logger: An instance of logging.Logger. |
| 183 | 215 |
| 184 Returns: | 216 Returns: |
| 185 The number of changes that were made. | 217 The number of changes that were made. |
| 186 """ | 218 """ |
| 187 logger.debug('Executing {0} on {1}'.format(self.raw_descriptor, uri)) | 219 logger.debug( |
| 220 'Executing %s %s on %s', command_name, self.raw_descriptor, storage_url) |
| 188 | 221 |
| 189 if self.perm == 'WRITE' and uri.names_object(): | 222 if self.perm == 'WRITER': |
| 190 logger.warning( | 223 if command_name == 'acl' and storage_url.IsObject(): |
| 191 'Skipping {0} on {1}, as WRITE does not apply to objects' | 224 logger.warning( |
| 192 .format(self.raw_descriptor, uri)) | 225 'Skipping %s on %s, as WRITER does not apply to objects', |
| 193 return 0 | 226 self.raw_descriptor, storage_url) |
| 227 return 0 |
| 228 elif command_name == 'defacl': |
| 229 raise CommandException('WRITER cannot be set as a default object ACL ' |
| 230 'because WRITER does not apply to objects') |
| 194 | 231 |
| 232 entry_class = self._GetEntriesClass(current_acl) |
| 195 matching_entries = list(self._YieldMatchingEntries(current_acl)) | 233 matching_entries = list(self._YieldMatchingEntries(current_acl)) |
| 196 change_count = 0 | 234 change_count = 0 |
| 197 if matching_entries: | 235 if matching_entries: |
| 198 for entry in matching_entries: | 236 for entry in matching_entries: |
| 199 if entry.permission != self.perm: | 237 if entry.role != self.perm: |
| 200 entry.permission = self.perm | 238 entry.role = self.perm |
| 201 change_count += 1 | 239 change_count += 1 |
| 202 else: | 240 else: |
| 203 self._AddEntry(current_acl) | 241 self._AddEntry(current_acl, entry_class) |
| 204 change_count = 1 | 242 change_count = 1 |
| 205 | 243 |
| 206 parsed_acl = minidom.parseString(current_acl.to_xml()) | 244 logger.debug('New Acl:\n%s', str(current_acl)) |
| 207 logger.debug('New Acl:\n{0}'.format(parsed_acl.toprettyxml())) | |
| 208 return change_count | 245 return change_count |
| 209 | 246 |
| 210 | 247 |
| 211 class AclDel(AclChange): | 248 class AclDel(object): |
| 212 """Represents a logical change from an access control list.""" | 249 """Represents a logical change from an access control list.""" |
| 213 scope_regexes = { | 250 scope_regexes = { |
| 214 r'All(Users)?$': 'AllUsers', | 251 r'All(Users)?$': 'AllUsers', |
| 215 r'AllAuth(enticatedUsers)?$': 'AllAuthenticatedUsers', | 252 r'AllAuth(enticatedUsers)?$': 'AllAuthenticatedUsers', |
| 216 } | 253 } |
| 217 | 254 |
| 218 def __init__(self, identifier): | 255 def __init__(self, identifier): |
| 219 self.raw_descriptor = '-d {0}'.format(identifier) | 256 self.raw_descriptor = '-d {0}'.format(identifier) |
| 220 self.identifier = identifier | 257 self.identifier = identifier |
| 221 for regex, scope in self.scope_regexes.items(): | 258 for regex, scope in self.scope_regexes.items(): |
| 222 if re.match(regex, self.identifier, re.IGNORECASE): | 259 if re.match(regex, self.identifier, re.IGNORECASE): |
| 223 self.identifier = scope | 260 self.identifier = scope |
| 224 self.scope_type = 'Any' | 261 self.scope_type = 'Any' |
| 225 self.perm = 'NONE' | 262 self.perm = 'NONE' |
| 226 | 263 |
| 227 def _YieldMatchingEntries(self, current_acl): | 264 def _YieldMatchingEntries(self, current_acl): |
| 228 for entry in current_acl.entries.entry_list: | 265 """Generator that yields entries that match the change descriptor. |
| 229 if self.identifier == entry.scope.id: | 266 |
| 267 Args: |
| 268 current_acl: An instance of apitools_messages.BucketAccessControls or |
| 269 ObjectAccessControls which will be searched for matching |
| 270 entries. |
| 271 |
| 272 Yields: |
| 273 An apitools_messages.BucketAccessControl or ObjectAccessControl. |
| 274 """ |
| 275 for entry in current_acl: |
| 276 if entry.entityId and self.identifier == entry.entityId: |
| 230 yield entry | 277 yield entry |
| 231 elif self.identifier == entry.scope.email_address: | 278 elif entry.email and self.identifier == entry.email: |
| 232 yield entry | 279 yield entry |
| 233 elif self.identifier == entry.scope.domain: | 280 elif entry.domain and self.identifier == entry.domain: |
| 234 yield entry | 281 yield entry |
| 235 elif self.identifier == 'AllUsers' and entry.scope.type == 'AllUsers': | 282 elif entry.entity.lower() == 'allusers' and self.identifier == 'AllUsers': |
| 236 yield entry | 283 yield entry |
| 237 elif (self.identifier == 'AllAuthenticatedUsers' | 284 elif (entry.entity.lower() == 'allauthenticatedusers' and |
| 238 and entry.scope.type == 'AllAuthenticatedUsers'): | 285 self.identifier == 'AllAuthenticatedUsers'): |
| 239 yield entry | 286 yield entry |
| 240 | 287 |
| 241 def Execute(self, uri, current_acl, logger): | 288 def Execute(self, storage_url, current_acl, command_name, logger): |
| 242 logger.debug('Executing {0} on {1}'.format(self.raw_descriptor, uri)) | 289 logger.debug( |
| 290 'Executing %s %s on %s', command_name, self.raw_descriptor, storage_url) |
| 243 matching_entries = list(self._YieldMatchingEntries(current_acl)) | 291 matching_entries = list(self._YieldMatchingEntries(current_acl)) |
| 244 for entry in matching_entries: | 292 for entry in matching_entries: |
| 245 current_acl.entries.entry_list.remove(entry) | 293 current_acl.remove(entry) |
| 246 parsed_acl = minidom.parseString(current_acl.to_xml()) | 294 logger.debug('New Acl:\n%s', str(current_acl)) |
| 247 logger.debug('New Acl:\n{0}'.format(parsed_acl.toprettyxml())) | |
| 248 return len(matching_entries) | 295 return len(matching_entries) |
| OLD | NEW |