blob: 24c6e259e7dbcdd5dc31591f7e7bd2fe9abd2e4e [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2011 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implementation of default object acl command for Google Cloud Storage."""
from __future__ import absolute_import
from gslib import aclhelpers
from gslib.cloud_api import AccessDeniedException
from gslib.cloud_api import BadRequestException
from gslib.cloud_api import Preconditions
from gslib.cloud_api import ServiceException
from gslib.command import Command
from gslib.command import SetAclExceptionHandler
from gslib.command import SetAclFuncWrapper
from gslib.command_argument import CommandArgument
from gslib.cs_api_map import ApiSelector
from gslib.exception import CommandException
from gslib.help_provider import CreateHelpText
from gslib.storage_url import StorageUrlFromString
from gslib.third_party.storage_apitools import storage_v1_messages as apitools_messages
from gslib.util import NO_MAX
from gslib.util import Retry
from gslib.util import UrlsAreForSingleProvider
_SET_SYNOPSIS = """
gsutil defacl set file-or-canned_acl_name url...
"""
_GET_SYNOPSIS = """
gsutil defacl get url
"""
_CH_SYNOPSIS = """
gsutil defacl ch [-f] -u|-g|-d|-p <grant>... url...
"""
_SET_DESCRIPTION = """
<B>SET</B>
The "defacl set" command sets default object ACLs for the specified buckets.
If you specify a default object ACL for a certain bucket, Google Cloud
Storage applies the default object ACL to all new objects uploaded to that
bucket, unless an ACL for that object is separately specified during upload.
Similar to the "acl set" command, the file-or-canned_acl_name names either a
canned ACL or the path to a file that contains ACL text. (See "gsutil
help acl" for examples of editing and setting ACLs via the
acl command.)
Setting a default object ACL on a bucket provides a convenient way to ensure
newly uploaded objects have a specific ACL. If you don't set the bucket's
default object ACL, it will default to project-private. If you then upload
objects that need a different ACL, you will need to perform a separate ACL
update operation for each object. Depending on how many objects require
updates, this could be very time-consuming.
"""
_GET_DESCRIPTION = """
<B>GET</B>
Gets the default ACL text for a bucket, which you can save and edit
for use with the "defacl set" command.
"""
_CH_DESCRIPTION = """
<B>CH</B>
The "defacl ch" (or "defacl change") command updates the default object
access control list for a bucket. The syntax is shared with the "acl ch"
command, so see the "CH" section of "gsutil help acl" for the full help
description.
<B>CH EXAMPLES</B>
Grant anyone on the internet READ access by default to any object created
in the bucket example-bucket:
gsutil defacl ch -u AllUsers:R gs://example-bucket
NOTE: By default, publicly readable objects are served with a Cache-Control
header allowing such objects to be cached for 3600 seconds. If you need to
ensure that updates become visible immediately, you should set a
Cache-Control header of "Cache-Control:private, max-age=0, no-transform" on
such objects. For help doing this, see "gsutil help setmeta".
Add the user john.doe@example.com to the default object ACL on bucket
example-bucket with READ access:
gsutil defacl ch -u john.doe@example.com:READ gs://example-bucket
Add the group admins@example.com to the default object ACL on bucket
example-bucket with OWNER access:
gsutil defacl ch -g admins@example.com:O gs://example-bucket
Grant the owners of project example-project-123 READ access to new objects
created in the bucket example-bucket:
gsutil acl ch -p owners-example-project-123:R gs://example-bucket
NOTE: You can replace 'owners' with 'viewers' or 'editors' to grant access
to a project's viewers/editors respectively.
<B>CH OPTIONS</B>
The "ch" sub-command has the following options
-d Remove all roles associated with the matching entity.
-f Normally gsutil stops at the first error. The -f option causes
it to continue when it encounters errors. With this option the
gsutil exit status will be 0 even if some ACLs couldn't be
changed.
-g Add or modify a group entity's role.
-p Add or modify a project viewers/editors/owners role.
-u Add or modify a user entity's role.
"""
_SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') +
_CH_SYNOPSIS.lstrip('\n') + '\n\n')
_DESCRIPTION = """
The defacl command has three sub-commands:
""" + '\n'.join([_SET_DESCRIPTION + _GET_DESCRIPTION + _CH_DESCRIPTION])
_DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION)
_get_help_text = CreateHelpText(_GET_SYNOPSIS, _GET_DESCRIPTION)
_set_help_text = CreateHelpText(_SET_SYNOPSIS, _SET_DESCRIPTION)
_ch_help_text = CreateHelpText(_CH_SYNOPSIS, _CH_DESCRIPTION)
class DefAclCommand(Command):
"""Implementation of gsutil defacl command."""
# Command specification. See base class for documentation.
command_spec = Command.CreateCommandSpec(
'defacl',
command_name_aliases=['setdefacl', 'getdefacl', 'chdefacl'],
usage_synopsis=_SYNOPSIS,
min_args=2,
max_args=NO_MAX,
supported_sub_args='fg:u:d:p:',
file_url_ok=False,
provider_url_ok=False,
urls_start_arg=1,
gs_api_support=[ApiSelector.XML, ApiSelector.JSON],
gs_default_api=ApiSelector.JSON,
argparse_arguments={
'set': [
CommandArgument.MakeFileURLOrCannedACLArgument(),
CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument()
],
'get': [
CommandArgument.MakeNCloudBucketURLsArgument(1)
],
'ch': [
CommandArgument.MakeZeroOrMoreCloudBucketURLsArgument()
],
}
)
# Help specification. See help_provider.py for documentation.
help_spec = Command.HelpSpec(
help_name='defacl',
help_name_aliases=[
'default acl', 'setdefacl', 'getdefacl', 'chdefacl'],
help_type='command_help',
help_one_line_summary='Get, set, or change default ACL on buckets',
help_text=_DETAILED_HELP_TEXT,
subcommand_help_text={
'get': _get_help_text, 'set': _set_help_text, 'ch': _ch_help_text},
)
def _CalculateUrlsStartArg(self):
if not self.args:
self.RaiseWrongNumberOfArgumentsException()
if (self.args[0].lower() == 'set' or
self.command_alias_used == 'setdefacl'):
return 1
else:
return 0
def _SetDefAcl(self):
if not StorageUrlFromString(self.args[-1]).IsBucket():
raise CommandException('URL must name a bucket for the %s command' %
self.command_name)
try:
self.SetAclCommandHelper(SetAclFuncWrapper, SetAclExceptionHandler)
except AccessDeniedException:
self._WarnServiceAccounts()
raise
def _GetDefAcl(self):
if not StorageUrlFromString(self.args[0]).IsBucket():
raise CommandException('URL must name a bucket for the %s command' %
self.command_name)
self.GetAndPrintAcl(self.args[0])
def _ChDefAcl(self):
"""Parses options and changes default object ACLs on specified buckets."""
self.parse_versions = True
self.changes = []
if self.sub_opts:
for o, a in self.sub_opts:
if o == '-g':
self.changes.append(
aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP))
if o == '-u':
self.changes.append(
aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER))
if o == '-p':
self.changes.append(
aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.PROJECT))
if o == '-d':
self.changes.append(aclhelpers.AclDel(a))
if not self.changes:
raise CommandException(
'Please specify at least one access change '
'with the -g, -u, or -d flags')
if (not UrlsAreForSingleProvider(self.args) or
StorageUrlFromString(self.args[0]).scheme != 'gs'):
raise CommandException(
'The "{0}" command can only be used with gs:// URLs'.format(
self.command_name))
bucket_urls = set()
for url_arg in self.args:
for result in self.WildcardIterator(url_arg):
if not result.storage_url.IsBucket():
raise CommandException(
'The defacl ch command can only be applied to buckets.')
bucket_urls.add(result.storage_url)
for storage_url in bucket_urls:
self.ApplyAclChanges(storage_url)
@Retry(ServiceException, tries=3, timeout_secs=1)
def ApplyAclChanges(self, url):
"""Applies the changes in self.changes to the provided URL."""
bucket = self.gsutil_api.GetBucket(
url.bucket_name, provider=url.scheme,
fields=['defaultObjectAcl', 'metageneration'])
# Default object ACLs can be blank if the ACL was set to private, or
# if the user doesn't have permission. We warn about this with defacl get,
# so just try the modification here and if the user doesn't have
# permission they'll get an AccessDeniedException.
current_acl = bucket.defaultObjectAcl
modification_count = 0
for change in self.changes:
modification_count += change.Execute(
url, current_acl, 'defacl', self.logger)
if modification_count == 0:
self.logger.info('No changes to %s', url)
return
try:
preconditions = Preconditions(meta_gen_match=bucket.metageneration)
bucket_metadata = apitools_messages.Bucket(defaultObjectAcl=current_acl)
self.gsutil_api.PatchBucket(url.bucket_name, bucket_metadata,
preconditions=preconditions,
provider=url.scheme, fields=['id'])
except BadRequestException as e:
# Don't retry on bad requests, e.g. invalid email address.
raise CommandException('Received bad request from server: %s' % str(e))
except AccessDeniedException:
self._WarnServiceAccounts()
raise CommandException('Failed to set acl for %s. Please ensure you have '
'OWNER-role access to this resource.' % url)
self.logger.info('Updated default ACL on %s', url)
def RunCommand(self):
"""Command entry point for the defacl command."""
action_subcommand = self.args.pop(0)
self.ParseSubOpts(check_args=True)
self.def_acl = True
self.continue_on_error = False
if action_subcommand == 'get':
func = self._GetDefAcl
elif action_subcommand == 'set':
func = self._SetDefAcl
elif action_subcommand in ('ch', 'change'):
func = self._ChDefAcl
else:
raise CommandException(('Invalid subcommand "%s" for the %s command.\n'
'See "gsutil help defacl".') %
(action_subcommand, self.command_name))
func()
return 0