Add access rules restriction tests

Adds new test cases and updates current test cases for access allow
and deny APIs. Such APIs now allow users to restrict the visibility
of some fields during the access creation, or even restricting the
deletion of access rules.

Depends-On: https://review.opendev.org/c/openstack/manila/+/887725
Change-Id: If0f7655dc6c11c6c12eeb76bd1cc853466235cca
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index 3cf70b4..9bd32a9 100755
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -1017,7 +1017,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(
@@ -1030,8 +1031,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(
@@ -1042,7 +1050,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):