refactor: Break up RbacMalformedException into discrete exceptions

This patch set breaks up RbacMalformedException into the following
discrete exceptions:

* RbacMissingAttributeResponseBody
* RbacPartialResponseBody
* RbacEmptyResponseBody

Each of the exception classes deals with a different type of
failure related to a soft authorization failure [0] which
means that a failure occurred server side related to RBAC
authorization, but the result of which an incomplete, partial
or empty response body (with a 2xx status code).

* incomplete means that the response body (for show or list)
  is missing certain attributes
* partial means that a list response only returned a subset of
  the possible results available.
* empty means that the show or list response body is entirely
  empty

Because RbacMalformedException is not part of a stable library
it is removed altogether; we do not need to deprecate it.

[0] http://git.openstack.org/cgit/openstack/patrole/tree/doc/source/rbac-overview.rst#n232

Story: 2003843
Task: 26633
Change-Id: I2c76c3c4d226e4877fc9d1e93707edfc230a1be4
This commit is contained in:
Felipe Monteiro 2018-09-30 12:33:49 -04:00
parent 742b73767b
commit 74f8e7d97f
20 changed files with 150 additions and 102 deletions

View File

@ -20,16 +20,34 @@ class BasePatroleException(exceptions.TempestException):
message = "An unknown RBAC exception occurred"
class RbacMalformedResponse(BasePatroleException):
message = ("The response body is missing the expected %(attribute)s due "
"to policy enforcement failure.")
class BasePatroleResponseBodyException(BasePatroleException):
message = "Response body incomplete due to RBAC authorization failure"
def __init__(self, empty=False, **kwargs):
if empty:
self.message = ("The response body is empty due to policy "
"enforcement failure.")
kwargs = {}
super(RbacMalformedResponse, self).__init__(**kwargs)
class RbacMissingAttributeResponseBody(BasePatroleResponseBodyException):
"""Raised when a list or show action is missing an attribute following
RBAC authorization failure.
"""
message = ("The response body is missing the expected %(attribute)s due "
"to policy enforcement failure")
class RbacPartialResponseBody(BasePatroleResponseBodyException):
"""Raised when a list action only returns a subset of the available
resources.
For example, admin can return more resources than member for a list action.
"""
message = ("The response body only lists a subset of the available "
"resources due to partial policy enforcement failure. Response "
"body: %(body)s")
class RbacEmptyResponseBody(BasePatroleResponseBodyException):
"""Raised when a list or show action is empty following RBAC authorization
failure.
"""
message = ("The response body is empty due to policy enforcement failure.")
class RbacResourceSetupFailed(BasePatroleException):

View File

@ -198,7 +198,8 @@ def action(service,
test_status = ('Error, %s' % (msg))
LOG.error(msg)
except (expected_exception,
rbac_exceptions.RbacMalformedResponse) as actual_exception:
rbac_exceptions.BasePatroleResponseBodyException) \
as actual_exception:
caught_exception = actual_exception
test_status = 'Denied'

View File

@ -50,7 +50,7 @@ class FlavorAccessRbacTest(rbac_base.BaseV2ComputeRbacTest):
expected_attr = 'os-flavor-access:is_public'
if expected_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -71,7 +71,7 @@ class FlavorAccessRbacTest(rbac_base.BaseV2ComputeRbacTest):
# If the `expected_attr` was not found in any flavor, then policy
# enforcement failed.
if not public_flavors:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@decorators.idempotent_id('39cb5c8f-9990-436f-9282-fc76a41d9bac')

View File

@ -45,7 +45,7 @@ class FlavorRxtxRbacTest(rbac_base.BaseV2ComputeRbacTest):
with self.rbac_utils.override_role(self):
result = self.flavors_client.list_flavors(detail=True)['flavors']
if 'rxtx_factor' not in result[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='rxtx_factor')
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -59,5 +59,5 @@ class FlavorRxtxRbacTest(rbac_base.BaseV2ComputeRbacTest):
result = self.flavors_client.show_flavor(
CONF.compute.flavor_ref)['flavor']
if 'rxtx_factor' not in result:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='rxtx_factor')

View File

