Repository: pulp/pulpcore

Issue:
Do not expose artifact digest via content-disposition header using Azure backend
**Version**
main and older

**Describe the bug**
When retrieving content from Azure backend `Content-disposition` header contains artifact's digest in the filename. 
 
**To Reproduce**
```
$ curl --head https://pulp3-source-fedora36.puffy.example.com/pulp/content/lukasku/Packages/t/tada.rpm -L
HTTP/1.1 302 Found
Server: nginx
Date: Fri, 23 Sep 2022 10:18:43 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 10
Connection: keep-alive
Location: https://pulp-azurite:10000/devstoreaccount1/pulp-test/pulp3/artifact/de/b341b9743f09c938277d645ec9f50d90143734e695cf58d14191c279ff6128?se=2022-09-23T10%3A20%3A43Z&sp=r&sv=2021-08-06&sr=b&rscd=attachment%253Bfilename%3Dartifact/de/b341b9743f09c938277d645ec9f50d90143734e695cf58d14191c279ff6128&sig=TYou/u4cjMat3kab1kCkynRruhGiYJrBDatXIB0cmaQ%3D

HTTP/1.1 200 OK
Server: Azurite-Blob/3.19.0
last-modified: Fri, 23 Sep 2022 10:10:38 GMT
x-ms-creation-time: Fri, 23 Sep 2022 10:10:38 GMT
x-ms-blob-type: BlockBlob
x-ms-lease-state: available
x-ms-lease-status: unlocked
content-length: 2494
content-type: application/octet-stream
etag: "0x21B8870A8293B60"
content-md5: PJ1thLs2HRCKNB4OsNW92A==
content-disposition: attachment%3Bfilename=artifact/de/b341b9743f09c938277d645ec9f50d90143734e695cf58d14191c279ff6128
x-ms-request-id: 6b3e8e1f-abb0-4902-8fc5-43cf81a2cd86
x-ms-version: 2021-10-04
date: Fri, 23 Sep 2022 10:18:43 GMT
accept-ranges: bytes
x-ms-server-encrypted: true
x-ms-access-tier: Hot
x-ms-access-tier-inferred: true
x-ms-access-tier-change-time: Fri, 23 Sep 2022 10:10:38 GMT
Connection: keep-alive
Keep-Alive: timeout=5

```

**Expected behavior**
Filename is user friendly and user readable and does not expose artifact digest.



Discussion:

Resolving patch:
diff --git a/pulpcore/app/util.py b/pulpcore/app/util.py
--- a/pulpcore/app/util.py
+++ b/pulpcore/app/util.py
@@ -276,23 +276,24 @@ def get_artifact_url(artifact, headers=None, http_method=None):
     or private cloud storage.
     """
     artifact_file = artifact.file
+    content_disposition = f"attachment;filename={artifact.pk}"
     if (
         settings.DEFAULT_FILE_STORAGE == "pulpcore.app.models.storage.FileSystem"
         or not settings.REDIRECT_TO_OBJECT_STORAGE
     ):
         return _artifact_serving_distribution().artifact_url(artifact)
     elif settings.DEFAULT_FILE_STORAGE == "storages.backends.s3boto3.S3Boto3Storage":
-        parameters = {"ResponseContentDisposition": f"attachment%3Bfilename={artifact_file.name}"}
+        parameters = {"ResponseContentDisposition": content_disposition}
         if headers and headers.get("Content-Type"):
             parameters["ResponseContentType"] = headers.get("Content-Type")
         url = artifact_file.storage.url(
             artifact_file.name, parameters=parameters, http_method=http_method
         )
     elif settings.DEFAULT_FILE_STORAGE == "storages.backends.azure_storage.AzureStorage":
-        parameters = {"content_disposition": f"attachment%3Bfilename={artifact_file.name}"}
+        parameters = {"content_disposition": content_disposition}
         if headers and headers.get("Content-Type"):
             parameters["content_type"] = headers.get("Content-Type")
-        url = artifact_file.storage.url(artifact_file.name)
+        url = artifact_file.storage.url(artifact_file.name, parameters=parameters)
     else:
         raise NotImplementedError(
             f"The value settings.DEFAULT_FILE_STORAGE={settings.DEFAULT_FILE_STORAGE} "
diff --git a/pulpcore/content/handler.py b/pulpcore/content/handler.py
--- a/pulpcore/content/handler.py
+++ b/pulpcore/content/handler.py
@@ -810,6 +810,7 @@ async def _serve_content_artifact(self, content_artifact, headers, request):
         """
         artifact_file = content_artifact.artifact.file
         artifact_name = artifact_file.name
+        content_disposition = f"attachment;filename={content_artifact.relative_path}"
 
         if settings.DEFAULT_FILE_STORAGE == "pulpcore.app.models.storage.FileSystem":
             path = os.path.join(settings.MEDIA_ROOT, artifact_name)
@@ -819,19 +820,17 @@ async def _serve_content_artifact(self, content_artifact, headers, request):
         elif not settings.REDIRECT_TO_OBJECT_STORAGE:
             return ArtifactResponse(content_artifact.artifact, headers=headers)
         elif settings.DEFAULT_FILE_STORAGE == "storages.backends.s3boto3.S3Boto3Storage":
-            content_disposition = f"attachment%3Bfilename={content_artifact.relative_path}"
             parameters = {"ResponseContentDisposition": content_disposition}
             if headers.get("Content-Type"):
                 parameters["ResponseContentType"] = headers.get("Content-Type")
             url = URL(
                 artifact_file.storage.url(
-                    artifact_file.name, parameters=parameters, http_method=request.method
+                    artifact_name, parameters=parameters, http_method=request.method
                 ),
                 encoded=True,
             )
             raise HTTPFound(url)
         elif settings.DEFAULT_FILE_STORAGE == "storages.backends.azure_storage.AzureStorage":
-            content_disposition = f"attachment%3Bfilename={artifact_name}"
             parameters = {"content_disposition": content_disposition}
             if headers.get("Content-Type"):
                 parameters["content_type"] = headers.get("Content-Type")


Test patch:
diff --git a/pulpcore/tests/functional/api/test_artifact_distribution.py b/pulpcore/tests/functional/api/test_artifact_distribution.py
--- a/pulpcore/tests/functional/api/test_artifact_distribution.py
+++ b/pulpcore/tests/functional/api/test_artifact_distribution.py
@@ -1,8 +1,17 @@
 import requests
+
+from django.conf import settings
+
 from hashlib import sha256
 from pulp_smash import utils
 
 
+OBJECT_STORAGES = (
+    "storages.backends.s3boto3.S3Boto3Storage",
+    "storages.backends.azure_storage.AzureStorage",
+)
+
+
 def test_artifact_distribution(cli_client, random_artifact):
     artifact_uuid = random_artifact.pulp_href.split("/")[-2]
 
@@ -18,3 +27,8 @@ def test_artifact_distribution(cli_client, random_artifact):
     hasher = sha256()
     hasher.update(response.content)
     assert hasher.hexdigest() == random_artifact.sha256
+    if settings.DEFAULT_FILE_STORAGE in OBJECT_STORAGES:
+        content_disposition = response.headers.get("Content-Disposition")
+        assert content_disposition is not None
+        filename = artifact_uuid
+        assert f"attachment;filename={filename}" == content_disposition