forked from CGM_Public/pretix_original
Fix placeholder injection with django templates
This commit is contained in:
@@ -39,7 +39,7 @@ from pretix.base.templatetags.rich_text import (
|
|||||||
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
|
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
|
||||||
markdown_compile_email, truelink_callback,
|
markdown_compile_email, truelink_callback,
|
||||||
)
|
)
|
||||||
from pretix.helpers.format import SafeFormatter, format_map
|
from pretix.helpers.format import FormattedString, SafeFormatter, format_map
|
||||||
|
|
||||||
from pretix.base.services.placeholders import ( # noqa
|
from pretix.base.services.placeholders import ( # noqa
|
||||||
get_available_placeholders, PlaceholderContext
|
get_available_placeholders, PlaceholderContext
|
||||||
@@ -141,6 +141,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
|||||||
return markdown_compile_email(plaintext, context=context)
|
return markdown_compile_email(plaintext, context=context)
|
||||||
|
|
||||||
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
|
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
|
||||||
|
apply_format_map = not isinstance(plain_body, FormattedString)
|
||||||
body_md = self.compile_markdown(plain_body, context)
|
body_md = self.compile_markdown(plain_body, context)
|
||||||
if context:
|
if context:
|
||||||
linker = bleach.Linker(
|
linker = bleach.Linker(
|
||||||
@@ -149,12 +150,13 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
|
|||||||
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
|
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
|
||||||
parse_email=True
|
parse_email=True
|
||||||
)
|
)
|
||||||
body_md = format_map(
|
if apply_format_map:
|
||||||
body_md,
|
body_md = format_map(
|
||||||
context=context,
|
body_md,
|
||||||
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
context=context,
|
||||||
linkifier=linker
|
mode=SafeFormatter.MODE_RICH_TO_HTML,
|
||||||
)
|
linkifier=linker
|
||||||
|
)
|
||||||
htmlctx = {
|
htmlctx = {
|
||||||
'site': settings.PRETIX_INSTANCE_NAME,
|
'site': settings.PRETIX_INSTANCE_NAME,
|
||||||
'site_url': settings.SITE_URL,
|
'site_url': settings.SITE_URL,
|
||||||
|
|||||||
@@ -81,7 +81,9 @@ from pretix.base.signals import (
|
|||||||
)
|
)
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
from pretix.helpers import OF_SELF
|
from pretix.helpers import OF_SELF
|
||||||
from pretix.helpers.format import FormattedString, SafeFormatter, format_map
|
from pretix.helpers.format import (
|
||||||
|
FormattedString, PlainHtmlAlternativeString, SafeFormatter, format_map,
|
||||||
|
)
|
||||||
from pretix.helpers.hierarkey import clean_filename
|
from pretix.helpers.hierarkey import clean_filename
|
||||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||||
from pretix.presale.ical import get_private_icals
|
from pretix.presale.ical import get_private_icals
|
||||||
@@ -266,7 +268,10 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
|||||||
signature = ""
|
signature = ""
|
||||||
|
|
||||||
# Build full plain-text body
|
# Build full plain-text body
|
||||||
body_plain = format_map(content_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
if not isinstance(content_plain, FormattedString):
|
||||||
|
body_plain = format_map(content_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
||||||
|
else:
|
||||||
|
body_plain = content_plain
|
||||||
body_plain = _wrap_plain_body(body_plain, signature, event, order, position, no_order_links)
|
body_plain = _wrap_plain_body(body_plain, signature, event, order, position, no_order_links)
|
||||||
|
|
||||||
# Build subject
|
# Build subject
|
||||||
@@ -793,7 +798,12 @@ def render_mail(template, context, placeholder_mode: Optional[int]=SafeFormatter
|
|||||||
body = format_map(body, context, mode=placeholder_mode)
|
body = format_map(body, context, mode=placeholder_mode)
|
||||||
else:
|
else:
|
||||||
tpl = get_template(template)
|
tpl = get_template(template)
|
||||||
body = tpl.render(context)
|
context = {
|
||||||
|
# Known bug, should behave differently for plain and HTML but we'll fix after security release
|
||||||
|
k: v.html if isinstance(v, PlainHtmlAlternativeString) else v
|
||||||
|
for k, v in context.items()
|
||||||
|
}
|
||||||
|
body = FormattedString(tpl.render(context))
|
||||||
return body
|
return body
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import pytest
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import mail as djmail
|
from django.core import mail as djmail
|
||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
|
from django.utils.html import escape
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django_scopes import scope, scopes_disabled
|
from django_scopes import scope, scopes_disabled
|
||||||
@@ -322,7 +323,7 @@ def _extract_html(mail):
|
|||||||
def test_placeholder_html_rendering_from_template(env):
|
def test_placeholder_html_rendering_from_template(env):
|
||||||
djmail.outbox = []
|
djmail.outbox = []
|
||||||
event, user, organizer = env
|
event, user, organizer = env
|
||||||
event.name = "<strong>event & co. kg</strong>"
|
event.name = "<strong>event & co. kg</strong> {currency}"
|
||||||
event.save()
|
event.save()
|
||||||
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', get_email_context(
|
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', get_email_context(
|
||||||
event=event,
|
event=event,
|
||||||
@@ -331,25 +332,26 @@ def test_placeholder_html_rendering_from_template(env):
|
|||||||
|
|
||||||
assert len(djmail.outbox) == 1
|
assert len(djmail.outbox) == 1
|
||||||
assert djmail.outbox[0].to == [user.email]
|
assert djmail.outbox[0].to == [user.email]
|
||||||
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
|
# Known bug for now: These should not have HTML for the plain body, but we'll fix this safter the security release
|
||||||
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
|
assert escape('Event name: <strong>event & co. kg</strong> {currency}') in djmail.outbox[0].body
|
||||||
assert '**Meta**: *Beep*' in djmail.outbox[0].body
|
assert '<strong>IBAN</strong>: 123<br>\n<strong>BIC</strong>: 456' in djmail.outbox[0].body
|
||||||
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
|
assert '**Meta**: <em>Beep</em>' in djmail.outbox[0].body
|
||||||
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
|
assert escape('Event website: [<strong>event & co. kg</strong> {currency}](https://example.org/dummy)') in djmail.outbox[0].body
|
||||||
assert '<' not in djmail.outbox[0].body
|
# todo: assert '<' not in djmail.outbox[0].body
|
||||||
assert '&' not in djmail.outbox[0].body
|
# todo: assert '&' not in djmail.outbox[0].body
|
||||||
|
assert 'Unevaluated placeholder: {currency}' in djmail.outbox[0].body
|
||||||
|
assert 'EUR' not in djmail.outbox[0].body
|
||||||
html = _extract_html(djmail.outbox[0])
|
html = _extract_html(djmail.outbox[0])
|
||||||
|
|
||||||
assert '<strong>event' not in html
|
assert '<strong>event' not in html
|
||||||
assert 'Event name: <strong>event & co. kg</strong>' in html
|
assert 'Event name: <strong>event & co. kg</strong> {currency}' in html
|
||||||
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
|
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
|
||||||
assert '<strong>Meta</strong>: <em>Beep</em>' in html
|
assert '<strong>Meta</strong>: <em>Beep</em>' in html
|
||||||
|
assert 'Unevaluated placeholder: {currency}' in html
|
||||||
|
assert 'EUR' not in html
|
||||||
assert re.search(
|
assert re.search(
|
||||||
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">'
|
||||||
html
|
r'<strong>event & co. kg</strong> {currency}</a>',
|
||||||
)
|
|
||||||
assert re.search(
|
|
||||||
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
|
||||||
html
|
html
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -367,7 +369,7 @@ def test_placeholder_html_rendering_from_string(env):
|
|||||||
})
|
})
|
||||||
djmail.outbox = []
|
djmail.outbox = []
|
||||||
event, user, organizer = env
|
event, user, organizer = env
|
||||||
event.name = "<strong>event & co. kg</strong>"
|
event.name = "<strong>event & co. kg</strong> {currency}"
|
||||||
event.save()
|
event.save()
|
||||||
ctx = get_email_context(
|
ctx = get_email_context(
|
||||||
event=event,
|
event=event,
|
||||||
@@ -378,9 +380,9 @@ def test_placeholder_html_rendering_from_string(env):
|
|||||||
|
|
||||||
assert len(djmail.outbox) == 1
|
assert len(djmail.outbox) == 1
|
||||||
assert djmail.outbox[0].to == [user.email]
|
assert djmail.outbox[0].to == [user.email]
|
||||||
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
|
assert 'Event name: <strong>event & co. kg</strong> {currency}' in djmail.outbox[0].body
|
||||||
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
|
assert 'Event website: [<strong>event & co. kg</strong> {currency}](https://example.org/dummy)' in djmail.outbox[0].body
|
||||||
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
|
assert 'Other website: [<strong>event & co. kg</strong> {currency}](https://example.com)' in djmail.outbox[0].body
|
||||||
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
|
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
|
||||||
assert '**Meta**: *Beep*' in djmail.outbox[0].body
|
assert '**Meta**: *Beep*' in djmail.outbox[0].body
|
||||||
assert 'URL: https://google.com' in djmail.outbox[0].body
|
assert 'URL: https://google.com' in djmail.outbox[0].body
|
||||||
@@ -395,11 +397,13 @@ def test_placeholder_html_rendering_from_string(env):
|
|||||||
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
|
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
|
||||||
assert '<strong>Meta</strong>: <em>Beep</em>' in html
|
assert '<strong>Meta</strong>: <em>Beep</em>' in html
|
||||||
assert re.search(
|
assert re.search(
|
||||||
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">'
|
||||||
|
r'<strong>event & co. kg</strong> {currency}</a>',
|
||||||
html
|
html
|
||||||
)
|
)
|
||||||
assert re.search(
|
assert re.search(
|
||||||
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank"><strong>event & co. kg</strong></a>',
|
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank">'
|
||||||
|
r'<strong>event & co. kg</strong> {currency}</a>',
|
||||||
html
|
html
|
||||||
)
|
)
|
||||||
assert re.search(
|
assert re.search(
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
This is a test file for sending mails.
|
This is a test file for sending mails.
|
||||||
Event name: {event}
|
Event name: {{ event }}
|
||||||
|
Unevaluated placeholder: {currency}
|
||||||
{% get_current_language as LANGUAGE_CODE %}
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
The language code used for rendering this email is {{ LANGUAGE_CODE }}.
|
The language code used for rendering this email is {{ LANGUAGE_CODE }}.
|
||||||
|
|
||||||
Payment info:
|
Payment info:
|
||||||
{payment_info}
|
{{ payment_info }}
|
||||||
|
|
||||||
**Meta**: {meta_Test}
|
**Meta**: {{ meta_Test }}
|
||||||
|
|
||||||
Event website: [{event}](https://example.org/{event_slug})
|
Event website: [{{event}}](https://example.org/{{event_slug}})
|
||||||
Other website: [{event}]({meta_Website})
|
|
||||||
Reference in New Issue
Block a user