diff --git a/openslides/users/serializers.py b/openslides/users/serializers.py index 1e9f3e173..4ba7984b8 100644 --- a/openslides/users/serializers.py +++ b/openslides/users/serializers.py @@ -4,7 +4,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy from openslides.utils.rest_api import ModelSerializer, PrimaryKeyRelatedField, RelatedField, ValidationError -from .models import Group, User +from .models import Group, Permission, User class UserShortSerializer(ModelSerializer): @@ -122,18 +122,40 @@ class PermissionRelatedField(RelatedField): """ A custom field to use for the permission relationship. """ + default_error_messages = { + 'incorrect_value': ugettext_lazy('Incorrect value "{value}". Expected app_label.codename string.'), + 'does_not_exist': ugettext_lazy('Invalid permission "{value}". Object does not exist.')} + def to_representation(self, value): """ - Returns the permission name (app_label.codename). + Returns the permission code string (app_label.codename). """ return '.'.join((value.content_type.app_label, value.codename,)) + def to_internal_value(self, data): + """ + Returns the permission object represented by data. The argument data is + what is sent by the client. This method expects permission code strings + (app_label.codename) like to_representation() returns. + """ + try: + app_label, codename = data.split('.') + except ValueError: + self.fail('incorrect_value', value=data) + try: + permission = Permission.objects.get(content_type__app_label=app_label, codename=codename) + except Permission.DoesNotExist: + self.fail('does_not_exist', value=data) + return permission + class GroupSerializer(ModelSerializer): """ Serializer for django.contrib.auth.models.Group objects. """ - permissions = PermissionRelatedField(many=True, read_only=True) + permissions = PermissionRelatedField( + many=True, + queryset=Permission.objects.all()) class Meta: model = Group diff --git a/openslides/users/views.py b/openslides/users/views.py index a7504e11f..6ab83ccfa 100644 --- a/openslides/users/views.py +++ b/openslides/users/views.py @@ -4,8 +4,9 @@ from django.contrib.auth import logout as auth_logout from django.contrib.auth.forms import AuthenticationForm, PasswordChangeForm from django.utils.translation import ugettext as _ from django.utils.translation import activate, ugettext_lazy +from rest_framework import status -from openslides.utils.rest_api import ModelViewSet +from openslides.utils.rest_api import ModelViewSet, Response from openslides.utils.views import ( CSVImportView, FormView, @@ -121,6 +122,19 @@ class GroupViewSet(ModelViewSet): request.user.has_perm('users.can_see_extra_data')))): self.permission_denied(request) + def destroy(self, request, *args, **kwargs): + """ + Protects builtin groups 'Anonymous' (pk=1) and 'Registered' (pk=2) + from being deleted. + """ + instance = self.get_object() + if instance.pk in (1, 2,): + self.permission_denied(request) + else: + self.perform_destroy(instance) + response = Response(status=status.HTTP_204_NO_CONTENT) + return response + class UserSettingsView(LoginMixin, UpdateView): required_permission = None diff --git a/tests/integration/users/test_viewset.py b/tests/integration/users/test_viewset.py index ef1537653..66a7a8deb 100644 --- a/tests/integration/users/test_viewset.py +++ b/tests/integration/users/test_viewset.py @@ -2,11 +2,11 @@ from django.core.urlresolvers import reverse from rest_framework import status from rest_framework.test import APIClient -from openslides.users.models import User +from openslides.users.models import Group, User from openslides.utils.test import TestCase -class UserCreation(TestCase): +class UserCreate(TestCase): """ Tests creation of users via REST API. """ @@ -22,26 +22,31 @@ class UserCreation(TestCase): def test_creation_with_group(self): self.client.login(username='admin', password='admin') + # These are the builtin groups 'Delegates' and 'Staff'. The pks are valid. + group_pks = (3, 4,) self.client.post( reverse('user-list'), {'last_name': 'Test name aedah1iequoof0Ashed4', - 'groups': ['3', '4']}) + 'groups': group_pks}) user = User.objects.get(username='Test name aedah1iequoof0Ashed4') - self.assertTrue(user.groups.filter(pk=3).exists()) - self.assertTrue(user.groups.filter(pk=4).exists()) + self.assertTrue(user.groups.filter(pk=group_pks[0]).exists()) + self.assertTrue(user.groups.filter(pk=group_pks[1]).exists()) def test_creation_with_anonymous_or_registered_group(self): self.client.login(username='admin', password='admin') + # These are the builtin groups 'Anonymous' and 'Registered'. + # The pks are valid. But these groups can not be added to users. + group_pks = (1, 2,) response = self.client.post( reverse('user-list'), {'last_name': 'Test name aedah1iequoof0Ashed4', - 'groups': ['1', '2']}) + 'groups': group_pks}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(response.data, {'groups': ["Invalid pk '1' - object does not exist."]}) + self.assertEqual(response.data, {'groups': ["Invalid pk '%d' - object does not exist." % group_pks[0]]}) class UserUpdate(TestCase): @@ -51,22 +56,26 @@ class UserUpdate(TestCase): def test_simple_update_via_patch(self): admin_client = APIClient() admin_client.login(username='admin', password='admin') + # This is the builtin user 'Administrator' with username 'admin'. The pk is valid. + user_pk = 1 response = admin_client.patch( - reverse('user-detail', args=['1']), + reverse('user-detail', args=[user_pk]), {'last_name': 'New name tu3ooh5Iez5Aec2laefo'}) self.assertEqual(response.status_code, status.HTTP_200_OK) - user = User.objects.get(pk=1) + user = User.objects.get(pk=user_pk) self.assertEqual(user.last_name, 'New name tu3ooh5Iez5Aec2laefo') self.assertEqual(user.username, 'admin') def test_simple_update_via_put(self): admin_client = APIClient() admin_client.login(username='admin', password='admin') + # This is the builtin user 'Administrator'. The pk is valid. + user_pk = 1 response = admin_client.put( - reverse('user-detail', args=['1']), + reverse('user-detail', args=[user_pk]), {'last_name': 'New name Ohy4eeyei5Sahzah0Os2'}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) @@ -81,9 +90,120 @@ class UserDelete(TestCase): admin_client = APIClient() admin_client.login(username='admin', password='admin') User.objects.create(username='Test name bo3zieT3iefahng0ahqu') - self.assertTrue(User.objects.filter(username='Test name bo3zieT3iefahng0ahqu').exists()) response = admin_client.delete(reverse('user-detail', args=['2'])) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) self.assertFalse(User.objects.filter(username='Test name bo3zieT3iefahng0ahqu').exists()) + + +class GroupCreate(TestCase): + """ + Tests creation of groups via REST API. + """ + def test_creation(self): + self.client.login(username='admin', password='admin') + # This contains two valid permissions of the users app. + permissions = ('users.can_see_name', 'users.can_see_extra_data') + + response = self.client.post( + reverse('group-list'), + {'name': 'Test name la8eephu9vaecheiKeif', + 'permissions': permissions}) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + group = Group.objects.get(name='Test name la8eephu9vaecheiKeif') + for permission in permissions: + app_label, codename = permission.split('.') + self.assertTrue(group.permissions.get(content_type__app_label=app_label, codename=codename)) + + def test_failed_creation_invalid_value(self): + self.client.login(username='admin', password='admin') + permissions = ('invalid_permission',) + + response = self.client.post( + reverse('group-list'), + {'name': 'Test name ool5aeb6Rai2aiLaith1', + 'permissions': permissions}) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual( + response.data, + {'permissions': ['Incorrect value "invalid_permission". Expected app_label.codename string.']}) + + def test_failed_creation_invalid_permission(self): + self.client.login(username='admin', password='admin') + permissions = ('invalid_app.invalid_permission',) + + response = self.client.post( + reverse('group-list'), + {'name': 'Test name wei2go2aiV3eophi9Ohg', + 'permissions': permissions}) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual( + response.data, + {'permissions': ['Invalid permission "invalid_app.invalid_permission". Object does not exist.']}) + + +class GroupUpdate(TestCase): + """ + Tests update of groups via REST API. + """ + def test_simple_update_via_patch(self): + admin_client = APIClient() + admin_client.login(username='admin', password='admin') + # This is the builtin group 'Delegates'. The pk is valid. + group_pk = 3 + # This contains one valid permission of the users app. + permissions = ('users.can_see_name',) + + response = admin_client.patch( + reverse('group-detail', args=[group_pk]), + {'permissions': permissions}) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + group = Group.objects.get(pk=group_pk) + for permission in permissions: + app_label, codename = permission.split('.') + self.assertTrue(group.permissions.get(content_type__app_label=app_label, codename=codename)) + + def test_simple_update_via_put(self): + admin_client = APIClient() + admin_client.login(username='admin', password='admin') + # This is the builtin group 'Delegates'. The pk is valid. + group_pk = 3 + # This contains one valid permission of the users app. + permissions = ('users.can_see_name',) + + response = admin_client.put( + reverse('group-detail', args=[group_pk]), + {'permissions': permissions}) + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertEqual(response.data, {'name': ['This field is required.']}) + + +class GroupDelete(TestCase): + """ + Tests delete of groups via REST API. + """ + def test_delete(self): + admin_client = APIClient() + admin_client.login(username='admin', password='admin') + group = Group.objects.create(name='Test name Koh4lohlaewoog9Ahsh5') + + response = admin_client.delete(reverse('group-detail', args=[group.pk])) + + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + self.assertFalse(Group.objects.filter(name='Test name Koh4lohlaewoog9Ahsh5').exists()) + + def test_delete_builtin_groups(self): + admin_client = APIClient() + admin_client.login(username='admin', password='admin') + # The pks of builtin groups 'Anonymous' and 'Registered' + group_pks = (1, 2,) + + for group_pk in group_pks: + response = admin_client.delete(reverse('group-detail', args=[group_pk])) + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)