From 4eccd687941af466973d8a795ea7d7f543958f10 Mon Sep 17 00:00:00 2001 From: silvacarloss Date: Wed, 12 Jul 2023 18:07:25 -0300 Subject: [PATCH] Allow restricting access rules Access rules can now have the visibility of sensible fields restricted, as well as the deletion can be prevented. To do so, two new parameters were implemented in the access create command: `restrict_visibility` and `restrict_deletion`. In order to drop the delete restriction, users must specify the `unrestrict` option while issuing the share access delete command. Depends-On: Iea422c9d6bc99a81cd88c5f4b7055d6a1cf97fdc Change-Id: I31899a563c621e6f799e320fd990f9e510a8a9cc --- manilaclient/api_versions.py | 2 +- manilaclient/osc/v2/resource_locks.py | 1 + manilaclient/osc/v2/share_access_rules.py | 116 ++++++++++- manilaclient/tests/functional/osc/base.py | 14 +- .../functional/osc/test_resource_locks.py | 2 +- .../functional/osc/test_share_access_rules.py | 78 ++++++- .../unit/osc/v2/test_share_access_rules.py | 193 ++++++++++++++++-- manilaclient/tests/unit/v2/test_shares.py | 2 +- manilaclient/v2/shares.py | 51 ++++- ...y-and-deletion-locks-69978f052e25334c.yaml | 9 + 10 files changed, 428 insertions(+), 40 deletions(-) create mode 100644 releasenotes/notes/add-access-visibility-and-deletion-locks-69978f052e25334c.yaml diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index c3cbd2bd2..2b8c35e5c 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.81' +MAX_VERSION = '2.82' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/osc/v2/resource_locks.py b/manilaclient/osc/v2/resource_locks.py index 6199631bf..6f0ca873e 100644 --- a/manilaclient/osc/v2/resource_locks.py +++ b/manilaclient/osc/v2/resource_locks.py @@ -48,6 +48,7 @@ LOCK_SUMMARY_ATTRIBUTES = [ RESOURCE_TYPE_MANAGERS = { 'share': 'shares', + 'access_rule': 'share_access_rules' } diff --git a/manilaclient/osc/v2/share_access_rules.py b/manilaclient/osc/v2/share_access_rules.py index bf72b08e2..8e69d2d05 100644 --- a/manilaclient/osc/v2/share_access_rules.py +++ b/manilaclient/osc/v2/share_access_rules.py @@ -84,6 +84,30 @@ class ShareAccessAllow(command.ShowOne): action='store_true', help=_("Wait for share access rule creation.") ) + parser.add_argument( + "--lock-visibility", + action='store_true', + default=False, + help=_("Whether the sensitive fields of the access rule redacted " + "to other users. Only available with API version >= 2.82.") + ) + parser.add_argument( + "--lock-deletion", + action='store_true', + default=False, + help=_("When enabled, a 'delete' lock will be placed against the " + "rule and the rule cannot be deleted while the lock " + "exists. Only available with API version >= 2.82.") + ) + parser.add_argument( + '--lock-reason', + metavar="", + type=str, + default=None, + help=_("Reason for locking the access rule. Should only be " + "provided alongside a deletion or visibility lock. " + "Only available with API version >= 2.82.") + ) return parser def take_action(self, parsed_args): @@ -91,6 +115,28 @@ class ShareAccessAllow(command.ShowOne): share = apiutils.find_resource(share_client.shares, parsed_args.share) + lock_kwargs = {} + if parsed_args.lock_visibility: + lock_kwargs['lock_visibility'] = parsed_args.lock_visibility + if parsed_args.lock_deletion: + lock_kwargs['lock_deletion'] = parsed_args.lock_deletion + if parsed_args.lock_reason: + lock_kwargs['lock_reason'] = parsed_args.lock_reason + + if (lock_kwargs + and share_client.api_version < api_versions.APIVersion( + "2.82")): + raise exceptions.CommandError( + 'Restricted access rules are only available starting ' + 'from API version 2.82.') + + if (lock_kwargs.get('lock_reason', None) + and not (lock_kwargs.get('lock_visibility', None) + or lock_kwargs.get('lock_deletion', None))): + raise exceptions.CommandError( + 'Lock reason can only be set while locking the deletion or ' + 'visibility.') + properties = {} if parsed_args.properties: if share_client.api_version >= api_versions.APIVersion("2.45"): @@ -104,7 +150,8 @@ class ShareAccessAllow(command.ShowOne): access_type=parsed_args.access_type, access=parsed_args.access_to, access_level=parsed_args.access_level, - metadata=properties + metadata=properties, + **lock_kwargs ) if parsed_args.wait: if not oscutils.wait_for_status( @@ -154,6 +201,13 @@ class ShareAccessDeny(command.Command): default=False, help=_("Wait for share access rule deletion") ) + parser.add_argument( + "--unrestrict", + action='store_true', + default=False, + help=_("Seek access rule deletion despite restrictions. Only " + "available with API version >= 2.82.") + ) return parser def take_action(self, parsed_args): @@ -161,9 +215,17 @@ class ShareAccessDeny(command.Command): share = apiutils.find_resource(share_client.shares, parsed_args.share) + kwargs = {} + if parsed_args.unrestrict: + if share_client.api_version < api_versions.APIVersion("2.82"): + raise exceptions.CommandError( + 'Restricted access rules are only available starting from ' + 'API version 2.82.') + kwargs['unrestrict'] = True + error = None try: - share.deny(parsed_args.id) + share.deny(parsed_args.id, **kwargs) if parsed_args.wait: if not oscutils.wait_for_delete( manager=share_client.share_access_rules, @@ -201,6 +263,30 @@ class ListShareAccess(command.Lister): 'OPTIONAL: Default=None. ' 'Available only for API microversion >= 2.45'), ) + parser.add_argument( + "--access-type", + metavar="", + default=None, + help=_("Filter access rules by the access type.") + ) + parser.add_argument( + "--access-key", + metavar="", + default=None, + help=_("Filter access rules by the access key.") + ) + parser.add_argument( + "--access-to", + metavar="", + default=None, + help=_("Filter access rules by the access to field.") + ) + parser.add_argument( + "--access-level", + metavar="", + default=None, + help=_("Filter access rules by the access level.") + ) return parser def take_action(self, parsed_args): @@ -208,9 +294,33 @@ class ListShareAccess(command.Lister): share = apiutils.find_resource(share_client.shares, parsed_args.share) + access_type = parsed_args.access_type + access_key = parsed_args.access_key + access_to = parsed_args.access_to + access_level = parsed_args.access_level + + extended_filter_keys = { + 'access_type': access_type, + 'access_key': access_key, + 'access_to': access_to, + 'access_level': access_level + } + + if (any(extended_filter_keys.values()) + and share_client.api_version < api_versions.APIVersion( + "2.82")): + raise exceptions.CommandError( + 'Filtering access rules by access_type, access_key, access_to ' + 'and access_level is available starting from API version ' + '2.82.') + + search_opts = {} + if share_client.api_version >= api_versions.APIVersion("2.82"): + for filter_key, filter_value in extended_filter_keys.items(): + if filter_value: + search_opts[filter_key] = filter_value if share_client.api_version >= api_versions.APIVersion("2.45"): - search_opts = {} if parsed_args.properties: search_opts = { 'metadata': utils.extract_properties( diff --git a/manilaclient/tests/functional/osc/base.py b/manilaclient/tests/functional/osc/base.py index 8d1c6a0ca..b3a204923 100644 --- a/manilaclient/tests/functional/osc/base.py +++ b/manilaclient/tests/functional/osc/base.py @@ -259,7 +259,11 @@ class OSCClientTestBase(base.ClientTestBase): def create_share_access_rule(self, share, access_type, access_to, properties=None, - access_level=None, wait=False): + access_level=None, wait=False, + lock_visibility=False, + lock_deletion=False, + lock_reason=None, + add_cleanup=False): cmd = f'access create {share} {access_type} {access_to} ' if access_level: @@ -267,7 +271,13 @@ class OSCClientTestBase(base.ClientTestBase): if properties: cmd += f'--properties {properties} ' if wait: - cmd += f'--wait' + cmd += '--wait ' + if lock_visibility: + cmd += '--lock-visibility ' + if lock_deletion: + cmd += '--lock-deletion ' + if lock_reason: + cmd += f'--lock-reason {lock_reason}' access_rule = self.dict_result('share', cmd) diff --git a/manilaclient/tests/functional/osc/test_resource_locks.py b/manilaclient/tests/functional/osc/test_resource_locks.py index e51724c77..1635b8081 100644 --- a/manilaclient/tests/functional/osc/test_resource_locks.py +++ b/manilaclient/tests/functional/osc/test_resource_locks.py @@ -37,7 +37,7 @@ LOCK_SUMMARY_ATTRIBUTES = [ ] -@utils.skip_if_microversion_not_supported('2.81') +@utils.skip_if_microversion_not_supported('2.82') class ResourceLockTests(base.OSCClientTestBase): """Lock CLI test cases""" diff --git a/manilaclient/tests/functional/osc/test_share_access_rules.py b/manilaclient/tests/functional/osc/test_share_access_rules.py index a6b2986ce..a23848942 100644 --- a/manilaclient/tests/functional/osc/test_share_access_rules.py +++ b/manilaclient/tests/functional/osc/test_share_access_rules.py @@ -10,11 +10,13 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt +from tempest.lib import exceptions as tempest_exc + from manilaclient.tests.functional.osc import base -import ddt - +@ddt.ddt class ShareAccessAllowTestCase(base.OSCClientTestBase): def test_share_access_allow(self): @@ -52,23 +54,63 @@ class ShareAccessAllowTestCase(base.OSCClientTestBase): self.assertEqual(access_rule['properties'], 'foo : bar') self.assertEqual(access_rule['access_level'], 'ro') + @ddt.data( + {'lock_visibility': True, 'lock_deletion': True, + 'lock_reason': None}, + {'lock_visibility': False, 'lock_deletion': True, + 'lock_reason': None}, + {'lock_visibility': True, 'lock_deletion': False, + 'lock_reason': 'testing'}, + {'lock_visibility': True, 'lock_deletion': False, + 'lock_reason': 'testing'}, + ) + @ddt.unpack + def test_share_access_allow_restrict(self, lock_visibility, + lock_deletion, lock_reason): + share = self.create_share() + access_rule = self.create_share_access_rule( + share=share['id'], + access_type='ip', + access_to='0.0.0.0/0', + wait=True, + lock_visibility=lock_visibility, + lock_deletion=lock_deletion, + lock_reason=lock_reason) + if lock_deletion: + self.assertRaises( + tempest_exc.CommandFailed, + self.openstack, + 'share', + params=f'access delete {share["id"]} {access_rule["id"]}' + ) + self.openstack( + 'share', + params=f'access delete {share["id"]} {access_rule["id"]} ' + f'--unrestrict --wait') + + +@ddt.ddt class ShareAccessDenyTestCase(base.OSCClientTestBase): - def test_share_access_deny(self): + @ddt.data(True, False) + def test_share_access_deny(self, lock_deletion): share = self.create_share() access_rule = self.create_share_access_rule( share=share['name'], access_type='ip', access_to='0.0.0.0/0', - wait=True) + wait=True, + lock_deletion=lock_deletion) access_rules = self.listing_result('share', f'access list {share["id"]}') num_access_rules = len(access_rules) - self.openstack('share', - params=f'access delete ' - f'{share["name"]} {access_rule["id"]} --wait') + delete_params = ( + f'access delete {share["name"]} {access_rule["id"]} --wait') + if lock_deletion: + delete_params += ' --unrestrict' + self.openstack('share', params=delete_params) access_rules = self.listing_result('share', f'access list {share["id"]}') @@ -127,6 +169,28 @@ class ListShareAccessRulesTestCase(base.OSCClientTestBase): self.assertEqual(access_rule_properties['id'], access_rule_properties[0]['ID']) + def test_share_access_list_with_filters(self): + share = self.create_share() + access_to_filter = '20.0.0.0/0' + self.create_share_access_rule( + share=share['name'], + access_type='ip', + access_to='0.0.0.0/0', + wait=True) + self.create_share_access_rule( + share=share['name'], + access_type='ip', + access_to=access_to_filter, + wait=True) + + output = self.openstack( + 'share', + params=f'access list {share["id"]} --access-to {access_to_filter}', + flags=f'--os-share-api-version 2.82') + access_rule_list = self.parser.listing(output) + + self.assertTrue(len(access_rule_list) == 1) + class ShowShareAccessRulesTestCase(base.OSCClientTestBase): def test_share_access_show(self): diff --git a/manilaclient/tests/unit/osc/v2/test_share_access_rules.py b/manilaclient/tests/unit/osc/v2/test_share_access_rules.py index 916119a25..96d99c2f2 100644 --- a/manilaclient/tests/unit/osc/v2/test_share_access_rules.py +++ b/manilaclient/tests/unit/osc/v2/test_share_access_rules.py @@ -10,8 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. # + from unittest import mock +import ddt from osc_lib import exceptions from osc_lib import utils as oscutils @@ -48,6 +50,7 @@ class TestShareAccess(manila_fakes.TestShare): self.access_rules_mock.reset_mock() +@ddt.ddt class TestShareAccessCreate(TestShareAccess): def setUp(self): @@ -84,7 +87,7 @@ class TestShareAccessCreate(TestShareAccess): access_type="user", access="demo", access_level=None, - metadata={} + metadata={}, ) self.assertEqual(ACCESS_RULE_ATTRIBUTES, columns) self.assertCountEqual(self.access_rule._info.values(), data) @@ -100,7 +103,7 @@ class TestShareAccessCreate(TestShareAccess): ("share", self.share.id), ("access_type", "user"), ("access_to", "demo"), - ('properties', ['key=value']) + ('properties', ['key=value']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -109,11 +112,92 @@ class TestShareAccessCreate(TestShareAccess): access_type="user", access="demo", access_level=None, - metadata={'key': 'value'} + metadata={'key': 'value'}, ) self.assertEqual(ACCESS_RULE_ATTRIBUTES, columns) self.assertCountEqual(self.access_rule._info.values(), data) + @ddt.data( + {'lock_visibility': True, 'lock_deletion': True, + 'lock_reason': 'testing resource locks'}, + {'lock_visibility': False, 'lock_deletion': True, 'lock_reason': None}, + {'lock_visibility': True, 'lock_deletion': False, 'lock_reason': None}, + ) + @ddt.unpack + def test_share_access_create_restrict(self, lock_visibility, + lock_deletion, lock_reason): + arglist = [ + self.share.id, + 'user', + 'demo', + '--properties', 'key=value' + ] + verifylist = [ + ("share", self.share.id), + ("access_type", "user"), + ("access_to", "demo"), + ('properties', ['key=value']), + ] + allow_call_kwargs = {} + if lock_visibility: + arglist.append('--lock-visibility') + verifylist.append(('lock_visibility', lock_visibility)) + allow_call_kwargs['lock_visibility'] = lock_visibility + if lock_deletion: + arglist.append('--lock-deletion') + verifylist.append(('lock_deletion', lock_deletion)) + allow_call_kwargs['lock_deletion'] = lock_deletion + if lock_reason: + arglist.append('--lock-reason') + arglist.append(lock_reason) + verifylist.append(('lock_reason', lock_reason)) + allow_call_kwargs['lock_reason'] = lock_reason + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.shares_mock.get.assert_called_with(self.share.id) + self.share.allow.assert_called_with( + access_type="user", + access="demo", + access_level=None, + metadata={'key': 'value'}, + **allow_call_kwargs + ) + self.assertEqual(ACCESS_RULE_ATTRIBUTES, columns) + self.assertCountEqual(self.access_rule._info.values(), data) + + @ddt.data( + {'lock_visibility': True, 'lock_deletion': False}, + {'lock_visibility': False, 'lock_deletion': True}, + ) + @ddt.unpack + def test_share_access_create_restrict_not_available( + self, lock_visibility, lock_deletion): + arglist = [ + self.share.id, + 'user', + 'demo', + ] + self.app.client_manager.share.api_version = api_versions.APIVersion( + "2.79") + verifylist = [ + ("share", self.share.id), + ("access_type", "user"), + ("access_to", "demo"), + ("lock_visibility", lock_visibility), + ("lock_deletion", lock_deletion), + ("lock_reason", None), + ] + if lock_visibility: + arglist.append('--lock-visibility') + if lock_deletion: + arglist.append('--lock-deletion') + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + def test_access_rule_create_access_level(self): arglist = [ self.share.id, @@ -125,7 +209,7 @@ class TestShareAccessCreate(TestShareAccess): ("share", self.share.id), ("access_type", "user"), ("access_to", "demo"), - ('access_level', 'ro') + ('access_level', 'ro'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -134,7 +218,7 @@ class TestShareAccessCreate(TestShareAccess): access_type="user", access="demo", access_level='ro', - metadata={} + metadata={}, ) self.assertEqual(ACCESS_RULE_ATTRIBUTES, columns) self.assertCountEqual(self.access_rule._info.values(), data) @@ -150,7 +234,7 @@ class TestShareAccessCreate(TestShareAccess): ("share", self.share.id), ("access_type", "user"), ("access_to", "demo"), - ("wait", True) + ("wait", True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -159,7 +243,7 @@ class TestShareAccessCreate(TestShareAccess): access_type="user", access="demo", access_level=None, - metadata={} + metadata={}, ) self.assertEqual(ACCESS_RULE_ATTRIBUTES, columns) self.assertCountEqual(self.access_rule._info.values(), data) @@ -176,7 +260,7 @@ class TestShareAccessCreate(TestShareAccess): ("share", self.share.id), ("access_type", "user"), ("access_to", "demo"), - ("wait", True) + ("wait", True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -188,7 +272,7 @@ class TestShareAccessCreate(TestShareAccess): access_type="user", access="demo", access_level=None, - metadata={} + metadata={}, ) mock_logger.error.assert_called_with( @@ -198,6 +282,7 @@ class TestShareAccessCreate(TestShareAccess): self.assertCountEqual(self.access_rule._info.values(), data) +@ddt.ddt class TestShareAccessDelete(TestShareAccess): def setUp(self): @@ -213,21 +298,47 @@ class TestShareAccessDelete(TestShareAccess): # Get the command object to test self.cmd = osc_share_access_rules.ShareAccessDeny(self.app, None) - def test_share_access_delete(self): + @ddt.data(True, False) + def test_share_access_delete(self, unrestrict): arglist = [ self.share.id, self.access_rule.id ] verifylist = [ ("share", self.share.id), - ("id", self.access_rule.id) + ("id", self.access_rule.id), ] + deny_kwargs = {} + if unrestrict: + arglist.append('--unrestrict') + verifylist.append(("unrestrict", unrestrict)) + deny_kwargs['unrestrict'] = unrestrict parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) self.shares_mock.get.assert_called_with(self.share.id) - self.share.deny.assert_called_with(self.access_rule.id) + self.share.deny.assert_called_with( + self.access_rule.id, **deny_kwargs) self.assertIsNone(result) + def test_share_access_delete_unrestrict_not_available(self): + self.app.client_manager.share.api_version = api_versions.APIVersion( + "2.79") + arglist = [ + self.share.id, + self.access_rule.id, + "--unrestrict" + ] + verifylist = [ + ("share", self.share.id), + ("id", self.access_rule.id), + ("unrestrict", True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + def test_share_access_delete_wait(self): arglist = [ self.share.id, @@ -237,7 +348,7 @@ class TestShareAccessDelete(TestShareAccess): verifylist = [ ("share", self.share.id), ("id", self.access_rule.id), - ('wait', True) + ('wait', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -246,7 +357,8 @@ class TestShareAccessDelete(TestShareAccess): result = self.cmd.take_action(parsed_args) self.shares_mock.get.assert_called_with(self.share.id) - self.share.deny.assert_called_with(self.access_rule.id) + self.share.deny.assert_called_with( + self.access_rule.id) self.assertIsNone(result) def test_share_access_delete_wait_error(self): @@ -270,6 +382,7 @@ class TestShareAccessDelete(TestShareAccess): ) +@ddt.ddt class TestShareAccessList(TestShareAccess): access_rules_columns = [ @@ -339,6 +452,58 @@ class TestShareAccessList(TestShareAccess): self.assertEqual(self.access_rules_columns, columns) self.assertEqual(tuple(self.values_list), tuple(data)) + @ddt.data( + {'access_to': '10.0.0.0/0', 'access_type': 'ip'}, + {'access_key': '10.0.0.0/0', 'access_level': 'rw'}, + ) + def test_access_rules_list_access_filters(self, filters): + arglist = [ + self.share.id, + ] + + verifylist = [ + ("share", self.share.id), + ] + for filter_key, filter_value in filters.items(): + filter_arg = filter_key.replace("_", "-") + arglist.append(f'--{filter_arg}') + arglist.append(filter_value) + verifylist.append((filter_key, filter_value)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.shares_mock.get.assert_called_with(self.share.id) + self.access_rules_mock.access_list.assert_called_with( + self.share, + filters) + self.assertEqual(self.access_rules_columns, columns) + self.assertEqual(tuple(self.values_list), tuple(data)) + + @ddt.data( + {'access_to': '10.0.0.0/0', 'access_type': 'ip'}, + {'access_key': '10.0.0.0/0', 'access_level': 'rw'}, + ) + def test_access_rules_list_access_filters_command_error(self, filters): + self.app.client_manager.share.api_version = api_versions.APIVersion( + "2.81") + arglist = [ + self.share.id, + ] + verifylist = [ + ("share", self.share.id), + ] + for filter_key, filter_value in filters.items(): + filter_arg = filter_key.replace("_", "-") + arglist.append(f'--{filter_arg}') + arglist.append(filter_value) + verifylist.append((filter_key, filter_value)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + class TestShareAccessShow(TestShareAccess): diff --git a/manilaclient/tests/unit/v2/test_shares.py b/manilaclient/tests/unit/v2/test_shares.py index a98eb30a1..d52b3a779 100644 --- a/manilaclient/tests/unit/v2/test_shares.py +++ b/manilaclient/tests/unit/v2/test_shares.py @@ -50,7 +50,7 @@ class SharesTest(utils.TestCase): self.share.allow(access_type, access_to, access_level) self.share.manager.allow.assert_called_once_with( - self.share, access_type, access_to, access_level, None) + self.share, access_type, access_to, access_level) # Testcases for class ShareManager diff --git a/manilaclient/v2/shares.py b/manilaclient/v2/shares.py index aae179c2d..a58fcf106 100644 --- a/manilaclient/v2/shares.py +++ b/manilaclient/v2/shares.py @@ -75,14 +75,13 @@ class Share(base.MetadataCapableResource): """Delete the specified share ignoring its current state.""" self.manager.force_delete(self) - def allow(self, access_type, access, access_level, metadata=None): + def allow(self, *args, **kwargs): """Allow access to a share.""" - return self.manager.allow( - self, access_type, access, access_level, metadata) + return self.manager.allow(self, *args, **kwargs) - def deny(self, id): + def deny(self, id, **kwargs): """Deny access from IP to a share.""" - return self.manager.deny(self, id) + return self.manager.deny(self, id, **kwargs) def access_list(self): """Get access list from a share.""" @@ -570,7 +569,8 @@ class ShareManager(base.MetadataCapableManager): raise exceptions.CommandError(msg) def _do_allow(self, share, access_type, access, access_level, action_name, - metadata=None): + metadata=None, lock_visibility=False, + lock_deletion=False, lock_reason=None): """Allow access to a share. :param share: either share object or text with its ID. @@ -587,6 +587,12 @@ class ShareManager(base.MetadataCapableManager): access_params['access_level'] = access_level if metadata: access_params['metadata'] = metadata + if lock_visibility: + access_params['lock_visibility'] = lock_visibility + if lock_deletion: + access_params['lock_deletion'] = lock_deletion + if lock_reason: + access_params['lock_reason'] = lock_reason access = self._action(action_name, share, access_params)[1]["access"] return access @@ -618,30 +624,53 @@ class ShareManager(base.MetadataCapableManager): return self._do_allow( share, access_type, access, access_level, "allow_access") - @api_versions.wraps("2.45") # noqa + @api_versions.wraps("2.45", "2.81") # noqa def allow(self, share, access_type, access, access_level, metadata=None): # noqa valid_access_types = ('ip', 'user', 'cert', 'cephx') self._validate_access(access_type, access, valid_access_types, enable_ipv6=True) return self._do_allow( - share, access_type, access, access_level, "allow_access", metadata) + share, access_type, access, access_level, "allow_access", + metadata=metadata) - def _do_deny(self, share, access_id, action_name): + @api_versions.wraps("2.82") # noqa + def allow(self, share, access_type, access, access_level, # pylint: disable=function-redefined # noqa F811 + metadata=None, lock_visibility=False, lock_deletion=False, + lock_reason=None): + valid_access_types = ('ip', 'user', 'cert', 'cephx') + self._validate_access(access_type, access, valid_access_types, + enable_ipv6=True) + return self._do_allow( + share, access_type, access, access_level, "allow_access", + metadata=metadata, lock_visibility=lock_visibility, + lock_deletion=lock_deletion, lock_reason=lock_reason) + + def _do_deny(self, share, access_id, action_name, unrestrict=False): """Deny access to a share. :param share: either share object or text with its ID. :param access_id: ID of share access rule """ - return self._action(action_name, share, {"access_id": access_id}) + body = { + "access_id": access_id, + } + if unrestrict: + body['unrestrict'] = True + return self._action(action_name, share, body) @api_versions.wraps("1.0", "2.6") def deny(self, share, access_id): return self._do_deny(share, access_id, "os-deny_access") - @api_versions.wraps("2.7") # noqa + @api_versions.wraps("2.7", "2.81") # noqa def deny(self, share, access_id): # noqa return self._do_deny(share, access_id, "deny_access") + @api_versions.wraps("2.82") # noqa + def deny(self, share, access_id, unrestrict=False): # noqa + return self._do_deny(share, access_id, "deny_access", + unrestrict=unrestrict) + def _do_access_list(self, share, action_name): """Get access list to a share. diff --git a/releasenotes/notes/add-access-visibility-and-deletion-locks-69978f052e25334c.yaml b/releasenotes/notes/add-access-visibility-and-deletion-locks-69978f052e25334c.yaml new file mode 100644 index 000000000..88cb2004d --- /dev/null +++ b/releasenotes/notes/add-access-visibility-and-deletion-locks-69978f052e25334c.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + It is now possible to restrict the visibility of access rules' sensitive + fields, as well as lock the access rule deletion while allowing access to + a share. A lock reason can also be provided. + - | + It is now possible to filter access rules while listing them by its + `access_to`, `access_type`, `access_key` and `access_level`.