Server-side filtering for Identity

Implements server-side filtering in Identity->Projects

Projects (Only V3 supports server filtering)
Users (Only V3 supports server filtering)
Groups
Roles

This filtering method allows a user to filter by
several fields:
- Name
- UUID
- Enabled

Cannot implement filter by email ttps://review.openstack.org/#/c/110970/

Implements blueprint: server-side-filtering
Co-Authored-By: Daniel Castellanos <luis.daniel.castellanos@intel.com>

Change-Id: I37d6afdef84593e2779d21bec0c2f55e2794ab78
This commit is contained in:
Eddie Ramirez 2016-06-01 21:56:41 +00:00 committed by Luis Daniel Castellanos
parent bff48543ba
commit bc1fb4910b
13 changed files with 150 additions and 66 deletions

View File

@ -315,7 +315,7 @@ def tenant_list(request, paginate=False, marker=None, domain=None, user=None,
admin=True, filters=None):
manager = VERSIONS.get_project_manager(request, admin=admin)
page_size = utils.get_page_size(request)
tenants = []
limit = None
if paginate:
limit = page_size + 1
@ -341,7 +341,15 @@ def tenant_list(request, paginate=False, marker=None, domain=None, user=None,
}
if filters is not None:
kwargs.update(filters)
tenants = manager.list(**kwargs)
if 'id' in kwargs:
try:
tenants = [tenant_get(request, kwargs['id'])]
except keystone_exceptions.NotFound:
tenants = []
except Exception:
exceptions.handle(request)
else:
tenants = manager.list(**kwargs)
return tenants, has_more_data
@ -360,6 +368,7 @@ def tenant_update(request, project, name=None, description=None,
def user_list(request, project=None, domain=None, group=None, filters=None):
users = []
if VERSIONS.active < 3:
kwargs = {"tenant_id": project}
else:
@ -370,7 +379,13 @@ def user_list(request, project=None, domain=None, group=None, filters=None):
}
if filters is not None:
kwargs.update(filters)
users = keystoneclient(request, admin=True).users.list(**kwargs)
if 'id' in kwargs:
try:
users = [user_get(request, kwargs['id'])]
except keystone_exceptions.NotFound:
raise exceptions.NotFound()
else:
users = keystoneclient(request, admin=True).users.list(**kwargs)
return [VERSIONS.upgrade_v2_user(user) for user in users]
@ -523,9 +538,23 @@ def group_delete(request, group_id):
return manager.delete(group_id)
def group_list(request, domain=None, project=None, user=None):
def group_list(request, domain=None, project=None, user=None, filters=None):
manager = keystoneclient(request, admin=True).groups
groups = manager.list(user=user, domain=domain)
groups = []
kwargs = {
"domain": domain,
"user": user,
"name": None
}
if filters is not None:
kwargs.update(filters)
if 'id' in kwargs:
try:
groups = [manager.get(kwargs['id'])]
except keystone_exceptions.NotFound:
raise exceptions.NotFound()
else:
groups = manager.list(**kwargs)
if project:
project_groups = []
@ -534,7 +563,6 @@ def group_list(request, domain=None, project=None, user=None):
if roles and len(roles) > 0:
project_groups.append(group)
groups = project_groups
return groups
@ -619,9 +647,23 @@ def role_delete(request, role_id):
return manager.delete(role_id)
def role_list(request):
def role_list(request, filters=None):
"""Returns a global list of available roles."""
return keystoneclient(request, admin=True).roles.list()
manager = keystoneclient(request, admin=True).roles
roles = []
kwargs = {}
if filters is not None:
kwargs.update(filters)
if 'id' in kwargs:
try:
roles = [manager.get(kwargs['id'])]
except keystone_exceptions.NotFound:
roles = []
except Exception:
exceptions.handle(request)
else:
roles = manager.list(**kwargs)
return roles
def roles_for_user(request, user, project=None, domain=None):

View File

@ -100,16 +100,9 @@ class ManageUsersLink(tables.LinkAction):
class GroupFilterAction(tables.FilterAction):
def filter(self, table, groups, filter_string):
"""Naive case-insensitive search."""
q = filter_string.lower()
def comp(group):
if q in group.name.lower():
return True
return False
return filter(comp, groups)
filter_type = "server"
filter_choices = (("name", _("Group Name ="), True),
("id", _("Group ID ="), True))
class GroupsTable(tables.DataTable):

View File

