Merge "Add access rules restriction tests"
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index 87b53e3..0a98ad5 100644
--- a/manila_tempest_tests/config.py
+++ b/manila_tempest_tests/config.py
@@ -40,7 +40,7 @@
                     "This value is only used to validate the versions "
                     "response from Manila."),
     cfg.StrOpt("max_api_microversion",
-               default="2.81",
+               default="2.82",
                help="The maximum api microversion is configured to be the "
                     "value of the latest microversion supported by Manila."),
     cfg.StrOpt("region",
diff --git a/manila_tempest_tests/services/share/v2/json/shares_client.py b/manila_tempest_tests/services/share/v2/json/shares_client.py
index d72c098..e3a022e 100644
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -819,7 +819,8 @@
     def create_access_rule(self, share_id, access_type="ip",
                            access_to="0.0.0.0", access_level=None,
                            version=LATEST_MICROVERSION, metadata=None,
-                           action_name=None):
+                           action_name=None, lock_visibility=False,
+                           lock_deletion=False):
         post_body = {
             self._get_access_action_name(version, 'os-allow_access'): {
                 "access_type": access_type,
@@ -829,6 +830,10 @@
         }
         if metadata is not None:
             post_body['allow_access']['metadata'] = metadata
+        if lock_visibility:
+            post_body['allow_access']['lock_visibility'] = True
+        if lock_deletion:
+            post_body['allow_access']['lock_deletion'] = True
         body = json.dumps(post_body)
         resp, body = self.post(
             "shares/%s/action" % share_id, body, version=version,
@@ -872,12 +877,15 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_access_rule(self, share_id, rule_id,
-                           version=LATEST_MICROVERSION, action_name=None):
+                           version=LATEST_MICROVERSION, action_name=None,
+                           unrestrict=False):
         post_body = {
             self._get_access_action_name(version, 'os-deny_access'): {
                 "access_id": rule_id,
             }
         }
+        if unrestrict:
+            post_body['deny_access']['unrestrict'] = True
         body = json.dumps(post_body)
         resp, body = self.post(
             "shares/%s/action" % share_id, body, version=version)
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index cb64ec8..2db2353 100755
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -1045,7 +1045,8 @@
     def allow_access(self, share_id, client=None, access_type=None,
                      access_level='rw', access_to=None, metadata=None,
                      version=LATEST_MICROVERSION, status='active',
-                     raise_rule_in_error_state=True, cleanup=True):
+                     raise_rule_in_error_state=True, lock_visibility=False,
+                     lock_deletion=False, cleanup=True):
 
         client = client or self.shares_v2_client
         a_type, a_to = utils.get_access_rule_data_from_config(
@@ -1058,8 +1059,15 @@
             'access_to': access_to,
             'access_level': access_level
         }
+        delete_kwargs = (
+            {'unrestrict': True} if lock_deletion else {}
+        )
         if client is self.shares_v2_client:
             kwargs.update({'metadata': metadata, 'version': version})
