authentik.stages.email.tests.test_templates

email tests

  1"""email tests"""
  2
  3from os import chmod, unlink
  4from pathlib import Path
  5from shutil import rmtree
  6from tempfile import mkdtemp, mkstemp
  7from typing import Any
  8from unittest.mock import PropertyMock, patch
  9
 10from django.conf import settings
 11from django.core.mail.backends.locmem import EmailBackend
 12from django.core.mail.message import sanitize_address
 13from django.urls import reverse
 14
 15from authentik.core.tests.utils import create_test_admin_user, create_test_flow
 16from authentik.events.models import Event, EventAction
 17from authentik.flows.markers import StageMarker
 18from authentik.flows.models import FlowDesignation, FlowStageBinding
 19from authentik.flows.planner import PLAN_CONTEXT_PENDING_USER, FlowPlan
 20from authentik.flows.tests import FlowTestCase
 21from authentik.flows.views.executor import SESSION_KEY_PLAN
 22from authentik.stages.email.models import EmailStage, get_template_choices
 23from authentik.stages.email.utils import TemplateEmailMessage
 24
 25
 26def get_templates_setting(temp_dir: str) -> dict[str, Any]:
 27    """Patch settings TEMPLATE's dir property"""
 28    templates_setting = settings.TEMPLATES
 29    templates_setting[0]["DIRS"] = [temp_dir, "foo"]
 30    return templates_setting
 31
 32
 33class TestEmailStageTemplates(FlowTestCase):
 34    """Email tests"""
 35
 36    def setUp(self) -> None:
 37        self.dir = Path(mkdtemp())
 38        self.user = create_test_admin_user()
 39
 40        self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
 41        self.stage = EmailStage.objects.create(
 42            name="email",
 43        )
 44        self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
 45
 46    def tearDown(self) -> None:
 47        rmtree(self.dir)
 48
 49    def test_custom_template(self):
 50        """Test with custom template"""
 51        with self.settings(TEMPLATES=get_templates_setting(self.dir)):
 52            _, file = mkstemp(suffix=".html", dir=self.dir)
 53            _, file2 = mkstemp(suffix=".html", dir=self.dir)
 54            chmod(file2, 0o000)  # Remove all permissions so we can't read the file
 55            choices = get_template_choices()
 56            self.assertEqual(choices[-1][0], Path(file).name)
 57            self.assertEqual(len(choices), 6)
 58            unlink(file)
 59            unlink(file2)
 60
 61    def test_custom_template_invalid_syntax(self):
 62        """Test with custom template"""
 63        with open(self.dir / Path("invalid.html"), "w+", encoding="utf-8") as _invalid:
 64            _invalid.write("{% blocktranslate %}")
 65        with self.settings(TEMPLATES=get_templates_setting(self.dir)):
 66            self.stage.template = "invalid.html"
 67            plan = FlowPlan(
 68                flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
 69            )
 70            plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
 71            session = self.client.session
 72            session[SESSION_KEY_PLAN] = plan
 73            session.save()
 74
 75            url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
 76            with patch(
 77                "authentik.stages.email.models.EmailStage.backend_class",
 78                PropertyMock(return_value=EmailBackend),
 79            ):
 80                response = self.client.get(url)
 81                self.assertEqual(response.status_code, 200)
 82                self.assertStageResponse(
 83                    response,
 84                    self.flow,
 85                    error_message="Unknown error",
 86                )
 87                events = Event.objects.filter(action=EventAction.CONFIGURATION_ERROR)
 88                self.assertEqual(len(events), 1)
 89                event = events.first()
 90                self.assertEqual(
 91                    event.context["message"], "Exception occurred while rendering E-mail template"
 92                )
 93                self.assertEqual(event.context["template"], "invalid.html")
 94
 95    def test_template_address(self):
 96        """Test addresses are correctly parsed"""
 97        message = TemplateEmailMessage(to=[("foo@bar.baz", "foo@bar.baz")])
 98        [sanitize_address(addr, "utf-8") for addr in message.recipients()]
 99        self.assertEqual(message.recipients(), ['"foo@bar.baz" <foo@bar.baz>'])
100        message = TemplateEmailMessage(to=[("some-name", "foo@bar.baz")])
101        [sanitize_address(addr, "utf-8") for addr in message.recipients()]
102        self.assertEqual(message.recipients(), ["some-name <foo@bar.baz>"])
def get_templates_setting(temp_dir: str) -> dict[str, typing.Any]:
27def get_templates_setting(temp_dir: str) -> dict[str, Any]:
28    """Patch settings TEMPLATE's dir property"""
29    templates_setting = settings.TEMPLATES
30    templates_setting[0]["DIRS"] = [temp_dir, "foo"]
31    return templates_setting

Patch settings TEMPLATE's dir property

