Home | History | Annotate | Download | only in commands
      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 default object acl command for Google Cloud Storage."""
     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_messages
     33 from gslib.translation_helper import PRIVATE_DEFAULT_OBJ_ACL
     34 from gslib.util import NO_MAX
     35 from gslib.util import Retry
     36 from gslib.util import UrlsAreForSingleProvider
     37 
     38 _SET_SYNOPSIS = """
     39   gsutil defacl set file-or-canned_acl_name url...
     40 """
     41 
     42 _GET_SYNOPSIS = """
     43   gsutil defacl get url
     44 """
     45 
     46 _CH_SYNOPSIS = """
     47   gsutil defacl ch [-f] -u|-g|-d|-p <grant>... url...
     48 """
     49 
     50 _SET_DESCRIPTION = """
     51 <B>SET</B>
     52   The "defacl set" command sets default object ACLs for the specified buckets.
     53   If you specify a default object ACL for a certain bucket, Google Cloud
     54   Storage applies the default object ACL to all new objects uploaded to that
     55   bucket, unless an ACL for that object is separately specified during upload.
     56 
     57   Similar to the "acl set" command, the file-or-canned_acl_name names either a
     58   canned ACL or the path to a file that contains ACL text. (See "gsutil
     59   help acl" for examples of editing and setting ACLs via the
     60   acl command.)
     61 
     62   Setting a default object ACL on a bucket provides a convenient way to ensure
     63   newly uploaded objects have a specific ACL. If you don't set the bucket's
     64   default object ACL, it will default to project-private. If you then upload
     65   objects that need a different ACL, you will need to perform a separate ACL
     66   update operation for each object. Depending on how many objects require
     67   updates, this could be very time-consuming.
     68 """
     69 
     70 _GET_DESCRIPTION = """
     71 <B>GET</B>
     72   Gets the default ACL text for a bucket, which you can save and edit
     73   for use with the "defacl set" command.
     74 """
     75 
     76 _CH_DESCRIPTION = """
     77 <B>CH</B>
     78   The "defacl ch" (or "defacl change") command updates the default object
     79   access control list for a bucket. The syntax is shared with the "acl ch"
     80   command, so see the "CH" section of "gsutil help acl" for the full help
     81   description.
     82 
     83 <B>CH EXAMPLES</B>
     84   Grant anyone on the internet READ access by default to any object created
     85   in the bucket example-bucket:
     86 
     87     gsutil defacl ch -u AllUsers:R gs://example-bucket
     88 
     89   NOTE: By default, publicly readable objects are served with a Cache-Control
     90   header allowing such objects to be cached for 3600 seconds. If you need to
     91   ensure that updates become visible immediately, you should set a
     92   Cache-Control header of "Cache-Control:private, max-age=0, no-transform" on
     93   such objects. For help doing this, see "gsutil help setmeta".
     94 
     95   Add the user john.doe (at] example.com to the default object ACL on bucket
     96   example-bucket with READ access:
     97 
     98     gsutil defacl ch -u john.doe (at] example.com:READ gs://example-bucket
     99 
    100   Add the group admins (at] example.com to the default object ACL on bucket
    101   example-bucket with OWNER access:
    102 
    103     gsutil defacl ch -g admins (at] example.com:O gs://example-bucket
    104 
    105   Remove the group admins (at] example.com from the default object ACL on bucket
    106   example-bucket:
    107 
    108     gsutil defacl ch -d admins (at] example.com gs://example-bucket
    109 
    110   Add the owners of project example-project-123 to the default object ACL on
    111   bucket example-bucket with READ access:
    112 
    113     gsutil defacl ch -p owners-example-project-123:R gs://example-bucket
    114 
    115   NOTE: You can replace 'owners' with 'viewers' or 'editors' to grant access
    116   to a project's viewers/editors respectively.
    117 
    118 <B>CH OPTIONS</B>
    119   The "ch" sub-command has the following options
    120 
    121     -d          Remove all roles associated with the matching entity.
    122 
    123     -f          Normally gsutil stops at the first error. The -f option causes
    124                 it to continue when it encounters errors. With this option the
    125                 gsutil exit status will be 0 even if some ACLs couldn't be
    126                 changed.
    127 
    128     -g          Add or modify a group entity's role.
    129 
    130     -p          Add or modify a project viewers/editors/owners role.
    131 
    132     -u          Add or modify a user entity's role.
    133 """
    134 
    135 _SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') +
    136              _CH_SYNOPSIS.lstrip('\n') + '\n\n')
    137 
    138 _DESCRIPTION = """
    139   The defacl command has three sub-commands:
    140 """ + '\n'.join([_SET_DESCRIPTION + _GET_DESCRIPTION + _CH_DESCRIPTION])
    141 
    142 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION)
    143 
    144 _get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION)
    145 _set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION)
    146 _ch_help_text = CreateHelpText(_CH_SYNOPSIS, _CH_DESCRIPTION)
    147 
    148 
    149 class DefAclCommand(Command):
    150   """Implementation of gsutil defacl command."""
    151 
    152   # Command specification. See base class for documentation.
    153   command_spec = Command.CreateCommandSpec(
    154       'defacl',
    155       command_name_aliases=['setdefacl', 'getdefacl', 'chdefacl'],
    156       usage_synopsis=_SYNOPSIS,
    157       min_args=2,
    158       max_args=NO_MAX,
    159       supported_sub_args='fg:u:d:p:',
    160       file_url_ok=False,
    161       provider_url_ok=False,
    162       urls_start_arg=1,
    163       gs_api_support=[ApiSelector.XML, ApiSelector.JSON],
    164       gs_default_api=ApiSelector.JSON,
    165       argparse_arguments={
    166           'set': [
    167               CommandArgument.MakeFileURLOrCannedACLArgument(),
    168               CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument()
    169           ],
    170           'get': [
    171               CommandArgument.MakeNCloudBucketURLsArgument(1)
    172           ],
    173           'ch': [
    174               CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument()
    175           ],
    176       }
    177   )
    178   # Help specification. See help_provider.py for documentation.
    179   help_spec = Command.HelpSpec(
    180       help_name='defacl',
    181       help_name_aliases=[
    182           'default acl', 'setdefacl', 'getdefacl', 'chdefacl'],
    183       help_type='command_help',
    184       help_one_line_summary='Get, set, or change default ACL on buckets',
    185       help_text=_DETAILED_HELP_TEXT,
    186       subcommand_help_text={
    187           'get': _get_help_text, 'set': _set_help_text, 'ch': _ch_help_text},
    188   )
    189 
    190   def _CalculateUrlsStartArg(self):
    191     if not self.args:
    192       self.RaiseWrongNumberOfArgumentsException()
    193     if (self.args[0].lower() == 'set' or
    194         self.command_alias_used == 'setdefacl'):
    195       return 1
    196     else:
    197       return 0
    198 
    199   def _SetDefAcl(self):
    200     if not StorageUrlFromString(self.args[-1]).IsBucket():
    201       raise CommandException('URL must name a bucket for the %s command' %
    202                              self.command_name)
    203     try:
    204       self.SetAclCommandHelper(SetAclFuncWrapper, SetAclExceptionHandler)
    205     except AccessDeniedException:
    206       self._WarnServiceAccounts()
    207       raise
    208 
    209   def _GetDefAcl(self):
    210     if not StorageUrlFromString(self.args[0]).IsBucket():
    211       raise CommandException('URL must name a bucket for the %s command' %
    212                              self.command_name)
    213     self.GetAndPrintAcl(self.args[0])
    214 
    215   def _ChDefAcl(self):
    216     """Parses options and changes default object ACLs on specified buckets."""
    217     self.parse_versions = True
    218     self.changes = []
    219 
    220     if self.sub_opts:
    221       for o, a in self.sub_opts:
    222         if o == '-g':
    223           self.changes.append(
    224               aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP))
    225         if o == '-u':
    226           self.changes.append(
    227               aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER))
    228         if o == '-p':
    229           self.changes.append(
    230               aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.PROJECT))
    231         if o == '-d':
    232           self.changes.append(aclhelpers.AclDel(a))
    233 
    234     if not self.changes:
    235       raise CommandException(
    236           'Please specify at least one access change '
    237           'with the -g, -u, or -d flags')
    238 
    239     if (not UrlsAreForSingleProvider(self.args) or
    240         StorageUrlFromString(self.args[0]).scheme != 'gs'):
    241       raise CommandException(
    242           'The "{0}" command can only be used with gs:// URLs'.format(
    243               self.command_name))
    244 
    245     bucket_urls = set()
    246     for url_arg in self.args:
    247       for result in self.WildcardIterator(url_arg):
    248         if not result.storage_url.IsBucket():
    249           raise CommandException(
    250               'The defacl ch command can only be applied to buckets.')
    251         bucket_urls.add(result.storage_url)
    252 
    253     for storage_url in bucket_urls:
    254       self.ApplyAclChanges(storage_url)
    255 
    256   @Retry(ServiceException, tries=3, timeout_secs=1)
    257   def ApplyAclChanges(self, url):
    258     """Applies the changes in self.changes to the provided URL."""
    259     bucket = self.gsutil_api.GetBucket(
    260         url.bucket_name, provider=url.scheme,
    261         fields=['defaultObjectAcl', 'metageneration'])
    262 
    263     # Default object ACLs can be blank if the ACL was set to private, or
    264     # if the user doesn't have permission. We warn about this with defacl get,
    265     # so just try the modification here and if the user doesn't have
    266     # permission they'll get an AccessDeniedException.
    267     current_acl = bucket.defaultObjectAcl
    268 
    269     modification_count = 0
    270     for change in self.changes:
    271       modification_count += change.Execute(
    272           url, current_acl, 'defacl', self.logger)
    273     if modification_count == 0:
    274       self.logger.info('No changes to %s', url)
    275       return
    276 
    277     if not current_acl:
    278       # Use a sentinel value to indicate a private (no entries) default
    279       # object ACL.
    280       current_acl.append(PRIVATE_DEFAULT_OBJ_ACL)
    281 
    282     try:
    283       preconditions = Preconditions(meta_gen_match=bucket.metageneration)
    284       bucket_metadata = apitools_messages.Bucket(defaultObjectAcl=current_acl)
    285       self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
    286                                   preconditions=preconditions,
    287                                   provider=url.scheme, fields=['id'])
    288     except BadRequestException as e:
    289       # Don't retry on bad requests, e.g. invalid email address.
    290       raise CommandException('Received bad request from server: %s' % str(e))
    291     except AccessDeniedException:
    292       self._WarnServiceAccounts()
    293       raise CommandException('Failed to set acl for %s. Please ensure you have '
    294                              'OWNER-role access to this resource.' % url)
    295 
    296     self.logger.info('Updated default ACL on %s', url)
    297 
    298   def RunCommand(self):
    299     """Command entry point for the defacl command."""
    300     action_subcommand = self.args.pop(0)
    301     self.ParseSubOpts(check_args=True)
    302     self.def_acl = True
    303     self.continue_on_error = False
    304     if action_subcommand == 'get':
    305       func = self._GetDefAcl
    306     elif action_subcommand == 'set':
    307       func = self._SetDefAcl
    308     elif action_subcommand in ('ch', 'change'):
    309       func = self._ChDefAcl
    310     else:
    311       raise CommandException(('Invalid subcommand "%s" for the %s command.\n'
    312                               'See "gsutil help defacl".') %
    313                              (action_subcommand, self.command_name))
    314     func()
    315     return 0
    316