From edd88427b0fc739a8610f1e6135680f1627f7c47 Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Wed, 20 May 2020 13:48:46 +0300 Subject: [PATCH] Add a viewset to control collection membership. --- django_etesync/models.py | 11 ++++++----- django_etesync/serializers.py | 26 +++++++++++++++++++++++++- django_etesync/views.py | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 61 insertions(+), 8 deletions(-) diff --git a/django_etesync/models.py b/django_etesync/models.py index 29c7e57..36851a1 100644 --- a/django_etesync/models.py +++ b/django_etesync/models.py @@ -121,12 +121,13 @@ class RevisionChunkRelation(models.Model): ordering = ('id', ) -class CollectionMember(models.Model): - class AccessLevels(models.TextChoices): - ADMIN = 'adm' - READ_WRITE = 'rw' - READ_ONLY = 'ro' +class AccessLevels(models.TextChoices): + ADMIN = 'adm' + READ_WRITE = 'rw' + READ_ONLY = 'ro' + +class CollectionMember(models.Model): collection = models.ForeignKey(Collection, related_name='members', on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) encryptionKey = models.BinaryField(editable=True, blank=False, null=False) diff --git a/django_etesync/serializers.py b/django_etesync/serializers.py index 6413ab1..06bb7a9 100644 --- a/django_etesync/serializers.py +++ b/django_etesync/serializers.py @@ -211,7 +211,7 @@ class CollectionSerializer(serializers.ModelSerializer): models.CollectionMember(collection=instance, user=validated_data.get('owner'), - accessLevel=models.CollectionMember.AccessLevels.ADMIN, + accessLevel=models.AccessLevels.ADMIN, encryptionKey=encryption_key, ).save() @@ -238,6 +238,30 @@ class CollectionSerializer(serializers.ModelSerializer): return instance +class CollectionMemberSerializer(serializers.ModelSerializer): + username = serializers.SlugRelatedField( + source='user', + slug_field=User.USERNAME_FIELD, + queryset=User.objects + ) + encryptionKey = BinaryBase64Field() + + class Meta: + model = models.CollectionMember + fields = ('username', 'encryptionKey', 'accessLevel') + + def create(self, validated_data): + raise NotImplementedError() + + def update(self, instance, validated_data): + with transaction.atomic(): + # We only allow updating accessLevel + instance.accessLevel = validated_data.pop('accessLevel') + instance.save() + + return instance + + class UserSerializer(serializers.ModelSerializer): class Meta: model = User diff --git a/django_etesync/views.py b/django_etesync/views.py index d100d7c..5703410 100644 --- a/django_etesync/views.py +++ b/django_etesync/views.py @@ -33,8 +33,8 @@ import nacl.signing import nacl.secret import nacl.hash -from . import app_settings -from .models import Collection, CollectionItem, CollectionItemRevision +from . import app_settings, permissions +from .models import Collection, CollectionItem, CollectionItemRevision, CollectionMember from .serializers import ( b64encode, AuthenticationSignupSerializer, @@ -47,6 +47,7 @@ from .serializers import ( CollectionItemDepSerializer, CollectionItemRevisionSerializer, CollectionItemChunkSerializer, + CollectionMemberSerializer, UserSerializer, ) @@ -395,6 +396,33 @@ class CollectionItemChunkViewSet(viewsets.ViewSet): return serve(request, basename, dirname) +class CollectionMemberViewSet(BaseViewSet): + allowed_methods = ['GET', 'PUT', 'DELETE'] + permission_classes = BaseViewSet.permission_classes + (permissions.IsCollectionAdmin, ) + queryset = CollectionMember.objects.all() + serializer_class = CollectionMemberSerializer + lookup_field = 'user__' + User.USERNAME_FIELD + lookup_url_kwarg = 'username' + + # FIXME: need to make sure that there's always an admin, and maybe also don't let an owner remove adm access + # (if we want to transfer, we need to do that specifically) + + def get_queryset(self, queryset=None): + collection_uid = self.kwargs['collection_uid'] + try: + collection = self.get_collection_queryset(Collection.objects).get(uid=collection_uid) + except Collection.DoesNotExist: + raise Http404('Collection does not exist') + + if queryset is None: + queryset = type(self).queryset + + return queryset.filter(collection=collection) + + def create(self, request): + return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED) + + class AuthenticationViewSet(viewsets.ViewSet): allowed_methods = ['POST']