• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# -*- coding: utf-8 -*-
2# Copyright 2013 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"""Integration tests for the acl command."""
16
17from __future__ import absolute_import
18
19import re
20
21from gslib import aclhelpers
22from gslib.command import CreateGsutilLogger
23from gslib.cs_api_map import ApiSelector
24from gslib.project_id import PopulateProjectId
25from gslib.storage_url import StorageUrlFromString
26import gslib.tests.testcase as testcase
27from gslib.tests.testcase.integration_testcase import SkipForGS
28from gslib.tests.testcase.integration_testcase import SkipForS3
29from gslib.tests.util import ObjectToURI as suri
30from gslib.translation_helper import AclTranslation
31from gslib.util import Retry
32
33PUBLIC_READ_JSON_ACL_TEXT = '"entity":"allUsers","role":"READER"'
34
35
36class TestAclBase(testcase.GsUtilIntegrationTestCase):
37  """Integration test case base class for acl command."""
38
39  _set_acl_prefix = ['acl', 'set']
40  _get_acl_prefix = ['acl', 'get']
41  _set_defacl_prefix = ['defacl', 'set']
42  _ch_acl_prefix = ['acl', 'ch']
43
44  _project_team = 'owners'
45  _project_test_acl = '%s-%s' % (_project_team, PopulateProjectId())
46
47
48@SkipForS3('Tests use GS ACL model.')
49class TestAcl(TestAclBase):
50  """Integration tests for acl command."""
51
52  def setUp(self):
53    super(TestAcl, self).setUp()
54    self.sample_uri = self.CreateBucket()
55    self.sample_url = StorageUrlFromString(str(self.sample_uri))
56    self.logger = CreateGsutilLogger('acl')
57
58  def test_set_invalid_acl_object(self):
59    """Ensures that invalid content returns a bad request error."""
60    obj_uri = suri(self.CreateObject(contents='foo'))
61    inpath = self.CreateTempFile(contents='badAcl')
62    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri],
63                            return_stderr=True, expected_status=1)
64    self.assertIn('ArgumentException', stderr)
65
66  def test_set_invalid_acl_bucket(self):
67    """Ensures that invalid content returns a bad request error."""
68    bucket_uri = suri(self.CreateBucket())
69    inpath = self.CreateTempFile(contents='badAcl')
70    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri],
71                            return_stderr=True, expected_status=1)
72    self.assertIn('ArgumentException', stderr)
73
74  def test_set_xml_acl_json_api_object(self):
75    """Ensures XML content returns a bad request error and migration warning."""
76    obj_uri = suri(self.CreateObject(contents='foo'))
77    inpath = self.CreateTempFile(contents='<ValidXml></ValidXml>')
78    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri],
79                            return_stderr=True, expected_status=1)
80    self.assertIn('ArgumentException', stderr)
81    self.assertIn('XML ACL data provided', stderr)
82
83  def test_set_xml_acl_json_api_bucket(self):
84    """Ensures XML content returns a bad request error and migration warning."""
85    bucket_uri = suri(self.CreateBucket())
86    inpath = self.CreateTempFile(contents='<ValidXml></ValidXml>')
87    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri],
88                            return_stderr=True, expected_status=1)
89    self.assertIn('ArgumentException', stderr)
90    self.assertIn('XML ACL data provided', stderr)
91
92  def test_set_valid_acl_object(self):
93    """Tests setting a valid ACL on an object."""
94    obj_uri = suri(self.CreateObject(contents='foo'))
95    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
96                                return_stdout=True)
97    inpath = self.CreateTempFile(contents=acl_string)
98    self.RunGsUtil(self._set_acl_prefix + ['public-read', obj_uri])
99    acl_string2 = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
100                                 return_stdout=True)
101    self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri])
102    acl_string3 = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
103                                 return_stdout=True)
104
105    self.assertNotEqual(acl_string, acl_string2)
106    self.assertEqual(acl_string, acl_string3)
107
108  def test_set_valid_permission_whitespace_object(self):
109    """Ensures that whitespace is allowed in role and entity elements."""
110    obj_uri = suri(self.CreateObject(contents='foo'))
111    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
112                                return_stdout=True)
113    acl_string = re.sub(r'"role"', r'"role" \n', acl_string)
114    acl_string = re.sub(r'"entity"', r'\n "entity"', acl_string)
115    inpath = self.CreateTempFile(contents=acl_string)
116
117    self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri])
118
119  def test_set_valid_acl_bucket(self):
120    """Ensures that valid canned and XML ACLs work with get/set."""
121    bucket_uri = suri(self.CreateBucket())
122    acl_string = self.RunGsUtil(self._get_acl_prefix + [bucket_uri],
123                                return_stdout=True)
124    inpath = self.CreateTempFile(contents=acl_string)
125    self.RunGsUtil(self._set_acl_prefix + ['public-read', bucket_uri])
126    acl_string2 = self.RunGsUtil(self._get_acl_prefix + [bucket_uri],
127                                 return_stdout=True)
128    self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri])
129    acl_string3 = self.RunGsUtil(self._get_acl_prefix + [bucket_uri],
130                                 return_stdout=True)
131
132    self.assertNotEqual(acl_string, acl_string2)
133    self.assertEqual(acl_string, acl_string3)
134
135  def test_invalid_canned_acl_object(self):
136    """Ensures that an invalid canned ACL returns a CommandException."""
137    obj_uri = suri(self.CreateObject(contents='foo'))
138    stderr = self.RunGsUtil(
139        self._set_acl_prefix + ['not-a-canned-acl', obj_uri],
140        return_stderr=True, expected_status=1)
141    self.assertIn('CommandException', stderr)
142    self.assertIn('Invalid canned ACL', stderr)
143
144  def test_set_valid_def_acl_bucket(self):
145    """Ensures that valid default canned and XML ACLs works with get/set."""
146    bucket_uri = self.CreateBucket()
147
148    # Default ACL is project private.
149    obj_uri1 = suri(self.CreateObject(bucket_uri=bucket_uri, contents='foo'))
150    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri1],
151                                return_stdout=True)
152
153    # Change it to authenticated-read.
154    self.RunGsUtil(
155        self._set_defacl_prefix + ['authenticated-read', suri(bucket_uri)])
156    obj_uri2 = suri(self.CreateObject(bucket_uri=bucket_uri, contents='foo2'))
157    acl_string2 = self.RunGsUtil(self._get_acl_prefix + [obj_uri2],
158                                 return_stdout=True)
159
160    # Now change it back to the default via XML.
161    inpath = self.CreateTempFile(contents=acl_string)
162    self.RunGsUtil(self._set_defacl_prefix + [inpath, suri(bucket_uri)])
163    obj_uri3 = suri(self.CreateObject(bucket_uri=bucket_uri, contents='foo3'))
164    acl_string3 = self.RunGsUtil(self._get_acl_prefix + [obj_uri3],
165                                 return_stdout=True)
166
167    self.assertNotEqual(acl_string, acl_string2)
168    self.assertIn('allAuthenticatedUsers', acl_string2)
169    self.assertEqual(acl_string, acl_string3)
170
171  def test_acl_set_version_specific_uri(self):
172    """Tests setting an ACL on a specific version of an object."""
173    bucket_uri = self.CreateVersionedBucket()
174    # Create initial object version.
175    uri = self.CreateObject(bucket_uri=bucket_uri, contents='data')
176    # Create a second object version.
177    inpath = self.CreateTempFile(contents='def')
178    self.RunGsUtil(['cp', inpath, uri.uri])
179
180    # Find out the two object version IDs.
181    lines = self.AssertNObjectsInBucket(bucket_uri, 2, versioned=True)
182    v0_uri_str, v1_uri_str = lines[0], lines[1]
183
184    # Check that neither version currently has public-read permission
185    # (default ACL is project-private).
186    orig_acls = []
187    for uri_str in (v0_uri_str, v1_uri_str):
188      acl = self.RunGsUtil(self._get_acl_prefix + [uri_str],
189                           return_stdout=True)
190      self.assertNotIn(PUBLIC_READ_JSON_ACL_TEXT,
191                       self._strip_json_whitespace(acl))
192      orig_acls.append(acl)
193
194    # Set the ACL for the older version of the object to public-read.
195    self.RunGsUtil(self._set_acl_prefix + ['public-read', v0_uri_str])
196    # Check that the older version's ACL is public-read, but newer version
197    # is not.
198    acl = self.RunGsUtil(self._get_acl_prefix + [v0_uri_str],
199                         return_stdout=True)
200    self.assertIn(PUBLIC_READ_JSON_ACL_TEXT, self._strip_json_whitespace(acl))
201    acl = self.RunGsUtil(self._get_acl_prefix + [v1_uri_str],
202                         return_stdout=True)
203    self.assertNotIn(PUBLIC_READ_JSON_ACL_TEXT,
204                     self._strip_json_whitespace(acl))
205
206    # Check that reading the ACL with the version-less URI returns the
207    # original ACL (since the version-less URI means the current version).
208    acl = self.RunGsUtil(self._get_acl_prefix + [uri.uri], return_stdout=True)
209    self.assertEqual(acl, orig_acls[0])
210
211  def _strip_json_whitespace(self, json_text):
212    return re.sub(r'\s*', '', json_text)
213
214  def testAclChangeWithUserId(self):
215    change = aclhelpers.AclChange(self.USER_TEST_ID + ':r',
216                                  scope_type=aclhelpers.ChangeType.USER)
217    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
218    change.Execute(self.sample_url, acl, 'acl', self.logger)
219    self._AssertHas(acl, 'READER', 'UserById', self.USER_TEST_ID)
220
221  def testAclChangeWithGroupId(self):
222    change = aclhelpers.AclChange(self.GROUP_TEST_ID + ':r',
223                                  scope_type=aclhelpers.ChangeType.GROUP)
224    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
225    change.Execute(self.sample_url, acl, 'acl', self.logger)
226    self._AssertHas(acl, 'READER', 'GroupById', self.GROUP_TEST_ID)
227
228  def testAclChangeWithUserEmail(self):
229    change = aclhelpers.AclChange(self.USER_TEST_ADDRESS + ':r',
230                                  scope_type=aclhelpers.ChangeType.USER)
231    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
232    change.Execute(self.sample_url, acl, 'acl', self.logger)
233    self._AssertHas(acl, 'READER', 'UserByEmail', self.USER_TEST_ADDRESS)
234
235  def testAclChangeWithGroupEmail(self):
236    change = aclhelpers.AclChange(self.GROUP_TEST_ADDRESS + ':fc',
237                                  scope_type=aclhelpers.ChangeType.GROUP)
238    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
239    change.Execute(self.sample_url, acl, 'acl', self.logger)
240    self._AssertHas(acl, 'OWNER', 'GroupByEmail', self.GROUP_TEST_ADDRESS)
241
242  def testAclChangeWithDomain(self):
243    change = aclhelpers.AclChange(self.DOMAIN_TEST + ':READ',
244                                  scope_type=aclhelpers.ChangeType.GROUP)
245    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
246    change.Execute(self.sample_url, acl, 'acl', self.logger)
247    self._AssertHas(acl, 'READER', 'GroupByDomain', self.DOMAIN_TEST)
248
249  def testAclChangeWithProjectOwners(self):
250    change = aclhelpers.AclChange(self._project_test_acl + ':READ',
251                                  scope_type=aclhelpers.ChangeType.PROJECT)
252    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
253    change.Execute(self.sample_url, acl, 'acl', self.logger)
254    self._AssertHas(acl, 'READER', 'Project', self._project_test_acl)
255
256  def testAclChangeWithAllUsers(self):
257    change = aclhelpers.AclChange('AllUsers:WRITE',
258                                  scope_type=aclhelpers.ChangeType.GROUP)
259    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
260    change.Execute(self.sample_url, acl, 'acl', self.logger)
261    self._AssertHas(acl, 'WRITER', 'AllUsers')
262
263  def testAclChangeWithAllAuthUsers(self):
264    change = aclhelpers.AclChange('AllAuthenticatedUsers:READ',
265                                  scope_type=aclhelpers.ChangeType.GROUP)
266    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
267    change.Execute(self.sample_url, acl, 'acl', self.logger)
268    self._AssertHas(acl, 'READER', 'AllAuthenticatedUsers')
269    remove = aclhelpers.AclDel('AllAuthenticatedUsers')
270    remove.Execute(self.sample_url, acl, 'acl', self.logger)
271    self._AssertHasNo(acl, 'READER', 'AllAuthenticatedUsers')
272
273  def testAclDelWithUser(self):
274    add = aclhelpers.AclChange(self.USER_TEST_ADDRESS + ':READ',
275                               scope_type=aclhelpers.ChangeType.USER)
276    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
277    add.Execute(self.sample_url, acl, 'acl', self.logger)
278    self._AssertHas(acl, 'READER', 'UserByEmail', self.USER_TEST_ADDRESS)
279
280    remove = aclhelpers.AclDel(self.USER_TEST_ADDRESS)
281    remove.Execute(self.sample_url, acl, 'acl', self.logger)
282    self._AssertHasNo(acl, 'READ', 'UserByEmail', self.USER_TEST_ADDRESS)
283
284  def testAclDelWithProjectOwners(self):
285    add = aclhelpers.AclChange(self._project_test_acl + ':READ',
286                               scope_type=aclhelpers.ChangeType.PROJECT)
287    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
288    add.Execute(self.sample_url, acl, 'acl', self.logger)
289    self._AssertHas(acl, 'READER', 'Project', self._project_test_acl)
290
291    remove = aclhelpers.AclDel(self._project_test_acl)
292    remove.Execute(self.sample_url, acl, 'acl', self.logger)
293    self._AssertHasNo(acl, 'READ', 'Project', self._project_test_acl)
294
295  def testAclDelWithGroup(self):
296    add = aclhelpers.AclChange(self.USER_TEST_ADDRESS + ':READ',
297                               scope_type=aclhelpers.ChangeType.GROUP)
298    acl = list(AclTranslation.BotoBucketAclToMessage(self.sample_uri.get_acl()))
299    add.Execute(self.sample_url, acl, 'acl', self.logger)
300    self._AssertHas(acl, 'READER', 'GroupByEmail', self.USER_TEST_ADDRESS)
301
302    remove = aclhelpers.AclDel(self.USER_TEST_ADDRESS)
303    remove.Execute(self.sample_url, acl, 'acl', self.logger)
304    self._AssertHasNo(acl, 'READER', 'GroupByEmail', self.GROUP_TEST_ADDRESS)
305
306  #
307  # Here are a whole lot of verbose asserts
308  #
309
310  def _AssertHas(self, current_acl, perm, scope, value=None):
311    matches = list(self._YieldMatchingEntriesJson(current_acl, perm, scope,
312                                                  value))
313    self.assertEqual(1, len(matches))
314
315  def _AssertHasNo(self, current_acl, perm, scope, value=None):
316    matches = list(self._YieldMatchingEntriesJson(current_acl, perm, scope,
317                                                  value))
318    self.assertEqual(0, len(matches))
319
320  def _YieldMatchingEntriesJson(self, current_acl, perm, scope, value=None):
321    """Generator that yields entries that match the change descriptor.
322
323    Args:
324      current_acl: A list of apitools_messages.BucketAccessControls or
325                   ObjectAccessControls which will be searched for matching
326                   entries.
327      perm: Role (permission) to match.
328      scope: Scope type to match.
329      value: Value to match (against the scope type).
330
331    Yields:
332      An apitools_messages.BucketAccessControl or ObjectAccessControl.
333    """
334    for entry in current_acl:
335      if (scope in ['UserById', 'GroupById'] and
336          entry.entityId and value == entry.entityId and
337          entry.role == perm):
338        yield entry
339      elif (scope in ['UserByEmail', 'GroupByEmail'] and
340            entry.email and value == entry.email and
341            entry.role == perm):
342        yield entry
343      elif (scope == 'GroupByDomain' and
344            entry.domain and value == entry.domain and
345            entry.role == perm):
346        yield entry
347      elif (scope == 'Project' and entry.role == perm and
348            value == entry.entityId):
349        yield entry
350      elif (scope in ['AllUsers', 'AllAuthenticatedUsers'] and
351            entry.entity.lower() == scope.lower() and
352            entry.role == perm):
353        yield entry
354
355  def _MakeScopeRegex(self, role, entity_type, email_address):
356    template_regex = (r'\{.*"entity":\s*"%s-%s".*"role":\s*"%s".*\}' %
357                      (entity_type, email_address, role))
358    return re.compile(template_regex, flags=re.DOTALL)
359
360  def _MakeProjectScopeRegex(self, role, project_team):
361    template_regex = (r'\{.*"entity":\s*"project-%s-\d+",\s*"projectTeam":\s*'
362                      r'\{\s*"projectNumber":\s*"(\d+)",\s*"team":\s*"%s"\s*\},'
363                      r'\s*"role":\s*"%s".*\}') % (project_team, project_team,
364                                                   role)
365
366    return re.compile(template_regex, flags=re.DOTALL)
367
368  def testBucketAclChange(self):
369    """Tests acl change on a bucket."""
370    test_regex = self._MakeScopeRegex(
371        'OWNER', 'user', self.USER_TEST_ADDRESS)
372    json_text = self.RunGsUtil(
373        self._get_acl_prefix + [suri(self.sample_uri)], return_stdout=True)
374    self.assertNotRegexpMatches(json_text, test_regex)
375
376    self.RunGsUtil(self._ch_acl_prefix +
377                   ['-u', self.USER_TEST_ADDRESS+':fc', suri(self.sample_uri)])
378    json_text = self.RunGsUtil(
379        self._get_acl_prefix + [suri(self.sample_uri)], return_stdout=True)
380    self.assertRegexpMatches(json_text, test_regex)
381
382    test_regex2 = self._MakeScopeRegex(
383        'WRITER', 'user', self.USER_TEST_ADDRESS)
384    self.RunGsUtil(self._ch_acl_prefix +
385                   ['-u', self.USER_TEST_ADDRESS+':w', suri(self.sample_uri)])
386    json_text2 = self.RunGsUtil(
387        self._get_acl_prefix + [suri(self.sample_uri)], return_stdout=True)
388    self.assertRegexpMatches(json_text2, test_regex2)
389
390    self.RunGsUtil(self._ch_acl_prefix +
391                   ['-d', self.USER_TEST_ADDRESS, suri(self.sample_uri)])
392
393    json_text3 = self.RunGsUtil(
394        self._get_acl_prefix + [suri(self.sample_uri)], return_stdout=True)
395    self.assertNotRegexpMatches(json_text3, test_regex)
396
397  def testProjectAclChangesOnBucket(self):
398    """Tests project entity acl changes on a bucket."""
399
400    if self.test_api == ApiSelector.XML:
401      stderr = self.RunGsUtil(self._ch_acl_prefix +
402                              ['-p', self._project_test_acl +':w',
403                               suri(self.sample_uri)],
404                              expected_status=1,
405                              return_stderr=True)
406      self.assertIn(('CommandException: XML API does not support project'
407                     ' scopes, cannot translate ACL.'), stderr)
408    else:
409      test_regex = self._MakeProjectScopeRegex(
410          'WRITER', self._project_team)
411      self.RunGsUtil(self._ch_acl_prefix +
412                     ['-p', self._project_test_acl +':w',
413                      suri(self.sample_uri)])
414      json_text = self.RunGsUtil(
415          self._get_acl_prefix + [suri(self.sample_uri)], return_stdout=True)
416
417      self.assertRegexpMatches(json_text, test_regex)
418
419      # The api will accept string project ids, but stores the numeric project
420      # ids internally, this extracts the numeric id from the returned acls.
421      proj_num_id = test_regex.search(json_text).group(1)
422      acl_to_remove = '%s-%s' % (self._project_team, proj_num_id)
423
424      self.RunGsUtil(self._ch_acl_prefix +
425                     ['-d', acl_to_remove, suri(self.sample_uri)])
426
427      json_text2 = self.RunGsUtil(
428          self._get_acl_prefix + [suri(self.sample_uri)], return_stdout=True)
429      self.assertNotRegexpMatches(json_text2, test_regex)
430
431  def testObjectAclChange(self):
432    """Tests acl change on an object."""
433    obj = self.CreateObject(bucket_uri=self.sample_uri, contents='something')
434    self.AssertNObjectsInBucket(self.sample_uri, 1)
435
436    test_regex = self._MakeScopeRegex(
437        'READER', 'group', self.GROUP_TEST_ADDRESS)
438    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
439                               return_stdout=True)
440    self.assertNotRegexpMatches(json_text, test_regex)
441
442    self.RunGsUtil(self._ch_acl_prefix +
443                   ['-g', self.GROUP_TEST_ADDRESS+':READ', suri(obj)])
444    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
445                               return_stdout=True)
446    self.assertRegexpMatches(json_text, test_regex)
447
448    test_regex2 = self._MakeScopeRegex(
449        'OWNER', 'group', self.GROUP_TEST_ADDRESS)
450    self.RunGsUtil(self._ch_acl_prefix +
451                   ['-g', self.GROUP_TEST_ADDRESS+':OWNER', suri(obj)])
452    json_text2 = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
453                                return_stdout=True)
454    self.assertRegexpMatches(json_text2, test_regex2)
455
456    self.RunGsUtil(self._ch_acl_prefix +
457                   ['-d', self.GROUP_TEST_ADDRESS, suri(obj)])
458    json_text3 = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
459                                return_stdout=True)
460    self.assertNotRegexpMatches(json_text3, test_regex2)
461
462    all_auth_regex = re.compile(
463        r'\{.*"entity":\s*"allAuthenticatedUsers".*"role":\s*"OWNER".*\}',
464        flags=re.DOTALL)
465
466    self.RunGsUtil(self._ch_acl_prefix + ['-g', 'AllAuth:O', suri(obj)])
467    json_text4 = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
468                                return_stdout=True)
469    self.assertRegexpMatches(json_text4, all_auth_regex)
470
471  def testObjectAclChangeAllUsers(self):
472    """Tests acl ch AllUsers:R on an object."""
473    obj = self.CreateObject(bucket_uri=self.sample_uri, contents='something')
474    self.AssertNObjectsInBucket(self.sample_uri, 1)
475
476    all_users_regex = re.compile(
477        r'\{.*"entity":\s*"allUsers".*"role":\s*"READER".*\}', flags=re.DOTALL)
478    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
479                               return_stdout=True)
480    self.assertNotRegexpMatches(json_text, all_users_regex)
481
482    self.RunGsUtil(self._ch_acl_prefix +
483                   ['-g', 'AllUsers:R', suri(obj)])
484    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
485                               return_stdout=True)
486    self.assertRegexpMatches(json_text, all_users_regex)
487
488  def testMultithreadedAclChange(self, count=10):
489    """Tests multi-threaded acl changing on several objects."""
490    objects = []
491    for i in range(count):
492      objects.append(self.CreateObject(
493          bucket_uri=self.sample_uri,
494          contents='something {0}'.format(i)))
495
496    self.AssertNObjectsInBucket(self.sample_uri, count)
497
498    test_regex = self._MakeScopeRegex(
499        'READER', 'group', self.GROUP_TEST_ADDRESS)
500    json_texts = []
501    for obj in objects:
502      json_texts.append(self.RunGsUtil(
503          self._get_acl_prefix + [suri(obj)], return_stdout=True))
504    for json_text in json_texts:
505      self.assertNotRegexpMatches(json_text, test_regex)
506
507    uris = [suri(obj) for obj in objects]
508    self.RunGsUtil(['-m', '-DD'] + self._ch_acl_prefix +
509                   ['-g', self.GROUP_TEST_ADDRESS+':READ'] + uris)
510
511    json_texts = []
512    for obj in objects:
513      json_texts.append(self.RunGsUtil(
514          self._get_acl_prefix + [suri(obj)], return_stdout=True))
515    for json_text in json_texts:
516      self.assertRegexpMatches(json_text, test_regex)
517
518  def testRecursiveChangeAcl(self):
519    """Tests recursively changing ACLs on nested objects."""
520    obj = self.CreateObject(bucket_uri=self.sample_uri, object_name='foo/bar',
521                            contents='something')
522    self.AssertNObjectsInBucket(self.sample_uri, 1)
523
524    test_regex = self._MakeScopeRegex(
525        'READER', 'group', self.GROUP_TEST_ADDRESS)
526    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
527                               return_stdout=True)
528    self.assertNotRegexpMatches(json_text, test_regex)
529
530    @Retry(AssertionError, tries=5, timeout_secs=1)
531    def _AddAcl():
532      self.RunGsUtil(
533          self._ch_acl_prefix +
534          ['-R', '-g', self.GROUP_TEST_ADDRESS+':READ', suri(obj)[:-3]])
535      json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
536                                 return_stdout=True)
537      self.assertRegexpMatches(json_text, test_regex)
538    _AddAcl()
539
540    @Retry(AssertionError, tries=5, timeout_secs=1)
541    def _DeleteAcl():
542      self.RunGsUtil(self._ch_acl_prefix +
543                     ['-d', self.GROUP_TEST_ADDRESS, suri(obj)])
544      json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
545                                 return_stdout=True)
546      self.assertNotRegexpMatches(json_text, test_regex)
547    _DeleteAcl()
548
549  def testMultiVersionSupport(self):
550    """Tests changing ACLs on multiple object versions."""
551    bucket = self.CreateVersionedBucket()
552    object_name = self.MakeTempName('obj')
553    self.CreateObject(
554        bucket_uri=bucket, object_name=object_name, contents='One thing')
555    # Create another on the same URI, giving us a second version.
556    self.CreateObject(
557        bucket_uri=bucket, object_name=object_name, contents='Another thing')
558
559    lines = self.AssertNObjectsInBucket(bucket, 2, versioned=True)
560
561    obj_v1, obj_v2 = lines[0], lines[1]
562
563    test_regex = self._MakeScopeRegex(
564        'READER', 'group', self.GROUP_TEST_ADDRESS)
565    json_text = self.RunGsUtil(self._get_acl_prefix + [obj_v1],
566                               return_stdout=True)
567    self.assertNotRegexpMatches(json_text, test_regex)
568
569    self.RunGsUtil(self._ch_acl_prefix +
570                   ['-g', self.GROUP_TEST_ADDRESS+':READ', obj_v1])
571    json_text = self.RunGsUtil(self._get_acl_prefix + [obj_v1],
572                               return_stdout=True)
573    self.assertRegexpMatches(json_text, test_regex)
574
575    json_text = self.RunGsUtil(self._get_acl_prefix + [obj_v2],
576                               return_stdout=True)
577    self.assertNotRegexpMatches(json_text, test_regex)
578
579  def testBadRequestAclChange(self):
580    stdout, stderr = self.RunGsUtil(
581        self._ch_acl_prefix +
582        ['-u', 'invalid_$$@hello.com:R', suri(self.sample_uri)],
583        return_stdout=True, return_stderr=True, expected_status=1)
584    self.assertIn('BadRequestException', stderr)
585    self.assertNotIn('Retrying', stdout)
586    self.assertNotIn('Retrying', stderr)
587
588  def testAclGetWithoutFullControl(self):
589    object_uri = self.CreateObject(contents='foo')
590    with self.SetAnonymousBotoCreds():
591      stderr = self.RunGsUtil(self._get_acl_prefix + [suri(object_uri)],
592                              return_stderr=True, expected_status=1)
593      self.assertIn('AccessDeniedException', stderr)
594
595  def testTooFewArgumentsFails(self):
596    """Tests calling ACL commands with insufficient number of arguments."""
597    # No arguments for get, but valid subcommand.
598    stderr = self.RunGsUtil(self._get_acl_prefix, return_stderr=True,
599                            expected_status=1)
600    self.assertIn('command requires at least', stderr)
601
602    # No arguments for set, but valid subcommand.
603    stderr = self.RunGsUtil(self._set_acl_prefix, return_stderr=True,
604                            expected_status=1)
605    self.assertIn('command requires at least', stderr)
606
607    # No arguments for ch, but valid subcommand.
608    stderr = self.RunGsUtil(self._ch_acl_prefix, return_stderr=True,
609                            expected_status=1)
610    self.assertIn('command requires at least', stderr)
611
612    # Neither arguments nor subcommand.
613    stderr = self.RunGsUtil(['acl'], return_stderr=True, expected_status=1)
614    self.assertIn('command requires at least', stderr)
615
616  def testMinusF(self):
617    """Tests -f option to continue after failure."""
618    bucket_uri = self.CreateBucket()
619    obj_uri = suri(self.CreateObject(bucket_uri=bucket_uri, object_name='foo',
620                                     contents='foo'))
621    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
622                                return_stdout=True)
623    self.RunGsUtil(self._set_acl_prefix +
624                   ['-f', 'public-read', suri(bucket_uri) + 'foo2', obj_uri],
625                   expected_status=1)
626    acl_string2 = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
627                                 return_stdout=True)
628
629    self.assertNotEqual(acl_string, acl_string2)
630
631
632class TestS3CompatibleAcl(TestAclBase):
633  """ACL integration tests that work for s3 and gs URLs."""
634
635  def testAclObjectGetSet(self):
636    bucket_uri = self.CreateBucket()
637    obj_uri = self.CreateObject(bucket_uri=bucket_uri, contents='foo')
638    self.AssertNObjectsInBucket(bucket_uri, 1)
639
640    stdout = self.RunGsUtil(self._get_acl_prefix + [suri(obj_uri)],
641                            return_stdout=True)
642    set_contents = self.CreateTempFile(contents=stdout)
643    self.RunGsUtil(self._set_acl_prefix + [set_contents, suri(obj_uri)])
644
645  def testAclBucketGetSet(self):
646    bucket_uri = self.CreateBucket()
647    stdout = self.RunGsUtil(self._get_acl_prefix + [suri(bucket_uri)],
648                            return_stdout=True)
649    set_contents = self.CreateTempFile(contents=stdout)
650    self.RunGsUtil(self._set_acl_prefix + [set_contents, suri(bucket_uri)])
651
652
653@SkipForGS('S3 ACLs accept XML and should not cause an XML warning.')
654class TestS3OnlyAcl(TestAclBase):
655  """ACL integration tests that work only for s3 URLs."""
656
657  # TODO: Format all test case names consistently.
658  def test_set_xml_acl(self):
659    """Ensures XML content does not return an XML warning for S3."""
660    obj_uri = suri(self.CreateObject(contents='foo'))
661    inpath = self.CreateTempFile(contents='<ValidXml></ValidXml>')
662    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri],
663                            return_stderr=True, expected_status=1)
664    self.assertIn('BadRequestException', stderr)
665    self.assertNotIn('XML ACL data provided', stderr)
666
667  def test_set_xml_acl_bucket(self):
668    """Ensures XML content does not return an XML warning for S3."""
669    bucket_uri = suri(self.CreateBucket())
670    inpath = self.CreateTempFile(contents='<ValidXml></ValidXml>')
671    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri],
672                            return_stderr=True, expected_status=1)
673    self.assertIn('BadRequestException', stderr)
674    self.assertNotIn('XML ACL data provided', stderr)
675
676
677class TestAclOldAlias(TestAcl):
678  _set_acl_prefix = ['setacl']
679  _get_acl_prefix = ['getacl']
680  _set_defacl_prefix = ['setdefacl']
681  _ch_acl_prefix = ['chacl']
682