OLD | NEW |
| (Empty) |
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 | |
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_m
essages | |
33 from gslib.util import NO_MAX | |
34 from gslib.util import Retry | |
35 from 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. gsutil -m acl set gs://bucket1 gs://bucket2 will run the | |
100 acl set operations sequentially. | |
101 | |
102 | |
103 <B>SET OPTIONS</B> | |
104 The "set" sub-command has the following options | |
105 | |
106 -R, -r Performs "acl set" request recursively, to all objects under | |
107 the specified URL. | |
108 | |
109 -a Performs "acl set" request on all object versions. | |
110 | |
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 | |
115 "gsutil -m acl...". | |
116 """ | |
117 | |
118 _CH_DESCRIPTION = """ | |
119 <B>CH</B> | |
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. | |
130 | |
131 <B>CH EXAMPLES</B> | |
132 Examples for "ch" sub-command: | |
133 | |
134 Grant anyone on the internet READ access to the object example-object: | |
135 | |
136 gsutil acl ch -u AllUsers:R gs://example-bucket/example-object | |
137 | |
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". | |
143 | |
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): | |
146 | |
147 gsutil acl ch -u AllUsers:W gs://example-bucket | |
148 | |
149 Grant the user john.doe@example.com WRITE access to the bucket | |
150 example-bucket: | |
151 | |
152 gsutil acl ch -u john.doe@example.com:WRITE gs://example-bucket | |
153 | |
154 Grant the group admins@example.com OWNER access to all jpg files in | |
155 the top level of example-bucket: | |
156 | |
157 gsutil acl ch -g admins@example.com:O gs://example-bucket/*.jpg | |
158 | |
159 Grant the owners of project example-project-123 WRITE access to the bucket | |
160 example-bucket: | |
161 | |
162 gsutil acl ch -p owners-example-project-123:W gs://example-bucket | |
163 | |
164 NOTE: You can replace 'owners' with 'viewers' or 'editors' to grant access | |
165 to a project's viewers/editors respectively. | |
166 | |
167 Grant the user with the specified canonical ID READ access to all objects | |
168 in example-bucket that begin with folder/: | |
169 | |
170 gsutil acl ch -r \\ | |
171 -u 84fac329bceSAMPLE777d5d22b8SAMPLE785ac2SAMPLE2dfcf7c4adf34da46:R \\ | |
172 gs://example-bucket/folder/ | |
173 | |
174 Grant the service account foo@developer.gserviceaccount.com WRITE access to | |
175 the bucket example-bucket: | |
176 | |
177 gsutil acl ch -u foo@developer.gserviceaccount.com:W gs://example-bucket | |
178 | |
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: | |
182 | |
183 gsutil acl ch -g my-domain.org:R gs://gcs.my-domain.org | |
184 | |
185 Remove any current access by john.doe@example.com from the bucket | |
186 example-bucket: | |
187 | |
188 gsutil acl ch -d john.doe@example.com gs://example-bucket | |
189 | |
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 | |
193 multi-threading: | |
194 | |
195 gsutil -m acl ch -r -u admin@example.org:O gs://example-bucket | |
196 | |
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: | |
200 | |
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 | |
203 | |
204 <B>CH ROLES</B> | |
205 You may specify the following roles with either their shorthand or | |
206 their full name: | |
207 | |
208 R: READ | |
209 W: WRITE | |
210 O: OWNER | |
211 | |
212 <B>CH ENTITIES</B> | |
213 There are four different entity types: Users, Groups, All Authenticated Users, | |
214 and All Users. | |
215 | |
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. | |
218 | |
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". | |
222 | |
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. | |
226 | |
227 Removing roles is specified with the -d flag and an ID, email | |
228 address, domain, or one of AllUsers or AllAuthenticatedUsers. | |
229 | |
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. | |
233 | |
234 <B>CH OPTIONS</B> | |
235 The "ch" sub-command has the following options | |
236 | |
237 -d Remove all roles associated with the matching entity. | |
238 | |
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 | |
242 changed. | |
243 | |
244 -g Add or modify a group entity's role. | |
245 | |
246 -p Add or modify a project viewers/editors/owners role. | |
247 | |
248 -R, -r Performs acl ch request recursively, to all objects under the | |
249 specified URL. | |
250 | |
251 -u Add or modify a user entity's role. | |
252 """ | |
253 | |
254 _SYNOPSIS = (_SET_SYNOPSIS + _GET_SYNOPSIS.lstrip('\n') + | |
255 _CH_SYNOPSIS.lstrip('\n') + '\n\n') | |
256 | |
257 _DESCRIPTION = (""" | |
258 The acl command has three sub-commands: | |
259 """ + '\n'.join([_GET_DESCRIPTION, _SET_DESCRIPTION, _CH_DESCRIPTION])) | |
260 | |
261 _DETAILED_HELP_TEXT = CreateHelpText(_SYNOPSIS, _DESCRIPTION) | |
262 | |
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) | |
266 | |
267 | |
268 def _ApplyExceptionHandler(cls, exception): | |
269 cls.logger.error('Encountered a problem: %s', exception) | |
270 cls.everything_set_okay = False | |
271 | |
272 | |
273 def _ApplyAclChangesWrapper(cls, url_or_expansion_result, thread_state=None): | |
274 cls.ApplyAclChanges(url_or_expansion_result, thread_state=thread_state) | |
275 | |
276 | |
277 class AclCommand(Command): | |
278 """Implementation of gsutil acl command.""" | |
279 | |
280 # Command specification. See base class for documentation. | |
281 command_spec = Command.CreateCommandSpec( | |
282 'acl', | |
283 command_name_aliases=['getacl', 'setacl', 'chacl'], | |
284 usage_synopsis=_SYNOPSIS, | |
285 min_args=2, | |
286 max_args=NO_MAX, | |
287 supported_sub_args='afRrg:u:d:p:', | |
288 file_url_ok=False, | |
289 provider_url_ok=False, | |
290 urls_start_arg=1, | |
291 gs_api_support=[ApiSelector.XML, ApiSelector.JSON], | |
292 gs_default_api=ApiSelector.JSON, | |
293 argparse_arguments={ | |
294 'set': [ | |
295 CommandArgument.MakeFileURLOrCannedACLArgument(), | |
296 CommandArgument.MakeZeroOrMoreCloudURLsArgument() | |
297 ], | |
298 'get': [ | |
299 CommandArgument.MakeNCloudURLsArgument(1) | |
300 ], | |
301 'ch': [ | |
302 CommandArgument.MakeZeroOrMoreCloudURLsArgument() | |
303 ], | |
304 } | |
305 ) | |
306 # Help specification. See help_provider.py for documentation. | |
307 help_spec = Command.HelpSpec( | |
308 help_name='acl', | |
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}, | |
315 ) | |
316 | |
317 def _CalculateUrlsStartArg(self): | |
318 if not self.args: | |
319 self.RaiseWrongNumberOfArgumentsException() | |
320 if (self.args[0].lower() == 'set') or (self.command_alias_used == 'setacl'): | |
321 return 1 | |
322 else: | |
323 return 0 | |
324 | |
325 def _SetAcl(self): | |
326 """Parses options and sets ACLs on the specified buckets/objects.""" | |
327 self.continue_on_error = False | |
328 if self.sub_opts: | |
329 for o, unused_a in self.sub_opts: | |
330 if o == '-a': | |
331 self.all_versions = True | |
332 elif o == '-f': | |
333 self.continue_on_error = True | |
334 elif o == '-r' or o == '-R': | |
335 self.recursion_requested = True | |
336 else: | |
337 self.RaiseInvalidArgumentException() | |
338 try: | |
339 self.SetAclCommandHelper(SetAclFuncWrapper, SetAclExceptionHandler) | |
340 except AccessDeniedException, unused_e: | |
341 self._WarnServiceAccounts() | |
342 raise | |
343 if not self.everything_set_okay: | |
344 raise CommandException('ACLs for some objects could not be set.') | |
345 | |
346 def _ChAcl(self): | |
347 """Parses options and changes ACLs on the specified buckets/objects.""" | |
348 self.parse_versions = True | |
349 self.changes = [] | |
350 self.continue_on_error = False | |
351 | |
352 if self.sub_opts: | |
353 for o, a in self.sub_opts: | |
354 if o == '-f': | |
355 self.continue_on_error = True | |
356 elif o == '-g': | |
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"') | |
361 self.changes.append( | |
362 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.GROUP)) | |
363 elif o == '-p': | |
364 self.changes.append( | |
365 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.PROJECT)) | |
366 elif o == '-u': | |
367 self.changes.append( | |
368 aclhelpers.AclChange(a, scope_type=aclhelpers.ChangeType.USER)) | |
369 elif o == '-d': | |
370 self.changes.append(aclhelpers.AclDel(a)) | |
371 elif o == '-r' or o == '-R': | |
372 self.recursion_requested = True | |
373 else: | |
374 self.RaiseInvalidArgumentException() | |
375 | |
376 if not self.changes: | |
377 raise CommandException( | |
378 'Please specify at least one access change ' | |
379 'with the -g, -u, or -d flags') | |
380 | |
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( | |
385 self.command_name)) | |
386 | |
387 self.everything_set_okay = True | |
388 self.ApplyAclFunc(_ApplyAclChangesWrapper, _ApplyExceptionHandler, | |
389 self.args) | |
390 if not self.everything_set_okay: | |
391 raise CommandException('ACLs for some objects could not be set.') | |
392 | |
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) | |
397 | |
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. | |
401 | |
402 Args: | |
403 name_expansion_result: NameExpansionResult describing the target object. | |
404 thread_state: If present, gsutil Cloud API instance to apply the changes. | |
405 """ | |
406 if thread_state: | |
407 gsutil_api = thread_state | |
408 else: | |
409 gsutil_api = self.gsutil_api | |
410 | |
411 url = name_expansion_result.expanded_storage_url | |
412 | |
413 if url.IsBucket(): | |
414 bucket = gsutil_api.GetBucket(url.bucket_name, provider=url.scheme, | |
415 fields=['acl', 'metageneration']) | |
416 current_acl = bucket.acl | |
417 elif url.IsObject(): | |
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 | |
423 if not current_acl: | |
424 self._RaiseForAccessDenied(url) | |
425 | |
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) | |
431 return | |
432 | |
433 try: | |
434 if url.IsBucket(): | |
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']) | |
440 else: # Object | |
441 preconditions = Preconditions(gen_match=gcs_object.generation, | |
442 meta_gen_match=gcs_object.metageneration) | |
443 | |
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) | |
454 | |
455 self.logger.info('Updated ACL on %s', url) | |
456 | |
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) | |
461 self.def_acl = False | |
462 if action_subcommand == 'get': | |
463 self.GetAndPrintAcl(self.args[0]) | |
464 elif action_subcommand == 'set': | |
465 self._SetAcl() | |
466 elif action_subcommand in ('ch', 'change'): | |
467 self._ChAcl() | |
468 else: | |
469 raise CommandException(('Invalid subcommand "%s" for the %s command.\n' | |
470 'See "gsutil help acl".') % | |
471 (action_subcommand, self.command_name)) | |
472 | |
473 return 0 | |
OLD | NEW |