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