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