From 4c6fb0ac61a76fa5b808c49585713c553aefd699 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Mon, 31 May 2021 17:11:27 +0200 Subject: [PATCH] api: prevent crash on invalid event_display_template (#54388) --- chrono/agendas/models.py | 15 +++++++++++++++ chrono/api/views.py | 7 +++++-- tests/manager/test_all.py | 24 ++++++++++++++++++++++++ tests/test_api.py | 12 ++++++++++++ 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index 0b2a525..716f0c4 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -130,6 +130,20 @@ def django_template_validator(value): raise ValidationError(_('syntax error: %s') % e) +def event_template_validator(value): + example_event = Event( + start_datetime=now(), + publication_date=now().date(), + recurrence_end_date=now().date(), + places=1, + duration=1, + ) + try: + event_text = Template(value).render(Context({'event': example_event})) + except (VariableDoesNotExist, TemplateSyntaxError) as e: + raise ValidationError(_('syntax error: %s') % e) + + class ICSError(Exception): pass @@ -221,6 +235,7 @@ class Agenda(models.Model): _('Event display template'), max_length=256, blank=True, + validators=[event_template_validator], help_text=_( 'By default event labels will be displayed to users. This allows for a custom template to include additional informations. For example, "{{ event.label }} - {{ event.start_datetime }}" will show event datetime after label. Available variables: event.label (label), event.start_datetime (start date/time), event.places (places), event.remaining_places (remaining places), event.duration (duration), event.pricing (pricing).' ), diff --git a/chrono/api/views.py b/chrono/api/views.py index 0de03eb..218b745 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -24,7 +24,7 @@ from django.db.models import Count, Prefetch, Q from django.db.models.functions import TruncDay from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 -from django.template import Context, Template +from django.template import Context, Template, TemplateSyntaxError, VariableDoesNotExist from django.urls import reverse from django.utils.dateparse import parse_date, parse_datetime from django.utils.encoding import force_text @@ -417,7 +417,10 @@ def get_event_detail(request, event, agenda=None, min_places=1): agenda = agenda or event.agenda event_text = force_text(event) if agenda.event_display_template: - event_text = Template(agenda.event_display_template).render(Context({'event': event})) + try: + event_text = Template(agenda.event_display_template).render(Context({'event': event})) + except (VariableDoesNotExist, TemplateSyntaxError): + pass elif event.label and event.primary_event is not None: event_text = '%s (%s)' % ( event.label, diff --git a/tests/manager/test_all.py b/tests/manager/test_all.py index 8ecd2c0..e2f9afd 100644 --- a/tests/manager/test_all.py +++ b/tests/manager/test_all.py @@ -571,6 +571,30 @@ def test_options_agenda_booking_check_options(app, admin_user): app.get(url, status=404) +def test_options_agenda_event_display_template(app, admin_user): + agenda = Agenda.objects.create(label='Foo bar', kind='events') + + app = login(app) + resp = app.get('/manage/agendas/%s/edit' % agenda.pk) + valid_template = '{{ event.label|default:event.slug }} - {{ event.remaining_places|add:"5" }} / {{ event.start_datetime|date }} - {{ event.agenda.name }}' + resp.form['event_display_template'] = valid_template + resp = resp.form.submit().follow() + + agenda.refresh_from_db() + assert agenda.event_display_template == valid_template + + invalid_templates = [ + '{{ syntax error }}', + '{{ event.label|invalidfilter }}', + '{{ event.label|default:notexist }}', + ] + for template in invalid_templates: + resp = app.get('/manage/agendas/%s/edit' % agenda.pk) + resp.form['event_display_template'] = template + resp = resp.form.submit() + assert 'syntax error' in resp.text + + def test_options_agenda_as_manager(app, manager_user): agenda = Agenda(label=u'Foo bar') agenda.view_role = manager_user.groups.all()[0] diff --git a/tests/test_api.py b/tests/test_api.py index 3f0db5a..644771b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -566,6 +566,18 @@ def test_datetime_api_label(app): resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) assert resp.json['data'][0]['text'] == 'Hello world (4/5 places remaining)' + # invalid template yields default text + invalid_templates = [ + '{{ syntax error }}', + '{{ event.label|invalidfilter }}', + '{{ event.label|default:notexist }}', + ] + for template in invalid_templates: + agenda.event_display_template = template + agenda.save() + resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) + assert resp.json['data'][0]['text'] == 'Hello world' + def test_datetime_api_urls(app): agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0) -- 2.20.1