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
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
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