From 89a876e141b3a700c38f49bdf833141c717af6f4 Mon Sep 17 00:00:00 2001 From: "gcp-cherry-pick-bot[bot]" <98988430+gcp-cherry-pick-bot[bot]@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:07:30 +0100 Subject: [PATCH] stages/email: fix issue when sending emails to users with same display as email (cherry-pick #8850) (#8852) stages/email: fix issue when sending emails to users with same display as email (#8850) Signed-off-by: Jens Langhammer Co-authored-by: Jens L --- authentik/core/api/users.py | 2 +- authentik/events/models.py | 2 +- .../email/management/commands/test_email.py | 2 +- authentik/stages/email/stage.py | 2 +- authentik/stages/email/tests/test_sending.py | 1 + authentik/stages/email/tests/test_templates.py | 11 +++++++++++ authentik/stages/email/utils.py | 15 +++++++++++++-- 7 files changed, 29 insertions(+), 6 deletions(-) diff --git a/authentik/core/api/users.py b/authentik/core/api/users.py index c281e3ad33..e26fed772d 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -611,7 +611,7 @@ class UserViewSet(UsedByMixin, ModelViewSet): email_stage: EmailStage = stages.first() message = TemplateEmailMessage( subject=_(email_stage.subject), - to=[for_user.email], + to=[(for_user.name, for_user.email)], template_name=email_stage.template, language=for_user.locale(request), template_context={ diff --git a/authentik/events/models.py b/authentik/events/models.py index 3bb2ff1458..b304bbb5ed 100644 --- a/authentik/events/models.py +++ b/authentik/events/models.py @@ -480,7 +480,7 @@ class NotificationTransport(SerializerModel): } mail = TemplateEmailMessage( subject=subject_prefix + context["title"], - to=[f"{notification.user.name} <{notification.user.email}>"], + to=[(notification.user.name, notification.user.email)], language=notification.user.locale(), template_name="email/event_notification.html", template_context=context, diff --git a/authentik/stages/email/management/commands/test_email.py b/authentik/stages/email/management/commands/test_email.py index e550097da0..ee4a6d4310 100644 --- a/authentik/stages/email/management/commands/test_email.py +++ b/authentik/stages/email/management/commands/test_email.py @@ -30,7 +30,7 @@ class Command(TenantCommand): delete_stage = True message = TemplateEmailMessage( subject="authentik Test-Email", - to=[options["to"]], + to=[("", options["to"])], template_name="email/setup.html", template_context={}, ) diff --git a/authentik/stages/email/stage.py b/authentik/stages/email/stage.py index 369fdfe9dd..9a81a7a287 100644 --- a/authentik/stages/email/stage.py +++ b/authentik/stages/email/stage.py @@ -111,7 +111,7 @@ class EmailStageView(ChallengeStageView): try: message = TemplateEmailMessage( subject=_(current_stage.subject), - to=[f"{pending_user.name} <{email}>"], + to=[(pending_user.name, email)], language=pending_user.locale(self.request), template_name=current_stage.template, template_context={ diff --git a/authentik/stages/email/tests/test_sending.py b/authentik/stages/email/tests/test_sending.py index fd7f3eca3e..631983ab67 100644 --- a/authentik/stages/email/tests/test_sending.py +++ b/authentik/stages/email/tests/test_sending.py @@ -39,6 +39,7 @@ class TestEmailStageSending(FlowTestCase): session = self.client.session session[SESSION_KEY_PLAN] = plan session.save() + Event.objects.filter(action=EventAction.EMAIL_SENT).delete() url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug}) with patch( diff --git a/authentik/stages/email/tests/test_templates.py b/authentik/stages/email/tests/test_templates.py index d65bdb2194..a9bee2ed13 100644 --- a/authentik/stages/email/tests/test_templates.py +++ b/authentik/stages/email/tests/test_templates.py @@ -9,6 +9,7 @@ from unittest.mock import PropertyMock, patch from django.conf import settings from django.core.mail.backends.locmem import EmailBackend +from django.core.mail.message import sanitize_address from django.urls import reverse from authentik.core.tests.utils import create_test_admin_user, create_test_flow @@ -19,6 +20,7 @@ from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan from authentik.flows.tests import FlowTestCase from authentik.flows.views.executor import SESSION_KEY_PLAN from authentik.stages.email.models import EmailStage, get_template_choices +from authentik.stages.email.utils import TemplateEmailMessage def get_templates_setting(temp_dir: str) -> dict[str, Any]: @@ -89,3 +91,12 @@ class TestEmailStageTemplates(FlowTestCase): event.context["message"], "Exception occurred while rendering E-mail template" ) self.assertEqual(event.context["template"], "invalid.html") + + def test_template_address(self): + """Test addresses are correctly parsed""" + message = TemplateEmailMessage(to=[("foo@bar.baz", "foo@bar.baz")]) + [sanitize_address(addr, "utf-8") for addr in message.recipients()] + self.assertEqual(message.recipients(), ["foo@bar.baz"]) + message = TemplateEmailMessage(to=[("some-name", "foo@bar.baz")]) + [sanitize_address(addr, "utf-8") for addr in message.recipients()] + self.assertEqual(message.recipients(), ["some-name "]) diff --git a/authentik/stages/email/utils.py b/authentik/stages/email/utils.py index a086250596..2fdebe85ab 100644 --- a/authentik/stages/email/utils.py +++ b/authentik/stages/email/utils.py @@ -25,8 +25,19 @@ def logo_data() -> MIMEImage: class TemplateEmailMessage(EmailMultiAlternatives): """Wrapper around EmailMultiAlternatives with integrated template rendering""" - def __init__(self, template_name=None, template_context=None, language="", **kwargs): - super().__init__(**kwargs) + def __init__( + self, to: list[tuple[str]], template_name=None, template_context=None, language="", **kwargs + ): + sanitized_to = [] + # Ensure that all recipients are valid + for recipient_name, recipient_email in to: + if recipient_name == recipient_email: + sanitized_to.append(recipient_email) + else: + sanitized_to.append(f"{recipient_name} <{recipient_email}>") + super().__init__(to=sanitized_to, **kwargs) + if not template_name: + return with translation.override(language): html_content = render_to_string(template_name, template_context) try: