diff --git a/tacker/api/vnflcm/v1/controller.py b/tacker/api/vnflcm/v1/controller.py index c066828d6..ef73866fe 100644 --- a/tacker/api/vnflcm/v1/controller.py +++ b/tacker/api/vnflcm/v1/controller.py @@ -930,13 +930,18 @@ class VnfLcmController(wsgi.Controller): @wsgi.expected_errors((http_client.FORBIDDEN, http_client.NOT_FOUND)) def show_lcm_op_occs(self, request, id): context = request.environ['tacker.context'] - context.can(vnf_lcm_policies.VNFLCM % 'show_lcm_op_occs') try: vnf_lcm_op_occs = objects.VnfLcmOpOcc.get_by_id(context, id) + vnf_instance = self._get_vnf_instance( + context, vnf_lcm_op_occs.vnf_instance_id) + context.can(vnf_lcm_policies.VNFLCM % 'show_lcm_op_occs', + target={'project_id': vnf_instance.tenant_id}) except exceptions.NotFound as occ_e: return self._make_problem_detail(str(occ_e), 404, title='VnfLcmOpOcc NOT FOUND') + except exceptions.PolicyNotAuthorized: + raise except Exception as e: LOG.error(traceback.format_exc()) return self._make_problem_detail(str(e), diff --git a/tacker/policies/vnf_lcm.py b/tacker/policies/vnf_lcm.py index 3e97c2d18..479a545b9 100644 --- a/tacker/policies/vnf_lcm.py +++ b/tacker/policies/vnf_lcm.py @@ -36,7 +36,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'create', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Creates vnf instance.", operations=[ { @@ -48,7 +48,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'instantiate', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Instantiate vnf instance.", operations=[ { @@ -60,7 +60,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'show', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_READER_OR_ADMIN, description="Query an Individual VNF instance.", operations=[ { @@ -72,7 +72,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'terminate', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Terminate a VNF instance.", operations=[ { @@ -84,7 +84,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'heal', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Heal a VNF instance.", operations=[ { @@ -96,7 +96,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'scale', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Scale a VNF instance.", operations=[ { @@ -108,7 +108,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'show_lcm_op_occs', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_READER_OR_ADMIN, description="Query an Individual VNF LCM operation occurrence", operations=[ { @@ -120,7 +120,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'list_lcm_op_occs', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_READER_OR_ADMIN, description="Query VNF LCM operation occurrence", operations=[ { @@ -132,7 +132,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'index', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_READER_OR_ADMIN, description="Query VNF instances.", operations=[ { @@ -144,7 +144,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'delete', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Delete an Individual VNF instance.", operations=[ { @@ -156,7 +156,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'update_vnf', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Update an Individual VNF instance.", operations=[ { @@ -168,7 +168,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'rollback', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Rollback a VNF instance.", operations=[ { @@ -180,7 +180,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'cancel', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Cancel a VNF instance.", operations=[ { @@ -192,7 +192,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'fail', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Fail a VNF instance.", operations=[ { @@ -204,7 +204,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'retry', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Retry a VNF instance.", operations=[ { @@ -216,7 +216,7 @@ rules = [ ), policy.DocumentedRuleDefault( name=VNFLCM % 'change_ext_conn', - check_str=base.RULE_ADMIN_OR_OWNER, + check_str=base.RULE_PROJECT_MEMBER_OR_ADMIN, description="Change external VNF connectivity.", operations=[ { diff --git a/tacker/tests/unit/policies/base.py b/tacker/tests/unit/policies/base.py index 21ba24ff9..4c0fe9ea7 100644 --- a/tacker/tests/unit/policies/base.py +++ b/tacker/tests/unit/policies/base.py @@ -15,11 +15,13 @@ import copy +from oslo_config import cfg from oslo_log import log as logging from oslo_utils.fixture import uuidsentinel as uuids from tacker.common import exceptions from tacker import context +from tacker import policy from tacker.tests.unit import base @@ -27,9 +29,26 @@ LOG = logging.getLogger(__name__) class BasePolicyTest(base.TestCase): + # NOTE(gmann): Set this flag to True if you would like to tests the + # new behaviour of policy without deprecated rules. + # This means you can simulate the phase when policies completely + # switch to new behaviour by removing the support of old rules. + enforce_new_defaults = False def setUp(self): super(BasePolicyTest, self).setUp() + if self.enforce_new_defaults: + cfg.CONF.set_override('enforce_new_defaults', True, + group='oslo_policy') + # NOTE(gmann): oslo policy config option enforce_new_defaults + # is changed here which is used while loading the rule in + # oslo_policy.init() method that is why we need to reset the + # policy and initialize again so that rule will be re-loaded + # considering the enforce_new_defaults new value. + policy.reset() + policy.init() + self.addCleanup(policy.reset) + self.admin_project_id = uuids.admin_project_id self.project_id = uuids.project_id self.other_project_id = uuids.project_id_other diff --git a/tacker/tests/unit/policies/test_vnf_lcm.py b/tacker/tests/unit/policies/test_vnf_lcm.py index a2617e987..ea848bd39 100644 --- a/tacker/tests/unit/policies/test_vnf_lcm.py +++ b/tacker/tests/unit/policies/test_vnf_lcm.py @@ -70,20 +70,39 @@ class VNFLCMPolicyTest(base_test.BasePolicyTest): ] self.project_unauthorized_contexts = [] - # Admin or any user in same project will be allowed to get, - # instantiate, terminate etc operations of VNF of their project. + # Admin or any user in same project will be allowed to instantiate, + # terminate etc write operations of VNF of their project. self.project_member_authorized_contexts = [ self.legacy_admin_context, self.project_admin_context, self.project_member_context, self.project_reader_context, self.project_foo_context ] - # User from other project will not be allowed to get or perform - # the other project's VNF operations. + # User from other project will not be allowed to perform write + # operation on the other project's VNF operations. self.project_member_unauthorized_contexts = [ self.other_project_member_context, self.other_project_reader_context ] + # Admin or any user in same project will be allowed to get, + # VNF of their project. + self.project_reader_authorized_contexts = ( + self.project_member_authorized_contexts) + # User from other project will not be allowed to get + # the other project's VNF. + self.project_reader_unauthorized_contexts = ( + self.project_member_unauthorized_contexts) + + # Below user's context will be allowed to list VNF or + # get VNF LCM operation occurrence. + self.get_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, self.project_reader_context, + self.project_foo_context, self.other_project_member_context, + self.other_project_reader_context + ] + self.get_unauthorized_contexts = [] + @mock.patch.object(vim_client.VimClient, "get_vim") @mock.patch.object(objects.VnfPackage, 'get_by_id') @mock.patch('tacker.api.vnflcm.v1.controller.' @@ -136,8 +155,8 @@ class VNFLCMPolicyTest(base_test.BasePolicyTest): fields.VnfInstanceState.INSTANTIATED, tenant_id=self.project_id) rule_name = policies.VNFLCM % 'show' - self.common_policy_check(self.project_member_authorized_contexts, - self.project_member_unauthorized_contexts, + self.common_policy_check(self.project_reader_authorized_contexts, + self.project_reader_unauthorized_contexts, rule_name, self.controller.show, req, uuidsentinel.instance_id) @@ -149,8 +168,8 @@ class VNFLCMPolicyTest(base_test.BasePolicyTest): vnf_instance_2 = fakes.return_vnf_instance() mock_vnf_list.return_value = [vnf_instance_1, vnf_instance_2] rule_name = policies.VNFLCM % 'index' - self.common_policy_check(self.project_authorized_contexts, - self.project_unauthorized_contexts, + self.common_policy_check(self.get_authorized_contexts, + self.get_unauthorized_contexts, rule_name, self.controller.index, req) @@ -386,13 +405,17 @@ class VNFLCMPolicyTest(base_test.BasePolicyTest): req, uuidsentinel.instance_id) @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") - def test_show_lcm_op_occs(self, mock_lcm_by_id): + @mock.patch.object(objects.VnfInstance, "get_by_id") + def test_show_lcm_op_occs(self, mock_vnf_by_id, mock_lcm_by_id): req = fake_request.HTTPRequest.blank( '/vnf_lcm_op_occs/%s' % uuidsentinel.instance_id) mock_lcm_by_id.return_value = fakes.return_vnf_lcm_opoccs_obj() + mock_vnf_by_id.return_value = fakes.return_vnf_instance( + fields.VnfInstanceState.INSTANTIATED, + tenant_id=self.project_id) rule_name = policies.VNFLCM % 'show_lcm_op_occs' - self.common_policy_check(self.project_authorized_contexts, - self.project_unauthorized_contexts, + self.common_policy_check(self.project_reader_authorized_contexts, + self.project_reader_unauthorized_contexts, rule_name, self.controller.show_lcm_op_occs, req, uuidsentinel.instance_id) @@ -402,8 +425,8 @@ class VNFLCMPolicyTest(base_test.BasePolicyTest): req = fake_request.HTTPRequest.blank( '/vnflcm/v1/vnf_lcm_op_occs') rule_name = policies.VNFLCM % 'list_lcm_op_occs' - self.common_policy_check(self.project_authorized_contexts, - self.project_unauthorized_contexts, + self.common_policy_check(self.get_authorized_contexts, + self.get_unauthorized_contexts, rule_name, self.controller.list_lcm_op_occs, req) @@ -470,3 +493,149 @@ class VNFLCMScopeTypePolicyTest(VNFLCMPolicyTest): self.system_reader_context, self.system_foo_context, self.other_project_member_context, self.other_project_reader_context] + + self.get_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, self.project_reader_context, + self.project_foo_context, self.other_project_member_context, + self.other_project_reader_context + ] + # With scope enabled, system scoped users will not be allowed + # to list VNF and get VNF LCM operation occurrence. + self.get_unauthorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context, self.system_foo_context, + ] + + +class VNFLCMNewDefaultsPolicyTest(VNFLCMPolicyTest): + """Test VNF LCM APIs policies with new defaults enabled + + This test class enable the new defaults means no legacy old rules + and check how permission level looks like. + """ + + enforce_new_defaults = True + + def setUp(self): + super(VNFLCMNewDefaultsPolicyTest, self).setUp() + + # In new defaults, admin or member roles users will be allowed + # to create VNF or a few of the VNF operations in their project. + # Project reader will not be able to create VNF. + self.project_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, self.other_project_member_context, + ] + # In new defaults, non admin or non member role (Project reader) + # user will not be able to create VNF. + self.project_unauthorized_contexts = [ + self.project_reader_context, self.project_foo_context, + self.other_project_reader_context] + + # In new defaults, all admin, project members will be allowed to + # instantiate, terminate etc write operations of VNF of their project. + self.project_member_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context + ] + # In new defaults, Project reader or any other non admin|member + # role (say foo role) will not be allowed to perform any write + # operation on VNF. + self.project_member_unauthorized_contexts = [ + self.project_reader_context, self.project_foo_context, + self.other_project_member_context, + self.other_project_reader_context + ] + + # In new defaults, Project reader also can get VNF. + self.project_reader_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, + self.project_reader_context + ] + # In new defaults, non admin|member|reader role (say foo role) + # will not be able to get VNF. + self.project_reader_unauthorized_contexts = [ + self.project_foo_context, + self.other_project_member_context, + self.other_project_reader_context + ] + + # In new defaults, project random role like foo will not + # be allowed. + self.get_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, self.project_reader_context, + self.other_project_member_context, + self.other_project_reader_context + ] + self.get_unauthorized_contexts = [ + self.project_foo_context, + ] + + +class VNFLCMNewDefaultsWithScopePolicyTest(VNFLCMNewDefaultsPolicyTest): + """Test VNF LCM APIs policies with new defaults rules and scope enabled + + This means scope enabled and no legacy old rules. This is the end goal + when operators will enable scope and new defaults. + """ + + def setUp(self): + super(VNFLCMNewDefaultsWithScopePolicyTest, self).setUp() + cfg.CONF.set_override('enforce_scope', True, + group='oslo_policy') + + # With scope enable and no legacy rule, only project admin/member + # will be able to create VNF in their project. + self.project_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, self.other_project_member_context + ] + # System scoped users will not be allowed. + self.project_unauthorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context, self.system_foo_context, + self.project_reader_context, self.project_foo_context, + self.other_project_reader_context] + + self.project_member_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, + ] + # System scoped users will not be allowed. + self.project_member_unauthorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context, self.system_foo_context, + self.project_reader_context, self.project_foo_context, + self.other_project_member_context, + self.other_project_reader_context] + + self.project_reader_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, + self.project_reader_context + ] + # System scoped users will not be allowed. + self.project_reader_unauthorized_contexts = [ + self.system_admin_context, self.system_member_context, + self.system_reader_context, self.system_foo_context, + self.project_foo_context, + self.other_project_member_context, + self.other_project_reader_context + ] + + self.get_authorized_contexts = [ + self.legacy_admin_context, self.project_admin_context, + self.project_member_context, self.project_reader_context, + self.other_project_member_context, + self.other_project_reader_context + ] + # With scope enabled, system scoped users will not be allowed + # to list VNF and get VNF LCM operation occurrence. + self.get_unauthorized_contexts = [ + self.project_foo_context, self.system_admin_context, + self.system_member_context, self.system_reader_context, + self.system_foo_context, + ] diff --git a/tacker/tests/unit/vnflcm/test_controller.py b/tacker/tests/unit/vnflcm/test_controller.py index 1944fa471..e84659f77 100644 --- a/tacker/tests/unit/vnflcm/test_controller.py +++ b/tacker/tests/unit/vnflcm/test_controller.py @@ -2094,12 +2094,19 @@ class TestController(base.TestCase): @mock.patch.object(TackerManager, 'get_service_plugins', return_value={'VNFM': FakeVNFMPlugin()}) + @mock.patch.object(objects.VnfInstance, "get_by_id") @mock.patch.object(objects.VnfLcmOpOcc, "get_by_id") def test_show_lcm_op_occs(self, mock_get_by_id, - mock_get_service_plugins): + mock_vnf_instance, mock_get_service_plugins): req = fake_request.HTTPRequest.blank( '/vnf_lcm_op_occs/%s' % constants.UUID) mock_get_by_id.return_value = fakes.return_vnf_lcm_opoccs_obj() + vnf_instance = fakes.return_vnf_instance( + fields.VnfInstanceState.NOT_INSTANTIATED, + task_state=fields.VnfInstanceTaskState.ERROR, + tenant_id=req.environ['tacker.context'].project_id) + mock_vnf_instance.return_value = vnf_instance + expected_result = fakes.VNFLCMOPOCC_RESPONSE res_dict = self.controller.show_lcm_op_occs(req, constants.UUID) self.assertEqual(expected_result, res_dict)