From ba82055f05df0ce5bc683e8b81a05f55931f66e7 Mon Sep 17 00:00:00 2001 From: David Gutman Date: Thu, 13 Sep 2018 15:07:13 +0200 Subject: [PATCH] Add role assignment tab in the user details view. Add an extra tab "role assignments" in the user details view which displays all the project/role domain/role of the current user. The role assignments are displayed in a table. The project/domain column is clickable and forward to the project /domain detail view. Change-Id: Iefe17856e60b84d089f722c3a30e9ede21d8ce47 Partial-Bug: #1792524 Co-Authored-By: Akihiro Motoki --- .../users/role_assignments/__init__.py | 0 .../identity/users/role_assignments/tables.py | 93 +++++++++++++++++++ .../dashboards/identity/users/tabs.py | 34 ++++++- .../dashboards/identity/users/tests.py | 90 ++++++++++++++++++ 4 files changed, 216 insertions(+), 1 deletion(-) create mode 100644 openstack_dashboard/dashboards/identity/users/role_assignments/__init__.py create mode 100644 openstack_dashboard/dashboards/identity/users/role_assignments/tables.py diff --git a/openstack_dashboard/dashboards/identity/users/role_assignments/__init__.py b/openstack_dashboard/dashboards/identity/users/role_assignments/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openstack_dashboard/dashboards/identity/users/role_assignments/tables.py b/openstack_dashboard/dashboards/identity/users/role_assignments/tables.py new file mode 100644 index 0000000000..386f8e4360 --- /dev/null +++ b/openstack_dashboard/dashboards/identity/users/role_assignments/tables.py @@ -0,0 +1,93 @@ +# 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 django import urls +from django.utils.translation import ugettext_lazy as _ + +from horizon import forms +from horizon import tables + +from openstack_dashboard import policy + + +def get_project_name(datum): + if "project" in datum.scope: + if "name" in datum.scope["project"]: + return datum.scope["project"]["name"] + return datum.scope["project"]["id"] + + +def get_project_link(datum, request): + if "project" not in datum.scope: + return + if policy.check((("identity", "identity:get_project"),), + request, target={"project": datum}): + return urls.reverse("horizon:identity:projects:detail", + args=(datum.scope["project"]["id"],)) + + +def get_domain_name(datum): + if "domain" in datum.scope: + if "name" in datum.scope["domain"]: + return datum.scope["domain"]["name"] + return datum.scope["domain"]["id"] + + +def get_system_scope(datum): + if "system" in datum.scope: + return ', '.join(datum.scope["system"].keys()) + + +def get_role_name(datum): + if "name" in datum.role: + return datum.role["name"] + return datum.role["id"] + + +class RoleAssignmentsTable(tables.DataTable): + + project = tables.WrappingColumn(get_project_name, + verbose_name=_('Project'), + link=get_project_link, + form_field=forms.CharField(max_length=64)) + + domain = tables.WrappingColumn(get_domain_name, + verbose_name=_('Domain'), + form_field=forms.CharField(max_length=64)) + + system = tables.WrappingColumn(get_system_scope, + verbose_name=_('System Scope'), + form_field=forms.CharField(max_length=64)) + + role = tables.Column(get_role_name, + verbose_name=_('Role'), + form_field=forms.CharField( + widget=forms.Textarea(attrs={'rows': 4}), + required=False)) + + def get_object_id(self, datum): + """Identifier of the role assignment.""" + + # Role assignment doesn't have identifier so one will be created + # from the identifier of scope, user and role. This will guaranty the + # unicity. + scope_id = "" + if "project" in datum.scope: + scope_id = datum.scope["project"]["id"] + elif "domain" in datum.scope: + scope_id = datum.scope["domain"]["id"] + + return "%s%s%s" % (datum.user["id"], datum.role["id"], scope_id) + + class Meta(object): + name = "roleassignmentstable" + verbose_name = _("Role assignments") diff --git a/openstack_dashboard/dashboards/identity/users/tabs.py b/openstack_dashboard/dashboards/identity/users/tabs.py index 55dfd7ca94..4ac8f45a23 100644 --- a/openstack_dashboard/dashboards/identity/users/tabs.py +++ b/openstack_dashboard/dashboards/identity/users/tabs.py @@ -13,8 +13,13 @@ from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions from horizon import tabs +from openstack_dashboard import api +from openstack_dashboard.dashboards.identity.users.role_assignments \ + import tables as role_assignments_tables + class OverviewTab(tabs.Tab): """Overview of the user. @@ -29,6 +34,33 @@ class OverviewTab(tabs.Tab): return {"user": self.tab_group.kwargs['user']} +class RoleAssignmentsTab(tabs.TableTab): + """Role assignment of the user to domain/project.""" + table_classes = (role_assignments_tables.RoleAssignmentsTable,) + name = _("Role assignments") + slug = "roleassignments" + template_name = "horizon/common/_detail_table.html" + preload = False + + def get_roleassignmentstable_data(self): + user = self.tab_group.kwargs['user'] + + try: + # Get all the roles of the user + user_roles = api.keystone.role_assignments_list( + self.request, user=user, include_subtree=False, + include_names=True) + + return user_roles + + except Exception: + exceptions.handle( + self.request, + _("Unable to display the role assignments of this user.")) + + return [] + + class UserDetailTabs(tabs.DetailTabsGroup): slug = "user_details" - tabs = (OverviewTab,) + tabs = (OverviewTab, RoleAssignmentsTab,) diff --git a/openstack_dashboard/dashboards/identity/users/tests.py b/openstack_dashboard/dashboards/identity/users/tests.py index 271d787806..280aa4877d 100644 --- a/openstack_dashboard/dashboards/identity/users/tests.py +++ b/openstack_dashboard/dashboards/identity/users/tests.py @@ -922,6 +922,96 @@ class UsersViewTests(test.BaseAdminViewTests): self.mock_tenant_get.assert_called_once_with(test.IsHttpRequest(), user.project_id) + @test.create_mocks({api.keystone: ('domain_get', + 'user_get', + 'tenant_get', + 'role_assignments_list')}) + def test_detail_view_role_assignments_tab(self): + """Test the role assignments tab of the detail view .""" + domain = self._get_default_domain() + user = self.users.get(id="1") + tenant = self.tenants.get(id=user.project_id) + role_assignments = self.role_assignments.filter(user={'id': user.id}) + + self.mock_domain_get.return_value = domain + self.mock_user_get.return_value = user + self.mock_tenant_get.return_value = tenant + self.mock_role_assignments_list.return_value = role_assignments + + # Url of the role assignment tab of the detail view + url = USER_DETAIL_URL % [user.id] + detail_view = tabs.UserDetailTabs(self.request, user=user) + role_assignments_tab_link = "?%s=%s" % ( + detail_view.param_name, + detail_view.get_tab("roleassignments").get_id() + ) + url += role_assignments_tab_link + + res = self.client.get(url) + + # Check the template expected has been used + self.assertTemplateUsed(res, + "horizon/common/_detail_table.html") + + # Check the table contains the expected data + role_assignments_expected = role_assignments + role_assignments_observed = res.context["table"].data + self.assertItemsEqual(role_assignments_expected, + role_assignments_observed) + + self.mock_domain_get.assert_called_once_with(test.IsHttpRequest(), '1') + self.mock_user_get.assert_called_once_with(test.IsHttpRequest(), '1', + admin=False) + self.mock_tenant_get.assert_called_once_with(test.IsHttpRequest(), + user.project_id) + self.mock_role_assignments_list.assert_called_once_with( + test.IsHttpRequest(), user=user, include_subtree=False, + include_names=True) + + @test.create_mocks({api.keystone: ('domain_get', + 'user_get', + 'tenant_get', + 'role_assignments_list')}) + def test_detail_view_role_assignments_tab_with_exception(self): + """Test the role assignments tab with exception. + + The table is displayed empty and an error message pop if the role + assignment request fails. + """ + domain = self._get_default_domain() + user = self.users.get(id="1") + tenant = self.tenants.get(id=user.project_id) + + self.mock_domain_get.return_value = domain + self.mock_user_get.return_value = user + self.mock_tenant_get.return_value = tenant + self.mock_role_assignments_list.side_effect = self.exceptions.keystone + + # Url of the role assignment tab of the detail view + url = USER_DETAIL_URL % [user.id] + detail_view = tabs.UserDetailTabs(self.request, user=user) + role_assignments_tab_link = "?%s=%s" % ( + detail_view.param_name, + detail_view.get_tab("roleassignments").get_id() + ) + url += role_assignments_tab_link + + res = self.client.get(url) + + # Check the role assignment table is empty + self.assertEqual(res.context["table"].data, []) + # Check one error message is displayed + self.assertMessageCount(res, error=1) + + self.mock_domain_get.assert_called_once_with(test.IsHttpRequest(), '1') + self.mock_user_get.assert_called_once_with(test.IsHttpRequest(), '1', + admin=False) + self.mock_tenant_get.assert_called_once_with(test.IsHttpRequest(), + user.project_id) + self.mock_role_assignments_list.assert_called_once_with( + test.IsHttpRequest(), user=user, include_subtree=False, + include_names=True) + @test.create_mocks({api.keystone: ('user_get', 'domain_get', 'tenant_list',)})