@ -294,7 +294,7 @@ class ImageSizeRbacTest(rbac_base.BaseV2ComputeRbacTest):
expected_attr = 'OS-EXT-IMG-SIZE:size'
if expected_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -310,5 +310,5 @@ class ImageSizeRbacTest(rbac_base.BaseV2ComputeRbacTest):
expected_attr = 'OS-EXT-IMG-SIZE:size'
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)

View File

@ -403,5 +403,5 @@ class ServerActionsV216RbacTest(rbac_base.BaseV2ComputeRbacTest):
server = self.servers_client.show_server(self.server_id)['server']
if 'host_status' not in server:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='host_status')

View File

@ -143,7 +143,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
expected_attr = 'config_drive'
# If the first server contains "config_drive", then all the others do.
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -159,7 +159,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
body = self.servers_client.show_server(self.server['id'])['server']
expected_attr = 'config_drive'
if expected_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@utils.requires_ext(extension='os-deferred-delete', service='compute')
@ -188,7 +188,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
body = self.servers_client.list_servers(detail=True)['servers']
# If the first server contains `expected_attr`, then all the others do.
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -205,7 +205,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
with self.rbac_utils.override_role(self):
body = self.servers_client.show_server(self.server['id'])['server']
if expected_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -229,7 +229,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
for attr in ('host', 'instance_name'):
whole_attr = 'OS-EXT-SRV-ATTR:%s' % attr
if whole_attr not in body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=whole_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -253,7 +253,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
for attr in ('host', 'instance_name'):
whole_attr = 'OS-EXT-SRV-ATTR:%s' % attr
if whole_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=whole_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -272,7 +272,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
'OS-EXT-STS:power_state')
for attr in expected_attrs:
if attr not in body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -291,7 +291,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
'OS-EXT-STS:power_state')
for attr in expected_attrs:
if attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -310,7 +310,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
with self.rbac_utils.override_role(self):
body = self.servers_client.list_servers(detail=True)['servers']
if expected_attr not in body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -329,7 +329,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
with self.rbac_utils.override_role(self):
body = self.servers_client.show_server(self.server['id'])['server']
if expected_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@utils.requires_ext(extension='os-instance-actions', service='compute')
@ -360,12 +360,12 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
self.server['id'], request_id)['instanceAction']
if 'events' not in instance_action:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='events')
# Microversion 2.51+ returns 'events' always, but not 'traceback'. If
# 'traceback' is also present then policy enforcement passed.
if 'traceback' not in instance_action['events'][0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='events.traceback')
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -379,7 +379,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
result = self.servers_client.show_server(self.server['id'])[
'server']
if 'key_name' not in result:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='key_name')
@testtools.skipIf(CONF.policy_feature_enabled.removed_nova_policies_stein,
@ -392,7 +392,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
with self.rbac_utils.override_role(self):
result = self.servers_client.list_servers(detail=True)['servers']
if 'key_name' not in result[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='key_name')
@rbac_rule_validation.action(
@ -514,7 +514,7 @@ class MiscPolicyActionsRbacTest(rbac_base.BaseV2ComputeRbacTest):
body = self.servers_client.show_server(self.server['id'])['server']
for expected_attr in expected_attrs:
if expected_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@utils.requires_ext(extension='os-simple-tenant-usage', service='compute')

View File

@ -118,4 +118,4 @@ class NetworkSegmentsRbacTest(base.BaseNetworkRbacTest):
LOG.info("NotFound or Forbidden exception are not thrown when "
"role doesn't have access to the endpoint. Instead, "
"the response will have an empty network body.")
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()

View File

@ -363,7 +363,7 @@ class NetworksRbacTest(base.BaseNetworkRbacTest):
self.network['id'], **kwargs)['network']
if len(retrieved_network) == 0:
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()
@utils.requires_ext(extension='provider', service='network')
@rbac_rule_validation.action(service="neutron",
@ -384,7 +384,7 @@ class NetworksRbacTest(base.BaseNetworkRbacTest):
self.network['id'], **kwargs)['network']
if len(retrieved_network) == 0:
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()
@utils.requires_ext(extension='provider', service='network')
@rbac_rule_validation.action(
@ -406,7 +406,7 @@ class NetworksRbacTest(base.BaseNetworkRbacTest):
self.network['id'], **kwargs)['network']
if len(retrieved_network) == 0:
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()
@utils.requires_ext(extension='provider', service='network')
@rbac_rule_validation.action(
@ -428,7 +428,7 @@ class NetworksRbacTest(base.BaseNetworkRbacTest):
self.network['id'], **kwargs)['network']
if len(retrieved_network) == 0:
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()
@rbac_rule_validation.action(service="neutron",
rules=["get_network", "delete_network"],

View File

@ -183,7 +183,7 @@ class PortsRbacTest(base.BaseNetworkRbacTest):
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='binding:vif_type')
@utils.requires_ext(extension='binding', service='network')
@ -203,7 +203,7 @@ class PortsRbacTest(base.BaseNetworkRbacTest):
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='binding:vif_details')
@utils.requires_ext(extension='binding', service='network')
@ -226,7 +226,7 @@ class PortsRbacTest(base.BaseNetworkRbacTest):
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='binding:host_id')
@utils.requires_ext(extension='binding', service='network')
@ -250,7 +250,7 @@ class PortsRbacTest(base.BaseNetworkRbacTest):
# Rather than throwing a 403, the field is not present, so raise exc.
if fields[0] not in retrieved_port:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='binding:profile')
@rbac_rule_validation.action(service="neutron",

View File

@ -179,7 +179,7 @@ class RouterRbacTest(base.BaseNetworkRbacTest):
# Rather than throwing a 403, the field is not present, so raise exc.
if 'distributed' not in retrieved_fields:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='distributed')
@decorators.idempotent_id('defc502c-4159-4824-b4d9-3cdcc39015b2')
@ -201,7 +201,7 @@ class RouterRbacTest(base.BaseNetworkRbacTest):
# Rather than throwing a 403, the field is not present, so raise exc.
if 'ha' not in retrieved_fields:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='ha')
@rbac_rule_validation.action(service="neutron",

View File

@ -126,7 +126,7 @@ class SecGroupRbacTest(base.BaseNetworkRbacTest):
# Neutron may return an empty list if access is denied.
if not security_groups['security_groups']:
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()
@rbac_rule_validation.action(service="neutron",
rules=["create_security_group_rule"])
@ -170,4 +170,4 @@ class SecGroupRbacTest(base.BaseNetworkRbacTest):
# Neutron may return an empty list if access is denied.
if not security_rules['security_group_rules']:
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()

View File

@ -73,7 +73,7 @@ class SubnetsRbacTest(base.BaseNetworkRbacTest):
# Neutron may return an empty list if access is denied.
if not subnets['subnets']:
raise rbac_exceptions.RbacMalformedResponse(empty=True)
raise rbac_exceptions.RbacEmptyResponseBody()
@decorators.idempotent_id('f36cd821-dd22-4bd0-b43d-110fc4b553eb')
@rbac_rule_validation.action(service="neutron",

View File

@ -208,7 +208,7 @@ class GroupTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
group_type = self.create_group_type(ignore_notfound=True)
if 'group_specs' not in group_type:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='group_specs')
@decorators.idempotent_id('8d9e2831-24c3-47b7-a76a-2e563287f12f')
@ -221,5 +221,5 @@ class GroupTypesV3RbacTest(rbac_base.BaseVolumeRbacTest):
resp_body = self.group_types_client.show_group_type(
group_type['id'])['group_type']
if 'group_specs' not in resp_body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute='group_specs')