@ -48,10 +48,12 @@ class GroupsViewTests(test.BaseAdminViewTests):
def test_index(self):
domain_id = self._get_domain_id()
groups = self._get_groups(domain_id)
filters = {}
domain = self.domains.get(id="1")
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
api.keystone.group_list(IgnoreArg(), domain=domain_id) \
api.keystone.group_list(IgnoreArg(),
domain=domain_id,
filters=filters) \
.AndReturn(groups)
self.mox.ReplayAll()
@ -72,7 +74,7 @@ class GroupsViewTests(test.BaseAdminViewTests):
'get_effective_domain_id')})
def test_index_with_domain(self):
domain = self.domains.get(id="1")
filters = {}
self.setSessionValues(domain_context=domain.id,
domain_context_name=domain.name)
groups = self._get_groups(domain.id)
@ -80,7 +82,8 @@ class GroupsViewTests(test.BaseAdminViewTests):
api.keystone.get_effective_domain_id(IgnoreArg()).AndReturn(domain.id)
api.keystone.group_list(IsA(http.HttpRequest),
domain=domain.id).AndReturn(groups)
domain=domain.id,
filters=filters).AndReturn(groups)
self.mox.ReplayAll()
@ -103,8 +106,11 @@ class GroupsViewTests(test.BaseAdminViewTests):
domain_id = self._get_domain_id()
groups = self._get_groups(domain_id)
domain = self.domains.get(id="1")
filters = {}
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
api.keystone.group_list(IgnoreArg(), domain=domain_id) \
api.keystone.group_list(IgnoreArg(),
domain=domain_id,
filters=filters) \
.AndReturn(groups)
api.keystone.keystone_can_edit_group() \
.MultipleTimes().AndReturn(False)
@ -195,11 +201,14 @@ class GroupsViewTests(test.BaseAdminViewTests):
'group_delete')})
def test_delete_group(self):
domain_id = self._get_domain_id()
filters = {}
group = self.groups.get(id="2")
domain = self.domains.get(id="1")
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
api.keystone.group_list(IgnoreArg(), domain=domain_id) \
api.keystone.group_list(IgnoreArg(),
domain=domain_id,
filters=filters) \
.AndReturn(self.groups.list())
api.keystone.group_delete(IgnoreArg(), group.id)

View File

@ -40,12 +40,13 @@ class IndexView(tables.DataTableView):
def get_data(self):
groups = []
domain_id = api.keystone.get_effective_domain_id(self.request)
filters = self.get_filters()
if policy.check((("identity", "identity:list_groups"),),
self.request):
try:
groups = api.keystone.group_list(self.request,
domain=domain_id)
domain=domain_id,
filters=filters)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve group list.'))

View File

@ -198,17 +198,13 @@ class DeleteTenantsAction(policy.PolicyTargetMixin, tables.DeleteAction):
class TenantFilterAction(tables.FilterAction):
def filter(self, table, tenants, filter_string):
"""Really naive case-insensitive search."""
# FIXME(gabriel): This should be smarter. Written for demo purposes.
q = filter_string.lower()
def comp(tenant):
if q in tenant.name.lower():
return True
return False
return filter(comp, tenants)
if api.keystone.VERSIONS.active < 3:
filter_type = "query"
else:
filter_type = "server"
filter_choices = (('name', _("Project Name ="), True),
('id', _("Project ID ="), True),
('enabled', _("Enabled ="), True, _('e.g. Yes/No')))
class UpdateRow(tables.Row):

View File

@ -48,10 +48,12 @@ class TenantsViewTests(test.BaseAdminViewTests):
quotas: ('enabled_quotas',)})
def test_index(self):
domain = self.domains.get(id="1")
filters = {}
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
api.keystone.tenant_list(IsA(http.HttpRequest),
domain=None,
paginate=True,
filters=filters,
marker=None) \
.AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
@ -70,7 +72,7 @@ class TenantsViewTests(test.BaseAdminViewTests):
quotas: ('enabled_quotas',)})
def test_index_with_domain_context(self):
domain = self.domains.get(id="1")
filters = {}
self.setSessionValues(domain_context=domain.id,
domain_context_name=domain.name)
@ -82,7 +84,8 @@ class TenantsViewTests(test.BaseAdminViewTests):
api.keystone.tenant_list(IsA(http.HttpRequest),
domain=domain.id,
paginate=True,
marker=None) \
marker=None,
filters=filters) \
.AndReturn([domain_tenants, False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
@ -101,10 +104,12 @@ class ProjectsViewNonAdminTests(test.TestCase):
'domain_lookup')})
def test_index(self):
domain = self.domains.get(id="1")
filters = {}
api.keystone.tenant_list(IsA(http.HttpRequest),
user=self.user.id,
paginate=True,
marker=None,
filters=filters,
admin=False) \
.AndReturn([self.tenants.list(), False])
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:

