mirror of
https://github.com/etesync/server
synced 2024-11-22 16:58:08 +00:00
Collection membership: implement leaving/revoking access.
This commit is contained in:
parent
d93a5d3f06
commit
6e7fd5d0dd
28
django_etesync/migrations/0013_collectionmemberremoved.py
Normal file
28
django_etesync/migrations/0013_collectionmemberremoved.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 3.0.3 on 2020-05-27 11:29
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('django_etesync', '0012_auto_20200527_0743'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CollectionMemberRemoved',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='removed_members', to='django_etesync.Collection')),
|
||||||
|
('stoken', models.OneToOneField(null=True, on_delete=django.db.models.deletion.PROTECT, to='django_etesync.Stoken')),
|
||||||
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('user', 'collection')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models, transaction
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
@ -158,6 +158,29 @@ class CollectionMember(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} {}'.format(self.collection.uid, self.user)
|
return '{} {}'.format(self.collection.uid, self.user)
|
||||||
|
|
||||||
|
def revoke(self):
|
||||||
|
with transaction.atomic():
|
||||||
|
CollectionMemberRemoved.objects.update_or_create(
|
||||||
|
collection=self.collection, user=self.user,
|
||||||
|
defaults={
|
||||||
|
'stoken': Stoken.objects.create(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionMemberRemoved(models.Model):
|
||||||
|
stoken = models.OneToOneField(Stoken, on_delete=models.PROTECT, null=True)
|
||||||
|
collection = models.ForeignKey(Collection, related_name='removed_members', on_delete=models.CASCADE)
|
||||||
|
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
unique_together = ('user', 'collection')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '{} {}'.format(self.collection.uid, self.user)
|
||||||
|
|
||||||
|
|
||||||
class CollectionInvitation(models.Model):
|
class CollectionInvitation(models.Model):
|
||||||
uid = models.CharField(db_index=True, blank=False, null=False,
|
uid = models.CharField(db_index=True, blank=False, null=False,
|
||||||
|
@ -247,11 +247,10 @@ class CollectionMemberSerializer(serializers.ModelSerializer):
|
|||||||
slug_field=User.USERNAME_FIELD,
|
slug_field=User.USERNAME_FIELD,
|
||||||
queryset=User.objects
|
queryset=User.objects
|
||||||
)
|
)
|
||||||
encryptionKey = BinaryBase64Field()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.CollectionMember
|
model = models.CollectionMember
|
||||||
fields = ('username', 'encryptionKey', 'accessLevel')
|
fields = ('username', 'accessLevel')
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
@ -324,6 +323,9 @@ class InvitationAcceptSerializer(serializers.Serializer):
|
|||||||
encryptionKey=encryption_key,
|
encryptionKey=encryption_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
models.CollectionMemberRemoved.objects.filter(
|
||||||
|
user=invitation.user, collection=invitation.collection).delete()
|
||||||
|
|
||||||
invitation.delete()
|
invitation.delete()
|
||||||
|
|
||||||
return member
|
return member
|
||||||
|
@ -42,6 +42,7 @@ from .models import (
|
|||||||
CollectionItem,
|
CollectionItem,
|
||||||
CollectionItemRevision,
|
CollectionItemRevision,
|
||||||
CollectionMember,
|
CollectionMember,
|
||||||
|
CollectionMemberRemoved,
|
||||||
CollectionInvitation,
|
CollectionInvitation,
|
||||||
Stoken,
|
Stoken,
|
||||||
UserInfo,
|
UserInfo,
|
||||||
@ -181,6 +182,15 @@ class CollectionViewSet(BaseViewSet):
|
|||||||
'data': serializer.data,
|
'data': serializer.data,
|
||||||
'stoken': new_stoken,
|
'stoken': new_stoken,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stoken_obj = self.get_stoken_obj(request)
|
||||||
|
if stoken_obj is not None:
|
||||||
|
# FIXME: honour limit? (the limit should be combined for data and this because of stoken)
|
||||||
|
remed = CollectionMemberRemoved.objects.filter(user=request.user, stoken__id__gt=stoken_obj.id) \
|
||||||
|
.values_list('collection__uid', flat=True)
|
||||||
|
if len(remed) > 0:
|
||||||
|
ret['removedMemberships'] = [{'uid': x} for x in remed]
|
||||||
|
|
||||||
return Response(ret)
|
return Response(ret)
|
||||||
|
|
||||||
|
|
||||||
@ -417,7 +427,8 @@ class CollectionItemChunkViewSet(viewsets.ViewSet):
|
|||||||
|
|
||||||
class CollectionMemberViewSet(BaseViewSet):
|
class CollectionMemberViewSet(BaseViewSet):
|
||||||
allowed_methods = ['GET', 'PUT', 'DELETE']
|
allowed_methods = ['GET', 'PUT', 'DELETE']
|
||||||
permission_classes = BaseViewSet.permission_classes + (permissions.IsCollectionAdmin, )
|
our_base_permission_classes = BaseViewSet.permission_classes
|
||||||
|
permission_classes = our_base_permission_classes + (permissions.IsCollectionAdmin, )
|
||||||
queryset = CollectionMember.objects.all()
|
queryset = CollectionMember.objects.all()
|
||||||
serializer_class = CollectionMemberSerializer
|
serializer_class = CollectionMemberSerializer
|
||||||
lookup_field = 'user__' + User.USERNAME_FIELD
|
lookup_field = 'user__' + User.USERNAME_FIELD
|
||||||
@ -441,6 +452,21 @@ class CollectionMemberViewSet(BaseViewSet):
|
|||||||
def create(self, request):
|
def create(self, request):
|
||||||
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
|
||||||
|
# FIXME: block leaving if we are the last admins - should be deleted / assigned in this case depending if there
|
||||||
|
# are other memebers.
|
||||||
|
def perform_destroy(self, instance):
|
||||||
|
instance.revoke()
|
||||||
|
|
||||||
|
@action_decorator(detail=False, methods=['POST'], permission_classes=our_base_permission_classes)
|
||||||
|
def leave(self, request, collection_uid=None):
|
||||||
|
collection_uid = self.kwargs['collection_uid']
|
||||||
|
col = get_object_or_404(self.get_collection_queryset(Collection.objects), uid=collection_uid)
|
||||||
|
|
||||||
|
member = col.members.get(user=request.user)
|
||||||
|
self.perform_destroy(member)
|
||||||
|
|
||||||
|
return Response({})
|
||||||
|
|
||||||
|
|
||||||
class InvitationOutgoingViewSet(BaseViewSet):
|
class InvitationOutgoingViewSet(BaseViewSet):
|
||||||
allowed_methods = ['GET', 'POST', 'PUT', 'DELETE']
|
allowed_methods = ['GET', 'POST', 'PUT', 'DELETE']
|
||||||
|
Loading…
Reference in New Issue
Block a user