View File

@ -51,4 +51,5 @@ class LimitsV3RbacTest(rbac_base.BaseVolumeRbacTest):
'limits']['absolute']
for key in expected_keys:
if key not in absolute_limits:
raise rbac_exceptions.RbacMalformedResponse(attribute=key)
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=key)

View File

@ -111,7 +111,7 @@ class VolumeMetadataV3RbacTest(rbac_base.BaseVolumeRbacTest):
'volumes']
expected_attr = 'volume_image_metadata'
if expected_attr not in resp_body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@decorators.idempotent_id('53f94d52-0dd5-42cf-a3a4-59b35150b3d5')
@ -129,7 +129,7 @@ class VolumeMetadataV3RbacTest(rbac_base.BaseVolumeRbacTest):
'volume']
expected_attr = 'volume_image_metadata'
if expected_attr not in resp_body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@decorators.idempotent_id('a9d9e825-5ea3-42e6-96f3-7ac4e97b2ed0')

View File

@ -210,7 +210,7 @@ class VolumesBackupsV318RbacTest(rbac_base.BaseVolumeRbacTest):
# Show backup API attempts to inject the attribute below into the
# response body but only if policy enforcement succeeds.
if self.expected_attr not in body:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=self.expected_attr)
@decorators.idempotent_id('aa40b7c0-5974-48be-8cbc-e23cc61c4c68')
@ -221,7 +221,7 @@ class VolumesBackupsV318RbacTest(rbac_base.BaseVolumeRbacTest):
body = self.backups_client.list_backups(detail=True)['backups']
if self.expected_attr not in body[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=self.expected_attr)

