diff --git a/authentik/brands/tests.py b/authentik/brands/tests.py index 41fcd98040..337c304794 100644 --- a/authentik/brands/tests.py +++ b/authentik/brands/tests.py @@ -20,11 +20,16 @@ class TestBrands(APITestCase): def setUp(self): super().setUp() - self.default_flags = {} - for flag in Flag.available(visibility="public"): - self.default_flags[flag().key] = flag.get() Brand.objects.all().delete() + @property + def default_flags(self) -> dict[str, object]: + """Get current public flags. + + Some tests define temporary Flag subclasses, so this can't be cached in setUp. + """ + return {flag().key: flag.get() for flag in Flag.available(visibility="public")} + def test_current_brand(self): """Test Current brand API""" brand = create_test_brand() diff --git a/authentik/core/templates/login/base_full.html b/authentik/core/templates/login/base_full.html index 1e55116354..f3554446e8 100644 --- a/authentik/core/templates/login/base_full.html +++ b/authentik/core/templates/login/base_full.html @@ -12,7 +12,7 @@ {% block head %} diff --git a/authentik/flows/templates/if/flow-sfe.html b/authentik/flows/templates/if/flow-sfe.html index 39f528d416..311b3f139e 100644 --- a/authentik/flows/templates/if/flow-sfe.html +++ b/authentik/flows/templates/if/flow-sfe.html @@ -23,7 +23,7 @@ height: 100%; } body { - background-image: url("{{ flow_background_url }}"); + background-image: url("{{ flow_background_url|iriencode|safe }}"); background-repeat: no-repeat; background-size: cover; } diff --git a/authentik/flows/templates/if/flow.html b/authentik/flows/templates/if/flow.html index ea5d0f60cb..848e535040 100644 --- a/authentik/flows/templates/if/flow.html +++ b/authentik/flows/templates/if/flow.html @@ -39,7 +39,7 @@ {% endblock %} diff --git a/authentik/flows/tests/test_stage_views.py b/authentik/flows/tests/test_stage_views.py index a7379c7498..458d237803 100644 --- a/authentik/flows/tests/test_stage_views.py +++ b/authentik/flows/tests/test_stage_views.py @@ -1,12 +1,14 @@ """stage view tests""" from collections.abc import Callable +from unittest.mock import patch from django.test import RequestFactory, TestCase +from django.urls import reverse from authentik.core.tests.utils import RequestFactory as AuthentikRequestFactory from authentik.core.tests.utils import create_test_flow -from authentik.flows.models import FlowStageBinding +from authentik.flows.models import Flow, FlowStageBinding from authentik.flows.stage import StageView from authentik.flows.views.executor import FlowExecutorView from authentik.lib.utils.reflection import all_subclasses @@ -42,6 +44,46 @@ class TestViews(TestCase): "/static/dist/assets/images/flow_background.jpg", ) + def test_flow_interface_css_background_preserves_presigned_url_query(self): + """Test flow CSS keeps signed URL query separators intact.""" + flow = create_test_flow() + background_url = ( + "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png" + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential" + "&X-Amz-Signature=signature" + ) + + with patch.object(Flow, "background_url", return_value=background_url): + response = self.client.get( + reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) + ) + + self.assertContains( + response, + f'--ak-global--background-image: url("{background_url}");', + html=False, + ) + + def test_flow_sfe_css_background_preserves_presigned_url_query(self): + """Test SFE flow CSS keeps signed URL query separators intact.""" + flow = create_test_flow() + background_url = ( + "https://s3.ca-central-1.amazonaws.com/example/media/public/background.png" + "?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=credential" + "&X-Amz-Signature=signature" + ) + + with patch.object(Flow, "background_url", return_value=background_url): + response = self.client.get( + reverse("authentik_core:if-flow", kwargs={"flow_slug": flow.slug}) + "?sfe" + ) + + self.assertContains( + response, + f'background-image: url("{background_url}");', + html=False, + ) + def view_tester_factory(view_class: type[StageView]) -> Callable: """Test a form"""