Support implied rules
Using keystone API[0] to get all role inference rules and makes it
possible to extend the used list of roles with implied roles.
[0] https://developer.openstack.org/api-ref/identity/v3/#list-all-role-inference-rules
Change-Id: Ia57351f3b21a82f4556ec61323abd295b427fc1e
diff --git a/patrole_tempest_plugin/tests/unit/base.py b/patrole_tempest_plugin/tests/unit/base.py
index d73ff43..9a801bd 100644
--- a/patrole_tempest_plugin/tests/unit/base.py
+++ b/patrole_tempest_plugin/tests/unit/base.py
@@ -14,9 +14,19 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslotest import base
+from tempest.tests import base
-class TestCase(base.BaseTestCase):
+class TestCase(base.TestCase):
"""Test case base class for all unit tests."""
+
+ def get_all_needed_roles(self, roles):
+ role_inferences_mapping = {
+ "admin": {"member", "reader"},
+ "member": {"reader"}
+ }
+ res = set(r.lower() for r in roles)
+ for role in res.copy():
+ res.update(role_inferences_mapping.get(role, set()))
+ return list(res)
diff --git a/patrole_tempest_plugin/tests/unit/fixtures.py b/patrole_tempest_plugin/tests/unit/fixtures.py
index 78e87e5..f7a9059 100644
--- a/patrole_tempest_plugin/tests/unit/fixtures.py
+++ b/patrole_tempest_plugin/tests/unit/fixtures.py
@@ -94,6 +94,8 @@
clients, 'Manager', spec=clients.Manager,
roles_v3_client=mock.Mock(), roles_client=mock.Mock()).start()
self.admin_roles_client = mock_admin_mgr.return_value.roles_v3_client
+ self.admin_roles_client.list_all_role_inference_rules.return_value = {
+ "role_inferences": []}
self.set_roles(['admin', 'member'], [])
@@ -157,3 +159,28 @@
self.admin_roles_client.list_roles.return_value = available_roles
self.admin_roles_client.list_user_roles_on_project.return_value = (
available_project_roles)
+
+ def get_all_needed_roles(self, roles):
+ self.admin_roles_client.list_all_role_inference_rules.return_value = {
+ "role_inferences": [
+ {
+ "implies": [{"id": "3", "name": "reader"}],
+ "prior_role": {"id": "2", "name": "member"}
+ },
+ {
+ "implies": [{"id": "2", "name": "member"}],
+ "prior_role": {"id": "1", "name": "admin"}
+ }
+ ]
+ }
+
+ # Call real get_all_needed_roles function
+ with mock.patch.object(rbac_utils.RbacUtils, '_override_role',
+ autospec=True):
+ obj = rbac_utils.RbacUtils(mock.Mock())
+ obj._role_map = {
+ "1": "admin", "admin": "1",
+ "2": "member", "member": "2",
+ "3": "reader", "reader": "3"
+ }
+ return obj.get_all_needed_roles(roles)
diff --git a/patrole_tempest_plugin/tests/unit/test_policy_authority.py b/patrole_tempest_plugin/tests/unit/test_policy_authority.py
index 90e45f9..12457cb 100644
--- a/patrole_tempest_plugin/tests/unit/test_policy_authority.py
+++ b/patrole_tempest_plugin/tests/unit/test_policy_authority.py
@@ -17,10 +17,10 @@
import os
from tempest import config
-from tempest.tests import base
from patrole_tempest_plugin import policy_authority
from patrole_tempest_plugin import rbac_exceptions
+from patrole_tempest_plugin.tests.unit import base
from patrole_tempest_plugin.tests.unit import fixtures
CONF = config.CONF
@@ -96,6 +96,8 @@
authority = policy_authority.PolicyAuthority(
test_tenant_id, test_user_id, service)
+ roles = self.get_all_needed_roles(roles)
+
for rule in allowed_rules:
allowed = authority.allowed(rule, roles)
self.assertTrue(allowed)
@@ -286,7 +288,8 @@
}
for rule in allowed_rules:
- allowed = authority.allowed(rule, ['member'])
+ allowed = authority.allowed(
+ rule, self.get_all_needed_roles(['member']))
self.assertTrue(allowed)
# for sure that roles are in same order
mock_try_rule.call_args[0][2]["roles"] = sorted(
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
index 73a34fc..79e8b1d 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_rule_validation.py
@@ -22,11 +22,11 @@
from tempest.lib import exceptions
from tempest import manager
from tempest import test
-from tempest.tests import base
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import rbac_rule_validation as rbac_rv
from patrole_tempest_plugin import rbac_utils
+from patrole_tempest_plugin.tests.unit import base
from patrole_tempest_plugin.tests.unit import fixtures as patrole_fixtures
CONF = cfg.CONF
@@ -34,19 +34,22 @@
class BaseRBACRuleValidationTest(base.TestCase):
+ test_roles = ['member']
+
def setUp(self):
super(BaseRBACRuleValidationTest, self).setUp()
self.mock_test_args = mock.Mock(spec=test.BaseTestCase)
self.mock_test_args.os_primary = mock.Mock(spec=manager.Manager)
self.mock_test_args.rbac_utils = mock.Mock(
spec_set=rbac_utils.RbacUtils)
+ self.mock_test_args.rbac_utils.get_all_needed_roles.side_effect = \
+ self.get_all_needed_roles
# Setup credentials for mock client manager.
mock_creds = mock.Mock(user_id=mock.sentinel.user_id,
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=self.test_roles,
group='patrole'))
@@ -56,28 +59,9 @@
group='patrole_log'))
-class BaseRBACMultiRoleRuleValidationTest(base.TestCase):
+class BaseRBACMultiRoleRuleValidationTest(BaseRBACRuleValidationTest):
- def setUp(self):
- super(BaseRBACMultiRoleRuleValidationTest, self).setUp()
- self.mock_test_args = mock.Mock(spec=test.BaseTestCase)
- self.mock_test_args.os_primary = mock.Mock(spec=manager.Manager)
- self.mock_test_args.rbac_utils = mock.Mock(
- spec_set=rbac_utils.RbacUtils)
-
- # Setup credentials for mock client manager.
- mock_creds = mock.Mock(user_id=mock.sentinel.user_id,
- 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=self.test_roles,
- group='patrole'))
- # Disable patrole log for unit tests.
- self.useFixture(
- patrole_fixtures.ConfPatcher(enable_reporting=False,
- group='patrole_log'))
+ test_roles = ['member', 'anotherrole']
class RBACRuleValidationTest(BaseRBACRuleValidationTest):
@@ -549,7 +533,7 @@
policy_authority = mock_authority.PolicyAuthority.return_value
policy_authority.allowed.assert_called_with(
mock.sentinel.action,
- CONF.patrole.rbac_test_roles)
+ self.get_all_needed_roles(CONF.patrole.rbac_test_roles))
mock_log.error.assert_not_called()
@@ -561,6 +545,8 @@
evaluated correctly.
"""
mock_authority.PolicyAuthority.return_value.allowed.return_value = True
+ expected_roles = self.get_all_needed_roles(
+ CONF.patrole.rbac_test_roles)
def partial_func(x):
return "foo" if x == "bar" else "qux"
@@ -581,14 +567,14 @@
policy_authority = mock_authority.PolicyAuthority.return_value
policy_authority.allowed.assert_called_with(
"foo",
- CONF.patrole.rbac_test_roles)
+ expected_roles)
policy_authority.allowed.reset_mock()
test_bar_policy(self.mock_test_args)
policy_authority = mock_authority.PolicyAuthority.return_value
policy_authority.allowed.assert_called_with(
"qux",
- CONF.patrole.rbac_test_roles)
+ expected_roles)
mock_log.error.assert_not_called()
@@ -639,7 +625,10 @@
def _assert_policy_authority_called_with(self, rules, mock_authority):
m_authority = mock_authority.PolicyAuthority.return_value
m_authority.allowed.assert_has_calls([
- mock.call(rule, CONF.patrole.rbac_test_roles) for rule in rules
+ mock.call(
+ rule,
+ self.get_all_needed_roles(CONF.patrole.rbac_test_roles)
+ ) for rule in rules
])
m_authority.allowed.reset_mock()
diff --git a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
index 9fe5ffa..8acc678 100644
--- a/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
+++ b/patrole_tempest_plugin/tests/unit/test_rbac_utils.py
@@ -18,10 +18,10 @@
from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest.tests import base
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import rbac_utils
+from patrole_tempest_plugin.tests.unit import base
from patrole_tempest_plugin.tests.unit import fixtures as patrole_fixtures
@@ -213,6 +213,58 @@
m_override_role.assert_called_once_with(test_obj)
m_validate.assert_called_once()
+ def test_prepare_role_inferences_mapping(self):
+ self.patchobject(rbac_utils.RbacUtils, '_override_role')
+ test_obj = mock.MagicMock()
+ _rbac_utils = rbac_utils.RbacUtils(test_obj)
+ _rbac_utils.admin_roles_client.list_all_role_inference_rules.\
+ return_value = {
+ "role_inferences": [
+ {
+ "implies": [{"id": "3", "name": "reader"}],
+ "prior_role": {"id": "2", "name": "member"}
+ },
+ {
+ "implies": [{"id": "2", "name": "member"}],
+ "prior_role": {"id": "1", "name": "admin"}
+ }
+ ]
+ }
+
+ expected_role_inferences_mapping = {
+ "2": {"3"}, # "member": ["reader"],
+ "1": {"2", "3"} # "admin": ["member", "reader"]
+ }
+ actual_role_inferences_mapping = _rbac_utils.\
+ _prepare_role_inferences_mapping()
+ self.assertEqual(expected_role_inferences_mapping,
+ actual_role_inferences_mapping)
+
+ def test_get_all_needed_roles(self):
+ self.patchobject(rbac_utils.RbacUtils, '_override_role')
+ test_obj = mock.MagicMock()
+ _rbac_utils = rbac_utils.RbacUtils(test_obj)
+ _rbac_utils._role_inferences_mapping = {
+ "2": {"3"}, # "member": ["reader"],
+ "1": {"2", "3"} # "admin": ["member", "reader"]
+ }
+ _rbac_utils._role_map = {
+ "1": "admin", "admin": "1",
+ "2": "member", "member": "2",
+ "3": "reader", "reader": "3"
+ }
+ for roles, expected_roles in (
+ (['admin'], ['admin', 'member', 'reader']),
+ (['member'], ['member', 'reader']),
+ (['reader'], ['reader']),
+ (['custom_role'], ['custom_role']),
+ (['custom_role', 'member'], ['custom_role', 'member', 'reader']),
+ (['admin', 'member'], ['admin', 'member', 'reader']),
+ ):
+ expected_roles = sorted(expected_roles)
+ actual_roles = sorted(_rbac_utils.get_all_needed_roles(roles))
+ self.assertEqual(expected_roles, actual_roles)
+
class RBACUtilsMixinTest(base.TestCase):
diff --git a/patrole_tempest_plugin/tests/unit/test_requirements_authority.py b/patrole_tempest_plugin/tests/unit/test_requirements_authority.py
index 94af81f..d069dcb 100644
--- a/patrole_tempest_plugin/tests/unit/test_requirements_authority.py
+++ b/patrole_tempest_plugin/tests/unit/test_requirements_authority.py
@@ -15,10 +15,10 @@
import os
from tempest.lib import exceptions
-from tempest.tests import base
from patrole_tempest_plugin import rbac_exceptions
from patrole_tempest_plugin import requirements_authority as req_auth
+from patrole_tempest_plugin.tests.unit import base
class BaseRequirementsAuthorityTest(base.TestCase):