diff --git a/arkindex/dataimport/api.py b/arkindex/dataimport/api.py index 2dc1834125b2f8b80d32217ae4ca0d996bf50c4f..587a12fbda62faa63391180de934cd94d8ef417e 100644 --- a/arkindex/dataimport/api.py +++ b/arkindex/dataimport/api.py @@ -1,4 +1,5 @@ from django.db.models import Sum +from django.http.response import Http404 from django.shortcuts import get_object_or_404 from django.core.exceptions import PermissionDenied from django.conf import settings @@ -41,13 +42,13 @@ class DataImportsList(CorpusACLMixin, ListCreateAPIView): if serializer.validated_data['mode'] not in (DataImportMode.Images, ): raise ValidationError('Unsupported mode for now, sorry.') - if Right.Write not in serializer.validated_data['corpus'].get_acl_rights(self.request.user): + if not self.has_write_access(serializer.validated_data['corpus']): raise PermissionDenied return super().perform_create(serializer) -class DataImportDetails(RetrieveUpdateDestroyAPIView): +class DataImportDetails(CorpusACLMixin, RetrieveUpdateDestroyAPIView): """ Retrieve and edit a data import """ @@ -60,7 +61,7 @@ class DataImportDetails(RetrieveUpdateDestroyAPIView): def perform_update(self, serializer): dataimport = serializer.instance - if Right.Write not in dataimport.corpus.get_acl_rights(self.request.user): + if not self.has_write_access(dataimport.corpus): raise PermissionDenied if dataimport.state not in (DataImportState.Created, DataImportState.Configured): @@ -84,7 +85,7 @@ class DataImportDetails(RetrieveUpdateDestroyAPIView): dataimport.save() def perform_destroy(self, instance): - if Right.Write not in instance.corpus.get_acl_rights(self.request.user): + if not self.has_write_access(instance.corpus): raise PermissionDenied if instance.state == DataImportState.Running: raise ValidationError("Cannot delete a workflow while it is running") @@ -106,7 +107,7 @@ class DataImportFailures(ListAPIView): ).prefetch_related('dataimport__revision__repo', 'element').order_by('path', 'line') -class DataImportDemo(CreateAPIView): +class DataImportDemo(CorpusACLMixin, CreateAPIView): """ Create, configure and start an Images workflow from a single DataFile """ @@ -117,7 +118,7 @@ class DataImportDemo(CreateAPIView): def create(self, request, pk=None, **kwargs): # Get Datafile - corpora = Corpus.objects.readable(self.request.user).filter(corpus_right__can_write=True) + corpora = Corpus.objects.writable(self.request.user) datafile = get_object_or_404(DataFile, corpus__in=corpora, id=pk) # Get volume, defaults to demo one @@ -133,7 +134,7 @@ class DataImportDemo(CreateAPIView): volume = get_object_or_404(Element, **filters) - assert Right.Write in volume.corpus.get_acl_rights(self.request.user), \ + assert self.has_write_access(volume.corpus), \ 'Corpus is not writable' # Start the import @@ -164,7 +165,7 @@ class DataFileList(CorpusACLMixin, ListAPIView): return DataFile.objects.filter(corpus=self.get_corpus(self.kwargs['pk'])) -class DataFileRetrieve(RetrieveUpdateDestroyAPIView): +class DataFileRetrieve(CorpusACLMixin, RetrieveUpdateDestroyAPIView): """ Get one file """ @@ -175,17 +176,17 @@ class DataFileRetrieve(RetrieveUpdateDestroyAPIView): return DataFile.objects.filter(corpus__in=Corpus.objects.readable(self.request.user)) def perform_update(self, serializer): - if Right.Write not in serializer.instance.corpus.get_acl_rights(self.request.user): + if not self.has_write_access(serializer.instance.corpus): raise PermissionDenied return super().perform_update(serializer) def perform_destroy(self, instance): - if Right.Write not in instance.corpus.get_acl_rights(self.request.user): + if not self.has_write_access(instance.corpus): raise PermissionDenied return super().perform_destroy(instance) -class DataFileUpload(APIView): +class DataFileUpload(CorpusACLMixin, APIView): """ Upload a new file to a corpus """ @@ -195,14 +196,10 @@ class DataFileUpload(APIView): def post(self, request, pk=None, format=None): if 'file' not in request.FILES: raise ValidationError({'file': ['No file was sent in the request']}) - corpus_qs = Corpus.objects.filter(id=pk) - if not corpus_qs.exists(): + try: + corpus = self.get_corpus(pk, right=Right.Write) + except Http404: raise ValidationError({'corpus': ['Corpus not found']}) - corpus = corpus_qs.get() - - # Check corpus is writable for current user - if Right.Write not in corpus.get_acl_rights(self.request.user): - raise PermissionDenied file_obj = request.FILES['file'] @@ -287,7 +284,7 @@ class AvailableRepositoriesList(ListCreateAPIView): return Response(data={'import_id': str(dataimport.id)}, status=status.HTTP_201_CREATED) -class RepositoryRetrieve(RetrieveUpdateDestroyAPIView): +class RepositoryRetrieve(CorpusACLMixin, RetrieveUpdateDestroyAPIView): permission_classes = (IsAuthenticated, ) serializer_class = RepositorySerializer @@ -298,12 +295,12 @@ class RepositoryRetrieve(RetrieveUpdateDestroyAPIView): ) def perform_update(self, serializer): - if Right.Write not in serializer.instance.corpus.get_acl_rights(self.request.user): + if not self.has_write_access(serializer.instance.corpus): raise PermissionDenied return super().perform_update(self, serializer) def perform_destroy(self, instance): - if Right.Write not in instance.corpus.get_acl_rights(self.request.user): + if not self.has_write_access(instance.corpus): raise PermissionDenied return super().perform_destroy(self, instance) @@ -314,7 +311,7 @@ class RepositoryStartImport(RetrieveAPIView): def get_queryset(self): return Repository.objects.filter( credentials__user=self.request.user, - corpus__in=Corpus.objects.readable(self.request.user).filter(corpus_right__can_write=True), + corpus__in=Corpus.objects.writable(self.request.user), ) def get(self, request, *args, **kwargs): diff --git a/arkindex/dataimport/serializers.py b/arkindex/dataimport/serializers.py index ae812acd4774f770d4a0f564237b45d7cb14b3b6..17c79039f97496403429c679fac97c947b8938c7 100644 --- a/arkindex/dataimport/serializers.py +++ b/arkindex/dataimport/serializers.py @@ -253,7 +253,6 @@ class ExternalRepositorySerializer(serializers.BaseSerializer): return { 'id': data['id'], - 'corpus': Corpus.objects.readable(self.request.user) - .filter(corpus_right__can_write=True) + 'corpus': Corpus.objects.writable(self.request.user) .get(id=data['corpus']) } diff --git a/arkindex/documents/managers.py b/arkindex/documents/managers.py index f8c4d2a216f34d00db220da5dfb12a8899a54fd1..a3cdf753b8e6d89ef9f7082018d2d8565d0d5165 100644 --- a/arkindex/documents/managers.py +++ b/arkindex/documents/managers.py @@ -140,3 +140,17 @@ class CorpusManager(models.Manager): qs = qs.prefetch_related('corpus_right') qs = qs.filter(models.Q(public=True) | models.Q(corpus_right__user=user)) return qs + + def writable(self, user): + # An anonymous user cannot write anything + if user.is_anonymous: + return super().none() + + qs = super().get_queryset().order_by('name') + + # Admins have access to every corpus + if user.is_admin: + return qs.all() + + # Authenticated users can write only on corpora with ACL + return qs.filter(corpus_right__user=user, corpus_right__can_write=True) diff --git a/arkindex/documents/serializers/transcriptions.py b/arkindex/documents/serializers/transcriptions.py index 3596477605ef181598df30b79639349514f7a588..8f7373f5ab9730018a34d12c69e4f30eaf6dc282 100644 --- a/arkindex/documents/serializers/transcriptions.py +++ b/arkindex/documents/serializers/transcriptions.py @@ -43,7 +43,7 @@ class TranscriptionCreateSerializer(serializers.Serializer): super().__init__(*args, **kwargs) assert 'request' in self.context, 'An API request is required to initialize this serializer' self.fields['element'].queryset = Element.objects.filter( - corpus__in=Corpus.objects.readable(self.context['request'].user).filter(corpus_right__can_write=True), + corpus__in=Corpus.objects.writable(self.context['request'].user), ) @@ -79,5 +79,5 @@ class TranscriptionsSerializer(serializers.Serializer): super().__init__(*args, **kwargs) assert 'request' in self.context, 'An API request is required to initialize this serializer' self.fields['parent'].queryset = Element.objects.filter( - corpus__in=Corpus.objects.readable(self.context['request'].user).filter(corpus_right__can_write=True), + corpus__in=Corpus.objects.writable(self.context['request'].user), ) diff --git a/arkindex/project/mixins.py b/arkindex/project/mixins.py index 36c82363100dba2abff81252ef7307fe63d8f9ad..fa2fc6238c50f03989bad13f971b2164d17eac2d 100644 --- a/arkindex/project/mixins.py +++ b/arkindex/project/mixins.py @@ -10,3 +10,17 @@ class CorpusACLMixin(object): if right not in corpus.get_acl_rights(self.request.user): raise PermissionDenied() return corpus + + def has_read_access(self, corpus): + assert isinstance(corpus, Corpus) + return corpus.public or self.request.user.is_admin or Right.Read in corpus.get_acl_rights(self.request.user) + + def has_write_access(self, corpus): + assert isinstance(corpus, Corpus) + return self.request.user.is_admin or not self.request.user.is_anonymous and \ + Right.Write in corpus.get_acl_rights(self.request.user) + + def has_admin_access(self, corpus): + assert isinstance(corpus, Corpus) + return self.request.user.is_admin or not self.request.user.is_anonymous and \ + Right.Admin in corpus.get_acl_rights(self.request.user)