class TestEmailStageTemplates(authentik.flows.tests.FlowTestCase):
 34class TestEmailStageTemplates(FlowTestCase):
 35    """Email tests"""
 36
 37    def setUp(self) -> None:
 38        self.dir = Path(mkdtemp())
 39        self.user = create_test_admin_user()
 40
 41        self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
 42        self.stage = EmailStage.objects.create(
 43            name="email",
 44        )
 45        self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)
 46
 47    def tearDown(self) -> None:
 48        rmtree(self.dir)
 49
 50    def test_custom_template(self):
 51        """Test with custom template"""
 52        with self.settings(TEMPLATES=get_templates_setting(self.dir)):
 53            _, file = mkstemp(suffix=".html", dir=self.dir)
 54            _, file2 = mkstemp(suffix=".html", dir=self.dir)
 55            chmod(file2, 0o000)  # Remove all permissions so we can't read the file
 56            choices = get_template_choices()
 57            self.assertEqual(choices[-1][0], Path(file).name)
 58            self.assertEqual(len(choices), 6)
 59            unlink(file)
 60            unlink(file2)
 61
 62    def test_custom_template_invalid_syntax(self):
 63        """Test with custom template"""
 64        with open(self.dir / Path("invalid.html"), "w+", encoding="utf-8") as _invalid:
 65            _invalid.write("{% blocktranslate %}")
 66        with self.settings(TEMPLATES=get_templates_setting(self.dir)):
 67            self.stage.template = "invalid.html"
 68            plan = FlowPlan(
 69                flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
 70            )
 71            plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
 72            session = self.client.session
 73            session[SESSION_KEY_PLAN] = plan
 74            session.save()
 75
 76            url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
 77            with patch(
 78                "authentik.stages.email.models.EmailStage.backend_class",
 79                PropertyMock(return_value=EmailBackend),
 80            ):
 81                response = self.client.get(url)
 82                self.assertEqual(response.status_code, 200)
 83                self.assertStageResponse(
 84                    response,
 85                    self.flow,
 86                    error_message="Unknown error",
 87                )
 88                events = Event.objects.filter(action=EventAction.CONFIGURATION_ERROR)
 89                self.assertEqual(len(events), 1)
 90                event = events.first()
 91                self.assertEqual(
 92                    event.context["message"], "Exception occurred while rendering E-mail template"
 93                )
 94                self.assertEqual(event.context["template"], "invalid.html")
 95
 96    def test_template_address(self):
 97        """Test addresses are correctly parsed"""
 98        message = TemplateEmailMessage(to=[("foo@bar.baz", "foo@bar.baz")])
 99        [sanitize_address(addr, "utf-8") for addr in message.recipients()]
100        self.assertEqual(message.recipients(), ['"foo@bar.baz" <foo@bar.baz>'])
101        message = TemplateEmailMessage(to=[("some-name", "foo@bar.baz")])
102        [sanitize_address(addr, "utf-8") for addr in message.recipients()]
103        self.assertEqual(message.recipients(), ["some-name <foo@bar.baz>"])

Email tests

def setUp(self) -> None:
37    def setUp(self) -> None:
38        self.dir = Path(mkdtemp())
39        self.user = create_test_admin_user()
40
41        self.flow = create_test_flow(FlowDesignation.AUTHENTICATION)
42        self.stage = EmailStage.objects.create(
43            name="email",
44        )
45        self.binding = FlowStageBinding.objects.create(target=self.flow, stage=self.stage, order=2)

Hook method for setting up the test fixture before exercising it.

def tearDown(self) -> None:
47    def tearDown(self) -> None:
48        rmtree(self.dir)

Hook method for deconstructing the test fixture after testing it.

def test_custom_template(self):
50    def test_custom_template(self):
51        """Test with custom template"""
52        with self.settings(TEMPLATES=get_templates_setting(self.dir)):
53            _, file = mkstemp(suffix=".html", dir=self.dir)
54            _, file2 = mkstemp(suffix=".html", dir=self.dir)
55            chmod(file2, 0o000)  # Remove all permissions so we can't read the file
56            choices = get_template_choices()
57            self.assertEqual(choices[-1][0], Path(file).name)
58            self.assertEqual(len(choices), 6)
59            unlink(file)
60            unlink(file2)

Test with custom template

def test_custom_template_invalid_syntax(self):
62    def test_custom_template_invalid_syntax(self):
63        """Test with custom template"""
64        with open(self.dir / Path("invalid.html"), "w+", encoding="utf-8") as _invalid:
65            _invalid.write("{% blocktranslate %}")
66        with self.settings(TEMPLATES=get_templates_setting(self.dir)):
67            self.stage.template = "invalid.html"
68            plan = FlowPlan(
69                flow_pk=self.flow.pk.hex, bindings=[self.binding], markers=[StageMarker()]
70            )
71            plan.context[PLAN_CONTEXT_PENDING_USER] = self.user
72            session = self.client.session
73            session[SESSION_KEY_PLAN] = plan
74            session.save()
75
76            url = reverse("authentik_api:flow-executor", kwargs={"flow_slug": self.flow.slug})
77            with patch(
78                "authentik.stages.email.models.EmailStage.backend_class",
79                PropertyMock(return_value=EmailBackend),
80            ):
81                response = self.client.get(url)
82                self.assertEqual(response.status_code, 200)
83                self.assertStageResponse(
84                    response,
85                    self.flow,
86                    error_message="Unknown error",
87                )
88                events = Event.objects.filter(action=EventAction.CONFIGURATION_ERROR)
89                self.assertEqual(len(events), 1)
90                event = events.first()
91                self.assertEqual(
92                    event.context["message"], "Exception occurred while rendering E-mail template"
93                )
94                self.assertEqual(event.context["template"], "invalid.html")

Test with custom template

def test_template_address(self):
 96    def test_template_address(self):
 97        """Test addresses are correctly parsed"""
 98        message = TemplateEmailMessage(to=[("foo@bar.baz", "foo@bar.baz")])
 99        [sanitize_address(addr, "utf-8") for addr in message.recipients()]
100        self.assertEqual(message.recipients(), ['"foo@bar.baz" <foo@bar.baz>'])
101        message = TemplateEmailMessage(to=[("some-name", "foo@bar.baz")])
102        [sanitize_address(addr, "utf-8") for addr in message.recipients()]
103        self.assertEqual(message.recipients(), ["some-name <foo@bar.baz>"])

Test addresses are correctly parsed