From 47e1eec122eec8c3cdba484ba098f3327e9551fe Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Wed, 20 May 2020 15:15:24 +0300 Subject: [PATCH] Incoming invitations: implement incoming invitations and accepting them --- django_etesync/models.py | 5 +++++ django_etesync/serializers.py | 38 +++++++++++++++++++++++++++++------ django_etesync/views.py | 26 ++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/django_etesync/models.py b/django_etesync/models.py index e0a79f6..cbfa269 100644 --- a/django_etesync/models.py +++ b/django_etesync/models.py @@ -150,6 +150,7 @@ class CollectionInvitation(models.Model): version = models.PositiveSmallIntegerField(default=1) fromMember = models.ForeignKey(CollectionMember, on_delete=models.CASCADE) # FIXME: make sure to delete all invitations for the same collection once one is accepted + # Make sure to not allow invitations if already a member user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='incoming_invitations', on_delete=models.CASCADE) signedEncryptionKey = models.BinaryField(editable=False, blank=False, null=False) @@ -165,6 +166,10 @@ class CollectionInvitation(models.Model): def __str__(self): return '{} {}'.format(self.fromMember.collection.uid, self.user) + @cached_property + def collection(self): + return self.fromMember.collection + class UserInfo(models.Model): owner = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True) diff --git a/django_etesync/serializers.py b/django_etesync/serializers.py index 54b8d9c..185eb6e 100644 --- a/django_etesync/serializers.py +++ b/django_etesync/serializers.py @@ -268,18 +268,20 @@ class CollectionInvitationSerializer(serializers.ModelSerializer): slug_field=User.USERNAME_FIELD, queryset=User.objects ) - collection = serializers.SlugRelatedField( - source='fromMember__collection', - slug_field='uid', - read_only=True, - ) - fromPubkey = BinaryBase64Field(source='fromMember__user__userinfo__pubkey', read_only=True) + collection = serializers.SerializerMethodField('get_collection') + fromPubkey = serializers.SerializerMethodField('get_from_pubkey') signedEncryptionKey = BinaryBase64Field() class Meta: model = models.CollectionInvitation fields = ('username', 'uid', 'collection', 'signedEncryptionKey', 'accessLevel', 'fromPubkey', 'version') + def get_collection(self, obj): + return obj.collection.uid + + def get_from_pubkey(self, obj): + return b64encode(obj.fromMember.user.userinfo.pubkey) + def create(self, validated_data): collection = self.context['collection'] request = self.context['request'] @@ -301,6 +303,30 @@ class CollectionInvitationSerializer(serializers.ModelSerializer): return instance +class InvitationAcceptSerializer(serializers.Serializer): + encryptionKey = BinaryBase64Field() + + def create(self, validated_data): + + with transaction.atomic(): + invitation = self.context['invitation'] + encryption_key = validated_data.get('encryptionKey') + + member = models.CollectionMember.objects.create( + collection=invitation.collection, + user=invitation.user, + accessLevel=invitation.accessLevel, + encryptionKey=encryption_key, + ) + + invitation.delete() + + return member + + def update(self, instance, validated_data): + raise NotImplementedError() + + class UserSerializer(serializers.ModelSerializer): class Meta: model = User diff --git a/django_etesync/views.py b/django_etesync/views.py index ffb9503..a1d6d09 100644 --- a/django_etesync/views.py +++ b/django_etesync/views.py @@ -49,6 +49,7 @@ from .serializers import ( CollectionItemChunkSerializer, CollectionMemberSerializer, CollectionInvitationSerializer, + InvitationAcceptSerializer, UserSerializer, ) @@ -456,6 +457,31 @@ class CollectionInvitationViewSet(BaseViewSet): return queryset.filter(fromMember__collection=collection) +class InvitationIncomingViewSet(BaseViewSet): + allowed_methods = ['GET', 'DELETE'] + queryset = CollectionInvitation.objects.all() + serializer_class = CollectionInvitationSerializer + lookup_field = 'uid' + lookup_url_kwarg = 'invitation_uid' + + def get_queryset(self, queryset=None): + if queryset is None: + queryset = type(self).queryset + + return queryset.filter(user=self.request.user) + + @action_decorator(detail=True, allowed_methods=['POST'], methods=['POST']) + def accept(self, request, invitation_uid=None): + invitation = get_object_or_404(self.get_queryset(), uid=invitation_uid) + context = self.get_serializer_context() + context.update({'invitation': invitation}) + + serializer = InvitationAcceptSerializer(data=request.data, context=context) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response(status=status.HTTP_201_CREATED) + + class AuthenticationViewSet(viewsets.ViewSet): allowed_methods = ['POST']