mirror of
https://github.com/suitenumerique/drive.git
synced 2026-08-17 20:15:40 +02:00
🔒️(backend) scan files written through WOPI
PutFile wrote the content straight to object storage, while malware detection only ran on the upload endpoint. Editing a document was therefore a way to store content that was never analysed. The item stays READY during the analysis: a collaborator cannot open a file that is not READY, so flipping the state would eject everyone from the document on every save.
This commit is contained in:
@@ -31,6 +31,7 @@ and this project adheres to
|
||||
- 🐛(docker) pin collabora image and adapt to its new runtime contract
|
||||
- 🐛(backend) delete malware detection record when purging an item
|
||||
- 🔒️(backend) reject unsafe filenames requested by WOPI renames
|
||||
- 🔒️(backend) analyze file content written through WOPI
|
||||
|
||||
## [v0.20.0] - 2026-07-15
|
||||
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
"""Test the PUT file content viewset."""
|
||||
|
||||
from unittest import mock
|
||||
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
import pytest
|
||||
from lasuite.malware_detection import malware_detection
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from core import factories, models
|
||||
@@ -38,6 +41,7 @@ def test_put_file_content_connected_user_with_access():
|
||||
client = APIClient()
|
||||
assert item.size == 0
|
||||
updated_at = item.updated_at
|
||||
with mock.patch.object(malware_detection, "analyse_file") as mock_analyse_file:
|
||||
response = client.post(
|
||||
f"/api/v1.0/wopi/files/{item.id}/contents/",
|
||||
data=b"new content",
|
||||
@@ -48,6 +52,8 @@ def test_put_file_content_connected_user_with_access():
|
||||
"X-WOPI-Lock": "1234567890",
|
||||
},
|
||||
)
|
||||
|
||||
mock_analyse_file.assert_called_once_with(item.file_key, item_id=item.id)
|
||||
assert response.status_code == 200
|
||||
assert "X-WOPI-ItemVersion" in response.headers
|
||||
|
||||
@@ -61,6 +67,7 @@ def test_put_file_content_connected_user_with_access():
|
||||
assert response.headers.get("X-WOPI-ItemVersion") == file["ETag"].strip('"')
|
||||
item.refresh_from_db()
|
||||
assert item.size == 11 # the size should have been updated
|
||||
assert item.upload_state == models.ItemUploadStateChoices.READY
|
||||
assert item.updated_at > updated_at
|
||||
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.core.files.storage import default_storage
|
||||
from django.db import transaction
|
||||
from django.http import StreamingHttpResponse
|
||||
|
||||
from lasuite.malware_detection import malware_detection
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
@@ -187,8 +188,12 @@ class WopiViewSet(viewsets.ViewSet):
|
||||
s3_client = default_storage.connection.meta.client
|
||||
default_storage.save(item.file_key, file)
|
||||
item.size = file.size
|
||||
# Keep the item READY during re-analysis: non-creators cannot open
|
||||
# non-READY files in WOPI.
|
||||
item.save(update_fields=["size", "updated_at"])
|
||||
|
||||
malware_detection.analyse_file(item.file_key, item_id=item.id)
|
||||
|
||||
head_response = s3_client.head_object(Bucket=default_storage.bucket_name, Key=item.file_key)
|
||||
return Response(
|
||||
status=200,
|
||||
|
||||
Reference in New Issue
Block a user