• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
17from __future__ import absolute_import
18
19from gslib import aclhelpers
20from gslib.cloud_api import AccessDeniedException
21from gslib.cloud_api import BadRequestException
22from gslib.cloud_api import Preconditions
23from gslib.cloud_api import ServiceException
24from gslib.command import Command
25from gslib.command import SetAclExceptionHandler
26from gslib.command import SetAclFuncWrapper
27from gslib.command_argument import CommandArgument
28from gslib.cs_api_map import ApiSelector
29from gslib.exception import CommandException
30from gslib.help_provider import CreateHelpText
31from gslib.storage_url import StorageUrlFromString
32from gslib.third_party.storage_apitools import storage_v1_messages as apitools_messages
33from gslib.translation_helper import PRIVATE_DEFAULT_OBJ_ACL
34from gslib.util import NO_MAX
35from gslib.util import Retry
36from 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@example.com to the default object ACL on bucket
96  example-bucket with READ access:
97
98    gsutil defacl ch -u john.doe@example.com:READ gs://example-bucket
99
100  Add the group admins@example.com to the default object ACL on bucket
101  example-bucket with OWNER access:
102
103    gsutil defacl ch -g admins@example.com:O gs://example-bucket
104
105  Remove the group admins@example.com from the default object ACL on bucket
106  example-bucket:
107
108    gsutil defacl ch -d admins@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
149class 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