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