OLD | NEW |
(Empty) | |
| 1 # -*- coding: utf-8 -*- |
| 2 # Copyright 2011 Google Inc. All Rights Reserved. |
| 3 # |
| 4 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 # you may not use this file except in compliance with the License. |
| 6 # You may obtain a copy of the License at |
| 7 # |
| 8 # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 # |
| 10 # Unless required by applicable law or agreed to in writing, software |
| 11 # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 # See the License for the specific language governing permissions and |
| 14 # limitations under the License. |
| 15 """Implementation of acl command for cloud storage providers.""" |
| 16 |
| 17 from __future__ import absolute_import |
| 18 |
| 19 from gslib import aclhelpers |
| 20 from gslib.cloud_api import AccessDeniedException |
| 21 from gslib.cloud_api import BadRequestException |
| 22 from gslib.cloud_api import Preconditions |
| 23 from gslib.cloud_api import ServiceException |
| 24 from gslib.command import Command |
| 25 from gslib.command import SetAclExceptionHandler |
| 26 from gslib.command import SetAclFuncWrapper |
| 27 from gslib.command_argument import CommandArgument |
| 28 from gslib.cs_api_map import ApiSelector |
| 29 from gslib.exception import CommandException |
| 30 from gslib.help_provider import CreateHelpText |
| 31 from gslib.storage_url import StorageUrlFromString |
| 32 from gslib.third_party.storage_apitools import storage_v1_messages as apitools_m
essages |
| 33 from gslib.util import NO_MAX |
| 34 from gslib.util import Retry |
| 35 from gslib.util import UrlsAreForSingleProvider |
| 36 |
| 37 _SET_SYNOPSIS = """ |
| 38 gsutil acl set [-f] [-r] [-a] file-or-canned_acl_name url... |
| 39 """ |
| 40 |
| 41 _GET_SYNOPSIS = """ |
| 42 gsutil acl get url |
| 43 """ |
| 44 |
| 45 _CH_SYNOPSIS = """ |
| 46 gsutil acl ch [-f] [-r] -u|-g|-d|-p <grant>... url... |
| 47 |
| 48 where each <grant> is one of the following forms: |
| 49 |
| 50 -u <id|email>:<perm> |
| 51 -g <id|email|domain|All|AllAuth>:<perm> |
| 52 -p <viewers|editors|owners>-<project number> |
| 53 -d <id|email|domain|All|AllAuth> |
| 54 """ |
| 55 |
| 56 _GET_DESCRIPTION = """ |
| 57 <B>GET</B> |
| 58 The "acl get" command gets the ACL text for a bucket or object, which you can |
| 59 save and edit for the acl set command. |
| 60 """ |
| 61 |
| 62 _SET_DESCRIPTION = """ |
| 63 <B>SET</B> |
| 64 The "acl set" command allows you to set an Access Control List on one or |
| 65 more buckets and objects. The simplest way to use it is to specify one of |
| 66 the canned ACLs, e.g.,: |
| 67 |
| 68 gsutil acl set private gs://bucket |
| 69 |
| 70 If you want to make an object or bucket publicly readable or writable, it is |
| 71 recommended to use "acl ch", to avoid accidentally removing OWNER permissions. |
| 72 See "gsutil help acl ch" for details. |
| 73 |
| 74 See "gsutil help acls" for a list of all canned ACLs. |
| 75 |
| 76 If you want to define more fine-grained control over your data, you can |
| 77 retrieve an ACL using the "acl get" command, save the output to a file, edit |
| 78 the file, and then use the "acl set" command to set that ACL on the buckets |
| 79 and/or objects. For example: |
| 80 |
| 81 gsutil acl get gs://bucket/file.txt > acl.txt |
| 82 |
| 83 Make changes to acl.txt such as adding an additional grant, then: |
| 84 |
| 85 gsutil acl set acl.txt gs://cats/file.txt |
| 86 |
| 87 Note that you can set an ACL on multiple buckets or objects at once, |
| 88 for example: |
| 89 |
| 90 gsutil acl set acl.txt gs://bucket/*.jpg |
| 91 |
| 92 If you have a large number of ACLs to update you might want to use the |
| 93 gsutil -m option, to perform a parallel (multi-threaded/multi-processing) |
| 94 update: |
| 95 |
| 96 gsutil -m acl set acl.txt gs://bucket/*.jpg |
| 97 |
| 98 Note that multi-threading/multi-processing is only done when the named URLs |
| 99 refer to objects. gsutil -m acl set gs://bucket1 gs://bucket2 will run the |
| 100 acl set operations sequentially. |
| 101 |
| 102 |
| 103 <B>SET OPTIONS</B> |
| 104 The "set" sub-command has the following options |
| 105 |
| 106 -R, -r Performs "acl set" request recursively, to all objects under |
| 107 the specified URL. |
| 108 |
| 109 -a Performs "acl set" request on all object versions. |
| 110 |
| 111 -f Normally gsutil stops at the first error. The -f option causes |
| 112 it to continue when it encounters errors. If some of the ACLs |
| 113 couldn't be set, gsutil's exit status will be non-zero even if |
| 114 this flag is set. This option is implicitly set when running |
| 115 "gsutil -m acl...". |
| 116 """ |
| 117 |
| 118 _CH_DESCRIPTION = """ |
| 119 <B>CH</B> |
| 120 The "acl ch" (or "acl change") command updates access control lists, similar |
| 121 in spirit to the Linux chmod command. You can specify multiple access grant |
| 122 additions and deletions in a single command run; all changes will be made |
| 123 atomically to each object in turn. For example, if the command requests |
| 124 deleting one grant and adding a different grant, the ACLs being updated will |
| 125 never be left in an intermediate state where one grant has been deleted but |
| 126 the second grant not yet added. Each change specifies a user or group grant |
| 127 to add or delete, and for grant additions, one of R, W, O (for the |
| 128 permission to be granted). A more formal description is provided in a later |
| 129 section; below we provide examples. |
| 130 |
| 131 <B>CH EXAMPLES</B> |
| 132 Examples for "ch" sub-command: |
| 133 |
| 134 Grant anyone on the internet READ access to the object example-object: |
| 135 |
| 136 gsutil acl ch -u AllUsers:R gs://example-bucket/example-object |
| 137 |
| 138 NOTE: By default, publicly readable objects are served with a Cache-Control |
| 139 header allowing such objects to be cached for 3600 seconds. If you need to |
| 140 ensure that updates become visible immediately, you should set a |
| 141 Cache-Control header of "Cache-Control:private, max-age=0, no-transform" on |
| 142 such objects. For help doing this, see "gsutil help setmeta". |
| 143 |
| 144 Grant anyone on the internet WRITE access to the bucket example-bucket |
| 145 (WARNING: this is not recommended as you will be responsible for the content): |
| 146 |
| 147 gsutil acl ch -u AllUsers:W gs://example-bucket |
| 148 |
| 149 Grant the user john.doe@example.com WRITE access to the bucket |
| 150 example-bucket: |
| 151 |
| 152 gsutil acl ch -u john.doe@example.com:WRITE gs://example-bucket |
| 153 |
| 154 Grant the group admins@example.com OWNER access to all jpg files in |
| 155 the top level of example-bucket: |
| 156 |
| 157 gsutil acl ch -g admins@example.com:O gs://example-bucket/*.jpg |
| 158 |
| 159 Grant the owners of project example-project-123 WRITE access to the bucket |
| 160 example-bucket: |
| 161 |
| 162 gsutil acl ch -p owners-example-project-123:W gs://example-bucket |
| 163 |
| 164 NOTE: You can replace 'owners' with 'viewers' or 'editors' to grant access |
| 165 to a project's viewers/editors respectively. |
| 166 |
| 167 Grant the user with the specified canonical ID READ access to all objects |
| 168 in example-bucket that begin with folder/: |
| 169 |
| 170 gsutil acl ch -r \\ |
| 171 -u 84fac329bceSAMPLE777d5d22b8SAMPLE785ac2SAMPLE2dfcf7c4adf34da46:R \\ |
| 172 gs://example-bucket/folder/ |
| 173 |
| 174 Grant the service account foo@developer.gserviceaccount.com WRITE access to |
| 175 the bucket example-bucket: |
| 176 |
| 177 gsutil acl ch -u foo@developer.gserviceaccount.com:W gs://example-bucket |
| 178 |
| 179 Grant all users from the `Google Apps |
| 180 <https://www.google.com/work/apps/business/>`_ domain my-domain.org READ |
| 181 access to the bucket gcs.my-domain.org: |
| 182 |
| 183 gsutil acl ch -g my-domain.org:R gs://gcs.my-domain.org |
| 184 |
| 185 Remove any current access by john.doe@example.com from the bucket |
| 186 example-bucket: |
| 187 |
| 188 gsutil acl ch -d john.doe@example.com gs://example-bucket |
| 189 |
| 190 If you have a large number of objects to update, enabling multi-threading |
| 191 with the gsutil -m flag can significantly improve performance. The |
| 192 following command adds OWNER for admin@example.org using |
| 193 multi-threading: |
| 194 |
| 195 gsutil -m acl ch -r -u admin@example.org:O gs://example-bucket |
| 196 |
| 197 Grant READ access to everyone from my-domain.org and to all authenticated |
| 198 users, and grant OWNER to admin@mydomain.org, for the buckets |
| 199 my-bucket and my-other-bucket, with multi-threading enabled: |
| 200 |
| 201 gsutil -m acl ch -r -g my-domain.org:R -g AllAuth:R \\ |
| 202 -u admin@mydomain.org:O gs://my-bucket/ gs://my-other-bucket |
| 203 |
| 204 <B>CH ROLES</B> |
| 205 You may specify the following roles with either their shorthand or |
| 206 their full name: |
| 207 |
| 208 R: READ |
| 209 W: WRITE |
| 210 O: OWNER |
| 211 |
| 212 <B>CH ENTITIES</B> |
| 213 There are four different entity types: Users, Groups, All Authenticated Users, |
| 214 and All Users. |
| 215 |
| 216 Users are added with -u and a plain ID or email address, as in |
| 217 "-u john-doe@gmail.com:r". Note: Service Accounts are considered to be users. |
| 218 |
| 219 Groups are like users, but specified with the -g flag, as in |
| 220 "-g power-users@example.com:fc". Groups may also be specified as a full |
| 221 domain, as in "-g my-company.com:r". |
| 222 |
| 223 AllAuthenticatedUsers and AllUsers are specified directly, as |
| 224 in "-g AllUsers:R" or "-g AllAuthenticatedUsers:O". These are case |
| 225 insensitive, and may be shortened to "all" and "allauth", respectively. |
| 226 |
| 227 Removing roles is specified with the -d flag and an ID, email |
| 228 address, domain, or one of AllUsers or AllAuthenticatedUsers. |
| 229 |
| 230 Many entities' roles can be specified on the same command line, allowing |
| 231 bundled changes to be executed in a single run. This will reduce the number of |
| 232 requests made to the server. |
| 233 |
| 234 <B>CH OPTIONS</B> |
| 235 The "ch" sub-command has the following options |
| 236 |
| 237 -d Remove all roles associated with the matching entity. |
| 238 |
| 239 -f Normally gsutil stops at the first error. The -f option causes |
| 240 it to continue when it encounters errors. With this option the |
| 241 gsutil exit status will be 0 even if some ACLs couldn't be |
| 242 changed. |
| 243 |
| 244 -g Add or modify a group entity's role. |
| 245 |
| 246 -p Add or modify a project viewers/editors/owners role. |
| 247 |
| 248 -R, -r Performs acl ch request recursively, to all objects under the |
| 249 specified URL. |
| 250 |
| 251 -u Add or modify a user entity's role. |
| 252 """ |
| 253 |
| 254 _SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + |
| 255 _CH_SYNOPSIS.lstrip('\n') + '\n\n') |
| 256 |
| 257 _DESCRIPTION = (""" |
| 258 The acl command has three sub-commands: |
| 259 """ + '\n'.join([_GET_DESCRIPTION, _SET_DESCRIPTION, _CH_DESCRIPTION])) |
| 260 |
| 261 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) |
| 262 |
| 263 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION) |
| 264 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION) |
| 265 _ch_help_text = CreateHelpText(_CH_SYNOPSIS, _CH_DESCRIPTION) |
| 266 |
| 267 |
| 268 def _ApplyExceptionHandler(cls, exception): |
| 269 cls.logger.error('Encountered a problem: %s', exception) |
| 270 cls.everything_set_okay = False |
| 271 |
| 272 |
| 273 def _ApplyAclChangesWrapper(cls, url_or_expansion_result, thread_state=None): |
| 274 cls.ApplyAclChanges(url_or_expansion_result, thread_state=thread_state) |
| 275 |
| 276 |
| 277 class AclCommand(Command): |
| 278 """Implementation of gsutil acl command.""" |
| 279 |
| 280 # Command specification. See base class for documentation. |
| 281 command_spec = Command.CreateCommandSpec( |
| 282 'acl', |
| 283 command_name_aliases=['getacl', 'setacl', 'chacl'], |
| 284 usage_synopsis=_SYNOPSIS, |
| 285 min_args=2, |
| 286 max_args=NO_MAX, |
| 287 supported_sub_args='afRrg:u:d:p:', |
| 288 file_url_ok=False, |
| 289 provider_url_ok=False, |
| 290 urls_start_arg=1, |
| 291 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], |
| 292 gs_default_api=ApiSelector.JSON, |
| 293 argparse_arguments={ |
| 294 'set': [ |
| 295 CommandArgument.MakeFileURLOrCannedACLArgument(), |
| 296 CommandArgument.MakeZeroOrMoreCloudURLsArgument() |
| 297 ], |
| 298 'get': [ |
| 299 CommandArgument.MakeNCloudURLsArgument(1) |
| 300 ], |
| 301 'ch': [ |
| 302 CommandArgument.MakeZeroOrMoreCloudURLsArgument() |
| 303 ], |
| 304 } |
| 305 ) |
| 306 # Help specification. See help_provider.py for documentation. |
| 307 help_spec = Command.HelpSpec( |
| 308 help_name='acl', |
| 309 help_name_aliases=['getacl', 'setacl', 'chmod', 'chacl'], |
| 310 help_type='command_help', |
| 311 help_one_line_summary='Get, set, or change bucket and/or object ACLs', |
| 312 help_text=_DETAILED_HELP_TEXT, |
| 313 subcommand_help_text={ |
| 314 'get': _get_help_text, 'set': _set_help_text, 'ch': _ch_help_text}, |
| 315 ) |
| 316 |
| 317 def _CalculateUrlsStartArg(self): |
| 318 if not self.args: |
| 319 self.RaiseWrongNumberOfArgumentsException() |
| 320 if (self.args[0].lower() == 'set') or (self.command_alias_used == 'setacl'): |
| 321 return 1 |
| 322 else: |
| 323 return 0 |
| 324 |
| 325 def _SetAcl(self): |
| 326 """Parses options and sets ACLs on the specified buckets/objects.""" |
| 327 self.continue_on_error = False |
| 328 if self.sub_opts: |
| 329 for o, unused_a in self.sub_opts: |
| 330 if o == '-a': |
| 331 self.all_versions = True |
| 332 elif o == '-f': |
| 333 self.continue_on_error = True |
| 334 elif o == '-r' or o == '-R': |
| 335 self.recursion_requested = True |
| 336 else: |
| 337 self.RaiseInvalidArgumentException() |
| 338 try: |
| 339 self.SetAclCommandHelper(SetAclFuncWrapper, SetAclExceptionHandler) |
| 340 except AccessDeniedException, unused_e: |
| 341 self._WarnServiceAccounts() |
| 342 raise |
| 343 if not self.everything_set_okay: |
| 344 raise CommandException('ACLs for some objects could not be set.') |
| 345 |
| 346 def _ChAcl(self): |
| 347 """Parses options and changes ACLs on the specified buckets/objects.""" |
| 348 self.parse_versions = True |
| 349 self.changes = [] |
| 350 self.continue_on_error = False |
| 351 |
| 352 if self.sub_opts: |
| 353 for o, a in self.sub_opts: |
| 354 if o == '-f': |
| 355 self.continue_on_error = True |
| 356 elif o == '-g': |
| 357 if 'gserviceaccount.com' in a: |
| 358 raise CommandException( |
| 359 'Service accounts are considered users, not groups; please use ' |
| 360 '"gsutil acl ch -u" instead of "gsutil acl ch -g"') |
| 361 self.changes.append( |
| 362 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP)) |
| 363 elif o == '-p': |
| 364 self.changes.append( |
| 365 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.PROJECT)) |
| 366 elif o == '-u': |
| 367 self.changes.append( |
| 368 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER)) |
| 369 elif o == '-d': |
| 370 self.changes.append(aclhelpers.AclDel(a)) |
| 371 elif o == '-r' or o == '-R': |
| 372 self.recursion_requested = True |
| 373 else: |
| 374 self.RaiseInvalidArgumentException() |
| 375 |
| 376 if not self.changes: |
| 377 raise CommandException( |
| 378 'Please specify at least one access change ' |
| 379 'with the -g, -u, or -d flags') |
| 380 |
| 381 if (not UrlsAreForSingleProvider(self.args) or |
| 382 StorageUrlFromString(self.args[0]).scheme != 'gs'): |
| 383 raise CommandException( |
| 384 'The "{0}" command can only be used with gs:// URLs'.format( |
| 385 self.command_name)) |
| 386 |
| 387 self.everything_set_okay = True |
| 388 self.ApplyAclFunc(_ApplyAclChangesWrapper, _ApplyExceptionHandler, |
| 389 self.args) |
| 390 if not self.everything_set_okay: |
| 391 raise CommandException('ACLs for some objects could not be set.') |
| 392 |
| 393 def _RaiseForAccessDenied(self, url): |
| 394 self._WarnServiceAccounts() |
| 395 raise CommandException('Failed to set acl for %s. Please ensure you have ' |
| 396 'OWNER-role access to this resource.' % url) |
| 397 |
| 398 @Retry(ServiceException, tries=3, timeout_secs=1) |
| 399 def ApplyAclChanges(self, name_expansion_result, thread_state=None): |
| 400 """Applies the changes in self.changes to the provided URL. |
| 401 |
| 402 Args: |
| 403 name_expansion_result: NameExpansionResult describing the target object. |
| 404 thread_state: If present, gsutil Cloud API instance to apply the changes. |
| 405 """ |
| 406 if thread_state: |
| 407 gsutil_api = thread_state |
| 408 else: |
| 409 gsutil_api = self.gsutil_api |
| 410 |
| 411 url = name_expansion_result.expanded_storage_url |
| 412 |
| 413 if url.IsBucket(): |
| 414 bucket = gsutil_api.GetBucket(url.bucket_name, provider=url.scheme, |
| 415 fields=['acl', 'metageneration']) |
| 416 current_acl = bucket.acl |
| 417 elif url.IsObject(): |
| 418 gcs_object = gsutil_api.GetObjectMetadata( |
| 419 url.bucket_name, url.object_name, provider=url.scheme, |
| 420 generation=url.generation, |
| 421 fields=['acl', 'generation', 'metageneration']) |
| 422 current_acl = gcs_object.acl |
| 423 if not current_acl: |
| 424 self._RaiseForAccessDenied(url) |
| 425 |
| 426 modification_count = 0 |
| 427 for change in self.changes: |
| 428 modification_count += change.Execute(url, current_acl, 'acl', self.logger) |
| 429 if modification_count == 0: |
| 430 self.logger.info('No changes to %s', url) |
| 431 return |
| 432 |
| 433 try: |
| 434 if url.IsBucket(): |
| 435 preconditions = Preconditions(meta_gen_match=bucket.metageneration) |
| 436 bucket_metadata = apitools_messages.Bucket(acl=current_acl) |
| 437 gsutil_api.PatchBucket(url.bucket_name, bucket_metadata, |
| 438 preconditions=preconditions, |
| 439 provider=url.scheme, fields=['id']) |
| 440 else: # Object |
| 441 preconditions = Preconditions(gen_match=gcs_object.generation, |
| 442 meta_gen_match=gcs_object.metageneration) |
| 443 |
| 444 object_metadata = apitools_messages.Object(acl=current_acl) |
| 445 gsutil_api.PatchObjectMetadata( |
| 446 url.bucket_name, url.object_name, object_metadata, |
| 447 preconditions=preconditions, provider=url.scheme, |
| 448 generation=url.generation) |
| 449 except BadRequestException as e: |
| 450 # Don't retry on bad requests, e.g. invalid email address. |
| 451 raise CommandException('Received bad request from server: %s' % str(e)) |
| 452 except AccessDeniedException: |
| 453 self._RaiseForAccessDenied(url) |
| 454 |
| 455 self.logger.info('Updated ACL on %s', url) |
| 456 |
| 457 def RunCommand(self): |
| 458 """Command entry point for the acl command.""" |
| 459 action_subcommand = self.args.pop(0) |
| 460 self.ParseSubOpts(check_args=True) |
| 461 self.def_acl = False |
| 462 if action_subcommand == 'get': |
| 463 self.GetAndPrintAcl(self.args[0]) |
| 464 elif action_subcommand == 'set': |
| 465 self._SetAcl() |
| 466 elif action_subcommand in ('ch', 'change'): |
| 467 self._ChAcl() |
| 468 else: |
| 469 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' |
| 470 'See "gsutil help acl".') % |
| 471 (action_subcommand, self.command_name)) |
| 472 |
| 473 return 0 |
OLD | NEW |