Incoming invitations: implement incoming invitations and accepting them

This commit is contained in:
Tom Hacohen 2020-05-20 15:15:24 +03:00
parent 8d1c02dcb9
commit 47e1eec122
3 changed files with 63 additions and 6 deletions

View File

@ -150,6 +150,7 @@ class CollectionInvitation(models.Model):
version = models.PositiveSmallIntegerField(default=1) version = models.PositiveSmallIntegerField(default=1)
fromMember = models.ForeignKey(CollectionMember, on_delete=models.CASCADE) fromMember = models.ForeignKey(CollectionMember, on_delete=models.CASCADE)
# FIXME: make sure to delete all invitations for the same collection once one is accepted # 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) user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='incoming_invitations', on_delete=models.CASCADE)
signedEncryptionKey = models.BinaryField(editable=False, blank=False, null=False) signedEncryptionKey = models.BinaryField(editable=False, blank=False, null=False)
@ -165,6 +166,10 @@ class CollectionInvitation(models.Model):
def __str__(self): def __str__(self):
return '{} {}'.format(self.fromMember.collection.uid, self.user) return '{} {}'.format(self.fromMember.collection.uid, self.user)
@cached_property
def collection(self):
return self.fromMember.collection
class UserInfo(models.Model): class UserInfo(models.Model):
owner = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True) owner = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, primary_key=True)

View File

@ -268,18 +268,20 @@ class CollectionInvitationSerializer(serializers.ModelSerializer):
slug_field=User.USERNAME_FIELD, slug_field=User.USERNAME_FIELD,
queryset=User.objects queryset=User.objects
) )
collection = serializers.SlugRelatedField( collection = serializers.SerializerMethodField('get_collection')
source='fromMember__collection', fromPubkey = serializers.SerializerMethodField('get_from_pubkey')
slug_field='uid',
read_only=True,
)
fromPubkey = BinaryBase64Field(source='fromMember__user__userinfo__pubkey', read_only=True)
signedEncryptionKey = BinaryBase64Field() signedEncryptionKey = BinaryBase64Field()
class Meta: class Meta:
model = models.CollectionInvitation model = models.CollectionInvitation
fields = ('username', 'uid', 'collection', 'signedEncryptionKey', 'accessLevel', 'fromPubkey', 'version') 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): def create(self, validated_data):
collection = self.context['collection'] collection = self.context['collection']
request = self.context['request'] request = self.context['request']
@ -301,6 +303,30 @@ class CollectionInvitationSerializer(serializers.ModelSerializer):
return instance 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 UserSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = User model = User

View File

@ -49,6 +49,7 @@ from .serializers import (
CollectionItemChunkSerializer, CollectionItemChunkSerializer,
CollectionMemberSerializer, CollectionMemberSerializer,
CollectionInvitationSerializer, CollectionInvitationSerializer,
InvitationAcceptSerializer,
UserSerializer, UserSerializer,
) )
@ -456,6 +457,31 @@ class CollectionInvitationViewSet(BaseViewSet):
return queryset.filter(fromMember__collection=collection) 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): class AuthenticationViewSet(viewsets.ViewSet):
allowed_methods = ['POST'] allowed_methods = ['POST']