# -*- coding: utf-8 -*-
# Copyright 2013 Google Inc.  All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Integration tests for the acl command."""

from __future__ import absolute_import
from __future__ import print_function
from __future__ import division
from __future__ import unicode_literals

import os
import re

from gslib.commands import acl
from gslib.command import CreateOrGetGsutilLogger
from gslib.cs_api_map import ApiSelector
from gslib.exception import CommandException
from gslib.storage_url import StorageUrlFromString
import gslib.tests.testcase as testcase
from gslib.tests.testcase.integration_testcase import SkipForGS
from gslib.tests.testcase.integration_testcase import SkipForS3
from gslib.tests.testcase.integration_testcase import SkipForXML
from gslib.tests.util import GenerationFromURI as urigen
from gslib.tests.util import ObjectToURI as suri
from gslib.tests.util import SetBotoConfigForTest
from gslib.tests.util import SetEnvironmentForTest
from gslib.tests.util import unittest
from gslib.utils import acl_helper
from gslib.utils.constants import UTF8
from gslib.utils.retry_util import Retry
from gslib.utils.translation_helper import AclTranslation
from gslib.utils import shim_util

from six import add_move, MovedModule

add_move(MovedModule('mock', 'mock', 'unittest.mock'))
from six.moves import mock

PUBLIC_READ_JSON_ACL_TEXT = '"entity":"allUsers","role":"READER"'


class TestAclBase(testcase.GsUtilIntegrationTestCase):
  """Integration test case base class for acl command."""

  _set_acl_prefix = ['acl', 'set']
  _get_acl_prefix = ['acl', 'get']
  _set_defacl_prefix = ['defacl', 'set']
  _ch_acl_prefix = ['acl', 'ch']

  _project_team = 'viewers'