View File

@ -78,9 +78,8 @@ class IndexView(tables.DataTableView):
tenants = []
marker = self.request.GET.get(
project_tables.TenantsTable._meta.pagination_param, None)
self._more = False
filters = self.get_filters()
if policy.check((("identity", "identity:list_projects"),),
self.request):
domain_context = api.keystone.get_effective_domain_id(self.request)
@ -89,6 +88,7 @@ class IndexView(tables.DataTableView):
self.request,
domain=domain_context,
paginate=True,
filters=filters,
marker=marker)
except Exception:
exceptions.handle(self.request,
@ -101,6 +101,7 @@ class IndexView(tables.DataTableView):
user=self.request.user.id,
paginate=True,
marker=marker,
filters=filters,
admin=False)
except Exception:
exceptions.handle(self.request,
@ -114,6 +115,7 @@ class IndexView(tables.DataTableView):
domain_lookup = api.keystone.domain_lookup(self.request)
for t in tenants:
t.domain_name = domain_lookup.get(t.domain_id)
return tenants

View File

@ -70,11 +70,9 @@ class DeleteRolesAction(tables.DeleteAction):
class RoleFilterAction(tables.FilterAction):
def filter(self, table, roles, filter_string):
"""Naive case-insensitive search."""
q = filter_string.lower()
return [role for role in roles
if q in role.name.lower()]
filter_type = "server"
filter_choices = (("name", _("Role Name ="), True),
("id", _("Role ID ="), True))
class RolesTable(tables.DataTable):

View File

@ -30,7 +30,11 @@ ROLES_UPDATE_URL = reverse('horizon:identity:roles:update', args=[1])
class RolesViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('role_list',)})
def test_index(self):
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
filters = {}
api.keystone.role_list(IgnoreArg(),
filters=filters) \
.AndReturn(self.roles.list())
self.mox.ReplayAll()
@ -45,7 +49,11 @@ class RolesViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('role_list',
'keystone_can_edit_role', )})
def test_index_with_keystone_can_edit_role_false(self):
api.keystone.role_list(IgnoreArg()).AndReturn(self.roles.list())
filters = {}
api.keystone.role_list(IgnoreArg(),
filters=filters) \
.AndReturn(self.roles.list())
api.keystone.keystone_can_edit_role() \
.MultipleTimes().AndReturn(False)
self.mox.ReplayAll()
@ -97,8 +105,10 @@ class RolesViewTests(test.BaseAdminViewTests):
@test.create_stubs({api.keystone: ('role_list', 'role_delete')})
def test_delete(self):
role = self.roles.first()
filters = {}
api.keystone.role_list(IsA(http.HttpRequest)) \
api.keystone.role_list(IsA(http.HttpRequest),
filters=filters) \
.AndReturn(self.roles.list())
api.keystone.role_delete(IsA(http.HttpRequest),
role.id).AndReturn(None)

View File

@ -38,10 +38,12 @@ class IndexView(tables.DataTableView):
def get_data(self):
roles = []
filters = self.get_filters()
if policy.check((("identity", "identity:list_roles"),),
self.request):
try:
roles = api.keystone.role_list(self.request)
roles = api.keystone.role_list(self.request,
filters=filters)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve roles list.'))

View File

@ -157,12 +157,13 @@ class DeleteUsersAction(policy.PolicyTargetMixin, tables.DeleteAction):
class UserFilterAction(tables.FilterAction):
def filter(self, table, users, filter_string):
"""Naive case-insensitive search."""
q = filter_string.lower()
return [user for user in users
if q in user.name.lower()
or q in (getattr(user, 'email', None) or '').lower()]
if api.keystone.VERSIONS.active < 3:
filter_type = "query"
else:
filter_type = "server"
filter_choices = (("name", _("User Name ="), True),
("id", _("User ID ="), True),
("enabled", _("Enabled ="), True, _('e.g. Yes/No')))
class UpdateRow(tables.Row):

View File

