diff --git a/authentik/root/storages.py b/authentik/root/storages.py index f1c6f516bee8..e76efb337440 100644 --- a/authentik/root/storages.py +++ b/authentik/root/storages.py @@ -1,6 +1,7 @@ """authentik storage backends""" import os +from urllib.parse import parse_qsl, urlsplit from django.conf import settings from django.core.exceptions import SuspiciousOperation @@ -110,3 +111,34 @@ def url(self, name, parameters=None, expire=None, http_method=None): if self.querystring_auth: return url return self._strip_signing_parameters(url) + + def _strip_signing_parameters(self, url): + # Boto3 does not currently support generating URLs that are unsigned. Instead + # we take the signed URLs and strip any querystring params related to signing + # and expiration. + # Note that this may end up with URLs that are still invalid, especially if + # params are passed in that only work with signed URLs, e.g. response header + # params. + # The code attempts to strip all query parameters that match names of known + # parameters from v2 and v4 signatures, regardless of the actual signature + # version used. + split_url = urlsplit(url) + qs = parse_qsl(split_url.query, keep_blank_values=True) + blacklist = { + "x-amz-algorithm", + "x-amz-credential", + "x-amz-date", + "x-amz-expires", + "x-amz-signedheaders", + "x-amz-signature", + "x-amz-security-token", + "awsaccesskeyid", + "expires", + "signature", + } + filtered_qs = ((key, val) for key, val in qs if key.lower() not in blacklist) + # Note: Parameters that did not have a value in the original query string will + # have an '=' sign appended to it, e.g ?foo&bar becomes ?foo=&bar= + joined_qs = ("=".join(keyval) for keyval in filtered_qs) + split_url = split_url._replace(query="&".join(joined_qs)) + return split_url.geturl()