User lockout protection, fixed #666

Protection of updating and deleting users and groups if this caused a lockout of the requesting user.
This commit is contained in:
Norman Jäckel 2013-06-03 20:13:06 +02:00
parent 7eed5e2928
commit aa0728ff60
4 changed files with 163 additions and 24 deletions

View File

@ -13,12 +13,11 @@
from django import forms from django import forms
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _, ugettext_lazy from django.utils.translation import ugettext as _, ugettext_lazy
from django.conf import settings from django.conf import settings
from openslides.utils.forms import CssClassMixin, LocalizedModelMultipleChoiceField from openslides.utils.forms import CssClassMixin, LocalizedModelMultipleChoiceField
from openslides.participant.models import User, Group from openslides.participant.models import User, Group, get_protected_perm
from openslides.participant.api import get_registered_group from openslides.participant.api import get_registered_group
@ -64,19 +63,15 @@ class UserUpdateForm(UserCreateForm):
def clean(self, *args, **kwargs): def clean(self, *args, **kwargs):
""" """
Raises a validation error, if a non-superuser user edits himself Raises a validation error if a non-superuser user edits himself
and removes the last group containing the permission to manage participants. and removes the last group containing the permission to manage participants.
""" """
# TODO: Check this in clean_groups # TODO: Check this in clean_groups
if self.request.user == self.instance and not self.instance.is_superuser: if (self.request.user == self.instance and
protected_perm = Permission.objects.get( not self.instance.is_superuser and
content_type=ContentType.objects.get(app_label='participant', not self.cleaned_data['groups'].filter(permissions__in=[get_protected_perm()]).exists()):
model='user'), error_msg = _('You can not remove the last group containing the permission to manage participants.')
codename='can_manage_participant') raise forms.ValidationError(error_msg)
if not self.cleaned_data['groups'].filter(permissions__in=[protected_perm]).exists():
error_msg = _('You can not remove the last group containing the permission to manage participants.')
messages.error(self.request, error_msg)
raise forms.ValidationError(error_msg)
return super(UserUpdateForm, self).clean(*args, **kwargs) return super(UserUpdateForm, self).clean(*args, **kwargs)
@ -87,7 +82,12 @@ class GroupForm(forms.ModelForm, CssClassMixin):
users = forms.ModelMultipleChoiceField( users = forms.ModelMultipleChoiceField(
queryset=User.objects.all(), label=ugettext_lazy('Participants'), required=False) queryset=User.objects.all(), label=ugettext_lazy('Participants'), required=False)
class Meta:
model = Group
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Take request argument
self.request = kwargs.pop('request', None)
# Initial users # Initial users
if kwargs.get('instance', None) is not None: if kwargs.get('instance', None) is not None:
initial = kwargs.setdefault('initial', {}) initial = kwargs.setdefault('initial', {})
@ -114,8 +114,32 @@ class GroupForm(forms.ModelForm, CssClassMixin):
return instance return instance
class Meta: def clean(self, *args, **kwargs):
model = Group """
Raises a validation error if a non-superuser user removes himself
from the last group containing the permission to manage participants.
Raises also a validation error if a non-superuser removes his last
permission to manage participants from the (last) group.
"""
# TODO: Check this in clean_users or clean_permissions
if (self.request and
not self.request.user.is_superuser and
not self.request.user in self.cleaned_data['users'] and
not Group.objects.exclude(pk=self.instance.pk).filter(
permissions__in=[get_protected_perm()],
user__pk=self.request.user.pk).exists()):
error_msg = _('You can not remove yourself from the last group containing the permission to manage participants.')
raise forms.ValidationError(error_msg)
if (self.request and
not self.request.user.is_superuser and
not get_protected_perm() in self.cleaned_data['permissions'] and
not Group.objects.exclude(pk=self.instance.pk).filter(
permissions__in=[get_protected_perm()],
user__pk=self.request.user.pk).exists()):
error_msg = _('You can not remove the permission to manage participants from the last group your are in.')
raise forms.ValidationError(error_msg)
return super(GroupForm, self).clean(*args, **kwargs)
class UsersettingsForm(forms.ModelForm, CssClassMixin): class UsersettingsForm(forms.ModelForm, CssClassMixin):

View File