+        if lock_visibility:
+            kwargs.update({'lock_visibility': True})
+        if lock_deletion:
+            kwargs.update({'lock_deletion': True})
 
         rule = client.create_access_rule(share_id, **kwargs)['access']
         waiters.wait_for_resource_status(
@@ -1070,7 +1078,9 @@
             self.addCleanup(
                 client.wait_for_resource_deletion, rule_id=rule['id'],
                 share_id=share_id, version=version)
-            self.addCleanup(client.delete_access_rule, share_id, rule['id'])
+            self.addCleanup(
+                client.delete_access_rule, share_id, rule['id'],
+                **delete_kwargs)
         return rule
 
 
diff --git a/manila_tempest_tests/tests/api/test_rules.py b/manila_tempest_tests/tests/api/test_rules.py
index 925b725..5f39852 100644
--- a/manila_tempest_tests/tests/api/test_rules.py
+++ b/manila_tempest_tests/tests/api/test_rules.py
@@ -28,6 +28,7 @@
 
 CONF = config.CONF
 LATEST_MICROVERSION = CONF.share.max_api_microversion
+RESTRICTED_RULES_VERSION = '2.82'
 
 
 def _create_delete_ro_access_rule(self, version):
@@ -444,6 +445,10 @@
         cls.share_type = cls.create_share_type()
         cls.share_type_id = cls.share_type['id']
         cls.share = cls.create_share(share_type_id=cls.share_type_id)
+        cls.user_project = cls.os_admin.projects_client.show_project(
+            cls.shares_v2_client.project_id)['project']
+        cls.new_user = cls.create_user_and_get_client(
+            project=cls.user_project)
 
     @decorators.idempotent_id('c52e95cc-d6ea-4d02-9b52-cd7c1913dfff')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -519,6 +524,92 @@
         msg = "expected id lists %s times in rule list" % (len(gen))
         self.assertEqual(1, len(gen), msg)
 
+    @decorators.idempotent_id('3bca373e-e54f-49e1-8789-99a383cf4df3')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @utils.skip_if_microversion_not_supported(RESTRICTED_RULES_VERSION)
+    @ddt.data(
+        *itertools.product(utils.deduplicate(
+            ["2.81", CONF.share.max_api_microversion]), (True, False))
+    )
+    @ddt.unpack
+    def test_list_restricted_rules_from_other_user(
+            self, older_version, lock_visibility):
+        # create rule
+        self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=self.access_type, access_to=self.access_to,
+            lock_visibility=lock_visibility, lock_deletion=True)
+
+        rule = (
+            self.shares_v2_client.list_access_rules(
+                self.share["id"])["access_list"][0])
+
+        rules = self.new_user.shares_v2_client.list_access_rules(
+            self.share['id'])['access_list']
+        rules_get_lower_version = (
+            self.new_user.shares_v2_client.list_access_rules(
+                self.share['id'], version=older_version)['access_list'])
+        expected_access_to = '******' if lock_visibility else self.access_to
+        expected_access_key = (
+            '******' if lock_visibility else rule['access_key'])
+
+        # verify values
+        rule_latest_rules_api = [r for r in rules if r['id'] == rule['id']][0]
+        rule_lower_version_rules_api = [r for r in rules_get_lower_version
+                                        if r['id'] == rule['id']][0]
+        self.assertEqual(
+            expected_access_to, rule_latest_rules_api["access_to"])
+        self.assertEqual(
+            expected_access_to,
+            rule_lower_version_rules_api['access_to'])
+        if self.access_type == 'cephx':
+            self.assertEqual(expected_access_key,
+                             rule_latest_rules_api['access_key'])
+            self.assertEqual(
+                expected_access_key,
+                rule_lower_version_rules_api['access_key'])
+
+    @decorators.idempotent_id('4829265a-eb32-400d-91a0-be06ce31a2ef')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_admin_listing_restricted_rules(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+
+        # create rule
+        self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=self.access_type, access_to=self.access_to,
+            lock_visibility=True)
+
+        rules = self.admin_shares_v2_client.list_access_rules(
+            self.share["id"])['access_list']
+
+        # ensure admin can see rules even if locked
+        self.assertEqual(self.access_to, rules[0]["access_to"])
+        if self.access_type == 'cephx':
+            self.assertIsNotNone(rules[0]['access_key'])
+            self.assertFalse(rules[0]['access_key'] == '******')
+        else:
+            self.assertIsNone(rules[0]['access_key'])
+
+    @decorators.idempotent_id('00202c6c-b4c7-4fa6-933a-562fbffde405')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_admin_delete_restricted_rules(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=self.access_type, access_to=self.access_to,
+            lock_visibility=True, lock_deletion=True, cleanup=False)
+
+        self.admin_shares_v2_client.delete_access_rule(
+            self.share['id'], rule['id'], unrestrict=True)
+
+        self.shares_v2_client.wait_for_resource_deletion(
+            rule_id=rule['id'], share_id=self.share['id'])
+
     @decorators.idempotent_id('b77bcbda-9754-48f0-9be6-79341ad1af64')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data(*utils.deduplicate(['1.0', '2.9', '2.27', '2.28',
diff --git a/manila_tempest_tests/tests/api/test_rules_negative.py b/manila_tempest_tests/tests/api/test_rules_negative.py
index a4b53f5..32c4e9f 100644
--- a/manila_tempest_tests/tests/api/test_rules_negative.py
+++ b/manila_tempest_tests/tests/api/test_rules_negative.py
@@ -28,6 +28,7 @@
 
 CONF = config.CONF
 LATEST_MICROVERSION = CONF.share.max_api_microversion
+RESTRICTED_RULES_VERSION = '2.82'
 
 
 @ddt.ddt
@@ -60,6 +61,11 @@
             # create snapshot
             cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"])
 
+        cls.user_project = cls.os_admin.projects_client.show_project(
+            cls.shares_v2_client.project_id)['project']
+        cls.new_user = cls.create_user_and_get_client(
+            project=cls.user_project)
+
     @decorators.idempotent_id('16781b45-d2bb-4891-aa97-c28c0769d5bd')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data('1.2.3.256',
@@ -170,6 +176,93 @@
                           self.admin_client.create_access_rule,
                           share["id"], access_type, access_to)
 
+    @decorators.idempotent_id('478d3c84-b0ea-41c8-a860-e87f182d991c')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_deny_access_unrestrict_other_user_rule(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+
+        access_type, access_to = utils.get_access_rule_data_from_config(
+            self.protocol)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=access_type, access_to=access_to,
+            lock_visibility=True, lock_deletion=True)
+
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.new_user.shares_v2_client.delete_access_rule,
+            self.share['id'],
+            rule['id'],
+            unrestrict=True
+        )
+
+    @decorators.idempotent_id('c107b0b7-7a3e-4114-af64-ca8fe6e836c9')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @ddt.data(True, False)
+    def test_deny_access_without_unrestrict_as_owner_user(self, same_user):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+        access_type, access_to = utils.get_access_rule_data_from_config(
+            self.protocol)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=access_type, access_to=access_to,
+            lock_visibility=True, lock_deletion=True)
+
+        client = (
+            self.shares_v2_client
+            if same_user else self.new_user.shares_v2_client
+        )
+        self.assertRaises(
+            lib_exc.Forbidden,
+            client.delete_access_rule,
+            self.share['id'],
+            rule['id'])
+        self.assertRaises(
+            lib_exc.Forbidden,
+            client.delete_access_rule,
+            self.share['id'],
+            rule['id'],
+            version='2.81'
+        )
+
+    @decorators.idempotent_id('f5b9e7c9-7e6b-4918-a1c4-e03c8d82c46a')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    def test_allow_access_multiple_visibility_locks_not_allowed(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+        access_type, access_to = utils.get_access_rule_data_from_config(
+            self.protocol)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=access_type, access_to=access_to,
+            lock_visibility=True, lock_deletion=True)
+
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.shares_v2_client.create_resource_lock,
+            rule['id'],
+            "access_rule",
+            resource_action="show",
+            lock_reason="locked for testing"
+        )
+
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.new_user.shares_v2_client.create_resource_lock,
+            rule['id'],
+            "access_rule",
+            resource_action="show",
+            lock_reason="locked for testing"
+        )
+
 
 @ddt.ddt
 class ShareIpRulesForCIFSNegativeTest(ShareIpRulesForNFSNegativeTest):