Merge "Identity: Add support for system role assignment"

This commit is contained in:
Zuul 2022-03-03 15:14:01 +00:00 committed by Gerrit Code Review
commit d4e13ba34a
12 changed files with 538 additions and 24 deletions

View File

@ -87,7 +87,10 @@ Role Assignment Operations
unassign_project_role_from_group, validate_group_has_project_role,
assign_domain_role_to_user, unassign_domain_role_from_user,
validate_user_has_domain_role, assign_domain_role_to_group,
unassign_domain_role_from_group, validate_group_has_domain_role
unassign_domain_role_from_group, validate_group_has_domain_role,
assign_system_role_to_user, unassign_system_role_from_user,
validate_user_has_system_role, assign_system_role_to_group,
unassign_system_role_from_group, validate_group_has_system_role
Service Operations
^^^^^^^^^^^^^^^^^^

View File

@ -945,6 +945,7 @@ class IdentityCloudMixin:
* 'group' (string) - Group ID to be used as query filter.
* 'project' (string) - Project ID to be used as query filter.
* 'domain' (string) - Domain ID to be used as query filter.
* 'system' (string) - System name to be used as query filter.
* 'role' (string) - Role ID to be used as query filter.
* 'os_inherit_extension_inherited_to' (string) - Return inherited
role assignments for either 'projects' or 'domains'
@ -991,6 +992,10 @@ class IdentityCloudMixin:
if k in filters:
filters['scope_%s_id' % k] = filters.pop(k)
if 'system' in filters:
system_scope = filters.pop('system')
filters['scope.system'] = system_scope
return list(self.identity.role_assignments(**filters))
@_utils.valid_kwargs('domain_id')
@ -1055,7 +1060,7 @@ class IdentityCloudMixin:
raise
def _get_grant_revoke_params(self, role, user=None, group=None,
project=None, domain=None):
project=None, domain=None, system=None):
data = {}
search_args = {}
if domain:
@ -1083,9 +1088,9 @@ class IdentityCloudMixin:
if data.get('user') is None and data.get('group') is None:
raise exc.OpenStackCloudException(
'Must specify either a user or a group')
if project is None and domain is None:
if project is None and domain is None and system is None:
raise exc.OpenStackCloudException(
'Must specify either a domain or project')
'Must specify either a domain, project or system')
if project:
data['project'] = self.identity.find_project(
@ -1093,7 +1098,8 @@ class IdentityCloudMixin:
return data
def grant_role(self, name_or_id, user=None, group=None,
project=None, domain=None, wait=False, timeout=60):
project=None, domain=None, system=None, wait=False,
timeout=60):
"""Grant a role to a user.
:param string name_or_id: The name or id of the role.
@ -1101,6 +1107,7 @@ class IdentityCloudMixin:
:param string group: The name or id of the group. (v3)
:param string project: The name or id of the project.
:param string domain: The id of the domain. (v3)
:param bool system: The name of the system. (v3)
:param bool wait: Wait for role to be granted
:param int timeout: Timeout to wait for role to be granted
@ -1112,13 +1119,15 @@ class IdentityCloudMixin:
NOTE: for wait and timeout, sometimes granting roles is not
instantaneous.
NOTE: precedence is given first to project, then domain, then system
:returns: True if the role is assigned, otherwise False
:raise OpenStackCloudException: if the role cannot be granted
"""
data = self._get_grant_revoke_params(
name_or_id, user=user, group=group,
project=project, domain=domain)
project=project, domain=domain, system=system)
user = data.get('user')
group = data.get('group')
@ -1127,7 +1136,7 @@ class IdentityCloudMixin:
role = data.get('role')
if project:
# Proceed with project - precedence over domain
# Proceed with project - precedence over domain and system
if user:
has_role = self.identity.validate_user_has_project_role(
project, user, role)
@ -1144,8 +1153,8 @@ class IdentityCloudMixin:
return False
self.identity.assign_project_role_to_group(
project, group, role)
else:
# Proceed with domain
elif domain:
# Proceed with domain - precedence over system
if user:
has_role = self.identity.validate_user_has_domain_role(
domain, user, role)
@ -1162,10 +1171,31 @@ class IdentityCloudMixin:
return False
self.identity.assign_domain_role_to_group(
domain, group, role)
else:
# Proceed with system
# System name must be 'all' due to checks performed in
# _get_grant_revoke_params
if user:
has_role = self.identity.validate_user_has_system_role(
user, role, system)
if has_role:
self.log.debug('Assignment already exists')
return False
self.identity.assign_system_role_to_user(
user, role, system)
else:
has_role = self.identity.validate_group_has_system_role(
group, role, system)
if has_role:
self.log.debug('Assignment already exists')
return False
self.identity.assign_system_role_to_group(
group, role, system)
return True
def revoke_role(self, name_or_id, user=None, group=None,
project=None, domain=None, wait=False, timeout=60):
project=None, domain=None, system=None,
wait=False, timeout=60):
"""Revoke a role from a user.
:param string name_or_id: The name or id of the role.
@ -1173,6 +1203,7 @@ class IdentityCloudMixin:
:param string group: The name or id of the group. (v3)
:param string project: The name or id of the project.
:param string domain: The id of the domain. (v3)
:param bool system: The name of the system. (v3)
:param bool wait: Wait for role to be revoked
:param int timeout: Timeout to wait for role to be revoked
@ -1181,13 +1212,15 @@ class IdentityCloudMixin:
NOTE: project is required for keystone v2
NOTE: precedence is given first to project, then domain, then system
:returns: True if the role is revoke, otherwise False
:raise OpenStackCloudException: if the role cannot be removed
"""
data = self._get_grant_revoke_params(
name_or_id, user=user, group=group,
project=project, domain=domain)
project=project, domain=domain, system=system)
user = data.get('user')
group = data.get('group')
@ -1196,7 +1229,7 @@ class IdentityCloudMixin:
role = data.get('role')
if project:
# Proceed with project - precedence over domain
# Proceed with project - precedence over domain and system
if user:
has_role = self.identity.validate_user_has_project_role(
project, user, role)
@ -1213,8 +1246,8 @@ class IdentityCloudMixin:
return False
self.identity.unassign_project_role_from_group(
project, group, role)
else:
# Proceed with domain
elif domain:
# Proceed with domain - precedence over system
if user:
has_role = self.identity.validate_user_has_domain_role(
domain, user, role)
@ -1231,6 +1264,26 @@ class IdentityCloudMixin:
return False
self.identity.unassign_domain_role_from_group(
domain, group, role)
else:
# Proceed with system
# System name must be 'all' due to checks performed in
# _get_grant_revoke_params
if user:
has_role = self.identity.validate_user_has_system_role(
user, role, system)
if not has_role:
self.log.debug('Assignment does not exist')
return False
self.identity.unassign_system_role_from_user(
user, role, system)
else:
has_role = self.identity.validate_group_has_system_role(
group, role, system)
if not has_role:
self.log.debug('Assignment does not exist')
return False
self.identity.unassign_system_role_from_group(
group, role, system)
return True
def _get_identity_params(self, domain_id=None, project=None):