@SkipForS3('Tests use GS ACL model.')
class TestAcl(TestAclBase):
  """Integration tests for acl command."""

  def setUp(self):
    super(TestAcl, self).setUp()
    self.sample_uri = self.CreateBucket()
    self.sample_url = StorageUrlFromString(str(self.sample_uri))
    self.logger = CreateOrGetGsutilLogger('acl')
    # Argument to acl ch -p must be the project number, not a name; create a
    # bucket to perform translation.
    self._project_number = self.json_api.GetBucket(
        self.CreateBucket().bucket_name, fields=['projectNumber']).projectNumber
    self._project_test_acl = '%s-%s' % (self._project_team,
                                        self._project_number)

  def test_set_invalid_acl_object(self):
    """Ensures that invalid content returns a bad request error."""
    obj_uri = suri(self.CreateObject(contents=b'foo'))
    inpath = self.CreateTempFile(contents=b'badAcl')
    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri],
                            return_stderr=True,
                            expected_status=1)
    if self._use_gcloud_storage:
      error_text = 'Found invalid JSON/YAML'
    else:
      error_text = 'ArgumentException'
    self.assertIn(error_text, stderr)

  def test_set_invalid_acl_bucket(self):
    """Ensures that invalid content returns a bad request error."""
    bucket_uri = suri(self.CreateBucket())
    inpath = self.CreateTempFile(contents=b'badAcl')
    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri],
                            return_stderr=True,
                            expected_status=1)
    if self._use_gcloud_storage:
      error_text = 'Found invalid JSON/YAML'
    else:
      error_text = 'ArgumentException'
    self.assertIn(error_text, stderr)

  def test_set_xml_acl_json_api_object(self):
    """Ensures XML content returns a bad request error and migration warning."""
    obj_uri = suri(self.CreateObject(contents=b'foo'))
    inpath = self.CreateTempFile(contents=b'<ValidXml></ValidXml>')
    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri],
                            return_stderr=True,
                            expected_status=1)

    if self._use_gcloud_storage:
      self.assertIn('Found invalid JSON/YAML', stderr)
      # XML not currently supported in gcloud storage.
    else:
      self.assertIn('ArgumentException', stderr)
      self.assertIn('XML ACL data provided', stderr)

  def test_set_xml_acl_json_api_bucket(self):
    """Ensures XML content returns a bad request error and migration warning."""
    bucket_uri = suri(self.CreateBucket())
    inpath = self.CreateTempFile(contents=b'<ValidXml></ValidXml>')
    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri],
                            return_stderr=True,
                            expected_status=1)
    if self._use_gcloud_storage:
      self.assertIn('Found invalid JSON/YAML', stderr)
      # XML not currently supported in gcloud storage.
    else:
      self.assertIn('ArgumentException', stderr)
      self.assertIn('XML ACL data provided', stderr)

  def test_set_valid_acl_object(self):
    """Tests setting a valid ACL on an object."""
    obj_uri = suri(self.CreateObject(contents=b'foo'))
    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
                                return_stdout=True)
    inpath = self.CreateTempFile(contents=acl_string.encode(UTF8))
    self.RunGsUtil(self._set_acl_prefix + ['public-read', obj_uri])
    acl_string2 = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
                                 return_stdout=True)
    self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri])
    acl_string3 = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
                                 return_stdout=True)

    self.assertNotEqual(acl_string, acl_string2)
    self.assertEqual(acl_string, acl_string3)

  def test_set_valid_permission_whitespace_object(self):
    """Ensures that whitespace is allowed in role and entity elements."""
    obj_uri = suri(self.CreateObject(contents=b'foo'))
    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
                                return_stdout=True)
    acl_string = re.sub(r'"role"', r'"role" \n', acl_string)
    acl_string = re.sub(r'"entity"', r'\n "entity"', acl_string)
    inpath = self.CreateTempFile(contents=acl_string.encode(UTF8))

    self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri])

  def test_set_valid_acl_bucket(self):
    """Ensures that valid canned and XML ACLs work with get/set."""
    if self._ServiceAccountCredentialsPresent():
      # See comments in _ServiceAccountCredentialsPresent
      return unittest.skip('Canned ACLs orphan service account permissions.')
    bucket_uri = suri(self.CreateBucket())
    acl_string = self.RunGsUtil(self._get_acl_prefix + [bucket_uri],
                                return_stdout=True)
    inpath = self.CreateTempFile(contents=acl_string.encode(UTF8))
    self.RunGsUtil(self._set_acl_prefix + ['public-read', bucket_uri])
    acl_string2 = self.RunGsUtil(self._get_acl_prefix + [bucket_uri],
                                 return_stdout=True)
    self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri])
    acl_string3 = self.RunGsUtil(self._get_acl_prefix + [bucket_uri],
                                 return_stdout=True)

    self.assertNotEqual(acl_string, acl_string2)
    self.assertEqual(acl_string, acl_string3)

  def test_invalid_canned_acl_object(self):
    """Ensures that an invalid canned ACL returns a CommandException."""
    obj_uri = suri(self.CreateObject(contents=b'foo'))
    stderr = self.RunGsUtil(self._set_acl_prefix +
                            ['not-a-canned-acl', obj_uri],
                            return_stderr=True,
                            expected_status=1)
    if self._use_gcloud_storage:
      self.assertIn('AttributeError', stderr)
    else:
      self.assertIn('CommandException', stderr)
      self.assertIn('Invalid canned ACL', stderr)

  def test_set_valid_def_acl_bucket(self):
    """Ensures that valid default canned and XML ACLs works with get/set."""
    bucket_uri = self.CreateBucket()

    # Default ACL is project private.
    obj_uri1 = suri(self.CreateObject(bucket_uri=bucket_uri, contents=b'foo'))
    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri1],
                                return_stdout=True)

    # Change it to authenticated-read.
    self.RunGsUtil(
        self._set_defacl_prefix +
        ['authenticated-read', suri(bucket_uri)])

    # Default object ACL may take some time to propagate.
    @Retry(AssertionError, tries=5, timeout_secs=1)
    def _Check1():
      obj_uri2 = suri(self.CreateObject(bucket_uri=bucket_uri,
                                        contents=b'foo2'))
      acl_string2 = self.RunGsUtil(self._get_acl_prefix + [obj_uri2],
                                   return_stdout=True)
      self.assertNotEqual(acl_string, acl_string2)
      self.assertIn('allAuthenticatedUsers', acl_string2)

    _Check1()

    # Now change it back to the default via XML.
    inpath = self.CreateTempFile(contents=acl_string.encode(UTF8))
    self.RunGsUtil(self._set_defacl_prefix + [inpath, suri(bucket_uri)])

    # Default object ACL may take some time to propagate.
    @Retry(AssertionError, tries=5, timeout_secs=1)
    def _Check2():
      obj_uri3 = suri(self.CreateObject(bucket_uri=bucket_uri,
                                        contents=b'foo3'))
      acl_string3 = self.RunGsUtil(self._get_acl_prefix + [obj_uri3],
                                   return_stdout=True)
      self.assertEqual(acl_string, acl_string3)

    _Check2()

  def test_acl_set_version_specific_uri(self):
    """Tests setting an ACL on a specific version of an object."""
    bucket_uri = self.CreateVersionedBucket()
    # Create initial object version.
    uri = self.CreateObject(bucket_uri=bucket_uri, contents=b'data')
    # Create a second object version.
    inpath = self.CreateTempFile(contents=b'def')
    self.RunGsUtil(['cp', inpath, uri.uri])

    # Find out the two object version IDs.
    lines = self.AssertNObjectsInBucket(bucket_uri, 2, versioned=True)
    v0_uri_str, v1_uri_str = lines[0], lines[1]

    # Check that neither version currently has public-read permission
    # (default ACL is project-private).
    orig_acls = []
    for uri_str in (v0_uri_str, v1_uri_str):
      acl = self.RunGsUtil(self._get_acl_prefix + [uri_str], return_stdout=True)
      self.assertNotIn(PUBLIC_READ_JSON_ACL_TEXT,
                       self._strip_json_whitespace(acl))
      orig_acls.append(acl)

    # Set the ACL for the older version of the object to public-read.
    self.RunGsUtil(self._set_acl_prefix + ['public-read', v0_uri_str])
    # Check that the older version's ACL is public-read, but newer version
    # is not.
    acl = self.RunGsUtil(self._get_acl_prefix + [v0_uri_str],
                         return_stdout=True)
    self.assertIn(PUBLIC_READ_JSON_ACL_TEXT, self._strip_json_whitespace(acl))
    acl = self.RunGsUtil(self._get_acl_prefix + [v1_uri_str],
                         return_stdout=True)
    self.assertNotIn(PUBLIC_READ_JSON_ACL_TEXT,
                     self._strip_json_whitespace(acl))

    # Check that reading the ACL with the version-less URI returns the
    # original ACL (since the version-less URI means the current version).
    acl = self.RunGsUtil(self._get_acl_prefix + [uri.uri], return_stdout=True)
    self.assertEqual(acl, orig_acls[0])

  def _strip_json_whitespace(self, json_text):
    return re.sub(r'\s*', '', json_text)

  def _MakeScopeRegex(self, role, entity_type, email_address):
    template_regex = (r'\{.*"entity":\s*"%s-%s".*"role":\s*"%s".*\}' %
                      (entity_type, email_address, role))
    return re.compile(template_regex, flags=re.DOTALL)

  def _MakeProjectScopeRegex(self, role, project_team, project_number):
    template_regex = (
        r'\{.*"entity":\s*"project-%s-%s",\s*"projectTeam":\s*\{\s*"'
        r'projectNumber":\s*"%s",\s*"team":\s*"%s"\s*\},\s*"role":\s*"%s".*\}' %
        (project_team, project_number, project_number, project_team, role))

    return re.compile(template_regex, flags=re.DOTALL)

  def testAclChangeWithUserId(self):
    test_regex = self._MakeScopeRegex('READER', 'user', self.USER_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-u', self.USER_TEST_ID +
                    ':r', suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', self.USER_TEST_ADDRESS,
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  def testAclChangeWithGroupId(self):
    test_regex = self._MakeScopeRegex('READER', 'group',
                                      self.GROUP_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-g', self.GROUP_TEST_ID + ':r',
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', self.GROUP_TEST_ADDRESS,
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  def testAclChangeWithUserEmail(self):
    test_regex = self._MakeScopeRegex('READER', 'user', self.USER_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-u', self.USER_TEST_ADDRESS + ':r',
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', self.USER_TEST_ADDRESS,
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  def testAclChangeWithGroupEmail(self):
    test_regex = self._MakeScopeRegex('OWNER', 'group', self.GROUP_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(
        self._ch_acl_prefix +
        ['-g', self.GROUP_TEST_ADDRESS +
         ':fc', suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', self.GROUP_TEST_ADDRESS,
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  def testAclChangeWithDomain(self):
    test_regex = self._MakeScopeRegex('READER', 'domain', self.DOMAIN_TEST)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-g', self.DOMAIN_TEST +
                    ':r', suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    self.RunGsUtil(
        self._ch_acl_prefix +
        ['-d', self.DOMAIN_TEST, suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  @SkipForXML('XML API does not support project scopes.')
  def testAclChangeWithProjectOwners(self):
    test_regex = self._MakeProjectScopeRegex('WRITER', self._project_team,
                                             self._project_number)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-p', self._project_test_acl + ':w',
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

  def testAclChangeWithAllUsers(self):
    test_regex = re.compile(
        r'\{.*"entity":\s*"allUsers".*"role":\s*"WRITER".*\}', flags=re.DOTALL)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-g', 'allusers' +
                    ':w', suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', 'allusers', suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  def testAclChangeWithAllAuthUsers(self):
    test_regex = re.compile(
        r'\{.*"entity":\s*"allAuthenticatedUsers".*"role":\s*"READER".*\}',
        flags=re.DOTALL)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(
        self._ch_acl_prefix +
        ['-g', 'allauthenticatedusers' +
         ':r', suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', 'allauthenticatedusers',
                    suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  def testBucketAclChange(self):
    """Tests acl change on a bucket."""
    test_regex = self._MakeScopeRegex('OWNER', 'user', self.USER_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(
        self._ch_acl_prefix +
        ['-u', self.USER_TEST_ADDRESS +
         ':fc', suri(self.sample_uri)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    test_regex2 = self._MakeScopeRegex('WRITER', 'user', self.USER_TEST_ADDRESS)
    s1, s2 = self.RunGsUtil(
        self._ch_acl_prefix +
        ['-u', self.USER_TEST_ADDRESS + ':w',
         suri(self.sample_uri)],
        return_stderr=True,
        return_stdout=True)

    json_text2 = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                                return_stdout=True)
    self.assertRegex(json_text2, test_regex2)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', self.USER_TEST_ADDRESS,
                    suri(self.sample_uri)])

    json_text3 = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                                return_stdout=True)
    self.assertNotRegex(json_text3, test_regex)

  def testProjectAclChangesOnBucket(self):
    """Tests project entity acl changes on a bucket."""
    if self.test_api == ApiSelector.XML:
      stderr = self.RunGsUtil(
          self._ch_acl_prefix +
          ['-p', self._project_test_acl + ':w',
           suri(self.sample_uri)],
          expected_status=1,
          return_stderr=True)
      self.assertIn(('CommandException: XML API does not support project'
                     ' scopes, cannot translate ACL.'), stderr)
    else:
      test_regex = self._MakeProjectScopeRegex('WRITER', self._project_team,
                                               self._project_number)
      self.RunGsUtil(
          self._ch_acl_prefix +
          ['-p', self._project_test_acl +
           ':w', suri(self.sample_uri)])
      json_text = self.RunGsUtil(self._get_acl_prefix + [suri(self.sample_uri)],
                                 return_stdout=True)

      self.assertRegex(json_text, test_regex)

      self.RunGsUtil(self._ch_acl_prefix +
                     ['-d', self._project_test_acl,
                      suri(self.sample_uri)])

      json_text2 = self.RunGsUtil(self._get_acl_prefix +
                                  [suri(self.sample_uri)],
                                  return_stdout=True)
      self.assertNotRegex(json_text2, test_regex)

  def testObjectAclChange(self):
    """Tests acl change on an object."""
    obj = self.CreateObject(bucket_uri=self.sample_uri, contents=b'something')
    self.AssertNObjectsInBucket(self.sample_uri, 1)

    test_regex = self._MakeScopeRegex('READER', 'group',
                                      self.GROUP_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-g', self.GROUP_TEST_ADDRESS +
                    ':READ', suri(obj)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    test_regex2 = self._MakeScopeRegex('OWNER', 'group',
                                       self.GROUP_TEST_ADDRESS)
    self.RunGsUtil(self._ch_acl_prefix +
                   ['-g', self.GROUP_TEST_ADDRESS + ':OWNER',
                    suri(obj)])
    json_text2 = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                                return_stdout=True)
    self.assertRegex(json_text2, test_regex2)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-d', self.GROUP_TEST_ADDRESS,
                    suri(obj)])
    json_text3 = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                                return_stdout=True)
    self.assertNotRegex(json_text3, test_regex2)

    all_auth_regex = re.compile(
        r'\{.*"entity":\s*"allAuthenticatedUsers".*"role":\s*"OWNER".*\}',
        flags=re.DOTALL)

    self.RunGsUtil(self._ch_acl_prefix + ['-g', 'AllAuth:O', suri(obj)])
    json_text4 = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                                return_stdout=True)
    self.assertRegex(json_text4, all_auth_regex)

  def testObjectAclChangeAllUsers(self):
    """Tests acl ch AllUsers:R on an object."""
    obj = self.CreateObject(bucket_uri=self.sample_uri, contents=b'something')
    self.AssertNObjectsInBucket(self.sample_uri, 1)

    all_users_regex = re.compile(
        r'\{.*"entity":\s*"allUsers".*"role":\s*"READER".*\}', flags=re.DOTALL)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                               return_stdout=True)
    self.assertNotRegex(json_text, all_users_regex)

    self.RunGsUtil(self._ch_acl_prefix + ['-g', 'AllUsers:R', suri(obj)])
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                               return_stdout=True)
    self.assertRegex(json_text, all_users_regex)

  def testSeekAheadAcl(self):
    """Tests seek-ahead iterator with ACL sub-commands."""
    object_uri = self.CreateObject(contents=b'foo')
    # Get the object's current ACL for application via set.
    current_acl = self.RunGsUtil(['acl', 'get', suri(object_uri)],
                                 return_stdout=True)
    current_acl_file = self.CreateTempFile(contents=current_acl.encode(UTF8))

    with SetBotoConfigForTest([('GSUtil', 'task_estimation_threshold', '1'),
                               ('GSUtil', 'task_estimation_force', 'True')]):
      stderr = self.RunGsUtil(
          ['-m', 'acl', 'ch', '-u', 'AllUsers:R',
           suri(object_uri)],
          return_stderr=True)
      self.assertIn('Estimated work for this command: objects: 1\n', stderr)

      stderr = self.RunGsUtil(
          ['-m', 'acl', 'set', current_acl_file,
           suri(object_uri)],
          return_stderr=True)
      self.assertIn('Estimated work for this command: objects: 1\n', stderr)

    with SetBotoConfigForTest([('GSUtil', 'task_estimation_threshold', '0'),
                               ('GSUtil', 'task_estimation_force', 'True')]):
      stderr = self.RunGsUtil(
          ['-m', 'acl', 'ch', '-u', 'AllUsers:R',
           suri(object_uri)],
          return_stderr=True)
      self.assertNotIn('Estimated work', stderr)

  def testMultithreadedAclChange(self, count=10):
    """Tests multi-threaded acl changing on several objects."""
    objects = []
    for i in range(count):
      objects.append(
          self.CreateObject(bucket_uri=self.sample_uri,
                            contents='something {0}'.format(i).encode('ascii')))

    self.AssertNObjectsInBucket(self.sample_uri, count)

    test_regex = self._MakeScopeRegex('READER', 'group',
                                      self.GROUP_TEST_ADDRESS)
    json_texts = []
    for obj in objects:
      json_texts.append(
          self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                         return_stdout=True))
    for json_text in json_texts:
      self.assertNotRegex(json_text, test_regex)

    uris = [suri(obj) for obj in objects]
    self.RunGsUtil(['-m', '-DD'] + self._ch_acl_prefix +
                   ['-g', self.GROUP_TEST_ADDRESS + ':READ'] + uris)

    json_texts = []
    for obj in objects:
      json_texts.append(
          self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                         return_stdout=True))
    for json_text in json_texts:
      self.assertRegex(json_text, test_regex)

  def testRecursiveChangeAcl(self):
    """Tests recursively changing ACLs on nested objects."""
    obj = self.CreateObject(bucket_uri=self.sample_uri,
                            object_name='foo/bar',
                            contents=b'something')
    self.AssertNObjectsInBucket(self.sample_uri, 1)

    test_regex = self._MakeScopeRegex('READER', 'group',
                                      self.GROUP_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    @Retry(AssertionError, tries=5, timeout_secs=1)
    def _AddAcl():
      self.RunGsUtil(
          self._ch_acl_prefix +
          ['-R', '-g', self.GROUP_TEST_ADDRESS + ':READ',
           suri(obj)[:-3]])
      json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                                 return_stdout=True)
      self.assertRegex(json_text, test_regex)

    _AddAcl()

    @Retry(AssertionError, tries=5, timeout_secs=1)
    def _DeleteAcl():
      # Make sure we treat grant addresses case insensitively.
      delete_grant = self.GROUP_TEST_ADDRESS.upper()
      self.RunGsUtil(self._ch_acl_prefix + ['-d', delete_grant, suri(obj)])
      json_text = self.RunGsUtil(self._get_acl_prefix + [suri(obj)],
                                 return_stdout=True)
      self.assertNotRegex(json_text, test_regex)

    _DeleteAcl()

  def testMultiVersionSupport(self):
    """Tests changing ACLs on multiple object versions."""
    bucket = self.CreateVersionedBucket()
    object_name = self.MakeTempName('obj')
    obj1_uri = self.CreateObject(bucket_uri=bucket,
                                 object_name=object_name,
                                 contents=b'One thing')
    # Create another on the same URI, giving us a second version.
    self.CreateObject(bucket_uri=bucket,
                      object_name=object_name,
                      contents=b'Another thing',
                      gs_idempotent_generation=urigen(obj1_uri))

    lines = self.AssertNObjectsInBucket(bucket, 2, versioned=True)

    obj_v1, obj_v2 = lines[0], lines[1]

    test_regex = self._MakeScopeRegex('READER', 'group',
                                      self.GROUP_TEST_ADDRESS)
    json_text = self.RunGsUtil(self._get_acl_prefix + [obj_v1],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

    self.RunGsUtil(self._ch_acl_prefix +
                   ['-g', self.GROUP_TEST_ADDRESS + ':READ', obj_v1])
    json_text = self.RunGsUtil(self._get_acl_prefix + [obj_v1],
                               return_stdout=True)
    self.assertRegex(json_text, test_regex)

    json_text = self.RunGsUtil(self._get_acl_prefix + [obj_v2],
                               return_stdout=True)
    self.assertNotRegex(json_text, test_regex)

  def testBadRequestAclChange(self):
    stdout, stderr = self.RunGsUtil(
        self._ch_acl_prefix +
        ['-u', 'invalid_$$@hello.com:R',
         suri(self.sample_uri)],
        return_stdout=True,
        return_stderr=True,
        expected_status=1)
    if self._use_gcloud_storage:
      self.assertIn('HTTPError', stderr)
    else:
      self.assertIn('BadRequestException', stderr)
    self.assertNotIn('Retrying', stdout)
    self.assertNotIn('Retrying', stderr)

  def testAclGetWithoutFullControl(self):
    object_uri = self.CreateObject(contents=b'foo')
    expected_error_regex = r'Anonymous \S+ do(es)? not have'
    with self.SetAnonymousBotoCreds():
      stderr = self.RunGsUtil(self._get_acl_prefix + [suri(object_uri)],
                              return_stderr=True,
                              expected_status=1)
      self.assertRegex(stderr, expected_error_regex)

  def testTooFewArgumentsFails(self):
    """Tests calling ACL commands with insufficient number of arguments."""
    # No arguments for get, but valid subcommand.
    stderr = self.RunGsUtil(self._get_acl_prefix,
                            return_stderr=True,
                            expected_status=1)
    self.assertIn('command requires at least', stderr)

    # No arguments for set, but valid subcommand.
    stderr = self.RunGsUtil(self._set_acl_prefix,
                            return_stderr=True,
                            expected_status=1)
    self.assertIn('command requires at least', stderr)

    # No arguments for ch, but valid subcommand.
    stderr = self.RunGsUtil(self._ch_acl_prefix,
                            return_stderr=True,
                            expected_status=1)
    self.assertIn('command requires at least', stderr)

    # Neither arguments nor subcommand.
    stderr = self.RunGsUtil(['acl'], return_stderr=True, expected_status=1)
    self.assertIn('command requires at least', stderr)

  def testMinusF(self):
    """Tests -f option to continue after failure."""
    bucket_uri = self.CreateBucket()
    obj_uri = suri(
        self.CreateObject(bucket_uri=bucket_uri,
                          object_name='foo',
                          contents=b'foo'))
    acl_string = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
                                return_stdout=True)
    self.RunGsUtil(['-d'] + self._set_acl_prefix +
                   ['-f', 'public-read', obj_uri + 'foo2', obj_uri],
                   expected_status=1)
    acl_string2 = self.RunGsUtil(self._get_acl_prefix + [obj_uri],
                                 return_stdout=True)
    self.assertNotEqual(acl_string, acl_string2)


class TestS3CompatibleAcl(TestAclBase):
  """ACL integration tests that work for s3 and gs URLs."""

  def testAclObjectGetSet(self):
    bucket_uri = self.CreateBucket()
    obj_uri = self.CreateObject(bucket_uri=bucket_uri, contents=b'foo')
    self.AssertNObjectsInBucket(bucket_uri, 1)

    stdout = self.RunGsUtil(self._get_acl_prefix + [suri(obj_uri)],
                            return_stdout=True)
    set_contents = self.CreateTempFile(contents=stdout.encode(UTF8))
    self.RunGsUtil(self._set_acl_prefix + [set_contents, suri(obj_uri)])

  def testAclBucketGetSet(self):
    bucket_uri = self.CreateBucket()
    stdout = self.RunGsUtil(self._get_acl_prefix + [suri(bucket_uri)],
                            return_stdout=True)
    set_contents = self.CreateTempFile(contents=stdout.encode(UTF8))
    self.RunGsUtil(self._set_acl_prefix + [set_contents, suri(bucket_uri)])


@SkipForGS('S3 ACLs accept XML and should not cause an XML warning.')
class TestS3OnlyAcl(TestAclBase):
  """ACL integration tests that work only for s3 URLs."""

  # TODO: Format all test case names consistently.
  def test_set_xml_acl(self):
    """Ensures XML content does not return an XML warning for S3."""
    obj_uri = suri(self.CreateObject(contents=b'foo'))
    inpath = self.CreateTempFile(contents=b'<ValidXml></ValidXml>')
    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, obj_uri],
                            return_stderr=True,
                            expected_status=1)
    self.assertIn('BadRequestException', stderr)
    self.assertNotIn('XML ACL data provided', stderr)

  def test_set_xml_acl_bucket(self):
    """Ensures XML content does not return an XML warning for S3."""
    bucket_uri = suri(self.CreateBucket())
    inpath = self.CreateTempFile(contents=b'<ValidXml></ValidXml>')
    stderr = self.RunGsUtil(self._set_acl_prefix + [inpath, bucket_uri],
                            return_stderr=True,
                            expected_status=1)
    self.assertIn('BadRequestException', stderr)
    self.assertNotIn('XML ACL data provided', stderr)


