From c79984a883acacccb9e398a4f3b1cb4ee599c8c5 Mon Sep 17 00:00:00 2001 From: Florent Chehab Date: Wed, 3 Jun 2026 15:15:24 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F(backend)=20prevent=20acce?= =?UTF-8?q?ssing=20files=20if=20they=20are=20not=20ready?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the addition of the ANALYSING state files could be accessed in the short time they were in that state. We now require files to be in ready. Also adds missing frontend types (no impact). --- CHANGELOG.md | 1 + src/backend/core/admin.py | 6 +++--- src/backend/core/api/serializers.py | 2 +- src/backend/core/api/viewsets.py | 2 +- src/backend/core/models.py | 6 +++--- src/backend/core/tests/files/test_api_files_media_auth.py | 8 ++++++-- src/frontend/src/features/files/api/types.ts | 2 +- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71bb0842..31a2cc5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to ### Fixed - 🔇(summary) make ffmpeg quiet #1404 +- 🔒️(backend) prevent accessing files if they are not ready #1395 ## [1.18.0] - 2026-06-03 diff --git a/src/backend/core/admin.py b/src/backend/core/admin.py index a8ee7568..afcea086 100644 --- a/src/backend/core/admin.py +++ b/src/backend/core/admin.py @@ -177,7 +177,7 @@ class FileAdmin(admin.ModelAdmin): "hard_deleted_at", "description", "malware_detection_info", - "is_pending_upload", + "is_ready", "preview_url", "extension", "key_base", @@ -226,7 +226,7 @@ class FileAdmin(admin.ModelAdmin): _("Derived info"), { "fields": ( - "is_pending_upload", + "is_ready", "extension", "key_base", "file_key", @@ -240,7 +240,7 @@ class FileAdmin(admin.ModelAdmin): @admin.display(description=_("File preview")) def preview_url(self, obj): """Return a clickable preview URL for the file.""" - if obj.is_pending_upload: + if not obj.is_ready: return "-" url = generate_download_file_url(obj, expires_in=60 * 60) diff --git a/src/backend/core/api/serializers.py b/src/backend/core/api/serializers.py index f12f9b4c..3379a550 100644 --- a/src/backend/core/api/serializers.py +++ b/src/backend/core/api/serializers.py @@ -456,7 +456,7 @@ class ListFileSerializer(serializers.ModelSerializer): def get_url(self, obj): """Return the URL of the file.""" - if obj.is_pending_upload: + if not obj.is_ready: return None return f"{settings.MEDIA_BASE_URL}{settings.MEDIA_URL}{quote(obj.file_key)}" diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py index f56dae11..242d8255 100644 --- a/src/backend/core/api/viewsets.py +++ b/src/backend/core/api/viewsets.py @@ -1417,7 +1417,7 @@ class FileViewSet( request, MEDIA_STORAGE_URL_PATTERN ) - if file.is_pending_upload: + if not file.is_ready: logger.warning("File '%s' is not ready", file.id) raise drf_exceptions.PermissionDenied() diff --git a/src/backend/core/models.py b/src/backend/core/models.py index 60879937..1a7ec3c9 100644 --- a/src/backend/core/models.py +++ b/src/backend/core/models.py @@ -925,9 +925,9 @@ class File(BaseModel): return super().delete(using, keep_parents) @property - def is_pending_upload(self): - """Return whether the file is in a pending upload state""" - return self.upload_state == FileUploadStateChoices.PENDING + def is_ready(self): + """Return whether the file is in a ready upload state""" + return self.upload_state == FileUploadStateChoices.READY @property def extension(self): diff --git a/src/backend/core/tests/files/test_api_files_media_auth.py b/src/backend/core/tests/files/test_api_files_media_auth.py index c537a5f7..51259f5a 100644 --- a/src/backend/core/tests/files/test_api_files_media_auth.py +++ b/src/backend/core/tests/files/test_api_files_media_auth.py @@ -86,7 +86,11 @@ def test_api_files_media_get_own(): assert response.content.decode("utf-8") == "my prose" -def test_api_files_media_auth_file_pending(): +@pytest.mark.parametrize( + "rejecting_status", + [models.FileUploadStateChoices.PENDING, models.FileUploadStateChoices.ANALYZING], +) +def test_api_files_media_auth_rejects(rejecting_status): """ Users who have a specific access to an file, whatever the role, should not be able to retrieve related attachments if the file is not ready. @@ -97,7 +101,7 @@ def test_api_files_media_auth_file_pending(): file = factories.FileFactory( type=models.FileTypeChoices.BACKGROUND_IMAGE, - upload_state=models.FileUploadStateChoices.PENDING, + upload_state=rejecting_status, creator=user, ) diff --git a/src/frontend/src/features/files/api/types.ts b/src/frontend/src/features/files/api/types.ts index 5c7891d6..92efc903 100644 --- a/src/frontend/src/features/files/api/types.ts +++ b/src/frontend/src/features/files/api/types.ts @@ -5,7 +5,7 @@ export type ApiFileCreator = { } export type ApiFileType = 'background_image' -export type ApiFileUploadState = 'pending' | 'ready' +export type ApiFileUploadState = 'pending' | 'analyzing' | 'ready' export type ApiFileItem = { id: string // UUID