diff --git a/src/dispatch/messaging/email/filters.py b/src/dispatch/messaging/email/filters.py index fa92b75782a2..44d18c7ff0ee 100644 --- a/src/dispatch/messaging/email/filters.py +++ b/src/dispatch/messaging/email/filters.py @@ -2,26 +2,30 @@ from datetime import datetime import markdown -from jinja2 import ( - Environment, - FileSystemLoader, -) +from jinja2 import FileSystemLoader +from jinja2.sandbox import ImmutableSandboxedEnvironment from markupsafe import Markup from dispatch import config here = os.path.dirname(os.path.realpath(__file__)) + autoescape = bool(config.DISPATCH_ESCAPE_HTML) -env = Environment(loader=FileSystemLoader(here), autoescape=autoescape) +env = ImmutableSandboxedEnvironment(loader=FileSystemLoader(here), autoescape=autoescape) -def format_datetime(value): - return datetime.fromisoformat(value).strftime("%A, %B %d, %Y") +def safe_format_datetime(value): + try: + return datetime.fromisoformat(value).strftime("%A, %B %d, %Y") + except (ValueError, TypeError): + return "" -def format_markdown(value): - return Markup(markdown.markdown(value)) +def safe_format_markdown(value): + if not isinstance(value, str): + return "" + return Markup(markdown.markdown(value, output_format="html5", extensions=["extra"])) -env.filters["datetime"] = format_datetime -env.filters["markdown"] = format_markdown +env.filters["datetime"] = safe_format_datetime +env.filters["markdown"] = safe_format_markdown diff --git a/tests/conftest.py b/tests/conftest.py index ee1d37fad193..e0ebeefec634 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -33,6 +33,7 @@ DefinitionFactory, DispatchUserFactory, DocumentFactory, + EmailTemplateFactory, EntityFactory, EntityTypeFactory, EventFactory, @@ -585,6 +586,11 @@ def incident_cost_types(session): return [IncidentCostTypeFactory(), IncidentCostTypeFactory()] +@pytest.fixture +def email_template(session): + return EmailTemplateFactory() + + @pytest.fixture def notification(session): return NotificationFactory() diff --git a/tests/factories.py b/tests/factories.py index 85a0ca76bc6e..7ffbfc9815b3 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -26,6 +26,8 @@ from dispatch.conversation.models import Conversation from dispatch.definition.models import Definition from dispatch.document.models import Document +from dispatch.email_templates.models import EmailTemplates +from dispatch.email_templates.enums import EmailTemplateTypes from dispatch.entity.models import Entity from dispatch.entity_type.models import EntityType from dispatch.event.models import Event @@ -1216,6 +1218,22 @@ class Meta: model = IncidentCostType +class EmailTemplateFactory(BaseFactory): + """Email Template Factory.""" + + # Columns + email_template_type = EmailTemplateTypes.welcome + welcome_text = "Welcome to Incident {{title}} " + welcome_body = "{{title}} Incident\n{{description}}" + components = ["title", "description"] + enabled = True + + class Meta: + """Factory Configuration.""" + + model = EmailTemplates + + class NotificationFactory(BaseFactory): """Notification Factory.""" diff --git a/tests/messaging/test_messaging.py b/tests/messaging/test_messaging.py new file mode 100644 index 000000000000..31104ec6442c --- /dev/null +++ b/tests/messaging/test_messaging.py @@ -0,0 +1,81 @@ +def test_render_message_template(): + """Tests the render_message_template function. + + The message template should be filled with the kwargs values, and the datetime field should be properly formatted. + """ + from dispatch.messaging.email.utils import render_message_template + from datetime import datetime, UTC + + message_template = [ + { + "title": "{{title}}", + "text": "{{body}}", + "datetime": "{{datetime | datetime}}", + } + ] + kwargs = { + "title": "Test Title", + "body": "Test Body", + "datetime": datetime.now(UTC).isoformat(), + } + + rendered = render_message_template(message_template, **kwargs) + + assert rendered + assert rendered[0].get("title") == kwargs.get("title") + assert rendered[0].get("text") == kwargs.get("body") + assert rendered[0].get("datetime") + + +def test_render_message_template__failed_filter(): + """Tests that render_message_template fails when the input does not adhere to the datetime filter rules.""" + from dispatch.messaging.email.utils import render_message_template + + message_template = [ + { + "datetime": "{{datetime | datetime}}", + } + ] + kwargs = {"datetime": "1234"} + + rendered = render_message_template(message_template, **kwargs) + + assert rendered + assert not rendered[0].get("datetime") + + +def test_render_message_template__disable_code_execution(): + """Tests that render_message_template does not execute scripts in user input.""" + from dispatch.messaging.email.utils import render_message_template + + message_template = [ + { + "title": "{{ title }}", + "body": "{% for i in [].append(123) %}...{% endfor %}", + } + ] + kwargs = {"title": "Test Title"} + + rendered = render_message_template(message_template, **kwargs) + + assert rendered + assert rendered[0].get("title") == kwargs.get("title") + assert rendered[0].get("body") == message_template[0].get("body") + + +def test_generate_welcome_message__default(): + """Tests the generate_welcome_message function.""" + from dispatch.messaging.strings import generate_welcome_message + + assert generate_welcome_message(None) + + +def test_generate_welcome_message__email_template(email_template): + """Tests the generate_welcome_message function with an email template.""" + from dispatch.messaging.strings import generate_welcome_message + + welcome_message = generate_welcome_message(email_template) + + assert welcome_message + assert welcome_message[0].get("title") == email_template.welcome_text + assert welcome_message[0].get("text") == email_template.welcome_body