From c8bf7546463a5b422d341d846c264237e71f564d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Sun, 6 Sep 2015 10:29:23 +0200 Subject: [PATCH] Added possibility to update your own user object. Hide default password for non admins. --- openslides/motions/views.py | 2 +- openslides/users/serializers.py | 3 +- openslides/users/views.py | 77 ++++++++++++++++++++++- tests/integration/motions/test_viewset.py | 21 ++++++- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/openslides/motions/views.py b/openslides/motions/views.py index d9a991c4c..3dac54d8e 100644 --- a/openslides/motions/views.py +++ b/openslides/motions/views.py @@ -104,7 +104,7 @@ class MotionViewSet(ModelViewSet): # Check permission to send submitter and supporter data. if (not request.user.has_perm('motions.can_manage') and - (request.data.getlist('submitters') or request.data.getlist('supporters'))): + (request.data.get('submitters_id') or request.data.get('supporters_id'))): # Non-staff users are not allowed to send submitter or supporter data. self.permission_denied(request) diff --git a/openslides/users/serializers.py b/openslides/users/serializers.py index dce7f156e..055af49d3 100644 --- a/openslides/users/serializers.py +++ b/openslides/users/serializers.py @@ -16,7 +16,7 @@ class UserShortSerializer(ModelSerializer): """ Serializer for users.models.User objects. - Serializes only name fields. + Serializes only name fields and about me field. """ class Meta: model = User @@ -27,6 +27,7 @@ class UserShortSerializer(ModelSerializer): 'first_name', 'last_name', 'structure_level', + 'about_me', 'groups',) diff --git a/openslides/users/views.py b/openslides/users/views.py index 30b1dde26..5e91c8902 100644 --- a/openslides/users/views.py +++ b/openslides/users/views.py @@ -33,9 +33,9 @@ class UserViewSet(ModelViewSet): """ Returns True if the user has required permissions. """ - if self.action in ('metadata', 'list', 'retrieve'): + if self.action in ('metadata', 'list', 'retrieve', 'partial_update'): result = self.request.user.has_perm('users.can_see_name') - elif self.action in ('create', 'partial_update', 'update', 'destroy', 'reset_password'): + elif self.action in ('create', 'update', 'destroy', 'reset_password'): result = (self.request.user.has_perm('users.can_see_name') and self.request.user.has_perm('users.can_see_extra_data') and self.request.user.has_perm('users.can_manage')) @@ -57,6 +57,79 @@ class UserViewSet(ModelViewSet): serializer_class = UserShortSerializer return serializer_class + def list(self, request, *args, **kwargs): + """ + Customized view endpoint to list all user. + + Does only the default_password check. + """ + response = super().list(request, *args, **kwargs) + self.extract_default_password(response) + return response + + def retrieve(self, request, *args, **kwargs): + """ + Customized view endpoint to retrieve a user. + + Does only the default_password check. + """ + response = super().retrieve(request, *args, **kwargs) + self.extract_default_password(response) + return response + + def extract_default_password(self, response): + """ + Checks if a user is not a manager. If yes, the default password is + extracted from the response. + """ + if not self.request.user.has_perm('users.can_manage'): + if isinstance(response.data, dict): + del response.data['default_password'] + elif isinstance(response.data, list): + for user in response.data: + del user['default_password'] + + def update(self, request, *args, **kwargs): + """ + Customized view endpoint to update an user. + + Checks also whether the requesting user can update the user. He + needs at least the permissions 'users.can_see_name' (see + self.check_view_permissions()). Also it is evaluated whether he + wants to update himself or is manager. + """ + # Check manager perms + if (request.user.has_perm('users.can_see_extra_data') and + request.user.has_perm('users.can_manage')): + response = super().update(request, *args, **kwargs) + else: + # Get user. + user = self.get_object() + # Check permissions only to update yourself. + if request.user != user: + self.permission_denied(request) + # Check permission to send only some data. + whitelist = ( + 'username', + 'title', + 'first_name', + 'last_name', + 'structure_level' + 'about_me',) + for data in request.data.keys(): + if data not in whitelist: + # Non-staff users are allowed to send only some data. + self.permission_denied(request) + # Validate data and update user. + serializer = self.get_serializer( + user, + data=request.data, + partial=kwargs.get('partial', False)) + serializer.is_valid(raise_exception=True) + serializer.save() + response = Response(serializer.data) + return response + @detail_route(methods=['post']) def reset_password(self, request, pk=None): """ diff --git a/tests/integration/motions/test_viewset.py b/tests/integration/motions/test_viewset.py index 6d40f2f54..0e23c8be4 100644 --- a/tests/integration/motions/test_viewset.py +++ b/tests/integration/motions/test_viewset.py @@ -1,3 +1,5 @@ +import json + from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from rest_framework import status @@ -154,12 +156,29 @@ class UpdateMotion(TestCase): password='test_password_XaeTe3aesh8ohg6Cohwo') response = self.client.patch( reverse('motion-detail', args=[self.motion.pk]), - {'supporters_id': [supporter.pk]}) + json.dumps({'supporters_id': [supporter.pk]}), + content_type='application/json') self.assertEqual(response.status_code, status.HTTP_200_OK) motion = Motion.objects.get() self.assertEqual(motion.title, 'test_title_aeng7ahChie3waiR8xoh') self.assertEqual(motion.supporters.get().username, 'test_username_ieB9eicah0uqu6Phoovo') + def test_patch_supporters_non_manager(self): + non_admin = get_user_model().objects.create_user( + username='test_username_uqu6PhoovieB9eicah0o', + password='test_password_Xaesh8ohg6CoheTe3awo') + self.client.login( + username='test_username_uqu6PhoovieB9eicah0o', + password='test_password_Xaesh8ohg6CoheTe3awo') + motion = Motion.objects.get() + motion.submitters.add(non_admin) + motion.supporters.clear() + response = self.client.patch( + reverse('motion-detail', args=[self.motion.pk]), + json.dumps({'supporters_id': [1]}), + content_type='application/json') + self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) + def test_removal_of_supporters(self): admin = get_user_model().objects.get(username='admin') group_staff = admin.groups.get(name='Staff')