Add support for users ACLs. Add support for groups ACLs.

Signed-off-by: Roberto Rosario <roberto.rosario.gonzalez@gmail.com>
This commit is contained in:
Roberto Rosario
2018-04-02 04:53:03 -04:00
parent 27bca4c438
commit db235a7e78
4 changed files with 161 additions and 147 deletions

View File

@@ -103,7 +103,11 @@
- Mark the feature to detect and fix the orientatin of PDF as experimental. - 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. - Don't show documents with 0 duplicates in the duplicated document list.
- Clean up the duplicated document model after a document is deleted. - 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) 2.7.3 (2017-09-11)
================== ==================

View File

@@ -5,6 +5,8 @@ from django.contrib.auth import get_user_model
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from acls import ModelPermission 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 import menu_multi_item, menu_object, menu_secondary, menu_setup
from common.apps import MayanAppConfig from common.apps import MayanAppConfig
from common.widgets import two_state_template from common.widgets import two_state_template
@@ -70,12 +72,14 @@ class UserManagementApp(MayanAppConfig):
) )
ModelPermission.register( ModelPermission.register(
model=Group, permissions=( model=Group, permissions=(
permission_acl_edit, permission_acl_view,
permission_group_delete, permission_group_edit, permission_group_delete, permission_group_edit,
permission_group_view, permission_group_view,
) )
) )
ModelPermission.register( ModelPermission.register(
model=User, permissions=( model=User, permissions=(
permission_acl_edit, permission_acl_view,
permission_user_delete, permission_user_edit, permission_user_delete, permission_user_edit,
permission_user_view permission_user_view
) )
@@ -112,12 +116,12 @@ class UserManagementApp(MayanAppConfig):
sources=(Group,) sources=(Group,)
) )
menu_object.bind_links( 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( menu_object.bind_links(
links=( links=(
link_user_edit, link_user_set_password, link_user_groups, link_user_edit, link_user_set_password, link_user_groups,
link_user_delete link_acl_list, link_user_delete
), sources=(User,) ), sources=(User,)
) )
menu_secondary.bind_links( menu_secondary.bind_links(

View File

@@ -14,10 +14,14 @@ from metadata.tests.literals import (
) )
from ..permissions 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' TEST_USER_TO_DELETE_USERNAME = 'user_to_delete'
@@ -27,157 +31,157 @@ class UserManagementViewTestCase(GenericViewTestCase):
super(UserManagementViewTestCase, self).setUp() super(UserManagementViewTestCase, self).setUp()
self.login_user() 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): def _request_set_password(self, password):
return self.post( 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 '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): def _request_multiple_user_set_password(self, password):
return self.post( return self.post(
'user_management:user_multiple_set_password', data={ 'user_management:user_multiple_set_password', data={
'id_list': self.user.pk, 'id_list': self.user_2.pk,
'new_password1': password, 'new_password1': password,
'new_password2': 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.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) self.assertEqual(get_user_model().objects.count(), 3)
def test_user_multiple_delete_view_with_permissions(self): def test_user_delete_view_with_access(self):
user = get_user_model().objects.create( self._create_test_user_2()
username=TEST_USER_TO_DELETE_USERNAME 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) def test_user_multiple_delete_view_no_access(self):
self.grant_permission(permission=permission_user_view) 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( def test_user_multiple_delete_view_with_access(self):
'user_management:user_multiple_delete', data={ self._create_test_user_2()
'id_list': user.pk, self.grant_access(permission=permission_user_delete, obj=self.user_2)
}, follow=True response = self._request_user_multiple_delete_view()
) self.assertEqual(response.status_code, 302)
self.assertContains(response, text='deleted', status_code=200)
self.assertEqual(get_user_model().objects.count(), 2) self.assertEqual(get_user_model().objects.count(), 2)

View File

@@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.forms import SetPasswordForm from django.contrib.auth.forms import SetPasswordForm
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
@@ -35,8 +36,8 @@ class GroupCreateView(SingleObjectCreateView):
class GroupEditView(SingleObjectEditView): class GroupEditView(SingleObjectEditView):
fields = ('name',) fields = ('name',)
model = Group model = Group
object_permission = permission_group_edit
post_action_redirect = reverse_lazy('user_management:group_list') post_action_redirect = reverse_lazy('user_management:group_list')
view_permission = permission_group_edit
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -51,13 +52,13 @@ class GroupListView(SingleObjectListView):
'title': _('Groups'), 'title': _('Groups'),
} }
model = Group model = Group
view_permission = permission_group_view object_permission = permission_group_view
class GroupDeleteView(SingleObjectDeleteView): class GroupDeleteView(SingleObjectDeleteView):
model = Group model = Group
object_permission = permission_group_delete
post_action_redirect = reverse_lazy('user_management:group_list') post_action_redirect = reverse_lazy('user_management:group_list')
view_permission = permission_group_delete
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -70,7 +71,7 @@ class GroupMembersView(AssignRemoveView):
decode_content_type = True decode_content_type = True
left_list_title = _('Available users') left_list_title = _('Available users')
right_list_title = _('Users in group') right_list_title = _('Users in group')
view_permission = permission_group_edit object_permission = permission_group_edit
@staticmethod @staticmethod
def generate_choices(choices): def generate_choices(choices):
@@ -133,11 +134,11 @@ class UserCreateView(SingleObjectCreateView):
class UserDeleteView(MultipleObjectConfirmActionView): class UserDeleteView(MultipleObjectConfirmActionView):
model = get_user_model() model = get_user_model()
object_permission = permission_user_delete
success_message = _('User delete request performed on %(count)d user') success_message = _('User delete request performed on %(count)d user')
success_message_plural = _( success_message_plural = _(
'User delete request performed on %(count)d users' 'User delete request performed on %(count)d users'
) )
view_permission = permission_user_delete
def get_extra_context(self): def get_extra_context(self):
queryset = self.get_queryset() queryset = self.get_queryset()
@@ -187,11 +188,11 @@ class UserDeleteView(MultipleObjectConfirmActionView):
class UserEditView(SingleObjectEditView): class UserEditView(SingleObjectEditView):
fields = ('username', 'first_name', 'last_name', 'email', 'is_active',) fields = ('username', 'first_name', 'last_name', 'email', 'is_active',)
object_permission = permission_user_edit
post_action_redirect = reverse_lazy('user_management:user_list') post_action_redirect = reverse_lazy('user_management:user_list')
queryset = get_user_model().objects.filter( queryset = get_user_model().objects.filter(
is_superuser=False, is_staff=False is_superuser=False, is_staff=False
) )
view_permission = permission_user_edit
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -204,7 +205,7 @@ class UserGroupsView(AssignRemoveView):
decode_content_type = True decode_content_type = True
left_list_title = _('Available groups') left_list_title = _('Available groups')
right_list_title = _('Groups joined') right_list_title = _('Groups joined')
view_permission = permission_user_edit object_permission = permission_user_edit
def add(self, item): def add(self, item):
item.user_set.add(self.get_object()) item.user_set.add(self.get_object())
@@ -233,7 +234,7 @@ class UserGroupsView(AssignRemoveView):
class UserListView(SingleObjectListView): class UserListView(SingleObjectListView):
view_permission = permission_user_view object_permission = permission_user_view
def get_extra_context(self): def get_extra_context(self):
return { return {
@@ -250,11 +251,11 @@ class UserListView(SingleObjectListView):
class UserSetPasswordView(MultipleObjectFormActionView): class UserSetPasswordView(MultipleObjectFormActionView):
form_class = SetPasswordForm form_class = SetPasswordForm
model = get_user_model() model = get_user_model()
object_permission = permission_user_edit
success_message = _('Password change request performed on %(count)d user') success_message = _('Password change request performed on %(count)d user')
success_message_plural = _( success_message_plural = _(
'Password change request performed on %(count)d users' 'Password change request performed on %(count)d users'
) )
view_permission = permission_user_edit
def get_extra_context(self): def get_extra_context(self):
queryset = self.get_queryset() queryset = self.get_queryset()
@@ -283,8 +284,9 @@ class UserSetPasswordView(MultipleObjectFormActionView):
result = {} result = {}
if queryset: if queryset:
result['user'] = queryset.first() result['user'] = queryset.first()
return result
return result else:
raise PermissionDenied
def object_action(self, form, instance): def object_action(self, form, instance):
try: try: