diff --git a/openstack_dashboard/dashboards/identity/projects/tabs.py b/openstack_dashboard/dashboards/identity/projects/tabs.py index ac95f1ec32..d97362552b 100644 --- a/openstack_dashboard/dashboards/identity/projects/tabs.py +++ b/openstack_dashboard/dashboards/identity/projects/tabs.py @@ -97,8 +97,51 @@ class UsersTab(tabs.TableTab): roles_list=roles ) + def _get_users_from_groups(self, project_id, roles, project_users): + """Update with users which have role on project through a group. + + :param project_id: ID of the project + :param roles: list of roles from keystone + :param project_users: list to be updated with the users found + """ + + # For keystone.group_list project_id is not passed as argument because + # it is ignored when using admin credentials + # Get all groups (to be able to find group name) + groups = api.keystone.group_list(self.request) + group_names = {group.id: group.name for group in groups} + + # Get a dictionary {group_id: [role_id_1, role_id_2]} + project_groups_roles = api.keystone.get_project_groups_roles( + self.request, + project=project_id) + + for group_id in project_groups_roles: + group_users = api.keystone.user_list(self.request, + group=group_id) + group_roles_names = [ + role.name for role in roles + if role.id in project_groups_roles[group_id]] + + roles_from_group = [(role_name, group_names[group_id]) + for role_name in group_roles_names] + + for user in group_users: + if user.id not in project_users: + # New user: Add the user to the list + project_users[user.id] = user + project_users[user.id].roles = [] + project_users[user.id].roles_from_groups = [] + + # Add roles from group + project_users[user.id].roles_from_groups.extend( + roles_from_group) + def get_userstable_data(self): - """Get users with roles on the project.""" + """Get users with roles on the project. + + Roles can be applied directly on the project or through a group. + """ project_users = {} project = self.tab_group.kwargs['project'] @@ -112,6 +155,12 @@ class UsersTab(tabs.TableTab): roles=roles, project_users=project_users) + # Update project_users with users which have role indirectly on + # the project, (through a group) + self._get_users_from_groups(project_id=project.id, + roles=roles, + project_users=project_users) + except Exception: exceptions.handle(self.request, _("Unable to display the users of this project.") diff --git a/openstack_dashboard/dashboards/identity/projects/tests.py b/openstack_dashboard/dashboards/identity/projects/tests.py index c68835a84f..8197381a5e 100644 --- a/openstack_dashboard/dashboards/identity/projects/tests.py +++ b/openstack_dashboard/dashboards/identity/projects/tests.py @@ -1350,6 +1350,14 @@ class DetailProjectViewTests(test.BaseAdminViewTests): self.tenant.id) self.mock_enabled_quotas.assert_called_once_with(test.IsHttpRequest()) + def _get_users_in_group(self, group_id): + users_in_group = [membership["user_id"] for membership in + self.user_group_membership.list() + if membership["group_id"] == group_id] + users = [user for user in self.users.list() if + user.id in users_in_group] + return users + def _project_user_roles(self, role_assignments): roles = {} for role_assignment in role_assignments: @@ -1358,25 +1366,45 @@ class DetailProjectViewTests(test.BaseAdminViewTests): role_assignment.role["id"]] return roles + def _project_group_roles(self, role_assignments): + roles = {} + for role_assignment in role_assignments: + if hasattr(role_assignment, 'group'): + roles[role_assignment.group['id']] = [ + role_assignment.role["id"]] + return roles + @test.create_mocks({api.keystone: ('tenant_get', 'user_list', 'get_project_users_roles', - 'role_list',), + 'get_project_groups_roles', + 'role_list', + 'group_list'), quotas: ('enabled_quotas',)}) def test_detail_view_users_tab(self): project = self.tenants.first() users = self.users.filter(domain_id=project.domain_id) + groups = self.groups.filter(domain_id=project.domain_id) role_assignments = self.role_assignments.filter( scope={'project': {'id': project.id}}) + # {user_id: [role_id1, role_id2]} as returned by the api project_users_roles = self._project_user_roles(role_assignments) + # {group_id: [role_id1, role_id2]} as returned by the api + project_groups_roles = self._project_group_roles(role_assignments) # Prepare mocks self.mock_tenant_get.return_value = project self.mock_enabled_quotas.return_value = ('instances',) self.mock_role_list.return_value = self.roles.list() - self.mock_user_list.return_value = users + def _user_list_side_effect(request, group=None): + if group: + return self._get_users_in_group(group) + return users + self.mock_user_list.side_effect = _user_list_side_effect + self.mock_group_list.return_value = groups self.mock_get_project_users_roles.return_value = project_users_roles + self.mock_get_project_groups_roles.return_value = project_groups_roles # Get project details view on user tab url = PROJECT_DETAIL_URL % [project.id] @@ -1392,27 +1420,40 @@ class DetailProjectViewTests(test.BaseAdminViewTests): # Check the content of the table users_expected = { - '1': {'roles': ['admin'], }, - '2': {'roles': ['_member_'], }, - '3': {'roles': ['_member_'], }, + '1': {'roles': ['admin'], + 'roles_from_groups': [('_member_', 'group_one'), ], }, + '2': {'roles': ['_member_'], + 'roles_from_groups': [], }, + '3': {'roles': ['_member_'], + 'roles_from_groups': [('_member_', 'group_one'), ], }, + '4': {'roles': [], + 'roles_from_groups': [('_member_', 'group_one'), ], } } users_id_observed = [user.id for user in res.context["userstable_table"].data] self.assertItemsEqual(users_expected.keys(), users_id_observed) - # Check the users roles + # Check the users groups and roles for user in res.context["userstable_table"].data: self.assertItemsEqual(users_expected[user.id]["roles"], user.roles) + self.assertItemsEqual(users_expected[user.id]["roles_from_groups"], + user.roles_from_groups) self.mock_tenant_get.assert_called_once_with(test.IsHttpRequest(), self.tenant.id) self.mock_enabled_quotas.assert_called_once_with(test.IsHttpRequest()) self.mock_role_list.assert_called_once_with(test.IsHttpRequest()) + self.mock_group_list.assert_called_once_with(test.IsHttpRequest()) self.mock_get_project_users_roles.assert_called_once_with( test.IsHttpRequest(), project=project.id) - self.mock_user_list.assert_called_once_with(test.IsHttpRequest()) + self.mock_get_project_groups_roles.assert_called_once_with( + test.IsHttpRequest(), project=project.id) + calls = [mock.call(test.IsHttpRequest()), + mock.call(test.IsHttpRequest(), group="1"), ] + + self.mock_user_list.assert_has_calls(calls) @test.create_mocks({api.keystone: ("tenant_get", "role_list",), diff --git a/openstack_dashboard/dashboards/identity/projects/users/tables.py b/openstack_dashboard/dashboards/identity/projects/users/tables.py index aeafc7a0d2..de38635a1b 100644 --- a/openstack_dashboard/dashboards/identity/projects/users/tables.py +++ b/openstack_dashboard/dashboards/identity/projects/users/tables.py @@ -28,6 +28,14 @@ class UsersTable(users_tables.UsersTable): widget=forms.Textarea(attrs={'rows': 4}), required=False)) + groups_roles = tables.Column( + lambda obj: ", ".join("%s (%s)" % (role, group) for role, group in + getattr(obj, 'roles_from_groups')), + verbose_name=_('Roles from Groups'), + form_field=forms.CharField( + widget=forms.Textarea(attrs={'rows': 4}), + required=False)) + class Meta(object): name = "userstable" verbose_name = _("Users") diff --git a/openstack_dashboard/test/test_data/keystone_data.py b/openstack_dashboard/test/test_data/keystone_data.py index 69f2a7af47..2b7f231386 100644 --- a/openstack_dashboard/test/test_data/keystone_data.py +++ b/openstack_dashboard/test/test_data/keystone_data.py @@ -127,6 +127,7 @@ def data(TEST): TEST.domains = utils.TestDataContainer() TEST.users = utils.TestDataContainer() TEST.groups = utils.TestDataContainer() + TEST.user_group_membership = utils.TestDataContainer() TEST.tenants = utils.TestDataContainer() TEST.role_assignments = utils.TestDataContainer() TEST.roles = utils.TestDataContainer() @@ -252,6 +253,19 @@ def data(TEST): TEST.groups.add(group, group2, group3, group4, group5) + user_group_membership = {"user_id": "1", "group_id": "1"} + TEST.user_group_membership.add(user_group_membership) + user_group_membership = {"user_id": "1", "group_id": "2"} + TEST.user_group_membership.add(user_group_membership) + user_group_membership = {"user_id": "2", "group_id": "2"} + TEST.user_group_membership.add(user_group_membership) + user_group_membership = {"user_id": "2", "group_id": "3"} + TEST.user_group_membership.add(user_group_membership) + user_group_membership = {"user_id": "3", "group_id": "1"} + TEST.user_group_membership.add(user_group_membership) + user_group_membership = {"user_id": "4", "group_id": "1"} + TEST.user_group_membership.add(user_group_membership) + role_assignments_dict = {'user': {'id': '1'}, 'role': {'id': '1'}, 'scope': {'project': {'id': '1'}}}