class TestAclOldAlias(TestAcl):
  _set_acl_prefix = ['setacl']
  _get_acl_prefix = ['getacl']
  _set_defacl_prefix = ['setdefacl']
  _ch_acl_prefix = ['chacl']


class TestAclShim(testcase.ShimUnitTestBase):

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_get_object(self):
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl', ['get', 'gs://bucket/object'],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage objects describe'
                       ' --format=multi(acl:format=json)'
                       ' gs://bucket/object').format(
                           shim_util._get_gcloud_binary_path('fake_dir')),
                      info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_get_bucket(self):
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl', ['get', 'gs://bucket'],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage buckets describe'
                       ' --format=multi(acl:format=json)'
                       ' gs://bucket').format(
                           shim_util._get_gcloud_binary_path('fake_dir')),
                      info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_set_object(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand(
            'acl', ['set', inpath, 'gs://bucket/object'],
            return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage objects update'
                       ' --acl-file={}').format(
                           shim_util._get_gcloud_binary_path('fake_dir'),
                           inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_set_bucket(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl',
                                           ['set', inpath, 'gs://bucket'],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage buckets update'
                       ' --acl-file={} gs://bucket').format(
                           shim_util._get_gcloud_binary_path('fake_dir'),
                           inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_predefined_acl_set_object(self):
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand(
            'acl', ['set', 'private', 'gs://bucket/object'],
            return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage objects update'
                       ' --predefined-acl=private gs://bucket/object'.format(
                           shim_util._get_gcloud_binary_path('fake_dir'))),
                      info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_predefined_acl_set_bucket(self):
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl',
                                           ['set', 'private', 'gs://bucket'],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage buckets update'
                       ' --predefined-acl=private gs://bucket').format(
                           shim_util._get_gcloud_binary_path('fake_dir')),
                      info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_xml_predefined_acl_for_set(self):
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand(
            'acl', ['set', 'public-read', 'gs://bucket'],
            return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage buckets update'
                       ' --predefined-acl=publicRead gs://bucket').format(
                           shim_util._get_gcloud_binary_path('fake_dir')),
                      info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_set_multiple_buckets_urls(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl', [
            'set', '-f', inpath, 'gs://bucket', 'gs://bucket1', 'gs://bucket2'
        ],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage buckets update'
                       ' --acl-file={} --continue-on-error'
                       ' gs://bucket gs://bucket1 gs://bucket2').format(
                           shim_util._get_gcloud_binary_path('fake_dir'),
                           inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_set_multiple_objects_urls(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl', [
            'set', '-f', inpath, 'gs://bucket/object', 'gs://bucket/object1',
            'gs://bucket/object2'
        ],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage objects update'
                       ' --acl-file={} --continue-on-error gs://bucket/object'
                       ' gs://bucket/object1 gs://bucket/object2').format(
                           shim_util._get_gcloud_binary_path('fake_dir'),
                           inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_set_multiple_buckets_urls_recursive_all_versions(
      self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl', [
            'set', '-r', '-a', inpath, 'gs://bucket', 'gs://bucket1/o',
            'gs://bucket2'
        ],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage objects update'
                       ' --acl-file={} --recursive --all-versions gs://bucket'
                       ' gs://bucket1/o gs://bucket2').format(
                           shim_util._get_gcloud_binary_path('fake_dir'),
                           inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_translates_acl_set_mix_buckets_and_objects_raises_error(self):
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        with self.assertRaisesRegex(
            CommandException,
            'Cannot operate on a mix of buckets and objects.'):
          self.RunCommand(
              'acl', ['set', 'acl-file', 'gs://bucket', 'gs://bucket1/object'])

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_changes_bucket_acls_for_user(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand(
            'acl',
            ['ch', '-u', 'user@example.com:R', 'gs://bucket1', 'gs://bucket2'],
            return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(
            ('Gcloud Storage Command: {} storage buckets update'
             ' --add-acl-grant entity=user-user@example.com,role=READER'
             ' gs://bucket1 gs://bucket2').format(
                 shim_util._get_gcloud_binary_path('fake_dir'),
                 inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_changes_object_acls_for_user(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand('acl', [
            'ch', '-u', 'user@example.com:R', 'gs://bucket1/o', 'gs://bucket2/o'
        ],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(
            ('Gcloud Storage Command: {} storage objects update'
             ' --add-acl-grant entity=user-user@example.com,role=READER'
             ' gs://bucket1/o gs://bucket2/o').format(
                 shim_util._get_gcloud_binary_path('fake_dir'),
                 inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_raises_error_for_mix_of_objects_and_buckets(self):
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        with self.assertRaisesRegex(
            CommandException,
            'Cannot operate on a mix of buckets and objects.'):
          self.RunCommand('acl', ['ch', 'gs://bucket', 'gs://bucket1/object'])

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_changes_acls_for_group(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand(
            'acl', ['ch', '-g', 'group@example.com:W', 'gs://bucket1/o'],
            return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(
            ('Gcloud Storage Command: {} storage objects update'
             ' --add-acl-grant entity=group-group@example.com,role=WRITER'
             ' gs://bucket1/o').format(
                 shim_util._get_gcloud_binary_path('fake_dir'),
                 inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_changes_acls_for_domain(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand(
            'acl', ['ch', '-g', 'example.com:O', 'gs://bucket1/o'],
            return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage objects update'
                       ' --add-acl-grant entity=domain-example.com,role=OWNER'
                       ' gs://bucket1/o').format(
                           shim_util._get_gcloud_binary_path('fake_dir'),
                           inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_changes_acls_for_project(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        mock_log_handler = self.RunCommand(
            'acl', ['ch', '-p', 'owners-example:O', 'gs://bucket1/o'],
            return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(
            ('Gcloud Storage Command: {} storage objects update'
             ' --add-acl-grant entity=project-owners-example,role=OWNER'
             ' gs://bucket1/o').format(
                 shim_util._get_gcloud_binary_path('fake_dir'),
                 inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_changes_acls_for_all_users(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        # Non-exhaustive set of strings allowed by gsutil's regex.
        for identifier in ['all', 'allUsers', 'AllUsers']:
          mock_log_handler = self.RunCommand(
              'acl', ['ch', '-g', identifier + ':O', 'gs://bucket1/o'],
              return_log_handler=True)
          info_lines = '\n'.join(mock_log_handler.messages['info'])
          self.assertIn(('Gcloud Storage Command: {} storage objects update'
                         ' --add-acl-grant entity=allUsers,role=OWNER'
                         ' gs://bucket1/o').format(
                             shim_util._get_gcloud_binary_path('fake_dir'),
                             inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_changes_acls_for_all_authenticated_users(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        # Non-exhaustive set of strings allowed by gsutil's regex.
        for identifier in [
            'allauth', 'allAuthenticatedUsers', 'AllAuthenticatedUsers'
        ]:
          mock_log_handler = self.RunCommand(
              'acl', ['ch', '-g', identifier + ':O', 'gs://bucket1/o'],
              return_log_handler=True)
          info_lines = '\n'.join(mock_log_handler.messages['info'])
          self.assertIn(
              ('Gcloud Storage Command: {} storage objects update'
               ' --add-acl-grant entity=allAuthenticatedUsers,role=OWNER'
               ' gs://bucket1/o').format(
                   shim_util._get_gcloud_binary_path('fake_dir'),
                   inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_deletes_acls(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        # Non-exhaustive set of strings allowed by gsutil's regex.
        mock_log_handler = self.RunCommand('acl', [
            'ch', '-d', 'user@example.com', '-d', 'user1@example.com',
            'gs://bucket1/o'
        ],
                                           return_log_handler=True)
        info_lines = '\n'.join(mock_log_handler.messages['info'])
        self.assertIn(('Gcloud Storage Command: {} storage objects update'
                       ' --remove-acl-grant user@example.com'
                       ' --remove-acl-grant user1@example.com'
                       ' gs://bucket1/o').format(
                           shim_util._get_gcloud_binary_path('fake_dir'),
                           inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_removes_acls_for_all_users(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        # Non-exhaustive set of strings allowed by gsutil's regex.
        for identifier in ['all', 'allUsers', 'AllUsers']:
          mock_log_handler = self.RunCommand(
              'acl', ['ch', '-d', identifier, 'gs://bucket1/o'],
              return_log_handler=True)
          info_lines = '\n'.join(mock_log_handler.messages['info'])
          self.assertIn(('Gcloud Storage Command: {} storage objects update'
                         ' --remove-acl-grant AllUsers'
                         ' gs://bucket1/o').format(
                             shim_util._get_gcloud_binary_path('fake_dir'),
                             inpath), info_lines)

  @mock.patch.object(acl.AclCommand, 'RunCommand', new=mock.Mock())
  def test_shim_removes_acls_for_all_authenticated_users(self):
    inpath = self.CreateTempFile()
    with SetBotoConfigForTest([('GSUtil', 'use_gcloud_storage', 'True'),
                               ('GSUtil', 'hidden_shim_mode', 'dry_run')]):
      with SetEnvironmentForTest({
          'CLOUDSDK_CORE_PASS_CREDENTIALS_TO_GSUTIL': 'True',
          'CLOUDSDK_ROOT_DIR': 'fake_dir',
      }):
        # Non-exhaustive set of strings allowed by gsutil's regex.
        for identifier in [
            'allauth', 'allAuthenticatedUsers', 'AllAuthenticatedUsers'
        ]:
          mock_log_handler = self.RunCommand(
              'acl', ['ch', '-d', identifier, 'gs://bucket1/o'],
              return_log_handler=True)
          info_lines = '\n'.join(mock_log_handler.messages['info'])
          self.assertIn(('Gcloud Storage Command: {} storage objects update'
                         ' --remove-acl-grant AllAuthenticatedUsers'
                         ' gs://bucket1/o').format(
                             shim_util._get_gcloud_binary_path('fake_dir'),
                             inpath), info_lines)
