Skip to content

Commit 7de7e88

Browse files
committed
root/storages: fix punycode handling for s3 presigned generation
1 parent 620c95d commit 7de7e88

File tree

2 files changed

+497
-18
lines changed

2 files changed

+497
-18
lines changed

authentik/root/storages.py

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""authentik storage backends"""
22

33
import os
4-
from urllib.parse import parse_qsl, urlsplit
4+
from urllib.parse import parse_qsl, urlsplit, urlunsplit
55

66
from django.conf import settings
77
from django.core.exceptions import SuspiciousOperation
@@ -74,7 +74,6 @@ def security_token(self, value: str):
7474

7575
def _normalize_name(self, name):
7676
try:
77-
7877
return safe_join(self.location, connection.schema_name, name)
7978
except ValueError:
8079
raise SuspiciousOperation(f"Attempted access to '{name}' denied.") from None
@@ -89,24 +88,60 @@ def url(self, name, parameters=None, expire=None, http_method=None):
8988

9089
params["Bucket"] = self.bucket.name
9190
params["Key"] = name
92-
url = self.bucket.meta.client.generate_presigned_url(
93-
"get_object",
94-
Params=params,
95-
ExpiresIn=expire,
96-
HttpMethod=http_method,
97-
)
9891

99-
if self.custom_domain:
100-
# Key parameter can't be empty. Use "/" and remove it later.
101-
params["Key"] = "/"
102-
root_url_signed = self.bucket.meta.client.generate_presigned_url(
103-
"get_object", Params=params, ExpiresIn=expire
92+
# Get the client and configure endpoint URL if custom_domain is set
93+
client = self.bucket.meta.client
94+
95+
# Save original endpoint URL if we need to restore it
96+
original_endpoint_url = getattr(client._endpoint, "host", None)
97+
98+
try:
99+
# If custom domain is set, configure the endpoint URL
100+
if self.custom_domain:
101+
scheme = "https" if self.secure_urls else "http"
102+
custom_endpoint = f"{scheme}://{self.custom_domain}"
103+
104+
# Set the endpoint URL temporarily for this request
105+
client._endpoint.host = custom_endpoint
106+
107+
# Generate the presigned URL using the correctly configured client
108+
url = client.generate_presigned_url(
109+
"get_object",
110+
Params=params,
111+
ExpiresIn=expire,
112+
HttpMethod=http_method,
104113
)
105-
# Remove signing parameter and previously added key "/".
106-
root_url = self._strip_signing_parameters(root_url_signed)[:-1]
107-
# Replace bucket domain with custom domain.
108-
custom_url = f"{self.url_protocol}//{self.custom_domain}/"
109-
url = url.replace(root_url, custom_url)
114+
115+
# If using custom domain, we need to handle the path correctly
116+
if self.custom_domain and self.bucket.name in url:
117+
# Parse the generated URL
118+
split_url = urlsplit(url)
119+
120+
# Get the path from the S3 URL
121+
s3_path = split_url.path
122+
123+
# Remove the leading bucket name from the path if it's present
124+
bucket_prefix = f"/{self.bucket.name}"
125+
if s3_path.startswith(bucket_prefix):
126+
final_path = s3_path[len(bucket_prefix) :]
127+
else:
128+
final_path = s3_path
129+
130+
# Create the custom domain URL with the corrected path and query parameters
131+
url = urlunsplit(
132+
(
133+
split_url.scheme,
134+
self.custom_domain,
135+
final_path,
136+
split_url.query,
137+
split_url.fragment,
138+
)
139+
)
140+
141+
finally:
142+
# Restore the original endpoint URL if we changed it
143+
if self.custom_domain and original_endpoint_url:
144+
client._endpoint.host = original_endpoint_url
110145

111146
if self.querystring_auth:
112147
return url

0 commit comments

Comments
 (0)