List assignments with names

When a client calls list assignment API what is returned is the
role id, user id or group id, and project id or domain id. Most users
then call the api again for each of these entities to get their names,
creating many api calls between the client and server.  This can
be reduced by having the server do all the work instead.

This commit adds the functionality to include the user, role, group,
project, and domain names with the response if the parameter
'include_names' is set to True.

Change-Id: I0a1cc986b8a35aeafe567e5e7fee6eeb848ae113
Closes-Bug: #1479569
Implements: blueprint list-assignment-with-names
This commit is contained in:
Tom Cocozzello 2015-11-25 10:25:28 -06:00 committed by Steve Martinelli
parent d78fcc361e
commit dc212cd4d2
7 changed files with 328 additions and 11 deletions

View File

@ -514,8 +514,15 @@ class RoleAssignmentV3(controller.V3Controller):
inherited_assignment = entity.get('inherited_to_projects')
if 'project_id' in entity:
formatted_entity['scope'] = {
'project': {'id': entity['project_id']}}
if 'project_name' in entity:
formatted_entity['scope'] = {'project': {
'id': entity['project_id'],
'name': entity['project_name'],
'domain': {'id': entity['project_domain_id'],
'name': entity['project_domain_name']}}}
else:
formatted_entity['scope'] = {
'project': {'id': entity['project_id']}}
if 'domain_id' in entity.get('indirect', {}):
inherited_assignment = True
@ -528,12 +535,24 @@ class RoleAssignmentV3(controller.V3Controller):
else:
formatted_link = '/projects/%s' % entity['project_id']
elif 'domain_id' in entity:
formatted_entity['scope'] = {'domain': {'id': entity['domain_id']}}
if 'domain_name' in entity:
formatted_entity['scope'] = {
'domain': {'id': entity['domain_id'],
'name': entity['domain_name']}}
else:
formatted_entity['scope'] = {
'domain': {'id': entity['domain_id']}}
formatted_link = '/domains/%s' % entity['domain_id']
if 'user_id' in entity:
formatted_entity['user'] = {'id': entity['user_id']}
if 'user_name' in entity:
formatted_entity['user'] = {
'id': entity['user_id'],
'name': entity['user_name'],
'domain': {'id': entity['user_domain_id'],
'name': entity['user_domain_name']}}
else:
formatted_entity['user'] = {'id': entity['user_id']}
if 'group_id' in entity.get('indirect', {}):
membership_url = (
self.base_url(context, '/groups/%s/users/%s' % (
@ -543,10 +562,21 @@ class RoleAssignmentV3(controller.V3Controller):
else:
formatted_link += '/users/%s' % entity['user_id']
elif 'group_id' in entity:
formatted_entity['group'] = {'id': entity['group_id']}
if 'group_name' in entity:
formatted_entity['group'] = {
'id': entity['group_id'],
'name': entity['group_name'],
'domain': {'id': entity['group_domain_id'],
'name': entity['group_domain_name']}}
else:
formatted_entity['group'] = {'id': entity['group_id']}
formatted_link += '/groups/%s' % entity['group_id']
formatted_entity['role'] = {'id': entity['role_id']}
if 'role_name' in entity:
formatted_entity['role'] = {'id': entity['role_id'],
'name': entity['role_name']}
else:
formatted_entity['role'] = {'id': entity['role_id']}
formatted_link += '/roles/%s' % entity['role_id']
if inherited_assignment:
@ -616,6 +646,8 @@ class RoleAssignmentV3(controller.V3Controller):
params = context['query_string']
effective = 'effective' in params and (
self.query_filter_is_true(params['effective']))
include_names = ('include_names' in params and
self.query_filter_is_true(params['include_names']))
if 'scope.OS-INHERIT:inherited_to' in params:
inherited = (
@ -642,7 +674,8 @@ class RoleAssignmentV3(controller.V3Controller):
domain_id=params.get('scope.domain.id'),
project_id=params.get('scope.project.id'),
include_subtree=include_subtree,
inherited=inherited, effective=effective)
inherited=inherited, effective=effective,
include_names=include_names)
formatted_refs = [self._format_entity(context, ref) for ref in refs]

View File

@ -823,7 +823,7 @@ class Manager(manager.Manager):
def list_role_assignments(self, role_id=None, user_id=None, group_id=None,
domain_id=None, project_id=None,
include_subtree=False, inherited=None,
effective=None):
effective=None, include_names=False):
"""List role assignments, honoring effective mode and provided filters.
Returns a list of role assignments, where their attributes match the
@ -841,6 +841,9 @@ class Manager(manager.Manager):
Think of effective mode as being the list of assignments that actually
affect a user, for example the roles that would be placed in a token.
If include_names is set to true the entities' names are returned
in addition to their id's
If OS-INHERIT extension is disabled or the used driver does not support
inherited roles retrieval, inherited role assignments will be ignored.
@ -857,14 +860,59 @@ class Manager(manager.Manager):
self.resource_api.list_projects_in_subtree(project_id)])
if effective:
return self._list_effective_role_assignments(
role_assignments = self._list_effective_role_assignments(
role_id, user_id, group_id, domain_id, project_id,
subtree_ids, inherited)
else:
return self._list_direct_role_assignments(
role_assignments = self._list_direct_role_assignments(
role_id, user_id, group_id, domain_id, project_id,
subtree_ids, inherited)
if include_names:
return self._get_names_from_role_assignments(role_assignments)
return role_assignments
def _get_names_from_role_assignments(self, role_assignments):
role_assign_list = []
for role_asgmt in role_assignments:
new_assign = {}
for id_type, id_ in role_asgmt.items():
if id_type == 'domain_id':
_domain = self.resource_api.get_domain(id_)
new_assign['domain_id'] = _domain['id']
new_assign['domain_name'] = _domain['name']
elif id_type == 'user_id':
_user = self.identity_api.get_user(id_)
new_assign['user_id'] = _user['id']
new_assign['user_name'] = _user['name']
new_assign['user_domain_id'] = _user['domain_id']
new_assign['user_domain_name'] = (
self.resource_api.get_domain(_user['domain_id'])
['name'])
elif id_type == 'group_id':
_group = self.identity_api.get_group(id_)
new_assign['group_id'] = _group['id']
new_assign['group_name'] = _group['name']
new_assign['group_domain_id'] = _group['domain_id']
new_assign['group_domain_name'] = (
self.resource_api.get_domain(_group['domain_id'])
['name'])
elif id_type == 'project_id':
_project = self.resource_api.get_project(id_)
new_assign['project_id'] = _project['id']
new_assign['project_name'] = _project['name']
new_assign['project_domain_id'] = _project['domain_id']
new_assign['project_domain_name'] = (
self.resource_api.get_domain(_project['domain_id'])
['name'])
elif id_type == 'role_id':
_role = self.role_api.get_role(id_)
new_assign['role_id'] = _role['id']
new_assign['role_name'] = _role['name']
role_assign_list.append(new_assign)
return role_assign_list
def delete_tokens_for_role_assignments(self, role_id):
assignments = self.list_role_assignments(role_id=role_id)

View File

@ -4127,6 +4127,80 @@ class IdentityTests(AssignmentTestHelperMixin):
{'name': self.role_member['name']})
# If the previous line didn't raise an exception then the test passes.
def test_list_role_assignment_containing_names(self):
# Create Refs
new_role = unit.new_role_ref()
new_domain = self._get_domain_fixture()
new_user = unit.new_user_ref(domain_id=new_domain['id'])
new_project = unit.new_project_ref(domain_id=new_domain['id'])
new_group = unit.new_group_ref(domain_id=new_domain['id'])
# Create entities
new_role = self.role_api.create_role(new_role['id'], new_role)
new_user = self.identity_api.create_user(new_user)
new_group = self.identity_api.create_group(new_group)
self.resource_api.create_project(new_project['id'], new_project)
self.assignment_api.create_grant(user_id=new_user['id'],
project_id=new_project['id'],
role_id=new_role['id'])
self.assignment_api.create_grant(group_id=new_group['id'],
project_id=new_project['id'],
role_id=new_role['id'])
self.assignment_api.create_grant(domain_id=new_domain['id'],
user_id=new_user['id'],
role_id=new_role['id'])
# Get the created assignments with the include_names flag
_asgmt_prj = self.assignment_api.list_role_assignments(
user_id=new_user['id'],
project_id=new_project['id'],
include_names=True)
_asgmt_grp = self.assignment_api.list_role_assignments(
group_id=new_group['id'],
project_id=new_project['id'],
include_names=True)
_asgmt_dmn = self.assignment_api.list_role_assignments(
domain_id=new_domain['id'],
user_id=new_user['id'],
include_names=True)
# Make sure we can get back the correct number of assignments
self.assertThat(_asgmt_prj, matchers.HasLength(1))
self.assertThat(_asgmt_grp, matchers.HasLength(1))
self.assertThat(_asgmt_dmn, matchers.HasLength(1))
# get the first assignment
first_asgmt_prj = _asgmt_prj[0]
first_asgmt_grp = _asgmt_grp[0]
first_asgmt_dmn = _asgmt_dmn[0]
# Assert the names are correct in the project response
self.assertEqual(new_project['name'],
first_asgmt_prj['project_name'])
self.assertEqual(new_project['domain_id'],
first_asgmt_prj['project_domain_id'])
self.assertEqual(new_user['name'],
first_asgmt_prj['user_name'])
self.assertEqual(new_user['domain_id'],
first_asgmt_prj['user_domain_id'])
self.assertEqual(new_role['name'],
first_asgmt_prj['role_name'])
# Assert the names are correct in the group response
self.assertEqual(new_group['name'],
first_asgmt_grp['group_name'])
self.assertEqual(new_group['domain_id'],
first_asgmt_grp['group_domain_id'])
self.assertEqual(new_project['name'],
first_asgmt_grp['project_name'])
self.assertEqual(new_project['domain_id'],
first_asgmt_grp['project_domain_id'])
self.assertEqual(new_role['name'],
first_asgmt_grp['role_name'])
# Assert the names are correct in the domain response
self.assertEqual(new_domain['name'],
first_asgmt_dmn['domain_name'])
self.assertEqual(new_user['name'],
first_asgmt_dmn['user_name'])
self.assertEqual(new_user['domain_id'],
first_asgmt_dmn['user_domain_id'])
self.assertEqual(new_role['name'],
first_asgmt_dmn['role_name'])
class TokenTests(object):
def _create_token_id(self):

View File

@ -329,6 +329,9 @@ class BaseLDAPIdentity(test_backend.IdentityTests):
def test_delete_group_with_user_project_domain_links(self):
self.skipTest('N/A: LDAP does not support multiple domains')
def test_list_role_assignment_containing_names(self):
self.skipTest('N/A: LDAP does not support multiple domains')
def test_list_projects_for_user(self):
domain = self._get_domain_fixture()
user1 = self.new_user_ref(domain_id=domain['id'])

View File

@ -1321,3 +1321,64 @@ class AssignmentTestMixin(object):
entity['scope']['OS-INHERIT:inherited_to'] = 'projects'
return entity
def build_role_assignment_entity_include_names(self,
domain_ref=None,
role_ref=None,
group_ref=None,
user_ref=None,
project_ref=None,
inherited_assignment=None):
"""Build and return a role assignment entity with provided attributes.
The expected attributes are: domain_ref or project_ref,
user_ref or group_ref, role_ref and, optionally, inherited_to_projects.
"""
entity = {'links': {}}
attributes_for_links = {}
if project_ref:
dmn_name = self.resource_api.get_domain(
project_ref['domain_id'])['name']
entity['scope'] = {'project': {
'id': project_ref['id'],
'name': project_ref['name'],
'domain': {
'id': project_ref['domain_id'],
'name': dmn_name}}}
attributes_for_links['project_id'] = project_ref['id']
else:
entity['scope'] = {'domain': {'id': domain_ref['id'],
'name': domain_ref['name']}}
attributes_for_links['domain_id'] = domain_ref['id']
if user_ref:
dmn_name = self.resource_api.get_domain(
user_ref['domain_id'])['name']
entity['user'] = {'id': user_ref['id'],
'name': user_ref['name'],
'domain': {'id': user_ref['domain_id'],
'name': dmn_name}}
attributes_for_links['user_id'] = user_ref['id']
else:
dmn_name = self.resource_api.get_domain(
group_ref['domain_id'])['name']
entity['group'] = {'id': group_ref['id'],
'name': group_ref['name'],
'domain': {
'id': group_ref['domain_id'],
'name': dmn_name}}
attributes_for_links['group_id'] = group_ref['id']
if role_ref:
entity['role'] = {'id': role_ref['id'],
'name': role_ref['name']}
attributes_for_links['role_id'] = role_ref['id']
if inherited_assignment:
entity['scope']['OS-INHERIT:inherited_to'] = 'projects'
attributes_for_links['inherited_to_projects'] = True
entity['links']['assignment'] = self.build_role_assignment_link(
**attributes_for_links)
return entity

View File

@ -1429,6 +1429,98 @@ class AssignmentInheritanceTestCase(test_v3.RestfulTestCase,
inherited_to_projects=True)
self.assertRoleAssignmentInListResponse(r, up_entity)
def test_list_role_assignments_include_names(self):
"""Call ``GET /role_assignments with include names``.
Test Plan:
- Create a domain with a group and a user
- Create a project with a group and a user
"""
role1 = unit.new_role_ref()
self.role_api.create_role(role1['id'], role1)
user1 = unit.create_user(self.identity_api, domain_id=self.domain_id)
group = unit.new_group_ref(domain_id=self.domain_id)
group = self.identity_api.create_group(group)
project1 = unit.new_project_ref(domain_id=self.domain_id)
self.resource_api.create_project(project1['id'], project1)
expected_entity1 = self.build_role_assignment_entity_include_names(
role_ref=role1,
project_ref=project1,
user_ref=user1)
self.put(expected_entity1['links']['assignment'])
expected_entity2 = self.build_role_assignment_entity_include_names(
role_ref=role1,
domain_ref=self.domain,
group_ref=group)
self.put(expected_entity2['links']['assignment'])
expected_entity3 = self.build_role_assignment_entity_include_names(
role_ref=role1,
domain_ref=self.domain,
user_ref=user1)
self.put(expected_entity3['links']['assignment'])
expected_entity4 = self.build_role_assignment_entity_include_names(
role_ref=role1,
project_ref=project1,
group_ref=group)
self.put(expected_entity4['links']['assignment'])
collection_url_domain = (
'/role_assignments?include_names&scope.domain.id=%(domain_id)s' % {
'domain_id': self.domain_id})
rs_domain = self.get(collection_url_domain)
collection_url_project = (
'/role_assignments?include_names&'
'scope.project.id=%(project_id)s' % {
'project_id': project1['id']})
rs_project = self.get(collection_url_project)
collection_url_group = (
'/role_assignments?include_names&group.id=%(group_id)s' % {
'group_id': group['id']})
rs_group = self.get(collection_url_group)
collection_url_user = (
'/role_assignments?include_names&user.id=%(user_id)s' % {
'user_id': user1['id']})
rs_user = self.get(collection_url_user)
collection_url_role = (
'/role_assignments?include_names&role.id=%(role_id)s' % {
'role_id': role1['id']})
rs_role = self.get(collection_url_role)
# Make sure all entities were created successfully
self.assertEqual(rs_domain.status_int, http_client.OK)
self.assertEqual(rs_project.status_int, http_client.OK)
self.assertEqual(rs_group.status_int, http_client.OK)
self.assertEqual(rs_user.status_int, http_client.OK)
# Make sure we can get back the correct number of entities
self.assertValidRoleAssignmentListResponse(
rs_domain,
expected_length=2,
resource_url=collection_url_domain)
self.assertValidRoleAssignmentListResponse(
rs_project,
expected_length=2,
resource_url=collection_url_project)
self.assertValidRoleAssignmentListResponse(
rs_group,
expected_length=2,
resource_url=collection_url_group)
self.assertValidRoleAssignmentListResponse(
rs_user,
expected_length=2,
resource_url=collection_url_user)
self.assertValidRoleAssignmentListResponse(
rs_role,
expected_length=4,
resource_url=collection_url_role)
# Verify all types of entities have the correct format
self.assertRoleAssignmentInListResponse(rs_domain, expected_entity2)
self.assertRoleAssignmentInListResponse(rs_project, expected_entity1)
self.assertRoleAssignmentInListResponse(rs_group, expected_entity4)
self.assertRoleAssignmentInListResponse(rs_user, expected_entity3)
self.assertRoleAssignmentInListResponse(rs_role, expected_entity1)
def test_list_role_assignments_for_disabled_inheritance_extension(self):
"""Call ``GET /role_assignments with inherited domain grants``.

View File

@ -0,0 +1,6 @@
---
features:
- >
[`bug 1479569 <https://bugs.launchpad.net/keystone/+bug/1479569>`_]
Names have been added to list role assignments, rather than returning
just the internal ids of the objects the names are also returned.