View File

@ -76,7 +76,7 @@ class VolumesSnapshotV3RbacTest(rbac_base.BaseVolumeRbacTest):
self.snapshot['id'])['snapshot']
for expected_attr in expected_attrs:
if expected_attr not in resp:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)
@rbac_rule_validation.action(service="cinder",
@ -136,5 +136,5 @@ class VolumesSnapshotV3RbacTest(rbac_base.BaseVolumeRbacTest):
resp = self._list_by_param_values(with_detail=True, **params)
for expected_attr in expected_attrs:
if expected_attr not in resp[0]:
raise rbac_exceptions.RbacMalformedResponse(
raise rbac_exceptions.RbacMissingAttributeResponseBody(
attribute=expected_attr)

View File

@ -46,8 +46,9 @@ class BaseRBACRuleValidationTest(base.TestCase):
project_id=mock.sentinel.project_id)
setattr(self.mock_test_args.os_primary, 'credentials', mock_creds)
self.test_roles = ['member']
self.useFixture(
patrole_fixtures.ConfPatcher(rbac_test_roles=['member'],
patrole_fixtures.ConfPatcher(rbac_test_roles=self.test_roles,
group='patrole'))
# Disable patrole log for unit tests.
self.useFixture(
@ -69,9 +70,10 @@ class BaseRBACMultiRoleRuleValidationTest(base.TestCase):
project_id=mock.sentinel.project_id)
setattr(self.mock_test_args.os_primary, 'credentials', mock_creds)
self.test_roles = ['member', 'anotherrole']
self.useFixture(
patrole_fixtures.ConfPatcher(
rbac_test_roles=['member', 'anotherrole'], group='patrole'))
patrole_fixtures.ConfPatcher(rbac_test_roles=self.test_roles,
group='patrole'))
# Disable patrole log for unit tests.
self.useFixture(
patrole_fixtures.ConfPatcher(enable_reporting=False,
@ -150,43 +152,66 @@ class RBACRuleValidationTest(BaseRBACRuleValidationTest):
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
def test_rule_validation_rbac_malformed_response_positive(
def test_rule_validation_rbac_failed_response_body_positive(
self, mock_authority, mock_log):
"""Test RbacMalformedResponse error is thrown without permission
passes.
"""Test BasePatroleResponseBodyException error is thrown without
permission passes.
Positive test case: if RbacMalformedResponse is thrown and the user is
not allowed to perform the action, then this is a success.
Positive test case: if subclass of BasePatroleResponseBodyException is
thrown and the user is not allowed to perform the action, then this is
a success.
"""
mock_authority.PolicyAuthority.return_value.allowed.return_value =\
False
@rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
def test_policy(*args):
raise rbac_exceptions.RbacMalformedResponse()
def _do_test(exception_cls, **kwargs):
@rbac_rv.action(mock.sentinel.service,
rules=[mock.sentinel.action])
def test_policy(*args):
raise exception_cls(**kwargs)
mock_log.error.assert_not_called()
mock_log.error.assert_not_called()
mock_log.warning.assert_not_called()
_do_test(rbac_exceptions.RbacMissingAttributeResponseBody,
attribute=mock.sentinel.attr)
_do_test(rbac_exceptions.RbacPartialResponseBody,
body=mock.sentinel.body)
_do_test(rbac_exceptions.RbacEmptyResponseBody)
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
def test_rule_validation_rbac_malformed_response_negative(
def test_rule_validation_soft_authorization_exceptions(
self, mock_authority, mock_log):
"""Test RbacMalformedResponse error is thrown with permission fails.
"""Test RbacUnderPermissionException error is thrown when any of the
soft authorization-related exceptions are raised by a test.
Negative test case: if RbacMalformedResponse is thrown and the user is
allowed to perform the action, then this is an expected failure.
Negative test case: if subclass of BasePatroleResponseBodyException is
thrown and the user is allowed to perform the action, then this is an
expected failure.
"""
mock_authority.PolicyAuthority.return_value.allowed.return_value = True
@rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
def test_policy(*args):
raise rbac_exceptions.RbacMalformedResponse()
def _do_test(exception_cls, **kwargs):
@rbac_rv.action(mock.sentinel.service,
rules=[mock.sentinel.action])
def test_policy(*args):
raise exception_cls(**kwargs)
test_re = ("User with roles \['member'\] was not allowed to perform "
"the following actions: \[%s\]. " % (mock.sentinel.action))
self.assertRaisesRegex(rbac_exceptions.RbacUnderPermissionException,
test_re, test_policy, self.mock_test_args)
self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
test_re = (".*User with roles \[%s\] was not allowed to "
"perform the following actions: \[%s\].*" % (
', '.join("'%s'" % r for r in self.test_roles),
mock.sentinel.action))
self.assertRaisesRegex(
rbac_exceptions.RbacUnderPermissionException, test_re,
test_policy, self.mock_test_args)
self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
_do_test(rbac_exceptions.RbacMissingAttributeResponseBody,
attribute=mock.sentinel.attr)
_do_test(rbac_exceptions.RbacPartialResponseBody,
body=mock.sentinel.body)
_do_test(rbac_exceptions.RbacEmptyResponseBody)
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
@ -397,28 +422,6 @@ class RBACMultiRoleRuleValidationTest(BaseRBACMultiRoleRuleValidationTest,
self.mock_test_args)
self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
def test_rule_validation_rbac_malformed_response_negative(
self, mock_authority, mock_log):
"""Test RbacMalformedResponse error is thrown with permission fails.
Negative test case: if RbacMalformedResponse is thrown and the user is
allowed to perform the action, then this is an expected failure.
"""
mock_authority.PolicyAuthority.return_value.allowed.return_value = True
@rbac_rv.action(mock.sentinel.service, rules=[mock.sentinel.action])
def test_policy(*args):
raise rbac_exceptions.RbacMalformedResponse()
test_re = ("User with roles \['member', 'anotherrole'\] was not "
"allowed to perform the following actions: \[%s\]. " %
(mock.sentinel.action))
self.assertRaisesRegex(rbac_exceptions.RbacUnderPermissionException,
test_re, test_policy, self.mock_test_args)
self.assertRegex(mock_log.error.mock_calls[0][1][0], test_re)
@mock.patch.object(rbac_rv, 'LOG', autospec=True)
@mock.patch.object(rbac_rv, 'policy_authority', autospec=True)
def test_expect_not_found_and_raise_not_found(self, mock_authority,
@ -960,7 +963,7 @@ class RBACOverrideRoleValidationTest(BaseRBACRuleValidationTest):
def test_rule_validation_override_role_patrole_exception_ignored(
self, mock_authority):
"""Test success case where Patrole exception is raised (which is
valid in case of e.g. RbacMalformedException) after override_role
valid in case of e.g. RbacPartialResponseBody) after override_role
passes.
"""
mock_authority.PolicyAuthority.return_value.allowed.return_value =\

View File

@ -0,0 +1,25 @@
---
features:
- |
The exception class ``RbacMalformedException`` has been broken up into the
following discrete exceptions:
* ``RbacMissingAttributeResponseBody`` - incomplete means that the
response body (for show or list) is missing certain attributes
* ``RbacPartialResponseBody`` - partial means that a list response
only returned a subset of the possible results available.
* ``RbacEmptyResponseBody`` - empty means that the show or list
response body is entirely empty
Each of the exception classes above deals with a different type of failure
related to a soft authorization failure. This means that, rather than a
403 error code getting returned by the server, the response body is
incomplete in some way.
upgrade:
- |
The exception class ``RbacMalformedException`` has been removed. Use one
of the following exception classes instead:
* ``RbacMissingAttributeResponseBody``
* ``RbacPartialResponseBody``
* ``RbacEmptyResponseBody``