@ -10,7 +10,8 @@
:license: GNU GPL, see LICENSE for more details. :license: GNU GPL, see LICENSE for more details.
""" """
from django.contrib.auth.models import User as DjangoUser, Group as DjangoGroup from django.contrib.auth.models import User as DjangoUser, Group as DjangoGroup, Permission
from django.contrib.contenttypes.models import ContentType
from django.db import models from django.db import models
from django.db.models import signals from django.db.models import signals
from django.dispatch import receiver from django.dispatch import receiver
@ -245,3 +246,13 @@ def user_post_save(sender, instance, *args, **kwargs):
registered = get_registered_group() registered = get_registered_group()
instance.groups.add(registered) instance.groups.add(registered)
instance.save() instance.save()
def get_protected_perm():
"""
Returns the permission to manage participants. This function is a helper
function used to protect manager users from locking out themselves.
"""
return Permission.objects.get(
content_type=ContentType.objects.get(app_label='participant', model='user'),
codename='can_manage_participant')

View File

@ -47,7 +47,7 @@ from openslides.participant.api import gen_username, gen_password, import_users
from openslides.participant.forms import ( from openslides.participant.forms import (
UserCreateForm, UserUpdateForm, UsersettingsForm, UserCreateForm, UserUpdateForm, UsersettingsForm,
UserImportForm, GroupForm) UserImportForm, GroupForm)
from openslides.participant.models import User, Group from openslides.participant.models import User, Group, get_protected_perm
class UserOverview(ListView): class UserOverview(ListView):
@ -153,11 +153,17 @@ class UserDeleteView(DeleteView):
success_url_name = 'user_overview' success_url_name = 'user_overview'
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
if self.get_object() == self.request.user: if self.object == self.request.user:
messages.error(request, _("You can not delete yourself.")) messages.error(request, _("You can not delete yourself."))
else: else:
super(UserDeleteView, self).pre_redirect(request, *args, **kwargs) super(UserDeleteView, self).pre_redirect(request, *args, **kwargs)
def pre_post_redirect(self, request, *args, **kwargs):
if self.object == self.request.user:
messages.error(self.request, _("You can not delete yourself."))
else:
super(UserDeleteView, self).pre_post_redirect(request, *args, **kwargs)
class SetUserStatusView(RedirectView, SingleObjectMixin): class SetUserStatusView(RedirectView, SingleObjectMixin):
""" """
@ -174,10 +180,10 @@ class SetUserStatusView(RedirectView, SingleObjectMixin):
if action == 'activate': if action == 'activate':
self.object.is_active = True self.object.is_active = True
elif action == 'deactivate': elif action == 'deactivate':
if self.get_object().user == self.request.user: if self.object.user == self.request.user:
messages.error(request, _("You can not deactivate yourself.")) messages.error(request, _("You can not deactivate yourself."))
return return
elif self.get_object().is_superuser: elif self.object.is_superuser:
messages.error(request, _("You can not deactivate the administrator.")) messages.error(request, _("You can not deactivate the administrator."))
return return
self.object.is_active = False self.object.is_active = False
@ -412,21 +418,47 @@ class GroupUpdateView(UpdateView):
delete_default_permissions() delete_default_permissions()
return super(GroupUpdateView, self).get(request, *args, **kwargs) return super(GroupUpdateView, self).get(request, *args, **kwargs)
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(GroupUpdateView, self).get_form_kwargs(*args, **kwargs)
form_kwargs.update({'request': self.request})
return form_kwargs
class GroupDeleteView(DeleteView): class GroupDeleteView(DeleteView):
""" """
Delete a Group. Delete a group.
""" """
permission_required = 'participant.can_manage_participant' permission_required = 'participant.can_manage_participant'
model = Group model = Group
success_url_name = 'user_group_overview' success_url_name = 'user_group_overview'
def pre_redirect(self, request, *args, **kwargs): def pre_redirect(self, request, *args, **kwargs):
if self.get_object().pk in [1, 2]: if not self.is_protected_from_deleting():
messages.error(request, _("You can not delete this Group."))
else:
super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs) super(GroupDeleteView, self).pre_redirect(request, *args, **kwargs)
def pre_post_redirect(self, request, *args, **kwargs):
if not self.is_protected_from_deleting():
super(GroupDeleteView, self).pre_post_redirect(request, *args, **kwargs)
def is_protected_from_deleting(self):
"""
Checks whether the group is protected.
"""
if self.object.pk in [1, 2]:
messages.error(request, _('You can not delete this group.'))
return True
if (not self.request.user.is_superuser and
get_protected_perm() in self.object.permissions.all() and
not Group.objects.exclude(pk=self.object.pk).filter(
permissions__in=[get_protected_perm()],
user__pk=self.request.user.pk).exists()):
messages.error(
self.request,
_('You can not delete the last group containing the permission '
'to manage participants you are in.'))
return True
return False
def login(request): def login(request):
extra_content = {} extra_content = {}

