Merge "Removes force_backup_delete test"
diff --git a/doc/source/installation.rst b/doc/source/installation.rst
index b532d63..e342dd8 100644
--- a/doc/source/installation.rst
+++ b/doc/source/installation.rst
@@ -50,3 +50,13 @@
# not found in the policy.json. Otherwise, they throw a
# skipException.
strict_policy_check = False
+
+ # The following config options set the location of the service's
+ # policy file. For services that have their policy in code (e.g.,
+ # Nova), this would be the location of a custom policy.json, if
+ # one exists.
+ cinder_policy_file = /etc/cinder/policy.json
+ glance_policy_file = /etc/glance/policy.json
+ keystone_policy_file = /etc/keystone/policy.json
+ neutron_policy_file = /etc/neutron/policy.json
+ nova_policy_file = /etc/nova/policy.json
diff --git a/patrole_tempest_plugin/config.py b/patrole_tempest_plugin/config.py
index dfb6cef..cb00269 100644
--- a/patrole_tempest_plugin/config.py
+++ b/patrole_tempest_plugin/config.py
@@ -30,5 +30,20 @@
default=False,
help="If true, throws RbacParsingException for"
" policies which don't exist. If false, "
- "throws skipException.")
+ "throws skipException."),
+ cfg.StrOpt('cinder_policy_file',
+ default='/etc/cinder/policy.json',
+ help="Location of the neutron policy file."),
+ cfg.StrOpt('glance_policy_file',
+ default='/etc/glance/policy.json',
+ help="Location of the glance policy file."),
+ cfg.StrOpt('keystone_policy_file',
+ default='/etc/keystone/policy.json',
+ help="Location of the keystone policy file."),
+ cfg.StrOpt('neutron_policy_file',
+ default='/etc/neutron/policy.json',
+ help="Location of the neutron policy file."),
+ cfg.StrOpt('nova_policy_file',
+ default='/etc/nova/policy.json',
+ help="Location of the nova policy file.")
]
diff --git a/patrole_tempest_plugin/rbac_auth.py b/patrole_tempest_plugin/rbac_auth.py
deleted file mode 100644
index ae497f3..0000000
--- a/patrole_tempest_plugin/rbac_auth.py
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright 2017 AT&T Corporation.
-# 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.
-
-import testtools
-
-from oslo_log import log as logging
-
-from tempest import config
-
-from patrole_tempest_plugin import rbac_exceptions
-from patrole_tempest_plugin import rbac_policy_parser
-
-LOG = logging.getLogger(__name__)
-CONF = config.CONF
-
-
-class RbacAuthority(object):
- def __init__(self, project_id, user_id, service, extra_target_data):
- self.policy_parser = rbac_policy_parser.RbacPolicyParser(
- project_id, user_id, service, extra_target_data=extra_target_data)
-
- def get_permission(self, rule_name, role):
- try:
- is_allowed = self.policy_parser.allowed(rule_name, role)
- if is_allowed:
- LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule_name,
- role)
- else:
- LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!",
- rule_name, role)
- return is_allowed
- except rbac_exceptions.RbacParsingException as e:
- if CONF.rbac.strict_policy_check:
- raise e
- else:
- raise testtools.TestCase.skipException(str(e))
- return False
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 2686736..e68921f 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -40,7 +40,7 @@
each role, whether a given rule is allowed using oslo policy.
"""
- def __init__(self, project_id, user_id, service=None, path=None,
+ def __init__(self, project_id, user_id, service=None,
extra_target_data={}):
"""Initialization of Rbac Policy Parser.
@@ -76,7 +76,12 @@
# Use default path in /etc/<service_name/policy.json if no path
# is provided.
- self.path = path or os.path.join('/etc', service, 'policy.json')
+ path = getattr(CONF.rbac, '%s_policy_file' % str(service), None)
+ if not path:
+ LOG.info("No config option found for %s,"
+ " using default path" % str(service))
+ path = os.path.join('/etc', service, 'policy.json')
+ self.path = path
self.rules = policy.Rules.load(self._get_policy_data(service),
'default')
self.project_id = project_id
@@ -98,12 +103,18 @@
# Check whether policy file exists.
if os.path.isfile(self.path):
- with open(self.path, 'r') as policy_file:
- file_policy_data = policy_file.read()
try:
+ with open(self.path, 'r') as policy_file:
+ file_policy_data = policy_file.read()
file_policy_data = json.loads(file_policy_data)
- except ValueError:
- file_policy_data = None
+ except (IOError, ValueError) as e:
+ msg = "Failed to read policy file for service. "
+ if isinstance(e, IOError):
+ msg += "Please check that policy path exists."
+ else:
+ msg += "JSON may be improperly formatted."
+ LOG.debug(msg)
+ file_policy_data = {}
# Check whether policy actions are defined in code. Nova and Keystone,
# for example, define their default policy actions in code.
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index a225c7d..4382259 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -15,6 +15,7 @@
import logging
import sys
+import testtools
import six
@@ -22,8 +23,8 @@
from tempest.lib import exceptions
from tempest import test
-from patrole_tempest_plugin import rbac_auth
from patrole_tempest_plugin import rbac_exceptions
+from patrole_tempest_plugin import rbac_policy_parser
CONF = config.CONF
LOG = logging.getLogger(__name__)
@@ -62,18 +63,15 @@
:raises RbacOverPermission: for bullet (3) above.
"""
def decorator(func):
+ role = CONF.rbac.rbac_test_role
+
def wrapper(*args, **kwargs):
- try:
- caller_ref = None
- if args and isinstance(args[0], test.BaseTestCase):
- caller_ref = args[0]
- project_id = caller_ref.auth_provider.credentials.project_id
- user_id = caller_ref.auth_provider.credentials.user_id
- except AttributeError as e:
- msg = ("{0}: project_id/user_id not found in "
- "cls.auth_provider.credentials".format(e))
- LOG.error(msg)
- raise rbac_exceptions.RbacResourceSetupFailed(msg)
+ if args and isinstance(args[0], test.BaseTestCase):
+ test_obj = args[0]
+ else:
+ raise rbac_exceptions.RbacResourceSetupFailed(
+ '`rbac_rule_validation` decorator can only be applied to '
+ 'an instance of `tempest.test.BaseTestCase`.')
if admin_only:
LOG.info("As admin_only is True, only admin role should be "
@@ -81,33 +79,28 @@
"check for policy action {0}.".format(rule))
allowed = CONF.rbac.rbac_test_role == 'admin'
else:
- authority = rbac_auth.RbacAuthority(
- project_id, user_id, service,
- _format_extra_target_data(caller_ref, extra_target_data))
- allowed = authority.get_permission(
- rule, CONF.rbac.rbac_test_role)
+ allowed = _is_authorized(test_obj, service, rule,
+ extra_target_data)
expected_exception, irregular_msg = _get_exception_type(
expected_error_code)
try:
- func(*args)
+ func(*args, **kwargs)
except rbac_exceptions.RbacInvalidService as e:
msg = ("%s is not a valid service." % service)
LOG.error(msg)
raise exceptions.NotFound(
- "%s RbacInvalidService was: %s" %
- (msg, e))
+ "%s RbacInvalidService was: %s" % (msg, e))
except (expected_exception, rbac_exceptions.RbacActionFailed) as e:
if irregular_msg:
LOG.warning(irregular_msg.format(rule, service))
if allowed:
msg = ("Role %s was not allowed to perform %s." %
- (CONF.rbac.rbac_test_role, rule))
+ (role, rule))
LOG.error(msg)
raise exceptions.Forbidden(
- "%s exception was: %s" %
- (msg, e))
+ "%s exception was: %s" % (msg, e))
except Exception as e:
exc_info = sys.exc_info()
error_details = exc_info[1].__str__()
@@ -119,21 +112,58 @@
else:
if not allowed:
LOG.error("Role %s was allowed to perform %s" %
- (CONF.rbac.rbac_test_role, rule))
+ (role, rule))
raise rbac_exceptions.RbacOverPermission(
"OverPermission: Role %s was allowed to perform %s" %
- (CONF.rbac.rbac_test_role, rule))
+ (role, rule))
finally:
- caller_ref.rbac_utils.switch_role(caller_ref,
- toggle_rbac_role=False)
- return wrapper
+ test_obj.rbac_utils.switch_role(test_obj,
+ toggle_rbac_role=False)
+
+ _wrapper = testtools.testcase.attr(role)(wrapper)
+ return _wrapper
return decorator
+def _is_authorized(test_obj, service, rule_name, extra_target_data):
+ try:
+ project_id = test_obj.auth_provider.credentials.project_id
+ user_id = test_obj.auth_provider.credentials.user_id
+ except AttributeError as e:
+ msg = ("{0}: project_id/user_id not found in "
+ "cls.auth_provider.credentials".format(e))
+ LOG.error(msg)
+ raise rbac_exceptions.RbacResourceSetupFailed(msg)
+
+ try:
+ role = CONF.rbac.rbac_test_role
+ formatted_target_data = _format_extra_target_data(
+ test_obj, extra_target_data)
+ policy_parser = rbac_policy_parser.RbacPolicyParser(
+ project_id, user_id, service,
+ extra_target_data=formatted_target_data)
+ is_allowed = policy_parser.allowed(rule_name, role)
+
+ if is_allowed:
+ LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule_name,
+ role)
+ else:
+ LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!",
+ rule_name, role)
+ return is_allowed
+ except rbac_exceptions.RbacParsingException as e:
+ if CONF.rbac.strict_policy_check:
+ raise e
+ else:
+ raise testtools.TestCase.skipException(str(e))
+ return False
+
+
def _get_exception_type(expected_error_code):
expected_exception = None
irregular_msg = None
supported_error_codes = [403, 404]
+
if expected_error_code == 403:
expected_exception = exceptions.Forbidden
elif expected_error_code == 404:
diff --git a/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py b/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py
index 94c704f..552a137 100644
--- a/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v2/test_image_resource_types_rbac.py
@@ -23,6 +23,21 @@
class ImageResourceTypesRbacTest(rbac_base.BaseV2ImageRbacTest):
+ @classmethod
+ def resource_setup(cls):
+ super(ImageResourceTypesRbacTest, cls).resource_setup()
+ cls.namespace_name = data_utils.rand_name('test-ns')
+ cls.namespaces_client.create_namespace(
+ namespace=cls.namespace_name,
+ protected=False)
+
+ @classmethod
+ def resource_cleanup(cls):
+ test_utils.call_and_ignore_notfound_exc(
+ cls.namespaces_client.delete_namespace,
+ cls.namespace_name)
+ super(ImageResourceTypesRbacTest, cls).resource_setup()
+
@rbac_rule_validation.action(service="glance",
rule="list_metadef_resource_types")
@decorators.idempotent_id('0416fc4d-cfdc-447b-88b6-d9f1dd0382f7')
@@ -42,15 +57,15 @@
RBAC test for the glance get_metadef_resource_type policy.
"""
- namespace_name = data_utils.rand_name('test-ns')
- self.namespaces_client.create_namespace(
- namespace=namespace_name,
- protected=False)
- self.addCleanup(
- test_utils.call_and_ignore_notfound_exc,
- self.namespaces_client.delete_namespace,
- namespace_name)
-
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
self.resource_types_client.list_resource_type_association(
- namespace_name)
+ self.namespace_name)
+
+ @rbac_rule_validation.action(service="glance",
+ rule="add_metadef_resource_type_association")
+ @decorators.idempotent_id('ef9fbc60-3e28-4164-a25c-d30d892f7939')
+ def test_add_metadef_resource_type(self):
+ type_name = data_utils.rand_name()
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.resource_types_client.create_resource_type_association(
+ self.namespace_name, name=type_name)
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
index 416fb4d..16e77ed 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_actions_rbac.py
@@ -158,6 +158,48 @@
self.rbac_utils.switch_role(self, toggle_rbac_role=True)
self.client.retype_volume(volume['id'], new_type=vol_type)
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="volume_extension:volume_admin_actions:reset_status")
+ @decorators.idempotent_id('4b3dad7d-0e73-4839-8781-796dd3d7af1d')
+ def test_volume_reset_status(self):
+ volume = self.create_volume()
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.reset_volume_status(volume['id'], status='error')
+
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="volume_extension:volume_admin_actions:force_delete")
+ @decorators.idempotent_id('a312a937-6abf-4b91-a950-747086cbce48')
+ def test_volume_force_delete(self):
+ volume = self.create_volume()
+ self.client.reset_volume_status(volume['id'], status='error')
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.client.force_delete_volume(volume['id'])
+ self.client.wait_for_resource_deletion(volume['id'])
+
+ @decorators.idempotent_id('48bd302b-950a-4830-840c-3158246ecdcc')
+ @test.services('compute')
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="volume_extension:volume_admin_actions:force_detach")
+ def test_force_detach_volume_from_instance(self):
+ server = self._create_server()
+ self._attach_volume(server)
+ attachment = self.volumes_client.show_volume(
+ self.volume['id'])['volume']['attachments'][0]
+
+ # Reset volume's status to error.
+ self.volumes_client.reset_volume_status(self.volume['id'],
+ status='error')
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self.volumes_client.force_detach_volume(
+ self.volume['id'], connector=None,
+ attachment_id=attachment['attachment_id'])
+
class VolumesActionsV3RbacTest(VolumesActionsRbacTest):
_api_version = 3
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py
new file mode 100644
index 0000000..7a9d7ba
--- /dev/null
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_manage_rbac.py
@@ -0,0 +1,113 @@
+# Copyright 2017 AT&T Corporation.
+# 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.
+
+from tempest.common import waiters
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions
+
+from patrole_tempest_plugin import rbac_rule_validation
+from patrole_tempest_plugin.tests.api.volume import rbac_base
+
+CONF = config.CONF
+
+
+class VolumesManageRbacTest(rbac_base.BaseVolumeRbacTest):
+
+ @classmethod
+ def skip_checks(cls):
+ super(VolumesManageRbacTest, cls).skip_checks()
+
+ if not CONF.volume_feature_enabled.manage_volume:
+ raise cls.skipException("Manage volume tests are disabled")
+
+ if len(CONF.volume.manage_volume_ref) != 2:
+ raise cls.skipException("Manage volume ref is not correctly "
+ "configured")
+
+ @classmethod
+ def setup_clients(cls):
+ super(VolumesManageRbacTest, cls).setup_clients()
+ cls.volume_manage_client = cls.os_primary.volume_manage_v2_client
+
+ def _manage_volume(self, org_volume):
+ # Manage volume
+ new_volume_name = data_utils.rand_name(
+ self.__class__.__name__ + '-volume')
+
+ new_volume_ref = {
+ 'name': new_volume_name,
+ 'host': org_volume['os-vol-host-attr:host'],
+ 'ref': {CONF.volume.manage_volume_ref[0]:
+ CONF.volume.manage_volume_ref[1] % org_volume['id']},
+ 'volume_type': org_volume['volume_type'],
+ 'availability_zone': org_volume['availability_zone']}
+
+ new_volume_id = self.volume_manage_client.manage_volume(
+ **new_volume_ref)['volume']['id']
+
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ new_volume_id, 'available')
+ self.addCleanup(self.delete_volume,
+ self.volumes_client, new_volume_id)
+
+ def _unmanage_volume(self, volume):
+ self.volumes_client.unmanage_volume(volume['id'])
+ self.volumes_client.wait_for_resource_deletion(volume['id'])
+
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="volume_extension:volume_manage")
+ @decorators.idempotent_id('114f9708-939b-407e-aeac-d21ebfabaad3')
+ def test_volume_manage(self):
+ volume_id = self.create_volume()['id']
+ volume = self.volumes_client.show_volume(volume_id)['volume']
+
+ # By default, the volume is managed after creation. We need to
+ # unmanage the volume first before testing manage volume.
+ self._unmanage_volume(volume)
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ try:
+ self._manage_volume(volume)
+ except exceptions.Forbidden as e:
+ # Since the test role under test does not have permission to
+ # manage the volume, Forbidden exception is thrown and the
+ # manageable list will not be cleaned up. Therefore, we need to
+ # re-manage the volume at the end of the test case for proper
+ # resource clean up.
+ self.addCleanup(self._manage_volume, volume)
+ raise exceptions.Forbidden(e)
+
+ @rbac_rule_validation.action(
+ service="cinder",
+ rule="volume_extension:volume_unmanage")
+ @decorators.idempotent_id('d5d72abe-60bc-45ac-a8f2-c21b24f0b5d6')
+ def test_volume_unmanage(self):
+ volume_id = self.create_volume()['id']
+ volume = self.volumes_client.show_volume(volume_id)['volume']
+
+ self.rbac_utils.switch_role(self, toggle_rbac_role=True)
+ self._unmanage_volume(volume)
+
+ # In order to clean up the manageable list, we need to re-manage the
+ # volume after the test. The _manage_volume method will set up the
+ # proper resource cleanup
+ self.addCleanup(self._manage_volume, volume)
+
+
+class VolumesManageV3RbacTest(VolumesManageRbacTest):
+ _api_version = 3
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py
deleted file mode 100644
index b10f5b3..0000000
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright 2017 AT&T Corporation.
-# 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.
-
-from oslo_log import log as logging
-
-from tempest import config
-from tempest.lib import decorators
-
-from patrole_tempest_plugin import rbac_rule_validation
-from patrole_tempest_plugin.tests.api.volume import rbac_base
-
-CONF = config.CONF
-LOG = logging.getLogger(__name__)
-
-
-class VolumesRbacTest(rbac_base.BaseVolumeRbacTest):
-
- @classmethod
- def setup_clients(cls):
- super(VolumesRbacTest, cls).setup_clients()
- cls.client = cls.volumes_client
-
- @rbac_rule_validation.action(
- service="cinder",
- rule="volume_extension:volume_admin_actions:reset_status")
- @decorators.idempotent_id('4b3dad7d-0e73-4839-8781-796dd3d7af1d')
- def test_volume_reset_status(self):
- volume = self.create_volume()
- # Test volume reset status : available->error->available
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.reset_volume_status(volume['id'], status='error')
- self.client.reset_volume_status(volume['id'], status='available')
-
- @rbac_rule_validation.action(
- service="cinder",
- rule="volume_extension:volume_admin_actions:force_delete")
- @decorators.idempotent_id('a312a937-6abf-4b91-a950-747086cbce48')
- def test_volume_force_delete_when_volume_is_error(self):
- volume = self.create_volume()
- self.client.reset_volume_status(volume['id'], status='error')
- # Test force delete when status of volume is error
- self.rbac_utils.switch_role(self, toggle_rbac_role=True)
- self.client.force_delete_volume(volume['id'])
- self.client.wait_for_resource_deletion(volume['id'])
-
-
-class VolumesV3RbacTest(VolumesRbacTest):
- _api_version = 3
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
index 0906222..6889b44 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_policy_parser.py
@@ -32,6 +32,8 @@
super(RbacPolicyTest, self).setUp()
self.mock_admin_mgr = mock.patch.object(
rbac_policy_parser, 'credentials').start()
+ self.mock_path = mock.patch.object(
+ rbac_policy_parser, 'os').start()
current_directory = os.path.dirname(os.path.realpath(__file__))
self.custom_policy_file = os.path.join(current_directory,
@@ -88,8 +90,9 @@
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
+ self.mock_path.path.join.return_value = self.custom_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.custom_policy_file)
+ test_tenant_id, test_user_id, "test")
expected = {
'policy_action_1': ['two', 'four', 'six', 'eight'],
@@ -110,8 +113,9 @@
def test_admin_policy_file_with_admin_role(self):
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
+ self.mock_path.path.join.return_value = self.admin_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.admin_policy_file)
+ test_tenant_id, test_user_id, "test")
role = 'admin'
allowed_rules = [
@@ -130,8 +134,9 @@
def test_admin_policy_file_with_member_role(self):
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
+ self.mock_path.path.join.return_value = self.admin_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.admin_policy_file)
+ test_tenant_id, test_user_id, "test")
role = 'Member'
allowed_rules = [
@@ -151,8 +156,9 @@
def test_admin_policy_file_with_context_is_admin(self):
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
+ self.mock_path.path.join.return_value = self.alt_admin_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.alt_admin_policy_file)
+ test_tenant_id, test_user_id, "test")
role = 'fake_admin'
allowed_rules = ['non_admin_rule']
@@ -187,8 +193,9 @@
"""
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
+ self.mock_path.path.join.return_value = self.tenant_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.tenant_policy_file)
+ test_tenant_id, test_user_id, "test")
# Check whether Member role can perform expected actions.
allowed_rules = ['rule1', 'rule2', 'rule3', 'rule4']
@@ -268,9 +275,9 @@
def test_invalid_policy_rule_throws_rbac_parsing_exception(self, m_log):
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
-
+ self.mock_path.path.join.return_value = self.custom_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.custom_policy_file)
+ test_tenant_id, test_user_id, "test")
fake_rule = 'fake_rule'
expected_message = "Policy action: {0} not found in policy file: {1}."\
@@ -285,9 +292,9 @@
def test_unknown_exception_throws_rbac_parsing_exception(self, m_log):
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
-
+ self.mock_path.path.join.return_value = self.custom_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.custom_policy_file)
+ test_tenant_id, test_user_id, "test")
parser.rules = mock.MagicMock(
**{'__getitem__.return_value.side_effect': Exception(
mock.sentinel.error)})
@@ -320,9 +327,9 @@
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
-
+ self.mock_path.path.join.return_value = self.tenant_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.tenant_policy_file)
+ test_tenant_id, test_user_id, "test")
policy_data = parser._get_policy_data('fake_service')
@@ -364,9 +371,9 @@
test_tenant_id = mock.sentinel.tenant_id
test_user_id = mock.sentinel.user_id
-
+ self.mock_path.path.join.return_value = self.tenant_policy_file
parser = rbac_policy_parser.RbacPolicyParser(
- test_tenant_id, test_user_id, "test", self.tenant_policy_file)
+ test_tenant_id, test_user_id, "test")
policy_data = parser._get_policy_data('fake_service')
@@ -393,10 +400,10 @@
mock_creds.AdminManager.return_value.identity_services_v3_client.\
list_services.return_value = {
'services': [{'name': 'test_service'}]}
-
+ self.mock_path.path.join.return_value = '/etc/test_service/policy.json'
e = self.assertRaises(rbac_exceptions.RbacParsingException,
rbac_policy_parser.RbacPolicyParser,
- None, None, 'test_service', None)
+ None, None, 'test_service')
expected_error = \
'Policy file for {0} service neither found in code '\
@@ -405,14 +412,12 @@
self.assertIn(expected_error, str(e))
- @mock.patch.object(rbac_policy_parser, 'os', autospec=True)
@mock.patch.object(rbac_policy_parser, 'json', autospec=True)
@mock.patch.object(rbac_policy_parser, 'credentials', autospec=True)
@mock.patch.object(rbac_policy_parser, 'stevedore', autospec=True)
def test_get_policy_data_without_valid_policy(self, mock_stevedore,
- mock_credentials, mock_json,
- mock_os):
- mock_os.path.isfile.return_value = False
+ mock_credentials, mock_json):
+ self.mock_path.path.isfile.return_value = False
test_policy_action = mock.Mock(check='rule:bar')
test_policy_action.configure_mock(name='foo')
@@ -432,7 +437,7 @@
e = self.assertRaises(rbac_exceptions.RbacParsingException,
rbac_policy_parser.RbacPolicyParser,
- None, None, 'test_service', None)
+ None, None, 'test_service')
expected_error = "Policy file for {0} service is invalid."\
.format("test_service")
@@ -459,11 +464,10 @@
}
mock_stevedore.named.NamedExtensionManager.return_value = None
mock_json.loads.side_effect = ValueError
-
+ self.mock_path.path.join.return_value = self.tenant_policy_file
e = self.assertRaises(rbac_exceptions.RbacParsingException,
rbac_policy_parser.RbacPolicyParser,
- None, None, 'test_service',
- self.tenant_policy_file)
+ None, None, 'test_service')
expected_error = 'Policy file for {0} service neither found in code '\
'nor at {1}.'.format('test_service',
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
index 174945e..41af3b2 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -19,7 +19,6 @@
from tempest import test
from tempest.tests import base
-from patrole_tempest_plugin import rbac_auth
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import rbac_rule_validation as rbac_rv
@@ -43,8 +42,9 @@
self.addCleanup(CONF.clear_override, 'rbac_test_role', group='rbac')
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_rule_validation_have_permission_no_exc(self, mock_auth, mock_log):
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_have_permission_no_exc(self, mock_policy,
+ mock_log):
"""Test that having permission and no exception thrown is success.
Positive test case success scenario.
@@ -54,9 +54,7 @@
mock_function = mock.Mock()
wrapper = decorator(mock_function)
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = True
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
result = wrapper(self.mock_args)
@@ -65,8 +63,8 @@
mock_log.error.assert_not_called()
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_rule_validation_lack_permission_throw_exc(self, mock_auth,
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_lack_permission_throw_exc(self, mock_policy,
mock_log):
"""Test that having no permission and exception thrown is success.
@@ -78,9 +76,7 @@
mock_function.side_effect = exceptions.Forbidden
wrapper = decorator(mock_function)
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = False
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
result = wrapper(self.mock_args)
@@ -89,8 +85,8 @@
mock_log.error.assert_not_called()
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_rule_validation_forbidden_negative(self, mock_auth, mock_log):
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_forbidden_negative(self, mock_policy, mock_log):
"""Test Forbidden error is thrown and have permission fails.
Negative test case: if Forbidden is thrown and the user should be
@@ -102,9 +98,7 @@
mock_function.side_effect = exceptions.Forbidden
wrapper = decorator(mock_function)
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = True
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
e = self.assertRaises(exceptions.Forbidden, wrapper, self.mock_args)
self.assertIn(
@@ -114,8 +108,8 @@
" perform sentinel.action.")
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_rule_validation_rbac_action_failed_positive(self, mock_auth,
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_rbac_action_failed_positive(self, mock_policy,
mock_log):
"""Test RbacActionFailed error is thrown without permission passes.
@@ -127,9 +121,7 @@
mock_function.side_effect = rbac_exceptions.RbacActionFailed
wrapper = decorator(mock_function)
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = False
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
result = wrapper(self.mock_args)
@@ -138,8 +130,8 @@
mock_log.warning.assert_not_called()
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_rule_validation_rbac_action_failed_negative(self, mock_auth,
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_rbac_action_failed_negative(self, mock_policy,
mock_log):
"""Test RbacActionFailed error is thrown with permission fails.
@@ -151,9 +143,7 @@
mock_function.side_effect = rbac_exceptions.RbacActionFailed
wrapper = decorator(mock_function)
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = True
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = True
e = self.assertRaises(exceptions.Forbidden, wrapper, self.mock_args)
self.assertIn(
@@ -164,8 +154,9 @@
" perform sentinel.action.")
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_expect_not_found_but_raises_forbidden(self, mock_auth, mock_log):
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_expect_not_found_but_raises_forbidden(self, mock_policy,
+ mock_log):
"""Test that expecting 404 but getting 403 works for all scenarios.
Tests the following scenarios:
@@ -186,9 +177,8 @@
"NotFound, which was not thrown."
for permission in [True, False]:
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = permission
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value =\
+ permission
e = self.assertRaises(exceptions.Forbidden, wrapper,
self.mock_args)
@@ -197,8 +187,8 @@
mock_log.error.reset_mock()
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_expect_not_found_and_raise_not_found(self, mock_auth, mock_log):
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_expect_not_found_and_raise_not_found(self, mock_policy, mock_log):
"""Test that expecting 404 and getting 404 works for all scenarios.
Tests the following scenarios:
@@ -220,9 +210,8 @@
]
for pos, permission in enumerate([True, False]):
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = permission
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value =\
+ permission
expected_error = expected_errors[pos]
@@ -245,8 +234,8 @@
mock_log.error.reset_mock()
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_rule_validation_overpermission_negative(self, mock_auth,
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_rule_validation_overpermission_negative(self, mock_policy,
mock_log):
"""Test that OverPermission is correctly handled.
@@ -258,9 +247,7 @@
mock_function = mock.Mock()
wrapper = decorator(mock_function)
- mock_permission = mock.Mock()
- mock_permission.get_permission.return_value = False
- mock_auth.return_value = mock_permission
+ mock_policy.RbacPolicyParser.return_value.allowed.return_value = False
e = self.assertRaises(rbac_exceptions.RbacOverPermission, wrapper,
self.mock_args)
@@ -270,7 +257,7 @@
mock_log.error.assert_called_once_with(
"Role Member was allowed to perform sentinel.action")
- @mock.patch.object(rbac_auth, 'rbac_policy_parser', autospec=True)
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
def test_invalid_policy_rule_throws_parsing_exception(
self, mock_rbac_policy_parser):
"""Test that invalid policy action causes test to be skipped."""
@@ -295,8 +282,8 @@
mock.sentinel.project_id, mock.sentinel.user_id,
mock.sentinel.service, extra_target_data={})
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_get_exception_type_404(self, mock_auth):
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_get_exception_type_404(self, mock_policy):
"""Test that getting a 404 exception type returns NotFound."""
expected_exception = exceptions.NotFound
expected_irregular_msg = ("NotFound exception was caught for policy "
@@ -309,8 +296,8 @@
self.assertEqual(expected_exception, actual_exception)
self.assertEqual(expected_irregular_msg, actual_irregular_msg)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_get_exception_type_403(self, mock_auth):
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_get_exception_type_403(self, mock_policy):
"""Test that getting a 404 exception type returns Forbidden."""
expected_exception = exceptions.Forbidden
expected_irregular_msg = None
@@ -322,8 +309,9 @@
self.assertEqual(expected_irregular_msg, actual_irregular_msg)
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_exception_thrown_when_type_is_not_int(self, mock_auth, mock_log):
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_exception_thrown_when_type_is_not_int(self, mock_policy,
+ mock_log):
"""Test that non-integer exception type raises error."""
self.assertRaises(rbac_exceptions.RbacInvalidErrorCode,
rbac_rv._get_exception_type, "403")
@@ -333,8 +321,8 @@
"codes: [403, 404]")
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
- @mock.patch.object(rbac_auth, 'RbacAuthority', autospec=True)
- def test_exception_thrown_when_type_is_403_or_404(self, mock_auth,
+ @mock.patch.object(rbac_rv, 'rbac_policy_parser', autospec=True)
+ def test_exception_thrown_when_type_is_403_or_404(self, mock_policy,
mock_log):
"""Test that unsupported exceptions throw error."""
invalid_exceptions = [200, 400, 500]
diff --git a/releasenotes/notes/add-metadef-resource-type-7973621c5e8fff7f.yaml b/releasenotes/notes/add-metadef-resource-type-7973621c5e8fff7f.yaml
new file mode 100644
index 0000000..61bec83
--- /dev/null
+++ b/releasenotes/notes/add-metadef-resource-type-7973621c5e8fff7f.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - |
+ Adds test for glance's
+ add_metadef_resource_type_association policy.
diff --git a/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml b/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml
new file mode 100644
index 0000000..3e63c9d
--- /dev/null
+++ b/releasenotes/notes/config-opts-paths-01e2a5096a1579b8.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Refactored framework to remove unused "path"
+ argument. Added config options to allow the path
+ to the policy.json files for Nova, Keystone, Cinder,
+ Neutron, and Glance to be configured without needing
+ to manually change code.
diff --git a/releasenotes/notes/merge-rbac-auth-with-rbac-rule-validation-5d7c286788a95ee9.yaml b/releasenotes/notes/merge-rbac-auth-with-rbac-rule-validation-5d7c286788a95ee9.yaml
new file mode 100644
index 0000000..b96c73a
--- /dev/null
+++ b/releasenotes/notes/merge-rbac-auth-with-rbac-rule-validation-5d7c286788a95ee9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ Merges `rbac_auth` with `rbac_rule_validation`, because `rbac_auth`
+ decentralized logic from `rbac_rule_validation` without providing any
+ authentication-related utility. This change facilitates code maintenance
+ and code readability.