From b4d40263167c69598d5aba8739749f4537cbb894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Norman=20J=C3=A4ckel?= Date: Wed, 31 Aug 2016 16:53:02 +0200 Subject: [PATCH] Refactored user serializers for different client permissions. See #1871. --- openslides/motions/static/js/motions/site.js | 6 +- openslides/users/access_permissions.py | 27 +++--- openslides/users/serializers.py | 89 ++++++++++++++------ openslides/users/static/js/users/site.js | 6 +- openslides/users/views.py | 67 +-------------- 5 files changed, 85 insertions(+), 110 deletions(-) diff --git a/openslides/motions/static/js/motions/site.js b/openslides/motions/static/js/motions/site.js index 1e92964f4..9b37765c3 100644 --- a/openslides/motions/static/js/motions/site.js +++ b/openslides/motions/static/js/motions/site.js @@ -1404,7 +1404,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid }); // TODO (Issue #2293): - // fix _.uniqWith(importedXXX, _.isEqual); + // fix _.uniqWith(importedXXX, _.isEqual); // (You need lodash version >= 4.0.0) // unique users @@ -1466,7 +1466,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid var index = motion.submitter.indexOf(' '); var first_name = motion.submitter.substr(0, index); var last_name = motion.submitter.substr(index+1); - + // search for user, set id. importedUsersUnique.forEach(function (user) { if (user.first_name == first_name && @@ -1478,7 +1478,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions', 'OpenSlid // add category if (!motion.category_id && motion.category) { var name = motion.category; - + // search for category, set id. importedCategoriesUnique.forEach(function (category) { if (category.name == name) { diff --git a/openslides/users/access_permissions.py b/openslides/users/access_permissions.py index a00cf3c17..71d048a36 100644 --- a/openslides/users/access_permissions.py +++ b/openslides/users/access_permissions.py @@ -15,35 +15,34 @@ class UserAccessPermissions(BaseAccessPermissions): """ Returns different serializer classes with respect user's permissions. """ - from .serializers import UserFullSerializer, UserShortSerializer + from .serializers import UserCanSeeSerializer, UserCanSeeExtraSerializer, UserFullSerializer - if user is None or user.has_perm('users.can_see_extra_data'): - # Return the UserFullSerializer for requests of users with more - # permissions. + if (user is None or (user.has_perm('users.can_see_extra_data') and user.has_perm('users.can_manage'))): serializer_class = UserFullSerializer + elif user.has_perm('users.can_see_extra_data'): + serializer_class = UserCanSeeExtraSerializer else: - serializer_class = UserShortSerializer + serializer_class = UserCanSeeSerializer return serializer_class def get_restricted_data(self, full_data, user): """ Returns the restricted serialized data for the instance prepared for the user. Removes several fields for non admins so that they do - not get the default_password or even get only the fields as the - UserShortSerializer would give them. + not get the fields they should not get. """ - from .serializers import USERSHORTSERIALIZER_FIELDS + from .serializers import USERCANSEESERIALIZER_FIELDS, USERCANSEEEXTRASERIALIZER_FIELDS if user.has_perm('users.can_manage'): data = full_data - elif user.has_perm('users.can_see_extra_data'): - # Only remove default password from full data. - data = full_data.copy() - del data['default_password'] else: - # Let only fields as in the UserShortSerializer pass this method. + if user.has_perm('users.can_see_extra_data'): + fields = USERCANSEEEXTRASERIALIZER_FIELDS + else: + fields = USERCANSEESERIALIZER_FIELDS + # Let only some fields pass this method. data = {} for key in full_data.keys(): - if key in USERSHORTSERIALIZER_FIELDS: + if key in fields: data[key] = full_data[key] return data diff --git a/openslides/users/serializers.py b/openslides/users/serializers.py index 7ab7ad71c..fb87d9b2d 100644 --- a/openslides/users/serializers.py +++ b/openslides/users/serializers.py @@ -11,7 +11,8 @@ from ..utils.rest_api import ( ) from .models import Group, User -USERSHORTSERIALIZER_FIELDS = ( + +USERCANSEESERIALIZER_FIELDS = ( 'id', 'username', 'title', @@ -25,33 +26,24 @@ USERSHORTSERIALIZER_FIELDS = ( ) -class UserShortSerializer(ModelSerializer): +class UserCanSeeSerializer(ModelSerializer): """ - Serializer for users.models.User objects. + Serializer for users.models.User objects to be used by users who have + only the permission to see users and to change some date of theirselfs. - Serializes only name fields and about me field. + Attention: Viewset has to ensure that a user can update only himself. """ class Meta: model = User - fields = USERSHORTSERIALIZER_FIELDS + fields = USERCANSEESERIALIZER_FIELDS + read_only_fields = ( + 'number', + 'groups', + 'is_comittee', + ) -class UserFullSerializer(ModelSerializer): - """ - Serializer for users.models.User objects. - - Serializes all relevant fields. - """ - groups = IdPrimaryKeyRelatedField( - many=True, - queryset=Group.objects.exclude(pk=1), - help_text=ugettext_lazy('The groups this user belongs to. A user will ' - 'get all permissions granted to each of ' - 'his/her groups.')) - - class Meta: - model = User - fields = ( +USERCANSEEEXTRASERIALIZER_FIELDS = ( 'id', 'is_present', 'username', @@ -63,16 +55,60 @@ class UserFullSerializer(ModelSerializer): 'about_me', 'comment', 'groups', - 'default_password', 'is_active', 'is_committee', ) + +class UserCanSeeExtraSerializer(ModelSerializer): + """ + Serializer for users.models.User objects to be used by users who have + the permission to see users with extra data and to change some date of + theirselfs. + + Attention: Viewset has to ensure that a user can update only himself. + """ + groups = IdPrimaryKeyRelatedField( + many=True, + queryset=Group.objects.exclude(pk=1), + help_text=ugettext_lazy('The groups this user belongs to. A user will ' + 'get all permissions granted to each of ' + 'his/her groups.')) + + class Meta: + model = User + fields = USERCANSEEEXTRASERIALIZER_FIELDS + read_only_fields = ( + 'is_present', + 'number', + 'comment', + 'groups', + 'is_comittee', + 'is_active', + ) + + +class UserFullSerializer(ModelSerializer): + """ + Serializer for users.models.User objects. + + Serializes all relevant fields for manager. + """ + groups = IdPrimaryKeyRelatedField( + many=True, + queryset=Group.objects.exclude(pk=1), + help_text=ugettext_lazy('The groups this user belongs to. A user will ' + 'get all permissions granted to each of ' + 'his/her groups.')) + + class Meta: + model = User + fields = USERCANSEEEXTRASERIALIZER_FIELDS + ('default_password',) + def validate(self, data): """ - Checks that first_name or last_name is given. - - Generates the username if it is empty. + Checks that first_name or last_name is given. Generates the + username if it is empty. """ if not (data.get('username') or data.get('first_name') or data.get('last_name')): raise ValidationError({'detail': _('Username, first name and last name can not all be empty.')}) @@ -92,8 +128,7 @@ class UserFullSerializer(ModelSerializer): def create(self, validated_data): """ - Creates the user. Sets the default password. Adds the new user to the - registered group. + Creates the user. Sets the default password. """ # Prepare setup password. if not validated_data.get('default_password'): diff --git a/openslides/users/static/js/users/site.js b/openslides/users/static/js/users/site.js index 2f1bbccc5..88c413357 100644 --- a/openslides/users/static/js/users/site.js +++ b/openslides/users/static/js/users/site.js @@ -421,7 +421,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) 'Group', function($scope, $state, ngDialog, UserForm, User, Group) { User.bindAll({}, $scope, 'users'); - Group.bindAll({where: {id: {'>': 2}}}, $scope, 'groups'); + Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups'); $scope.alert = {}; $scope.groupFilter = undefined; @@ -532,7 +532,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) 'Group', function($scope, ngDialog, UserForm, User, user, Group) { User.bindOne(user.id, $scope, 'user'); - Group.bindAll({where: {id: {'>': 2}}}, $scope, 'groups'); + Group.bindAll({where: {id: {'>': 1}}}, $scope, 'groups'); // open edit dialog $scope.openDialog = function (user) { @@ -629,7 +629,7 @@ angular.module('OpenSlidesApp.users.site', ['OpenSlidesApp.users']) $scope.user = user; // autoupdate is not activated $scope.tinymceOption = Editor.getOptions(); $scope.save = function (user) { - User.save(user, { method: 'PATCH' }).then( + User.save(user).then( function(success) { $state.go('users.user.list'); }, diff --git a/openslides/users/views.py b/openslides/users/views.py index 6bedc8ac1..6e5c6bc0b 100644 --- a/openslides/users/views.py +++ b/openslides/users/views.py @@ -57,47 +57,11 @@ class UserViewSet(ModelViewSet): # Return the UserFullSerializer for edit requests. serializer_class = UserFullSerializer else: + # Return different serializers according to user permsissions via + # access permissions class. serializer_class = super().get_serializer_class() return serializer_class - def list(self, request, *args, **kwargs): - """ - Customized view endpoint to list all user. - - Hides the default_password for non admins. - """ - 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. - - Hides the default_password for non admins. - """ - 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): - try: - del response.data['default_password'] - except KeyError: - pass - elif isinstance(response.data, list): - for user in response.data: - try: - del user['default_password'] - except KeyError: - pass - def update(self, request, *args, **kwargs): """ Customized view endpoint to update an user. @@ -113,34 +77,11 @@ class UserViewSet(ModelViewSet): if request.data.get('is_active') is False and self.get_object() == request.user: # A user can not deactivate himself. raise ValidationError({'detail': _('You can not deactivate yourself.')}) - response = super().update(request, *args, **kwargs) else: - # Get user. - user = self.get_object() # Check permissions only to update yourself. - if request.user != user: + if str(request.user.pk) != self.kwargs['pk']: self.permission_denied(request) - # Check permission to send only some data. - whitelist = ( - 'username', - 'title', - 'first_name', - 'last_name', - 'structure_level', - 'about_me',) - keys = list(request.data.keys()) - for key in keys: - if key not in whitelist: - # Non-staff users are allowed to send only some data. Ignore other data. - del request.data[key] - # 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) + response = super().update(request, *args, **kwargs) return response def destroy(self, request, *args, **kwargs):