View File

@ -35,7 +35,12 @@ from openstack.identity.v3 import role_project_group_assignment \
as _role_project_group_assignment
from openstack.identity.v3 import role_project_user_assignment \
as _role_project_user_assignment
from openstack.identity.v3 import role_system_group_assignment \
as _role_system_group_assignment
from openstack.identity.v3 import role_system_user_assignment \
as _role_system_user_assignment
from openstack.identity.v3 import service as _service
from openstack.identity.v3 import system as _system
from openstack.identity.v3 import trust as _trust
from openstack.identity.v3 import user as _user
from openstack import proxy
@ -949,8 +954,8 @@ class Proxy(proxy.Proxy):
"""
return self._update(_role.Role, role, **attrs)
def role_assignments_filter(self, domain=None, project=None, group=None,
user=None):
def role_assignments_filter(self, domain=None, project=None, system=None,
group=None, user=None):
"""Retrieve a generator of roles assigned to user/group
:param domain: Either the ID of a domain or a
@ -958,6 +963,9 @@ class Proxy(proxy.Proxy):
:param project: Either the ID of a project or a
:class:`~openstack.identity.v3.project.Project`
instance.
:param system: Either the system name or a
:class:`~openstack.identity.v3.system.System`
instance.
:param group: Either the ID of a group or a
:class:`~openstack.identity.v3.group.Group` instance.
:param user: Either the ID of a user or a
@ -965,13 +973,13 @@ class Proxy(proxy.Proxy):
:return: A generator of role instances.
:rtype: :class:`~openstack.identity.v3.role.Role`
"""
if domain and project:
if domain and project and system:
raise exception.InvalidRequest(
'Only one of domain or project can be specified')
'Only one of domain, project, or system can be specified')
if domain is None and project is None:
if domain is None and project is None and system is None:
raise exception.InvalidRequest(
'Either domain or project should be specified')
'Either domain, project, or system should be specified')
if group and user:
raise exception.InvalidRequest(
@ -993,7 +1001,7 @@ class Proxy(proxy.Proxy):
return self._list(
_role_domain_user_assignment.RoleDomainUserAssignment,
domain_id=domain.id, user_id=user.id)
else:
elif project:
project = self._get_resource(_project.Project, project)
if group:
group = self._get_resource(_group.Group, group)
@ -1005,6 +1013,18 @@ class Proxy(proxy.Proxy):
return self._list(
_role_project_user_assignment.RoleProjectUserAssignment,
project_id=project.id, user_id=user.id)
else:
system = self._get_resource(_project.System, system)
if group:
group = self._get_resource(_group.Group, group)
return self._list(
_role_system_group_assignment.RoleSystemGroupAssignment,
system_id=system.id, group_id=group.id)
else:
user = self._get_resource(_user.User, user)
return self._list(
_role_system_user_assignment.RoleSystemUserAssignment,
system_id=system.id, user_id=user.id)
def role_assignments(self, **query):
"""Retrieve a generator of role assignments
@ -1355,6 +1375,96 @@ class Proxy(proxy.Proxy):
role = self._get_resource(_role.Role, role)
return project.validate_group_has_role(self, group, role)
def assign_system_role_to_user(self, user, role, system):
"""Assign a role to user on a system
:param user: Either the ID of a user or a
:class:`~openstack.identity.v3.user.User` instance.
:param role: Either the ID of a role or a
:class:`~openstack.identity.v3.role.Role` instance.
:param system: The system name
:return: ``None``
"""
user = self._get_resource(_user.User, user)
role = self._get_resource(_role.Role, role)
system = self._get_resource(_system.System, system)
system.assign_role_to_user(self, user, role)
def unassign_system_role_from_user(self, user, role, system):
"""Unassign a role from user on a system
:param user: Either the ID of a user or a
:class:`~openstack.identity.v3.user.User` instance.
:param role: Either the ID of a role or a
:class:`~openstack.identity.v3.role.Role` instance.
:param system: The system name
:return: ``None``
"""
user = self._get_resource(_user.User, user)
role = self._get_resource(_role.Role, role)
system = self._get_resource(_system.System, system)
system.unassign_role_from_user(self, user, role)
def validate_user_has_system_role(self, user, role, system):
"""Validates that a user has a role on a system
:param user: Either the ID of a user or a
:class:`~openstack.identity.v3.user.User` instance.
:param role: Either the ID of a role or a
:class:`~openstack.identity.v3.role.Role` instance.
:param system: The system name
:returns: True if user has role in system
"""
user = self._get_resource(_user.User, user)
role = self._get_resource(_role.Role, role)
system = self._get_resource(_system.System, system)
return system.validate_user_has_role(self, user, role)
def assign_system_role_to_group(self, group, role, system):
"""Assign a role to group on a system
:param group: Either the ID of a group or a
:class:`~openstack.identity.v3.group.Group` instance.
:param role: Either the ID of a role or a
:class:`~openstack.identity.v3.role.Role` instance.
:param system: The system name
:return: ``None``
"""
group = self._get_resource(_group.Group, group)
role = self._get_resource(_role.Role, role)
system = self._get_resource(_system.System, system)
system.assign_role_to_group(self, group, role)
def unassign_system_role_from_group(self, group, role, system):
"""Unassign a role from group on a system
:param group: Either the ID of a group or a
:class:`~openstack.identity.v3.group.Group` instance.
:param role: Either the ID of a role or a
:class:`~openstack.identity.v3.role.Role` instance.
:param system: The system name
:return: ``None``
"""
group = self._get_resource(_group.Group, group)
role = self._get_resource(_role.Role, role)
system = self._get_resource(_system.System, system)
system.unassign_role_from_group(self, group, role)
def validate_group_has_system_role(self, group, role, system):
"""Validates that a group has a role on a system
:param group: Either the ID of a group or a
:class:`~openstack.identity.v3.group.Group` instance.
:param role: Either the ID of a role or a
:class:`~openstack.identity.v3.role.Role` instance.
:param system: The system name
:returns: True if group has role on system
"""
group = self._get_resource(_group.Group, group)
role = self._get_resource(_role.Role, role)
system = self._get_resource(_system.System, system)
return system.validate_group_has_role(self, group, role)
def application_credentials(self, user, **query):
"""Retrieve a generator of application credentials

View File

@ -0,0 +1,28 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import resource
class RoleSystemGroupAssignment(resource.Resource):
resource_key = 'role'
resources_key = 'roles'
base_path = '/system/groups/%(group_id)s/roles'
# capabilities
allow_list = True
# Properties
#: The ID of the group to list assignment from. *Type: string*
group_id = resource.URI('group_id')
#: The name of the system to list assignment from. *Type: string*
system_id = resource.URI('system_id')

View File

@ -0,0 +1,28 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import resource
class RoleSystemUserAssignment(resource.Resource):
resource_key = 'role'
resources_key = 'roles'
base_path = '/system/users/%(user_id)s/roles'
# capabilities
allow_list = True
# Properties
#: The name of the system to list assignment from. *Type: string*
system_id = resource.URI('system_id')
#: The ID of the user to list assignment from. *Type: string*
user_id = resource.URI('user_id')

View File

@ -0,0 +1,75 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack import resource
from openstack import utils
class System(resource.Resource):
resource_key = 'system'
base_path = '/system'
# capabilities
def assign_role_to_user(self, session, user, role):
"""Assign role to user on system"""
url = utils.urljoin(self.base_path, 'users', user.id,
'roles', role.id)
resp = session.put(url,)
if resp.status_code == 204:
return True
return False
def validate_user_has_role(self, session, user, role):
"""Validates that a user has a role on a system"""
url = utils.urljoin(self.base_path, 'users', user.id,
'roles', role.id)
resp = session.head(url,)
if resp.status_code == 204:
return True
return False
def unassign_role_from_user(self, session, user, role):
"""Unassigns a role from a user on a system"""
url = utils.urljoin(self.base_path, 'users', user.id,
'roles', role.id)
resp = session.delete(url,)
if resp.status_code == 204:
return True
return False
def assign_role_to_group(self, session, group, role):
"""Assign role to group on system"""
url = utils.urljoin(self.base_path, 'groups', group.id,
'roles', role.id)
resp = session.put(url,)
if resp.status_code == 204:
return True
return False
def validate_group_has_role(self, session, group, role):
"""Validates that a group has a role on a system"""
url = utils.urljoin(self.base_path, 'groups', group.id,
'roles', role.id)
resp = session.head(url,)
if resp.status_code == 204:
return True
return False
def unassign_role_from_group(self, session, group, role):
"""Unassigns a role from a group on a system"""
url = utils.urljoin(self.base_path, 'groups', group.id,
'roles', role.id)
resp = session.delete(url,)
if resp.status_code == 204:
return True
return False

View File

@ -248,3 +248,58 @@ class TestIdentity(base.KeystoneBaseFunctionalTest):
})
self.assertIsInstance(assignments, list)
self.assertEqual(0, len(assignments))
def test_grant_revoke_role_user_system(self):
role_name = self.role_prefix + '_grant_user_system'
role = self.operator_cloud.create_role(role_name)
user_name = self.user_prefix + '_user_system'
user_email = 'nobody@nowhere.com'
user = self._create_user(name=user_name,
email=user_email,
default_project='demo')
self.assertTrue(self.operator_cloud.grant_role(
role_name, user=user['id'], system='all'))
assignments = self.operator_cloud.list_role_assignments({
'role': role['id'],
'user': user['id'],
'system': 'all'
})
self.assertIsInstance(assignments, list)
self.assertEqual(1, len(assignments))
self.assertTrue(self.operator_cloud.revoke_role(
role_name, user=user['id'], system='all'))
assignments = self.operator_cloud.list_role_assignments({
'role': role['id'],
'user': user['id'],
'system': 'all'
})
self.assertIsInstance(assignments, list)
self.assertEqual(0, len(assignments))
def test_grant_revoke_role_group_system(self):
if self.identity_version in ('2', '2.0'):
self.skipTest("Identity service does not support system or group")
role_name = self.role_prefix + '_grant_group_system'
role = self.operator_cloud.create_role(role_name)
group_name = self.group_prefix + '_group_system'
group = self.operator_cloud.create_group(
name=group_name,
description='test group')
self.assertTrue(self.operator_cloud.grant_role(
role_name, group=group['id'], system='all'))
assignments = self.operator_cloud.list_role_assignments({
'role': role['id'],
'group': group['id'],
'system': 'all'
})
self.assertIsInstance(assignments, list)
self.assertEqual(1, len(assignments))
self.assertTrue(self.operator_cloud.revoke_role(
role_name, group=group['id'], system='all'))
assignments = self.operator_cloud.list_role_assignments({
'role': role['id'],
'group': group['id'],
'system': 'all'
})
self.assertIsInstance(assignments, list)
self.assertEqual(0, len(assignments))

View File

@ -1364,14 +1364,14 @@ class TestRoleAssignment(base.TestCase):
with testtools.ExpectedException(
exc.OpenStackCloudException,
'Must specify either a domain or project'
'Must specify either a domain, project or system'
):
self.cloud.grant_role(
self.role_data.role_name,
user=self.user_data.name)
self.assert_calls()
def test_revoke_no_project_or_domain(self):
def test_revoke_no_project_or_domain_or_system(self):
uris = self._get_mock_role_query_urls(
self.role_data,
user_data=self.user_data,
@ -1381,7 +1381,7 @@ class TestRoleAssignment(base.TestCase):
with testtools.ExpectedException(
exc.OpenStackCloudException,
'Must specify either a domain or project'
'Must specify either a domain, project or system'
):
self.cloud.revoke_role(
self.role_data.role_name,

View File

@ -499,3 +499,75 @@ class TestIdentityProxyRoleAssignments(TestIdentityProxyBase):
self.proxy._get_resource(role.Role, 'rid')
]
)
def test_assign_system_role_to_user(self):
self._verify(
"openstack.identity.v3.system.System.assign_role_to_user",
self.proxy.assign_system_role_to_user,
method_kwargs={'user': 'uid', 'role': 'rid', 'system': 'all'},
expected_args=[
self.proxy,
self.proxy._get_resource(user.User, 'uid'),
self.proxy._get_resource(role.Role, 'rid')
]
)
def test_unassign_system_role_from_user(self):
self._verify(
"openstack.identity.v3.system.System.unassign_role_from_user",
self.proxy.unassign_system_role_from_user,
method_kwargs={'user': 'uid', 'role': 'rid', 'system': 'all'},
expected_args=[
self.proxy,
self.proxy._get_resource(user.User, 'uid'),
self.proxy._get_resource(role.Role, 'rid')
]
)
def test_validate_user_has_system_role(self):
self._verify(
"openstack.identity.v3.system.System.validate_user_has_role",
self.proxy.validate_user_has_system_role,
method_kwargs={'user': 'uid', 'role': 'rid', 'system': 'all'},
expected_args=[
self.proxy,
self.proxy._get_resource(user.User, 'uid'),
self.proxy._get_resource(role.Role, 'rid')
]
)
def test_assign_system_role_to_group(self):
self._verify(
"openstack.identity.v3.system.System.assign_role_to_group",
self.proxy.assign_system_role_to_group,
method_kwargs={'group': 'uid', 'role': 'rid', 'system': 'all'},
expected_args=[
self.proxy,
self.proxy._get_resource(group.Group, 'uid'),
self.proxy._get_resource(role.Role, 'rid')
]
)
def test_unassign_system_role_from_group(self):
self._verify(
"openstack.identity.v3.system.System.unassign_role_from_group",
self.proxy.unassign_system_role_from_group,
method_kwargs={'group': 'uid', 'role': 'rid', 'system': 'all'},
expected_args=[
self.proxy,
self.proxy._get_resource(group.Group, 'uid'),
self.proxy._get_resource(role.Role, 'rid')
]
)
def test_validate_group_has_system_role(self):
self._verify(
"openstack.identity.v3.system.System.validate_group_has_role",
self.proxy.validate_group_has_system_role,
method_kwargs={'group': 'uid', 'role': 'rid', 'system': 'all'},
expected_args=[
self.proxy,
self.proxy._get_resource(group.Group, 'uid'),
self.proxy._get_resource(role.Role, 'rid')
]
)

View File

@ -0,0 +1,40 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack.identity.v3 import role_system_group_assignment
from openstack.tests.unit import base
IDENTIFIER = 'IDENTIFIER'
EXAMPLE = {
'id': IDENTIFIER,
'name': '2',
'group_id': '4'
}
class TestRoleSystemGroupAssignment(base.TestCase):
def test_basic(self):
sot = role_system_group_assignment.RoleSystemGroupAssignment()
self.assertEqual('role', sot.resource_key)
self.assertEqual('roles', sot.resources_key)
self.assertEqual('/system/groups/%(group_id)s/roles',
sot.base_path)
self.assertTrue(sot.allow_list)
def test_make_it(self):
sot = \
role_system_group_assignment.RoleSystemGroupAssignment(**EXAMPLE)
self.assertEqual(EXAMPLE['id'], sot.id)
self.assertEqual(EXAMPLE['name'], sot.name)
self.assertEqual(EXAMPLE['group_id'], sot.group_id)

View File

@ -0,0 +1,39 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from openstack.identity.v3 import role_system_user_assignment
from openstack.tests.unit import base
IDENTIFIER = 'IDENTIFIER'
EXAMPLE = {
'id': IDENTIFIER,
'name': '2',
'user_id': '4'
}
class TestRoleSystemUserAssignment(base.TestCase):
def test_basic(self):
sot = role_system_user_assignment.RoleSystemUserAssignment()
self.assertEqual('role', sot.resource_key)
self.assertEqual('roles', sot.resources_key)
self.assertEqual('/system/users/%(user_id)s/roles',
sot.base_path)
self.assertTrue(sot.allow_list)
def test_make_it(self):
sot = \
role_system_user_assignment.RoleSystemUserAssignment(**EXAMPLE)
self.assertEqual(EXAMPLE['id'], sot.id)
self.assertEqual(EXAMPLE['name'], sot.name)

View File

@ -0,0 +1,11 @@
---
features:
- |
Add support for system role assignment. A system role assignment
ultimately controls access to system-level API calls.
Good examples of system-level APIs include management of the
service catalog and compute hypervisors.
`System role assignment API reference
<https://docs.openstack.org/api-ref/identity/v3/#system-role-assignments>`_.