🐛(backend) handle object storage metadata keys case-insensitively

Object storage metadata keys are case insensitive per the S3
specification, but implementations don't agree on the case they give
back. When head_object returns a capitalized "Status", updating the
attachment status added a second, lowercase entry instead of replacing
it, and the copy request ended up carrying two x-amz-meta-status
headers. Ceph RadosGW loses one of them behind a proxy, which
invalidates the request signature.

The same assumption was made when reading the status back in media-auth
and media-check, where an attachment stored on such a backend stayed in
"processing" forever.

Metadata read from the storage is now normalized to lowercase keys
before being consumed or copied over.

Signed-off-by: risk-alt <aldu6974@gmail.com>
This commit is contained in:
risk-alt
2026-08-14 11:30:52 +02:00
committed by Anthony LC
parent 0b933ed3a2
commit fb984abab3
9 changed files with 164 additions and 7 deletions
+4 -3
View File
@@ -71,6 +71,7 @@ from core.services.search_indexers import (
from core.tasks.access import reset_service_connections_in_cascade
from core.tasks.mail import send_ask_for_access_mail
from core.utils.analytics import PosthogEventName, posthog_capture
from core.utils.dicts import lowercase_keys
from core.utils.paths import filter_descendants
from core.utils.s3_response_stream import content_stream
from core.utils.treebeard import create_tree_node_with_retry
@@ -1884,7 +1885,7 @@ class DocumentViewSet(
extra_args = {
"Metadata": {
"owner": str(request.user.id),
"status": enums.DocumentAttachmentStatus.PROCESSING,
"status": enums.DocumentAttachmentStatus.PROCESSING.value,
},
"ContentType": serializer.validated_data["content_type"],
}
@@ -2034,7 +2035,7 @@ class DocumentViewSet(
head_resp = s3_client.head_object(Bucket=bucket_name, Key=key)
except ClientError as err:
raise drf.exceptions.PermissionDenied() from err
metadata = head_resp.get("Metadata", {})
metadata = lowercase_keys(head_resp.get("Metadata", {}))
# In order to be compatible with existing upload without `status` metadata,
# we consider them as ready.
if (
@@ -2238,7 +2239,7 @@ class DocumentViewSet(
{"detail": "Media not found"},
status=drf.status.HTTP_404_NOT_FOUND,
)
metadata = head_resp.get("Metadata", {})
metadata = lowercase_keys(head_resp.get("Metadata", {}))
body = {
"status": metadata.get("status", enums.DocumentAttachmentStatus.PROCESSING),