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