@ -60,12 +60,14 @@ class UsersViewTests(test.BaseAdminViewTests):
def test_index(self):
domain = self._get_default_domain()
domain_id = domain.id
filters = {}
users = self._get_users(domain_id)
api.keystone.get_effective_domain_id(IgnoreArg()).AndReturn(domain_id)
api.keystone.user_list(IgnoreArg(),
domain=domain_id).AndReturn(users)
domain=domain_id,
filters=filters).AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
@ -653,12 +655,16 @@ class UsersViewTests(test.BaseAdminViewTests):
def test_enable_user(self):
domain = self._get_default_domain()
domain_id = domain.id
filters = {}
user = self.users.get(id="2")
users = self._get_users(domain_id)
user.enabled = False
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
api.keystone.user_list(IgnoreArg(), domain=domain_id).AndReturn(users)
api.keystone.user_list(IgnoreArg(),
domain=domain_id,
filters=filters)\
.AndReturn(users)
api.keystone.user_update_enabled(IgnoreArg(),
user.id,
True).AndReturn(user)
@ -679,13 +685,16 @@ class UsersViewTests(test.BaseAdminViewTests):
def test_disable_user(self):
domain = self._get_default_domain()
domain_id = domain.id
filters = {}
user = self.users.get(id="2")
users = self._get_users(domain_id)
self.assertTrue(user.enabled)
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
api.keystone.user_list(IgnoreArg(),
domain=domain_id,
filters=filters)\
.AndReturn(users)
api.keystone.user_update_enabled(IgnoreArg(),
user.id,
@ -707,12 +716,15 @@ class UsersViewTests(test.BaseAdminViewTests):
def test_enable_disable_user_exception(self):
domain = self._get_default_domain()
domain_id = domain.id
filters = {}
user = self.users.get(id="2")
users = self._get_users(domain_id)
user.enabled = False
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
api.keystone.user_list(IgnoreArg(),
domain=domain_id,
filters=filters)\
.AndReturn(users)
api.keystone.user_update_enabled(IgnoreArg(), user.id, True) \
.AndRaise(self.exceptions.keystone)
@ -731,10 +743,13 @@ class UsersViewTests(test.BaseAdminViewTests):
def test_disabling_current_user(self):
domain = self._get_default_domain()
domain_id = domain.id
filters = {}
users = self._get_users(domain_id)
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
for i in range(0, 2):
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
api.keystone.user_list(IgnoreArg(),
domain=domain_id,
filters=filters) \
.AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
@ -754,6 +769,7 @@ class UsersViewTests(test.BaseAdminViewTests):
def test_disabling_current_user_domain_name(self):
domain = self._get_default_domain()
domains = self.domains.list()
filters = {}
domain_id = domain.id
users = self._get_users(domain_id)
domain_lookup = dict((d.id, d.name) for d in domains)
@ -764,7 +780,9 @@ class UsersViewTests(test.BaseAdminViewTests):
for i in range(0, 2):
api.keystone.domain_lookup(IgnoreArg()).AndReturn(domain_lookup)
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
api.keystone.user_list(IgnoreArg(),
domain=domain_id,
filters=filters) \
.AndReturn(users)
self.mox.ReplayAll()
@ -782,10 +800,13 @@ class UsersViewTests(test.BaseAdminViewTests):
def test_delete_user_with_improper_permissions(self):
domain = self._get_default_domain()
domain_id = domain.id
filters = {}
users = self._get_users(domain_id)
api.keystone.domain_get(IsA(http.HttpRequest), '1').AndReturn(domain)
for i in range(0, 2):
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
api.keystone.user_list(IgnoreArg(),
domain=domain_id,
filters=filters) \
.AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn({domain.id:
domain.name})
@ -806,6 +827,7 @@ class UsersViewTests(test.BaseAdminViewTests):
domain = self._get_default_domain()
domains = self.domains.list()
domain_id = domain.id
filters = {}
users = self._get_users(domain_id)
domain_lookup = dict((d.id, d.name) for d in domains)
@ -814,7 +836,9 @@ class UsersViewTests(test.BaseAdminViewTests):
u.domain_name = domain_lookup.get(u.domain_id)
for i in range(0, 2):
api.keystone.user_list(IgnoreArg(), domain=domain_id) \
api.keystone.user_list(IgnoreArg(),
domain=domain_id,
filters=filters) \
.AndReturn(users)
api.keystone.domain_lookup(IgnoreArg()).AndReturn(domain_lookup)

View File

@ -51,13 +51,14 @@ class IndexView(tables.DataTableView):
def get_data(self):
users = []
filters = self.get_filters()
if policy.check((("identity", "identity:list_users"),),
self.request):
domain_context = api.keystone.get_effective_domain_id(self.request)
try:
users = api.keystone.user_list(self.request,
domain=domain_context)
domain=domain_context,
filters=filters)
except Exception:
exceptions.handle(self.request,
_('Unable to retrieve user list.'))