1 # -*- coding: utf-8 -*-
2 # Copyright 2011 Google Inc. All Rights Reserved.
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
8 # http://www.apache.org/licenses/LICENSE-2.0
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."""
17 from __future__
import absolute_import
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
.util
import NO_MAX
34 from gslib
.util
import Retry
35 from gslib
.util
import UrlsAreForSingleProvider
38 gsutil acl set [-f] [-r] [-a] file-or-canned_acl_name url...
46 gsutil acl ch [-f] [-r] -u|-g|-d|-p <grant>... url...
48 where each <grant> is one of the following forms:
51 -g <id|email|domain|All|AllAuth>:<perm>
52 -p <viewers|editors|owners>-<project number>
53 -d <id|email|domain|All|AllAuth>
56 _GET_DESCRIPTION
= """
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.
62 _SET_DESCRIPTION
= """
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.,:
68 gsutil acl set private gs://bucket
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.
74 See "gsutil help acls" for a list of all canned ACLs.
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:
81 gsutil acl get gs://bucket/file.txt > acl.txt
83 Make changes to acl.txt such as adding an additional grant, then:
85 gsutil acl set acl.txt gs://cats/file.txt
87 Note that you can set an ACL on multiple buckets or objects at once,
90 gsutil acl set acl.txt gs://bucket/*.jpg
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)
96 gsutil -m acl set acl.txt gs://bucket/*.jpg
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.
104 The "set" sub-command has the following options
106 -R, -r Performs "acl set" request recursively, to all objects under
109 -a Performs "acl set" request on all object versions.
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
118 _CH_DESCRIPTION
= """
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.
132 Examples for "ch" sub-command:
134 Grant anyone on the internet READ access to the object example-object:
136 gsutil acl ch -u AllUsers:R gs://example-bucket/example-object
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".
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):
147 gsutil acl ch -u AllUsers:W gs://example-bucket
149 Grant the user john.doe@example.com WRITE access to the bucket
152 gsutil acl ch -u john.doe@example.com:WRITE gs://example-bucket
154 Grant the group admins@example.com OWNER access to all jpg files in
155 the top level of example-bucket:
157 gsutil acl ch -g admins@example.com:O gs://example-bucket/*.jpg
159 Grant the owners of project example-project-123 WRITE access to the bucket
162 gsutil acl ch -p owners-example-project-123:W gs://example-bucket
164 NOTE: You can replace 'owners' with 'viewers' or 'editors' to grant access
165 to a project's viewers/editors respectively.
167 Grant the user with the specified canonical ID READ access to all objects
168 in example-bucket that begin with folder/:
171 -u 84fac329bceSAMPLE777d5d22b8SAMPLE785ac2SAMPLE2dfcf7c4adf34da46:R \\
172 gs://example-bucket/folder/
174 Grant the service account foo@developer.gserviceaccount.com WRITE access to
175 the bucket example-bucket:
177 gsutil acl ch -u foo@developer.gserviceaccount.com:W gs://example-bucket
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:
183 gsutil acl ch -g my-domain.org:R gs://gcs.my-domain.org
185 Remove any current access by john.doe@example.com from the bucket
188 gsutil acl ch -d john.doe@example.com gs://example-bucket
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
195 gsutil -m acl ch -r -u admin@example.org:O gs://example-bucket
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:
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
205 You may specify the following roles with either their shorthand or
213 There are four different entity types: Users, Groups, All Authenticated Users,
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.
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".
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.
227 Removing roles is specified with the -d flag and an ID, email
228 address, domain, or one of AllUsers or AllAuthenticatedUsers.
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.
235 The "ch" sub-command has the following options
237 -d Remove all roles associated with the matching entity.
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
244 -g Add or modify a group entity's role.
246 -p Add or modify a project viewers/editors/owners role.
248 -R, -r Performs acl ch request recursively, to all objects under the
251 -u Add or modify a user entity's role.
254 _SYNOPSIS
= (_SET_SYNOPSIS
+ _GET_SYNOPSIS
.lstrip('\n') +
255 _CH_SYNOPSIS
.lstrip('\n') + '\n\n')
258 The acl command has three sub-commands:
259 """ + '\n'.join([_GET_DESCRIPTION
, _SET_DESCRIPTION
, _CH_DESCRIPTION
]))
261 _DETAILED_HELP_TEXT
= CreateHelpText(_SYNOPSIS
, _DESCRIPTION
)
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
)
268 def _ApplyExceptionHandler(cls
, exception
):
269 cls
.logger
.error('Encountered a problem: %s', exception
)
270 cls
.everything_set_okay
= False
273 def _ApplyAclChangesWrapper(cls
, url_or_expansion_result
, thread_state
=None):
274 cls
.ApplyAclChanges(url_or_expansion_result
, thread_state
=thread_state
)
277 class AclCommand(Command
):
278 """Implementation of gsutil acl command."""
280 # Command specification. See base class for documentation.
281 command_spec
= Command
.CreateCommandSpec(
283 command_name_aliases
=['getacl', 'setacl', 'chacl'],
284 usage_synopsis
=_SYNOPSIS
,
287 supported_sub_args
='afRrg:u:d:p:',
289 provider_url_ok
=False,
291 gs_api_support
=[ApiSelector
.XML
, ApiSelector
.JSON
],
292 gs_default_api
=ApiSelector
.JSON
,
295 CommandArgument
.MakeFileURLOrCannedACLArgument(),
296 CommandArgument
.MakeZeroOrMoreCloudURLsArgument()
299 CommandArgument
.MakeNCloudURLsArgument(1)
302 CommandArgument
.MakeZeroOrMoreCloudURLsArgument()
306 # Help specification. See help_provider.py for documentation.
307 help_spec
= Command
.HelpSpec(
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
},
317 def _CalculateUrlsStartArg(self
):
319 self
.RaiseWrongNumberOfArgumentsException()
320 if (self
.args
[0].lower() == 'set') or (self
.command_alias_used
== 'setacl'):
326 """Parses options and sets ACLs on the specified buckets/objects."""
327 self
.continue_on_error
= False
329 for o
, unused_a
in self
.sub_opts
:
331 self
.all_versions
= True
333 self
.continue_on_error
= True
334 elif o
== '-r' or o
== '-R':
335 self
.recursion_requested
= True
337 self
.RaiseInvalidArgumentException()
339 self
.SetAclCommandHelper(SetAclFuncWrapper
, SetAclExceptionHandler
)
340 except AccessDeniedException
, unused_e
:
341 self
._WarnServiceAccounts
()
343 if not self
.everything_set_okay
:
344 raise CommandException('ACLs for some objects could not be set.')
347 """Parses options and changes ACLs on the specified buckets/objects."""
348 self
.parse_versions
= True
350 self
.continue_on_error
= False
353 for o
, a
in self
.sub_opts
:
355 self
.continue_on_error
= True
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"')
362 aclhelpers
.AclChange(a
, scope_type
=aclhelpers
.ChangeType
.GROUP
))
365 aclhelpers
.AclChange(a
, scope_type
=aclhelpers
.ChangeType
.PROJECT
))
368 aclhelpers
.AclChange(a
, scope_type
=aclhelpers
.ChangeType
.USER
))
370 self
.changes
.append(aclhelpers
.AclDel(a
))
371 elif o
== '-r' or o
== '-R':
372 self
.recursion_requested
= True
374 self
.RaiseInvalidArgumentException()
377 raise CommandException(
378 'Please specify at least one access change '
379 'with the -g, -u, or -d flags')
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(
387 self
.everything_set_okay
= True
388 self
.ApplyAclFunc(_ApplyAclChangesWrapper
, _ApplyExceptionHandler
,
390 if not self
.everything_set_okay
:
391 raise CommandException('ACLs for some objects could not be set.')
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
)
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.
403 name_expansion_result: NameExpansionResult describing the target object.
404 thread_state: If present, gsutil Cloud API instance to apply the changes.
407 gsutil_api
= thread_state
409 gsutil_api
= self
.gsutil_api
411 url
= name_expansion_result
.expanded_storage_url
414 bucket
= gsutil_api
.GetBucket(url
.bucket_name
, provider
=url
.scheme
,
415 fields
=['acl', 'metageneration'])
416 current_acl
= bucket
.acl
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
424 self
._RaiseForAccessDenied
(url
)
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
)
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'])
441 preconditions
= Preconditions(gen_match
=gcs_object
.generation
,
442 meta_gen_match
=gcs_object
.metageneration
)
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
)
455 self
.logger
.info('Updated ACL on %s', url
)
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)
462 if action_subcommand
== 'get':
463 self
.GetAndPrintAcl(self
.args
[0])
464 elif action_subcommand
== 'set':
466 elif action_subcommand
in ('ch', 'change'):
469 raise CommandException(('Invalid subcommand "%s" for the %s command.\n'
470 'See "gsutil help acl".') %
471 (action_subcommand
, self
.command_name
))