From db235a7e785a4fded6208f2fb25f69267d93ba8c Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Mon, 2 Apr 2018 04:53:03 -0400 Subject: [PATCH] Add support for users ACLs. Add support for groups ACLs. Signed-off-by: Roberto Rosario --- HISTORY.rst | 6 +- mayan/apps/user_management/apps.py | 8 +- .../apps/user_management/tests/test_views.py | 270 +++++++++--------- mayan/apps/user_management/views.py | 24 +- 4 files changed, 161 insertions(+), 147 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 12ae34e55a..2d85c6440b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -103,7 +103,11 @@ - Mark the feature to detect and fix the orientatin of PDF as experimental. - Don't show documents with 0 duplicates in the duplicated document list. - Clean up the duplicated document model after a document is deleted. -- Add support Role ACLs. +- Add support for roles ACLs. +- Add support for users ACLs. +- Add support for groups ACLs. +- Sort permission namespaces and permissions in the role permission views. + 2.7.3 (2017-09-11) ================== diff --git a/mayan/apps/user_management/apps.py b/mayan/apps/user_management/apps.py index a7acd3cf67..908491f7bc 100644 --- a/mayan/apps/user_management/apps.py +++ b/mayan/apps/user_management/apps.py @@ -5,6 +5,8 @@ from django.contrib.auth import get_user_model from django.utils.translation import ugettext_lazy as _ from acls import ModelPermission +from acls.links import link_acl_list +from acls.permissions import permission_acl_edit, permission_acl_view from common import menu_multi_item, menu_object, menu_secondary, menu_setup from common.apps import MayanAppConfig from common.widgets import two_state_template @@ -70,12 +72,14 @@ class UserManagementApp(MayanAppConfig): ) ModelPermission.register( model=Group, permissions=( + permission_acl_edit, permission_acl_view, permission_group_delete, permission_group_edit, permission_group_view, ) ) ModelPermission.register( model=User, permissions=( + permission_acl_edit, permission_acl_view, permission_user_delete, permission_user_edit, permission_user_view ) @@ -112,12 +116,12 @@ class UserManagementApp(MayanAppConfig): sources=(Group,) ) menu_object.bind_links( - links=(link_group_delete,), position=99, sources=(Group,) + links=(link_acl_list, link_group_delete,), position=99, sources=(Group,) ) menu_object.bind_links( links=( link_user_edit, link_user_set_password, link_user_groups, - link_user_delete + link_acl_list, link_user_delete ), sources=(User,) ) menu_secondary.bind_links( diff --git a/mayan/apps/user_management/tests/test_views.py b/mayan/apps/user_management/tests/test_views.py index 15b13be5a9..5af70f354c 100644 --- a/mayan/apps/user_management/tests/test_views.py +++ b/mayan/apps/user_management/tests/test_views.py @@ -14,10 +14,14 @@ from metadata.tests.literals import ( ) from ..permissions import ( - permission_user_delete, permission_user_edit, permission_user_view + permission_user_create, permission_user_delete, permission_user_edit, + permission_user_view ) -from .literals import TEST_USER_PASSWORD_EDITED, TEST_USER_USERNAME +from .literals import ( + TEST_USER_PASSWORD_EDITED, TEST_USER_USERNAME, TEST_USER_2_USERNAME, + TEST_USER_2_USERNAME_EDITED +) TEST_USER_TO_DELETE_USERNAME = 'user_to_delete' @@ -27,157 +31,157 @@ class UserManagementViewTestCase(GenericViewTestCase): super(UserManagementViewTestCase, self).setUp() self.login_user() + def _request_user_create_view(self): + return self.post( + viewname='user_management:user_add', data={ + 'username': TEST_USER_2_USERNAME + } + ) + + def test_user_create_view_no_permission(self): + response = self._request_user_create_view() + self.assertEqual(response.status_code, 403) + self.assertEqual(get_user_model().objects.count(), 2) + self.assertFalse(TEST_USER_2_USERNAME in get_user_model().objects.values_list('username', flat=True)) + + def test_user_create_view_with_permission(self): + self.grant_permission(permission=permission_user_create) + response = self._request_user_create_view() + self.assertEqual(response.status_code, 302) + self.assertEqual(get_user_model().objects.count(), 3) + self.assertTrue(TEST_USER_2_USERNAME in get_user_model().objects.values_list('username', flat=True)) + def _request_set_password(self, password): return self.post( - 'user_management:user_set_password', args=(self.user.pk,), data={ + viewname='user_management:user_set_password', args=(self.user_2.pk,), + data={ 'new_password1': password, 'new_password2': password } ) + def _create_test_user_2(self): + self.user_2 = get_user_model().objects.create( + username=TEST_USER_2_USERNAME + ) + + def test_user_set_password_view_no_access(self): + self._create_test_user_2() + response = self._request_set_password( + password=TEST_USER_PASSWORD_EDITED + ) + + self.assertEqual(response.status_code, 403) + + self.logout() + + with self.assertRaises(AssertionError): + self.login( + username=TEST_USER_2_USERNAME, password=TEST_USER_PASSWORD_EDITED + ) + + response = self.get('common:current_user_details') + + self.assertEqual(response.status_code, 302) + + def test_user_set_password_view_with_access(self): + self._create_test_user_2() + self.grant_access(permission=permission_user_edit, obj=self.user_2) + + response = self._request_set_password( + password=TEST_USER_PASSWORD_EDITED + ) + + self.assertEqual(response.status_code, 302) + + self.logout() + self.login( + username=TEST_USER_2_USERNAME, password=TEST_USER_PASSWORD_EDITED + ) + response = self.get('common:current_user_details') + + self.assertEqual(response.status_code, 200) + def _request_multiple_user_set_password(self, password): return self.post( 'user_management:user_multiple_set_password', data={ - 'id_list': self.user.pk, + 'id_list': self.user_2.pk, 'new_password1': password, 'new_password2': password - }, follow=True - ) - - def test_user_set_password_view_no_permissions(self): - self.grant_permission(permission=permission_user_view) - - response = self._request_set_password( - password=TEST_USER_PASSWORD_EDITED - ) - - self.assertEqual(response.status_code, 403) - - self.logout() - - with self.assertRaises(AssertionError): - self.login( - username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD_EDITED - ) - - response = self.get('common:current_user_details') - - self.assertEqual(response.status_code, 302) - - def test_user_set_password_view_with_permissions(self): - self.grant_permission(permission=permission_user_edit) - self.grant_permission(permission=permission_user_view) - - response = self._request_set_password( - password=TEST_USER_PASSWORD_EDITED - ) - - self.assertEqual(response.status_code, 302) - - self.logout() - self.login( - username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD_EDITED - ) - response = self.get('common:current_user_details') - - self.assertEqual(response.status_code, 200) - - def test_user_multiple_set_password_view_no_permissions(self): - self.grant_permission(permission=permission_user_view) - - response = self._request_multiple_user_set_password( - password=TEST_USER_PASSWORD_EDITED - ) - - self.assertEqual(response.status_code, 403) - - self.logout() - - with self.assertRaises(AssertionError): - self.login( - username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD_EDITED - ) - - response = self.get('common:current_user_details') - self.assertEqual(response.status_code, 302) - - def test_user_multiple_set_password_view_with_permissions(self): - self.grant_permission(permission=permission_user_edit) - self.grant_permission(permission=permission_user_view) - - response = self._request_multiple_user_set_password( - password=TEST_USER_PASSWORD_EDITED - ) - - self.assertEqual(response.status_code, 200) - - self.logout() - self.login( - username=TEST_USER_USERNAME, password=TEST_USER_PASSWORD_EDITED - ) - response = self.get('common:current_user_details') - - self.assertEqual(response.status_code, 200) - - def test_user_delete_view_no_permissions(self): - user = get_user_model().objects.create( - username=TEST_USER_TO_DELETE_USERNAME - ) - - self.grant_permission(permission=permission_user_view) - - response = self.post( - 'user_management:user_delete', args=(user.pk,) - ) - - self.assertEqual(response.status_code, 403) - self.assertEqual(get_user_model().objects.count(), 3) - - def test_user_delete_view_with_permissions(self): - user = get_user_model().objects.create( - username=TEST_USER_TO_DELETE_USERNAME - ) - - self.grant_permission(permission=permission_user_delete) - self.grant_permission(permission=permission_user_view) - - response = self.post( - 'user_management:user_delete', args=(user.pk,), follow=True - ) - - self.assertContains(response, text='deleted', status_code=200) - self.assertEqual(get_user_model().objects.count(), 2) - - def test_user_multiple_delete_view_no_permissions(self): - user = get_user_model().objects.create( - username=TEST_USER_TO_DELETE_USERNAME - ) - - self.grant_permission(permission=permission_user_view) - - response = self.post( - 'user_management:user_multiple_delete', data={ - 'id_list': user.pk } ) + def test_user_multiple_set_password_view_no_access(self): + self._create_test_user_2() + response = self._request_multiple_user_set_password( + password=TEST_USER_PASSWORD_EDITED + ) + self.assertEqual(response.status_code, 403) + + self.logout() + + with self.assertRaises(AssertionError): + self.login( + username=TEST_USER_2_USERNAME, password=TEST_USER_PASSWORD_EDITED + ) + + response = self.get('common:current_user_details') + self.assertEqual(response.status_code, 302) + + def test_user_multiple_set_password_view_with_access(self): + self._create_test_user_2() + self.grant_access(permission=permission_user_edit, obj=self.user_2) + + response = self._request_multiple_user_set_password( + password=TEST_USER_PASSWORD_EDITED + ) + + self.assertEqual(response.status_code, 302) + + self.logout() + self.login( + username=TEST_USER_2_USERNAME, password=TEST_USER_PASSWORD_EDITED + ) + response = self.get('common:current_user_details') + + self.assertEqual(response.status_code, 200) + + def _request_user_delete(self): + return self.post( + viewname='user_management:user_delete', args=(self.user_2.pk,) + ) + + def test_user_delete_view_no_access(self): + self._create_test_user_2() + response = self._request_user_delete() + self.assertEqual(response.status_code, 302) self.assertEqual(get_user_model().objects.count(), 3) - def test_user_multiple_delete_view_with_permissions(self): - user = get_user_model().objects.create( - username=TEST_USER_TO_DELETE_USERNAME + def test_user_delete_view_with_access(self): + self._create_test_user_2() + self.grant_access(permission=permission_user_delete, obj=self.user_2) + response = self._request_user_delete() + self.assertEqual(response.status_code, 302) + self.assertEqual(get_user_model().objects.count(), 2) + + def _request_user_multiple_delete_view(self): + return self.post( + viewname='user_management:user_multiple_delete', data={ + 'id_list': self.user_2.pk + } ) - self.grant_permission(permission=permission_user_delete) - self.grant_permission(permission=permission_user_view) + def test_user_multiple_delete_view_no_access(self): + self._create_test_user_2() + response = self._request_user_multiple_delete_view() + self.assertEqual(response.status_code, 302) + self.assertEqual(get_user_model().objects.count(), 3) - response = self.post( - 'user_management:user_multiple_delete', data={ - 'id_list': user.pk, - }, follow=True - ) - - self.assertContains(response, text='deleted', status_code=200) + def test_user_multiple_delete_view_with_access(self): + self._create_test_user_2() + self.grant_access(permission=permission_user_delete, obj=self.user_2) + response = self._request_user_multiple_delete_view() + self.assertEqual(response.status_code, 302) self.assertEqual(get_user_model().objects.count(), 2) diff --git a/mayan/apps/user_management/views.py b/mayan/apps/user_management/views.py index caf44601cb..18fd2109fe 100644 --- a/mayan/apps/user_management/views.py +++ b/mayan/apps/user_management/views.py @@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model from django.contrib.auth.forms import SetPasswordForm from django.contrib.auth.models import Group from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import PermissionDenied from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.urls import reverse, reverse_lazy @@ -35,8 +36,8 @@ class GroupCreateView(SingleObjectCreateView): class GroupEditView(SingleObjectEditView): fields = ('name',) model = Group + object_permission = permission_group_edit post_action_redirect = reverse_lazy('user_management:group_list') - view_permission = permission_group_edit def get_extra_context(self): return { @@ -51,13 +52,13 @@ class GroupListView(SingleObjectListView): 'title': _('Groups'), } model = Group - view_permission = permission_group_view + object_permission = permission_group_view class GroupDeleteView(SingleObjectDeleteView): model = Group + object_permission = permission_group_delete post_action_redirect = reverse_lazy('user_management:group_list') - view_permission = permission_group_delete def get_extra_context(self): return { @@ -70,7 +71,7 @@ class GroupMembersView(AssignRemoveView): decode_content_type = True left_list_title = _('Available users') right_list_title = _('Users in group') - view_permission = permission_group_edit + object_permission = permission_group_edit @staticmethod def generate_choices(choices): @@ -133,11 +134,11 @@ class UserCreateView(SingleObjectCreateView): class UserDeleteView(MultipleObjectConfirmActionView): model = get_user_model() + object_permission = permission_user_delete success_message = _('User delete request performed on %(count)d user') success_message_plural = _( 'User delete request performed on %(count)d users' ) - view_permission = permission_user_delete def get_extra_context(self): queryset = self.get_queryset() @@ -187,11 +188,11 @@ class UserDeleteView(MultipleObjectConfirmActionView): class UserEditView(SingleObjectEditView): fields = ('username', 'first_name', 'last_name', 'email', 'is_active',) + object_permission = permission_user_edit post_action_redirect = reverse_lazy('user_management:user_list') queryset = get_user_model().objects.filter( is_superuser=False, is_staff=False ) - view_permission = permission_user_edit def get_extra_context(self): return { @@ -204,7 +205,7 @@ class UserGroupsView(AssignRemoveView): decode_content_type = True left_list_title = _('Available groups') right_list_title = _('Groups joined') - view_permission = permission_user_edit + object_permission = permission_user_edit def add(self, item): item.user_set.add(self.get_object()) @@ -233,7 +234,7 @@ class UserGroupsView(AssignRemoveView): class UserListView(SingleObjectListView): - view_permission = permission_user_view + object_permission = permission_user_view def get_extra_context(self): return { @@ -250,11 +251,11 @@ class UserListView(SingleObjectListView): class UserSetPasswordView(MultipleObjectFormActionView): form_class = SetPasswordForm model = get_user_model() + object_permission = permission_user_edit success_message = _('Password change request performed on %(count)d user') success_message_plural = _( 'Password change request performed on %(count)d users' ) - view_permission = permission_user_edit def get_extra_context(self): queryset = self.get_queryset() @@ -283,8 +284,9 @@ class UserSetPasswordView(MultipleObjectFormActionView): result = {} if queryset: result['user'] = queryset.first() - - return result + return result + else: + raise PermissionDenied def object_action(self, form, instance): try: