From bece44643118d2230fd0fd3e40506ebf3b85fd73 Mon Sep 17 00:00:00 2001 From: Jens L Date: Fri, 8 Mar 2024 14:42:06 +0000 Subject: [PATCH] stages/email: fix issue when sending emails to users with same display as email (#8850) Signed-off-by: Jens Langhammer --- 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 c281e3ad3387..e26fed772db1 100644 --- a/authentik/core/api/users.py +++ b/authentik/core/api/users.py @@ -611,7 +611,7 @@ def recovery_email(self, request: Request, pk: int) -> Response: 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 3bb2ff14581b..b304bbb5eda8 100644 --- a/authentik/events/models.py +++ b/authentik/events/models.py @@ -480,7 +480,7 @@ def send_email(self, notification: "Notification") -> list[str]: } 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 e550097da009..ee4a6d4310e5 100644 --- a/authentik/stages/email/management/commands/test_email.py +++ b/authentik/stages/email/management/commands/test_email.py @@ -30,7 +30,7 @@ def handle_per_tenant(self, *args, **options): 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 369fdfe9dd15..9a81a7a2871f 100644 --- a/authentik/stages/email/stage.py +++ b/authentik/stages/email/stage.py @@ -111,7 +111,7 @@ def send_email(self): 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 fd7f3eca3eab..631983ab671d 100644 --- a/authentik/stages/email/tests/test_sending.py +++ b/authentik/stages/email/tests/test_sending.py @@ -39,6 +39,7 @@ def test_pending_user(self): 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 d65bdb2194fc..a9bee2ed138a 100644 --- a/authentik/stages/email/tests/test_templates.py +++ b/authentik/stages/email/tests/test_templates.py @@ -9,6 +9,7 @@ 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.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 @@ def test_custom_template_invalid_syntax(self): 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 a086250596cd..2fdebe85ab36 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: