diff options
Diffstat (limited to 'nova/tests/unit/policies/test_servers.py')
-rw-r--r-- | nova/tests/unit/policies/test_servers.py | 864 |
1 files changed, 422 insertions, 442 deletions
diff --git a/nova/tests/unit/policies/test_servers.py b/nova/tests/unit/policies/test_servers.py index 33aadb948f..eee1e4ba51 100644 --- a/nova/tests/unit/policies/test_servers.py +++ b/nova/tests/unit/policies/test_servers.py @@ -10,9 +10,10 @@ # License for the specific language governing permissions and limitations # under the License. +import functools +from unittest import mock import fixtures -import mock from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import timeutils @@ -20,12 +21,14 @@ from nova.api.openstack.compute import migrate_server from nova.api.openstack.compute import servers from nova.compute import api as compute from nova.compute import vm_states +import nova.conf from nova import exception from nova.network import model from nova.network import neutron from nova import objects from nova.objects import fields from nova.objects.instance_group import InstanceGroup +from nova.policies import base as base_policy from nova.policies import extended_server_attributes as ea_policies from nova.policies import servers as policies from nova.tests.unit.api.openstack import fakes @@ -33,6 +36,8 @@ from nova.tests.unit import fake_flavor from nova.tests.unit import fake_instance from nova.tests.unit.policies import base +CONF = nova.conf.CONF + class ServersPolicyTest(base.BasePolicyTest): """Test Servers APIs policies with all possible context. @@ -114,137 +119,41 @@ class ServersPolicyTest(base.BasePolicyTest): 'OS-EXT-SRV-ATTR:user_data' ] - # Check that admin or and owner is able to update, delete - # or perform server action. - self.admin_or_owner_authorized_contexts = [ + # Users that can take action on *our* project resources + self.project_action_authorized_contexts = set([ self.legacy_admin_context, self.system_admin_context, self.project_admin_context, self.project_member_context, - self.project_reader_context, self.project_foo_context] - # Check that non-admin/owner is not able to update, delete - # or perform server action. - self.admin_or_owner_unauthorized_contexts = [ - self.system_member_context, self.system_reader_context, - self.system_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] + self.project_reader_context, self.project_foo_context, + ]) - # Check that system reader or owner is able to get - # the server. - self.system_reader_or_owner_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, self.project_member_context, - self.project_reader_context, self.system_member_context, - self.system_reader_context, self.project_foo_context - ] - self.system_reader_or_owner_unauthorized_contexts = [ - self.system_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] + # Users that can read *our* project resources + self.project_reader_authorized_contexts = ( + self.project_action_authorized_contexts) - # Check that everyone is able to list their own server. - self.everyone_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.system_member_context, self.system_reader_context, - self.system_foo_context, - self.other_project_member_context, - self.other_project_reader_context] - self.everyone_unauthorized_contexts = [ - ] - # Check that admin is able to create server with host request - # and get server extended attributes or host status. - self.admin_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context] - # Check that non-admin is not able to create server with host request - # and get server extended attributes or host status. - self.admin_unauthorized_contexts = [ - self.system_member_context, self.system_reader_context, - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] - # Check that sustem reader is able to list the server - # for all projects. - self.system_reader_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, self.system_member_context, - self.system_reader_context] - # Check that non-system reader is not able to list the server - # for all projects. - self.system_reader_unauthorized_contexts = [ - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] - # Check that project member is able to create serve - self.project_member_authorized_contexts = [ + # Users that _see_ project-scoped resources that they own + self.everyone_authorized_contexts = set(self.all_contexts) + + # Users that can _do_ things to project-scoped resources they own + self.project_member_authorized_contexts = set(self.all_contexts) + + # Users able to do admin things on project resources + self.project_admin_authorized_contexts = set([ self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, self.project_member_context, - self.system_member_context, self.system_reader_context, - self.other_project_member_context, self.system_foo_context, - self.project_reader_context, self.project_foo_context, - self.other_project_reader_context] - # Check that non-project member is not able to create server - self.project_member_unauthorized_contexts = [ - ] - # Check that project admin is able to create server with requested - # destination. - self.project_admin_authorized_contexts = [ + self.project_admin_context]) + + # Admin (for APIs does not pass the project id as policy target + # for example, create server, list detail server) able to get + # all projects servers, create server on specific host etc. + # This is admin on any project because policy does not check + # the project id but they will be able to create server, get + # servers(unless all-tenant policy is allowed) of their own + # project only. + self.all_projects_admin_authorized_contexts = set([ self.legacy_admin_context, self.system_admin_context, - self.project_admin_context] - # Check that non-project admin is not able to create server with - # requested destination - self.project_admin_unauthorized_contexts = [ - self.system_member_context, self.system_reader_context, - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] - # Check that no one is able to resize cross cell. + self.project_admin_context]) + + # Users able to do cross-cell migrations self.cross_cell_authorized_contexts = [] - self.cross_cell_unauthorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.system_member_context, self.system_reader_context, - self.system_foo_context, - self.other_project_member_context, - self.other_project_reader_context] - # Check that admin is able to access the zero disk flavor - # and external network policies. - self.zero_disk_external_net_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context] - # Check that non-admin is not able to caccess the zero disk flavor - # and external network policies. - self.zero_disk_external_net_unauthorized_contexts = [ - self.system_member_context, self.system_reader_context, - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] - # Check that admin is able to get server extended attributes - # or host status. - self.server_attr_admin_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context] - # Check that non-admin is not able to get server extended attributes - # or host status. - self.server_attr_admin_unauthorized_contexts = [ - self.system_member_context, self.system_reader_context, - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] def test_index_server_policy(self): @@ -261,9 +170,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.mock_get_all.side_effect = fake_get_all rule_name = policies.SERVERS % 'index' - self.common_policy_check( + self.common_policy_auth( self.everyone_authorized_contexts, - self.everyone_unauthorized_contexts, rule_name, self.controller.index, self.req) @@ -287,11 +195,16 @@ class ServersPolicyTest(base.BasePolicyTest): self.mock_get_all.side_effect = fake_get_all - self.common_policy_check(self.system_reader_authorized_contexts, - self.system_reader_unauthorized_contexts, - rule_name, - self.controller.index, - req) + if not CONF.oslo_policy.enforce_scope: + check_rule = rule_name + else: + check_rule = functools.partial( + base.rule_if_system, rule, rule_name) + + self.common_policy_auth(self.all_projects_admin_authorized_contexts, + check_rule, + self.controller.index, + req) @mock.patch('nova.compute.api.API.get_all') def test_detail_list_server_policy(self, mock_get): @@ -309,9 +222,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.mock_get_all.side_effect = fake_get_all rule_name = policies.SERVERS % 'detail' - self.common_policy_check( + self.common_policy_auth( self.everyone_authorized_contexts, - self.everyone_unauthorized_contexts, rule_name, self.controller.detail, self.req) @@ -335,11 +247,16 @@ class ServersPolicyTest(base.BasePolicyTest): self.mock_get_all.side_effect = fake_get_all - self.common_policy_check(self.system_reader_authorized_contexts, - self.system_reader_unauthorized_contexts, - rule_name, - self.controller.detail, - req) + if not CONF.oslo_policy.enforce_scope: + check_rule = rule_name + else: + check_rule = functools.partial( + base.rule_if_system, rule, rule_name) + + self.common_policy_auth(self.all_projects_admin_authorized_contexts, + check_rule, + self.controller.detail, + req) def test_index_server_allow_all_filters_policy(self): # 'index' policy is checked before 'allow_all_filters' so @@ -353,9 +270,9 @@ class ServersPolicyTest(base.BasePolicyTest): expected_attrs=None, sort_keys=None, sort_dirs=None, cell_down_support=False, all_tenants=False): self.assertIsNotNone(search_opts) - if context in self.system_reader_unauthorized_contexts: + if context not in self.all_projects_admin_authorized_contexts: self.assertNotIn('host', search_opts) - if context in self.system_reader_authorized_contexts: + if context in self.all_projects_admin_authorized_contexts: self.assertIn('host', search_opts) return objects.InstanceList(objects=self.servers) @@ -363,9 +280,8 @@ class ServersPolicyTest(base.BasePolicyTest): req = fakes.HTTPRequest.blank('/servers?host=1') rule_name = policies.SERVERS % 'allow_all_filters' - self.common_policy_check( - self.system_reader_authorized_contexts, - self.system_reader_unauthorized_contexts, + self.common_policy_auth( + self.all_projects_admin_authorized_contexts, rule_name, self.controller.index, req, fatal=False) @@ -382,18 +298,17 @@ class ServersPolicyTest(base.BasePolicyTest): expected_attrs=None, sort_keys=None, sort_dirs=None, cell_down_support=False, all_tenants=False): self.assertIsNotNone(search_opts) - if context in self.system_reader_unauthorized_contexts: + if context not in self.all_projects_admin_authorized_contexts: self.assertNotIn('host', search_opts) - if context in self.system_reader_authorized_contexts: + if context in self.all_projects_admin_authorized_contexts: self.assertIn('host', search_opts) return objects.InstanceList(objects=self.servers) self.mock_get_all.side_effect = fake_get_all req = fakes.HTTPRequest.blank('/servers?host=1') rule_name = policies.SERVERS % 'allow_all_filters' - self.common_policy_check( - self.system_reader_authorized_contexts, - self.system_reader_unauthorized_contexts, + self.common_policy_auth( + self.all_projects_admin_authorized_contexts, rule_name, self.controller.detail, req, fatal=False) @@ -401,22 +316,117 @@ class ServersPolicyTest(base.BasePolicyTest): @mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid') def test_show_server_policy(self, mock_bdm): rule_name = policies.SERVERS % 'show' - self.common_policy_check( - self.system_reader_or_owner_authorized_contexts, - self.system_reader_or_owner_unauthorized_contexts, + # Show includes readers + self.common_policy_auth( + self.project_reader_authorized_contexts, rule_name, self.controller.show, self.req, self.instance.uuid) + @mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid') + @mock.patch('nova.compute.api.API.get_instance_host_status') + def test_server_show_with_extra_specs_policy(self, mock_get, mock_block): + rule = policies.SERVERS % 'show' + # server 'show' policy is checked before flavor extra specs + # policy so we have to allow it for everyone otherwise it will fail + # first for unauthorized contexts. + self.policy.set_rules({rule: "@"}, overwrite=False) + req = fakes.HTTPRequest.blank('', version='2.47') + rule_name = policies.SERVERS % 'show:flavor-extra-specs' + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_reader_authorized_contexts, + rule_name, self.controller.show, req, + self.instance.uuid, fatal=False) + for resp in authorize_res: + self.assertIn('extra_specs', resp['server']['flavor']) + for resp in unauthorize_res: + self.assertNotIn('extra_specs', resp['server']['flavor']) + + @mock.patch('nova.compute.api.API.get_all') + def test_server_detail_with_extra_specs_policy(self, mock_get): + + def fake_get_all(context, search_opts=None, + limit=None, marker=None, + expected_attrs=None, sort_keys=None, sort_dirs=None, + cell_down_support=False, all_tenants=False): + self.assertIsNotNone(search_opts) + if 'project_id' in search_opts or 'user_id' in search_opts: + return objects.InstanceList(objects=self.servers) + else: + raise + + self.mock_get_all.side_effect = fake_get_all + rule = policies.SERVERS % 'detail' + # server 'detail' policy is checked before flavor extra specs + # policy so we have to allow it for everyone otherwise it will fail + # first for unauthorized contexts. + self.policy.set_rules({rule: "@"}, overwrite=False) + req = fakes.HTTPRequest.blank('', version='2.47') + rule_name = policies.SERVERS % 'show:flavor-extra-specs' + authorize_res, unauthorize_res = self.common_policy_auth( + self.everyone_authorized_contexts, + rule_name, self.controller.detail, req, + fatal=False) + for resp in authorize_res: + self.assertIn('extra_specs', resp['servers'][0]['flavor']) + for resp in unauthorize_res: + self.assertNotIn('extra_specs', resp['servers'][0]['flavor']) + + @mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid') + @mock.patch('nova.compute.api.API.get_instance_host_status') + @mock.patch('nova.compute.api.API.rebuild') + def test_server_rebuild_with_extra_specs_policy(self, mock_rebuild, + mock_get, mock_bdm): + rule = policies.SERVERS % 'rebuild' + # server 'rebuild' policy is checked before flavor extra specs + # policy so we have to allow it for everyone otherwise it will fail + # first for unauthorized contexts. + self.policy.set_rules({rule: "@"}, overwrite=False) + req = fakes.HTTPRequest.blank('', version='2.47') + rule_name = policies.SERVERS % 'show:flavor-extra-specs' + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_reader_authorized_contexts, + rule_name, self.controller._action_rebuild, + req, self.instance.uuid, + body={'rebuild': {"imageRef": uuids.fake_id}}, + fatal=False) + for resp in authorize_res: + self.assertIn('extra_specs', resp.obj['server']['flavor']) + for resp in unauthorize_res: + self.assertNotIn('extra_specs', resp.obj['server']['flavor']) + + @mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid') + @mock.patch.object(InstanceGroup, 'get_by_instance_uuid') + @mock.patch('nova.compute.api.API.update_instance') + def test_server_update_with_extra_specs_policy(self, + mock_update, mock_group, mock_bdm): + mock_update.return_value = self.instance + rule = policies.SERVERS % 'update' + # server 'update' policy is checked before flavor extra specs + # policy so we have to allow it for everyone otherwise it will fail + # first for unauthorized contexts. + self.policy.set_rules({rule: "@"}, overwrite=False) + req = fakes.HTTPRequest.blank('', version='2.47') + rule_name = policies.SERVERS % 'show:flavor-extra-specs' + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_reader_authorized_contexts, + rule_name, self.controller.update, + req, self.instance.uuid, + body={'server': {'name': 'test'}}, + fatal=False) + for resp in authorize_res: + self.assertIn('extra_specs', resp['server']['flavor']) + for resp in unauthorize_res: + self.assertNotIn('extra_specs', resp['server']['flavor']) + @mock.patch('nova.compute.api.API.create') def test_create_server_policy(self, mock_create): mock_create.return_value = ([self.instance], '') rule_name = policies.SERVERS % 'create' - self.common_policy_check(self.project_member_authorized_contexts, - self.project_member_unauthorized_contexts, - rule_name, - self.controller.create, - self.req, body=self.body) + self.common_policy_auth(self.project_member_authorized_contexts, + rule_name, + self.controller.create, + self.req, body=self.body) @mock.patch('nova.compute.api.API.create') @mock.patch('nova.compute.api.API.parse_availability_zone') @@ -431,11 +441,10 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) mock_create.return_value = ([self.instance], '') mock_az.return_value = ('test', 'host', None) - self.common_policy_check(self.project_admin_authorized_contexts, - self.project_admin_unauthorized_contexts, - self.rule_forced_host, - self.controller.create, - self.req, body=self.body) + self.common_policy_auth(self.all_projects_admin_authorized_contexts, + self.rule_forced_host, + self.controller.create, + self.req, body=self.body) @mock.patch('nova.compute.api.API.create') def test_create_attach_volume_server_policy(self, mock_create): @@ -453,11 +462,10 @@ class ServersPolicyTest(base.BasePolicyTest): 'block_device_mapping': [{'device_name': 'foo'}], }, } - self.common_policy_check(self.project_member_authorized_contexts, - self.project_member_unauthorized_contexts, - self.rule_attach_volume, - self.controller.create, - self.req, body=body) + self.common_policy_auth(self.project_member_authorized_contexts, + self.rule_attach_volume, + self.controller.create, + self.req, body=body) @mock.patch('nova.compute.api.API.create') def test_create_attach_network_server_policy(self, mock_create): @@ -477,11 +485,10 @@ class ServersPolicyTest(base.BasePolicyTest): }], }, } - self.common_policy_check(self.project_member_authorized_contexts, - self.project_member_unauthorized_contexts, - self.rule_attach_network, - self.controller.create, - self.req, body=body) + self.common_policy_auth(self.project_member_authorized_contexts, + self.rule_attach_network, + self.controller.create, + self.req, body=body) @mock.patch('nova.compute.api.API.create') def test_create_trusted_certs_server_policy(self, mock_create): @@ -504,20 +511,18 @@ class ServersPolicyTest(base.BasePolicyTest): }, } - self.common_policy_check(self.project_member_authorized_contexts, - self.project_member_unauthorized_contexts, - self.rule_trusted_certs, - self.controller.create, - req, body=body) + self.common_policy_auth(self.project_member_authorized_contexts, + self.rule_trusted_certs, + self.controller.create, + req, body=body) @mock.patch('nova.compute.api.API.delete') def test_delete_server_policy(self, mock_delete): rule_name = policies.SERVERS % 'delete' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller.delete, - self.req, self.instance.uuid) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller.delete, + self.req, self.instance.uuid) def test_delete_server_policy_failed_with_other_user(self): # Change the user_id in request context. @@ -547,11 +552,10 @@ class ServersPolicyTest(base.BasePolicyTest): rule_name = policies.SERVERS % 'update' body = {'server': {'name': 'test'}} - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller.update, - self.req, self.instance.uuid, body=body) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller.update, + self.req, self.instance.uuid, body=body) def test_update_server_policy_failed_with_other_user(self): # Change the user_id in request context. @@ -582,44 +586,40 @@ class ServersPolicyTest(base.BasePolicyTest): def test_confirm_resize_server_policy(self, mock_confirm_resize): rule_name = policies.SERVERS % 'confirm_resize' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_confirm_resize, - self.req, self.instance.uuid, - body={'confirmResize': 'null'}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._action_confirm_resize, + self.req, self.instance.uuid, + body={'confirmResize': 'null'}) @mock.patch('nova.compute.api.API.revert_resize') def test_revert_resize_server_policy(self, mock_revert_resize): rule_name = policies.SERVERS % 'revert_resize' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_revert_resize, - self.req, self.instance.uuid, - body={'revertResize': 'null'}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._action_revert_resize, + self.req, self.instance.uuid, + body={'revertResize': 'null'}) @mock.patch('nova.compute.api.API.reboot') def test_reboot_server_policy(self, mock_reboot): rule_name = policies.SERVERS % 'reboot' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_reboot, - self.req, self.instance.uuid, - body={'reboot': {'type': 'soft'}}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._action_reboot, + self.req, self.instance.uuid, + body={'reboot': {'type': 'soft'}}) @mock.patch('nova.compute.api.API.resize') def test_resize_server_policy(self, mock_resize): rule_name = policies.SERVERS % 'resize' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_resize, - self.req, self.instance.uuid, - body={'resize': {'flavorRef': 'f1'}}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._action_resize, + self.req, self.instance.uuid, + body={'resize': {'flavorRef': 'f1'}}) def test_resize_server_policy_failed_with_other_user(self): # Change the user_id in request context. @@ -650,23 +650,21 @@ class ServersPolicyTest(base.BasePolicyTest): def test_start_server_policy(self, mock_start): rule_name = policies.SERVERS % 'start' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._start_server, - self.req, self.instance.uuid, - body={'os-start': 'null'}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._start_server, + self.req, self.instance.uuid, + body={'os-start': 'null'}) @mock.patch('nova.compute.api.API.stop') def test_stop_server_policy(self, mock_stop): rule_name = policies.SERVERS % 'stop' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._stop_server, - self.req, self.instance.uuid, - body={'os-stop': 'null'}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._stop_server, + self.req, self.instance.uuid, + body={'os-stop': 'null'}) def test_stop_server_policy_failed_with_other_user(self): # Change the user_id in request context. @@ -696,12 +694,11 @@ class ServersPolicyTest(base.BasePolicyTest): @mock.patch('nova.compute.api.API.rebuild') def test_rebuild_server_policy(self, mock_rebuild): rule_name = policies.SERVERS % 'rebuild' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_rebuild, - self.req, self.instance.uuid, - body={'rebuild': {"imageRef": uuids.fake_id}}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._action_rebuild, + self.req, self.instance.uuid, + body={'rebuild': {"imageRef": uuids.fake_id}}) def test_rebuild_server_policy_failed_with_other_user(self): # Change the user_id in request context. @@ -743,11 +740,17 @@ class ServersPolicyTest(base.BasePolicyTest): 'trusted_image_certificates': [uuids.fake_id], }, } - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_rebuild, - req, self.instance.uuid, body=body) + + if not CONF.oslo_policy.enforce_scope: + check_rule = rule_name + else: + check_rule = functools.partial( + base.rule_if_system, rule, rule_name) + + self.common_policy_auth(self.project_action_authorized_contexts, + check_rule, + self.controller._action_rebuild, + req, self.instance.uuid, body=body) def test_rebuild_trusted_certs_policy_failed_with_other_user(self): # Change the user_id in request context. @@ -796,12 +799,11 @@ class ServersPolicyTest(base.BasePolicyTest): def test_create_image_server_policy(self, mock_snapshot, mock_image, mock_bdm): rule_name = policies.SERVERS % 'create_image' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_create_image, - self.req, self.instance.uuid, - body={'createImage': {"name": 'test'}}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._action_create_image, + self.req, self.instance.uuid, + body={'createImage': {"name": 'test'}}) @mock.patch('nova.objects.BlockDeviceMappingList.get_by_instance_uuid') @mock.patch('nova.image.glance.API.generate_image_url') @@ -816,23 +818,26 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) rule_name = policies.SERVERS % 'create_image:allow_volume_backed' - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_create_image, - self.req, self.instance.uuid, - body={'createImage': {"name": 'test'}}) + if not CONF.oslo_policy.enforce_scope: + check_rule = rule_name + else: + check_rule = functools.partial( + base.rule_if_system, rule, rule_name) + self.common_policy_auth(self.project_action_authorized_contexts, + check_rule, + self.controller._action_create_image, + self.req, self.instance.uuid, + body={'createImage': {"name": 'test'}}) @mock.patch('nova.compute.api.API.trigger_crash_dump') def test_trigger_crash_dump_server_policy(self, mock_crash): rule_name = policies.SERVERS % 'trigger_crash_dump' req = fakes.HTTPRequest.blank('', version='2.17') - self.common_policy_check(self.admin_or_owner_authorized_contexts, - self.admin_or_owner_unauthorized_contexts, - rule_name, - self.controller._action_trigger_crash_dump, - req, self.instance.uuid, - body={'trigger_crash_dump': None}) + self.common_policy_auth(self.project_action_authorized_contexts, + rule_name, + self.controller._action_trigger_crash_dump, + req, self.instance.uuid, + body={'trigger_crash_dump': None}) def test_trigger_crash_dump_policy_failed_with_other_user(self): # Change the user_id in request context. @@ -876,9 +881,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.3') rule_name = ea_policies.BASE_POLICY_NAME - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.all_projects_admin_authorized_contexts, rule_name, self.controller.detail, req, fatal=False) for attr in self.extended_attr: @@ -897,9 +901,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.3') rule_name = ea_policies.BASE_POLICY_NAME - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller.show, req, 'fake', fatal=False) for attr in self.extended_attr: @@ -920,9 +923,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.75') rule_name = ea_policies.BASE_POLICY_NAME - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller._action_rebuild, req, self.instance.uuid, body={'rebuild': {"imageRef": uuids.fake_id}}, @@ -940,8 +942,11 @@ class ServersPolicyTest(base.BasePolicyTest): @mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid') @mock.patch.object(InstanceGroup, 'get_by_instance_uuid') @mock.patch('nova.compute.api.API.update_instance') + @mock.patch('nova.compute.api.API.get_instance_host_status') def test_server_update_with_extended_attr_policy(self, - mock_update, mock_group, mock_bdm): + mock_status, mock_update, mock_group, mock_bdm): + mock_update.return_value = self.instance + mock_status.return_value = fields.HostStatus.UP rule = policies.SERVERS % 'update' # server 'update' policy is checked before extended attributes # policy so we have to allow it for everyone otherwise it will fail @@ -949,9 +954,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.75') rule_name = ea_policies.BASE_POLICY_NAME - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller.update, req, self.instance.uuid, body={'server': {'name': 'test'}}, @@ -977,9 +981,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.16') rule_name = policies.SERVERS % 'show:host_status' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.all_projects_admin_authorized_contexts, rule_name, self.controller.detail, req, fatal=False) for resp in authorize_res: @@ -998,9 +1001,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.16') rule_name = policies.SERVERS % 'show:host_status' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller.show, req, 'fake', fatal=False) for resp in authorize_res: @@ -1020,9 +1022,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.75') rule_name = policies.SERVERS % 'show:host_status' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller._action_rebuild, req, self.instance.uuid, body={'rebuild': {"imageRef": uuids.fake_id}}, @@ -1035,8 +1036,11 @@ class ServersPolicyTest(base.BasePolicyTest): @mock.patch('nova.objects.BlockDeviceMappingList.bdms_by_instance_uuid') @mock.patch.object(InstanceGroup, 'get_by_instance_uuid') @mock.patch('nova.compute.api.API.update_instance') + @mock.patch('nova.compute.api.API.get_instance_host_status') def test_server_update_with_host_status_policy(self, - mock_update, mock_group, mock_bdm): + mock_status, mock_update, mock_group, mock_bdm): + mock_update.return_value = self.instance + mock_status.return_value = fields.HostStatus.UP rule = policies.SERVERS % 'update' # server 'update' policy is checked before host_status # policy so we have to allow it for everyone otherwise it will fail @@ -1044,9 +1048,8 @@ class ServersPolicyTest(base.BasePolicyTest): self.policy.set_rules({rule: "@"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.75') rule_name = policies.SERVERS % 'show:host_status' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller.update, req, self.instance.uuid, body={'server': {'name': 'test'}}, @@ -1079,9 +1082,8 @@ class ServersPolicyTest(base.BasePolicyTest): rule_host_status: "!"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.16') rule_name = policies.SERVERS % 'show:host_status:unknown-only' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.all_projects_admin_authorized_contexts, rule_name, self.controller.detail, req, fatal=False) for resp in authorize_res: @@ -1107,9 +1109,8 @@ class ServersPolicyTest(base.BasePolicyTest): rule_host_status: "!"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.16') rule_name = policies.SERVERS % 'show:host_status:unknown-only' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller.show, req, 'fake', fatal=False) for resp in authorize_res: @@ -1136,9 +1137,8 @@ class ServersPolicyTest(base.BasePolicyTest): rule_host_status: "!"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.75') rule_name = policies.SERVERS % 'show:host_status:unknown-only' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller._action_rebuild, req, self.instance.uuid, body={'rebuild': {"imageRef": uuids.fake_id}}, @@ -1156,6 +1156,7 @@ class ServersPolicyTest(base.BasePolicyTest): @mock.patch('nova.compute.api.API.update_instance') def test_server_update_with_unknown_host_status_policy(self, mock_update, mock_group, mock_status, mock_bdm): + mock_update.return_value = self.instance mock_status.return_value = fields.HostStatus.UNKNOWN rule = policies.SERVERS % 'update' # server 'update' policy is checked before unknown host_status @@ -1168,9 +1169,8 @@ class ServersPolicyTest(base.BasePolicyTest): rule_host_status: "!"}, overwrite=False) req = fakes.HTTPRequest.blank('', version='2.75') rule_name = policies.SERVERS % 'show:host_status:unknown-only' - authorize_res, unauthorize_res = self.common_policy_check( - self.server_attr_admin_authorized_contexts, - self.server_attr_admin_unauthorized_contexts, + authorize_res, unauthorize_res = self.common_policy_auth( + self.project_admin_authorized_contexts, rule_name, self.controller.update, req, self.instance.uuid, body={'server': {'name': 'test'}}, @@ -1194,9 +1194,9 @@ class ServersPolicyTest(base.BasePolicyTest): def fake_create(context, *args, **kwargs): for attr in ['requested_host', 'requested_hypervisor_hostname']: - if context in self.project_admin_authorized_contexts: + if context in self.all_projects_admin_authorized_contexts: self.assertIn(attr, kwargs) - if context in self.project_admin_unauthorized_contexts: + if context not in self.all_projects_admin_authorized_contexts: self.assertNotIn(attr, kwargs) return ([self.instance], '') mock_create.side_effect = fake_create @@ -1214,11 +1214,10 @@ class ServersPolicyTest(base.BasePolicyTest): }, } - self.common_policy_check(self.project_admin_authorized_contexts, - self.project_admin_unauthorized_contexts, - self.rule_requested_destination, - self.controller.create, - req, body=body) + self.common_policy_auth(self.all_projects_admin_authorized_contexts, + self.rule_requested_destination, + self.controller.create, + req, body=body) @mock.patch( 'nova.servicegroup.api.API.service_is_up', @@ -1230,10 +1229,9 @@ class ServersPolicyTest(base.BasePolicyTest): @mock.patch('nova.compute.api.API._allow_resize_to_same_host') @mock.patch('nova.objects.RequestSpec.get_by_instance_uuid') @mock.patch('nova.objects.Instance.save') - @mock.patch('nova.api.openstack.common.get_instance') @mock.patch('nova.conductor.ComputeTaskAPI.resize_instance') def test_cross_cell_resize_server_policy( - self, mock_resize, mock_get, mock_save, mock_rs, mock_allow, m_net + self, mock_resize, mock_save, mock_rs, mock_allow, m_net ): # 'migrate' policy is checked before 'resize:cross_cell' so @@ -1263,13 +1261,13 @@ class ServersPolicyTest(base.BasePolicyTest): ) return inst - mock_get.side_effect = fake_get + self.mock_get.side_effect = fake_get def fake_validate(context, instance, host_name, allow_cross_cell_resize): if context in self.cross_cell_authorized_contexts: self.assertTrue(allow_cross_cell_resize) - if context in self.cross_cell_unauthorized_contexts: + if context not in self.cross_cell_authorized_contexts: self.assertFalse(allow_cross_cell_resize) return objects.ComputeNode(host=1, hypervisor_hostname=2) @@ -1277,23 +1275,24 @@ class ServersPolicyTest(base.BasePolicyTest): 'nova.compute.api.API._validate_host_for_cold_migrate', fake_validate) - self.common_policy_check(self.cross_cell_authorized_contexts, - self.cross_cell_unauthorized_contexts, - rule_name, - self.m_controller._migrate, - req, self.instance.uuid, - body={'migrate': {'host': 'fake'}}, - fatal=False) + self.common_policy_auth(self.cross_cell_authorized_contexts, + rule_name, + self.m_controller._migrate, + req, self.instance.uuid, + body={'migrate': {'host': 'fake'}}, + fatal=False) def test_network_attach_external_network_policy(self): # NOTE(gmann): Testing policy 'network:attach_external_network' # which raise different error then PolicyNotAuthorized # if not allowed. neutron_api = neutron.API() - for context in self.zero_disk_external_net_authorized_contexts: + for context in self.all_projects_admin_authorized_contexts: neutron_api._check_external_network_attach(context, [{'id': 1, 'router:external': 'ext'}]) - for context in self.zero_disk_external_net_unauthorized_contexts: + unauth = (set(self.all_contexts) - + set(self.all_projects_admin_authorized_contexts)) + for context in unauth: self.assertRaises(exception.ExternalNetworkAttachForbidden, neutron_api._check_external_network_attach, context, [{'id': 1, 'router:external': 'ext'}]) @@ -1306,16 +1305,63 @@ class ServersPolicyTest(base.BasePolicyTest): flavor = objects.Flavor( vcpus=1, memory_mb=512, root_gb=0, extra_specs={'hw:pmu': "true"}) compute_api = compute.API() - for context in self.zero_disk_external_net_authorized_contexts: + for context in self.all_projects_admin_authorized_contexts: compute_api._validate_flavor_image_nostatus(context, image, flavor, None) - for context in self.zero_disk_external_net_unauthorized_contexts: + unauth = (set(self.all_contexts) - + set(self.all_projects_admin_authorized_contexts)) + for context in unauth: self.assertRaises( exception.BootFromVolumeRequiredForZeroDiskFlavor, compute_api._validate_flavor_image_nostatus, context, image, flavor, None) +class ServersNoLegacyNoScopeTest(ServersPolicyTest): + """Test Servers API policies with deprecated rules disabled, but scope + checking still disabled. + """ + without_deprecated_rules = True + rules_without_deprecation = { + policies.SERVERS % 'show:flavor-extra-specs': + base_policy.PROJECT_READER_OR_ADMIN, + } + + def setUp(self): + super(ServersNoLegacyNoScopeTest, self).setUp() + + # Disabling legacy rule support means that we no longer allow + # random roles on our project to take action on our + # resources. Legacy admin will have access. + self.project_action_authorized_contexts = ( + self.project_member_or_admin_with_no_scope_no_legacy) + + # The only additional role that can read our resources is our + # own project_reader. + self.project_reader_authorized_contexts = ( + self.project_reader_or_admin_with_no_scope_no_legacy) + + # Disabling legacy support means random roles lose power to + # see everything in their project. + self.reduce_set('everyone_authorized', + self.all_contexts - set([self.project_foo_context, + self.system_foo_context])) + + # Disabling legacy support means readers and random roles lose + # power to create things on their own projects. Note that + # system_admin and system_member are still here because we are + # not rejecting them by scope, even though these operations + # with those tokens are likely to fail because they have no + # project. + self.reduce_set('project_member_authorized', + self.all_contexts - set([ + self.system_reader_context, + self.system_foo_context, + self.project_reader_context, + self.project_foo_context, + self.other_project_reader_context])) + + class ServersScopeTypePolicyTest(ServersPolicyTest): """Test Servers APIs policies with system scope enabled. This class set the nova.conf [oslo_policy] enforce_scope to True @@ -1342,143 +1388,77 @@ class ServersScopeTypePolicyTest(ServersPolicyTest): self.rule_requested_destination = None self.rule_forced_host = None - # Check that system admin is able to create server with host request - # and get server extended attributes or host status. - self.admin_authorized_contexts = [ - self.system_admin_context - ] - # Check that non-system/admin is not able to create server with - # host request and get server extended attributes or host status. - self.admin_unauthorized_contexts = [ - self.project_admin_context, self.legacy_admin_context, - self.system_member_context, self.system_reader_context, - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] - # Check that system reader is able to list the server - # for all projects. - self.system_reader_authorized_contexts = [ - self.system_admin_context, self.system_member_context, - self.system_reader_context] - # Check that non-system reader is not able to list the server - # for all projects. - self.system_reader_unauthorized_contexts = [ - self.legacy_admin_context, self.project_admin_context, - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] - - # Check if project member can create the server. - self.project_member_authorized_contexts = [ - self.legacy_admin_context, - self.project_admin_context, self.project_member_context, - self.other_project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_reader_context - ] - # Check if non-project member cannot create the server. - self.project_member_unauthorized_contexts = [ - self.system_admin_context, self.system_member_context, - self.system_reader_context, self.system_foo_context - ] - - # Check that project admin is able to create server with requested - # destination. - self.project_admin_authorized_contexts = [ - self.legacy_admin_context, self.project_admin_context] - # Check that non-project admin is not able to create server with - # requested destination - self.project_admin_unauthorized_contexts = [ - self.system_admin_context, - self.system_member_context, self.system_reader_context, - self.system_foo_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 checking enabled, system admins no longer have + # admin-granted project resource access. + self.reduce_set('project_action_authorized', + set([self.legacy_admin_context, + self.project_admin_context, + self.project_member_context, + self.project_reader_context, + self.project_foo_context])) + + # No change from the base behavior here, but we need to + # re-build this from project_action_authorized, since we + # changed it above. + self.project_reader_authorized_contexts = ( + self.project_action_authorized_contexts) + + # With scope checking enabled, system users no longer have + # project access, even to create their own resources. + self.reduce_set('project_member_authorized', self.all_project_contexts) + + # With scope checking enabled, system admin is no longer an + # admin of project resources. + self.reduce_set('project_admin_authorized', + set([self.legacy_admin_context, + self.project_admin_context])) + self.reduce_set('all_projects_admin_authorized', + set([self.legacy_admin_context, + self.project_admin_context])) + + # With scope checking enabled, system users also lose access to read + # project resources. + self.reduce_set('everyone_authorized', + self.all_contexts - self.all_system_contexts) class ServersNoLegacyPolicyTest(ServersScopeTypePolicyTest): """Test Servers APIs policies with system scope enabled, - and no more deprecated rules that allow the legacy admin API to - access system_admin_or_owner APIs. + and no more deprecated rules. """ without_deprecated_rules = True + rules_without_deprecation = { + policies.SERVERS % 'show:flavor-extra-specs': + base_policy.PROJECT_READER_OR_ADMIN, + } def setUp(self): super(ServersNoLegacyPolicyTest, self).setUp() - # Check that system admin or owner is able to update, delete - # or perform server action. - self.admin_or_owner_authorized_contexts = [ - self.system_admin_context, - self.project_admin_context, self.project_member_context, - ] - # Check that non-system and non-admin/owner is not able to update, - # delete or perform server action. - self.admin_or_owner_unauthorized_contexts = [ - self.legacy_admin_context, self.system_member_context, - self.system_reader_context, self.project_reader_context, - self.project_foo_context, - self.system_foo_context, self.other_project_member_context, - self.other_project_reader_context] - - # Check that system reader or projct owner is able to get - # server. - self.system_reader_or_owner_authorized_contexts = [ - self.system_admin_context, - self.project_admin_context, self.system_member_context, - self.system_reader_context, self.project_reader_context, - self.project_member_context, - ] - - # Check that non-system reader nd non-admin/owner is not able to get - # server. - self.system_reader_or_owner_unauthorized_contexts = [ - self.legacy_admin_context, self.project_foo_context, - self.system_foo_context, self.other_project_member_context, - self.other_project_reader_context - ] - self.everyone_authorized_contexts = [ - self.legacy_admin_context, self.system_admin_context, - self.project_admin_context, - self.project_member_context, self.project_reader_context, - self.system_member_context, self.system_reader_context, - self.other_project_member_context, - self.other_project_reader_context, - ] - self.everyone_unauthorized_contexts = [ - self.project_foo_context, - self.system_foo_context - ] - # Check if project member can create the server. - self.project_member_authorized_contexts = [ - self.legacy_admin_context, self.project_admin_context, - self.project_member_context, - self.other_project_member_context - ] - # Check if non-project member cannot create the server. - self.project_member_unauthorized_contexts = [ - self.system_admin_context, - self.system_member_context, self.project_reader_context, - self.project_foo_context, self.other_project_reader_context, - self.system_reader_context, self.system_foo_context - ] - # Check that system admin is able to get server extended attributes - # or host status. - self.server_attr_admin_authorized_contexts = [ - self.system_admin_context] - # Check that non-system admin is not able to get server extended - # attributes or host status. - self.server_attr_admin_unauthorized_contexts = [ - self.legacy_admin_context, self.project_admin_context, - self.system_member_context, self.system_reader_context, - self.system_foo_context, self.project_member_context, - self.project_reader_context, self.project_foo_context, - self.other_project_member_context, - self.other_project_reader_context - ] + # Disabling legacy support means legacy_admin is no longer + # powerful on our project. Also, we drop the "any role on the + # project means you can do stuff" behavior, so project_reader + # and project_foo lose power. + self.project_action_authorized_contexts = ( + self.project_member_or_admin_with_scope_no_legacy) + + # Only project_reader has additional read access to our + # project resources. + self.project_reader_authorized_contexts = ( + self.project_action_authorized_contexts | + set([self.project_reader_context])) + + # Disabling legacy support means random roles lose power to + # see everything in their project. + self.reduce_set( + 'everyone_authorized', + self.all_project_contexts - set([self.project_foo_context])) + + # Disabling legacy support means readers and random roles lose + # power to create things on their own projects. + self.reduce_set('project_member_authorized', + self.all_project_contexts - set([ + self.project_foo_context, + self.project_reader_context, + self.other_project_reader_context, + ])) |