Compare commits

...

2 Commits

Author SHA1 Message Date
Manuel Raynaud
8cd84545ce 🔒️(nginx) manage Content-Security-Policy in nginx config
The media route is managed by nginx. On this route we want to add the
Content-Security-Header to forbid fetching any resources.
See : https://content-security-policy.com/
2025-02-27 16:23:04 +01:00
Manuel Raynaud
3804f7524a 🔒️(back) set ContentDisposition on media upload
On the media upload endpoint, we want to set the content-disposition
header. Its value is based on the uploaded file mime-type and if flagged
as unsafe. If the file is not an image or is unsafe then the
contentDisposition is set to attachment to force its download.
Otherwise, we set it to inline.
2025-02-27 16:19:17 +01:00
5 changed files with 23 additions and 0 deletions

View File

@@ -68,6 +68,8 @@ server {
# Get resource from Minio
proxy_pass http://minio:9000/impress-media-storage/;
proxy_set_header Host minio:9000;
add_header Content-Security-Policy "default-src 'none'" always;
}
location /media-auth {

View File

@@ -418,6 +418,7 @@ class FileUploadSerializer(serializers.Serializer):
self.context["expected_extension"] = extension
self.context["content_type"] = magic_mime_type
self.context["file_name"] = file.name
return file
@@ -426,6 +427,7 @@ class FileUploadSerializer(serializers.Serializer):
attrs["expected_extension"] = self.context["expected_extension"]
attrs["is_unsafe"] = self.context["is_unsafe"]
attrs["content_type"] = self.context["content_type"]
attrs["file_name"] = self.context["file_name"]
return attrs

View File

@@ -925,6 +925,19 @@ class DocumentViewSet(
if serializer.validated_data["is_unsafe"]:
extra_args["Metadata"]["is_unsafe"] = "true"
file_name = serializer.validated_data["file_name"]
if (
not serializer.validated_data["content_type"].startswith("image/")
or serializer.validated_data["is_unsafe"]
):
extra_args.update(
{"ContentDisposition": f'attachment; filename="{file_name:s}"'}
)
else:
extra_args.update(
{"ContentDisposition": f'inline; filename="{file_name:s}"'}
)
file = serializer.validated_data["file"]
default_storage.connection.meta.client.upload_fileobj(
file, default_storage.bucket_name, key, ExtraArgs=extra_args

View File

@@ -79,6 +79,7 @@ def test_api_documents_attachment_upload_anonymous_success():
assert file_head["Metadata"] == {"owner": "None"}
assert file_head["ContentType"] == "image/png"
assert file_head["ContentDisposition"] == 'inline; filename="test.png"'
@pytest.mark.parametrize(
@@ -217,6 +218,7 @@ def test_api_documents_attachment_upload_success(via, role, mock_user_teams):
)
assert file_head["Metadata"] == {"owner": str(user.id)}
assert file_head["ContentType"] == "image/png"
assert file_head["ContentDisposition"] == 'inline; filename="test.png"'
def test_api_documents_attachment_upload_invalid(client):
@@ -301,6 +303,7 @@ def test_api_documents_attachment_upload_fix_extension(
)
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
assert file_head["ContentType"] == content_type
assert file_head["ContentDisposition"] == f'attachment; filename="{name:s}"'
def test_api_documents_attachment_upload_empty_file():
@@ -350,3 +353,4 @@ def test_api_documents_attachment_upload_unsafe():
)
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
assert file_head["ContentType"] == "application/octet-stream"
assert file_head["ContentDisposition"] == 'attachment; filename="script.exe"'

View File

@@ -170,6 +170,8 @@ ingressMedia:
nginx.ingress.kubernetes.io/auth-url: https://impress.example.com/api/v1.0/documents/media-auth/
nginx.ingress.kubernetes.io/auth-response-headers: "Authorization, X-Amz-Date, X-Amz-Content-SHA256"
nginx.ingress.kubernetes.io/upstream-vhost: minio.impress.svc.cluster.local:9000
nginx.ingress.kubernetes.io/configuration-snippet: |
add_header Content-Security-Policy "default-src 'none'" always;
## @param serviceMedia.host
## @param serviceMedia.port