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 17 from __future__ import absolute_import 18 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.translation_helper import PRIVATE_DEFAULT_OBJ_ACL 34 from gslib.util import NO_MAX 35 from gslib.util import Retry 36 from 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 (at] example.com to the default object ACL on bucket 96 example-bucket with READ access: 97 98 gsutil defacl ch -u john.doe (at] example.com:READ gs://example-bucket 99 100 Add the group admins (at] example.com to the default object ACL on bucket 101 example-bucket with OWNER access: 102 103 gsutil defacl ch -g admins (at] example.com:O gs://example-bucket 104 105 Remove the group admins (at] example.com from the default object ACL on bucket 106 example-bucket: 107 108 gsutil defacl ch -d admins (at] 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 149 class 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