View File

@ -13,7 +13,7 @@ from django.test.client import Client
from openslides.config.api import config from openslides.config.api import config
from openslides.utils.test import TestCase from openslides.utils.test import TestCase
from openslides.participant.models import User, Group from openslides.participant.models import User, Group, get_protected_perm
class GroupViews(TestCase): class GroupViews(TestCase):
@ -52,3 +52,75 @@ class GroupViews(TestCase):
match = re.findall(pattern, response.content) match = re.findall(pattern, response.content)
self.assertEqual(match[1], 'mi6iu2Te6ei9iohue3ex chahshah7eiqueip5eiW') self.assertEqual(match[1], 'mi6iu2Te6ei9iohue3ex chahshah7eiqueip5eiW')
self.assertEqual(match[0], 'aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer') self.assertEqual(match[0], 'aWei4ien6Se0vie0xeiv uquahx3Wohtieph9baer')
class LockoutProtection(TestCase):
"""
Tests that a manager user can not lockout himself by doing
something that removes his last permission to manage participants.
"""
def setUp(self):
self.user = User.objects.create(last_name='AQu9ie7ach2ek2Xoozoo',
first_name='guR3La9alah7lahsief6',
username='Iedei0eecoh1aiwahnoo')
self.user.reset_password('default')
self.user.groups.add(Group.objects.get(pk=4))
self.client = Client()
self.client.login(username='Iedei0eecoh1aiwahnoo', password='default')
self.assertEqual(User.objects.count(), 1)
self.assertEqual(Group.objects.count(), 4)
def test_delete_yourself(self):
response = self.client.get('/participant/1/del/')
self.assertRedirects(response, '/participant/1/')
self.assertTrue('You can not delete yourself.' in response.cookies['messages'].value)
response = self.client.post('/participant/1/del/',
{'yes': 'yes'})
self.assertTrue('You can not delete yourself.' in response.cookies['messages'].value)
self.assertRedirects(response, '/participant/')
self.assertEqual(User.objects.count(), 1)
def test_delete_last_manager_group(self):
response = self.client.get('/participant/group/4/del/')
self.assertRedirects(response, '/participant/group/4/')
self.assertTrue('You can not delete the last group containing the permission '
'to manage participants you are in.' in response.cookies['messages'].value)
response = self.client.post('/participant/group/4/del/',
{'yes': 'yes'})
self.assertTrue('You can not delete the last group containing the permission '
'to manage participants you are in.' in response.cookies['messages'].value)
self.assertRedirects(response, '/participant/group/')
self.assertEqual(Group.objects.count(), 4)
def test_remove_user_from_last_manager_group_via_UserUpdateView(self):
response = self.client.post('/participant/1/edit/',
{'username': 'arae0eQu8eeghoogeik0',
'groups': '3'})
self.assertFormError(
response=response,
form='form',
field=None,
errors='You can not remove the last group containing the permission to manage participants.')
def test_remove_user_from_last_manager_group_via_GroupUpdateView(self):
User.objects.get_or_create(username='foo', pk=2)
response = self.client.post('/participant/group/4/edit/',
{'name': 'ChaeFaev4leephaiChae',
'users': '2'})
self.assertFormError(
response=response,
form='form',
field=None,
errors='You can not remove yourself from the last group containing the permission to manage participants.')
def test_remove_perm_from_last_manager_group(self):
self.assertNotEqual(get_protected_perm().pk, 90)
response = self.client.post('/participant/group/4/edit/',
{'name': 'ChaeFaev4leephaiChae',
'users': '1',
'permissions': '90'})
self.assertFormError(
response=response,
form='form',
field=None,
errors='You can not remove the permission to manage participants from the last group your are in.')