From cc833c52b6e10741f934b44aad8c10b041de86d6 Mon Sep 17 00:00:00 2001 From: Lance Edgar Date: Wed, 16 Dec 2020 14:28:41 -0600 Subject: [PATCH] Add common "form poster" logic, to make CSRF token/header names configurable also refactor the Feedback logic to use it --- tailbone/app.py | 9 ++-- tailbone/config.py | 9 +++- .../themes/falafel/js/tailbone.feedback.js | 30 +++---------- tailbone/subscribers.py | 2 + tailbone/templates/formposter.mako | 42 +++++++++++++++++++ tailbone/templates/themes/falafel/base.mako | 3 +- 6 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 tailbone/templates/formposter.mako diff --git a/tailbone/app.py b/tailbone/app.py index 44d9976f..bbb6d295 100644 --- a/tailbone/app.py +++ b/tailbone/app.py @@ -43,7 +43,7 @@ from zope.sqlalchemy import register import tailbone.db from tailbone.auth import TailboneAuthorizationPolicy - +from tailbone.config import csrf_token_name, csrf_header_name from tailbone.util import get_effective_theme, get_theme_template_path @@ -130,9 +130,12 @@ def make_pyramid_config(settings, configure_csrf=True): config.set_authorization_policy(TailboneAuthorizationPolicy()) config.set_authentication_policy(SessionAuthenticationPolicy()) - # always require CSRF token protection + # maybe require CSRF token protection if configure_csrf: - config.set_default_csrf_options(require_csrf=True, token='_csrf') + rattail_config = settings['rattail_config'] + config.set_default_csrf_options(require_csrf=True, + token=csrf_token_name(rattail_config), + header=csrf_header_name(rattail_config)) # Bring in some Pyramid goodies. config.include('tailbone.beaker') diff --git a/tailbone/config.py b/tailbone/config.py index 29359e06..6be175ae 100644 --- a/tailbone/config.py +++ b/tailbone/config.py @@ -53,6 +53,14 @@ class ConfigExtension(BaseExtension): config.setdefault('tailbone', 'themes.expose_picker', 'true') +def csrf_token_name(config): + return config.get('tailbone', 'csrf_token_name', default='_csrf') + + +def csrf_header_name(config): + return config.get('tailbone', 'csrf_header_name', default='X-CSRF-TOKEN') + + def global_help_url(config): return config.get('tailbone', 'global_help_url') @@ -64,4 +72,3 @@ def legacy_mobile_enabled(config): def protected_usernames(config): return config.getlist('tailbone', 'protected_usernames') - diff --git a/tailbone/static/themes/falafel/js/tailbone.feedback.js b/tailbone/static/themes/falafel/js/tailbone.feedback.js index 9b1d9c4f..e83b59ed 100644 --- a/tailbone/static/themes/falafel/js/tailbone.feedback.js +++ b/tailbone/static/themes/falafel/js/tailbone.feedback.js @@ -2,6 +2,7 @@ let FeedbackForm = { props: ['action', 'message'], template: '#feedback-template', + mixins: [FormPosterMixin], methods: { showFeedback() { @@ -20,30 +21,11 @@ let FeedbackForm = { message: this.message.trim(), } - let headers = { - // TODO: should find a better way to handle CSRF token - 'X-CSRF-TOKEN': this.csrftoken, - } - - this.$http.post(this.action, params, {headers: headers}).then(({ data }) => { - if (data.ok) { - alert("Message successfully sent.\n\nThank you for your feedback.") - this.showDialog = false - // clear out message, in case they need to send another - this.message = "" - } else { - this.$buefy.toast.open({ - message: "Failed to send feedback: " + data.error, - type: 'is-danger', - duration: 4000, // 4 seconds - }) - } - }, response => { - this.$buefy.toast.open({ - message: "Failed to send feedback! (unknown server error)", - type: 'is-danger', - duration: 4000, // 4 seconds - }) + this.submitForm(this.action, params, response => { + alert("Message successfully sent.\n\nThank you for your feedback.") + this.showDialog = false + // clear out message, in case they need to send another + this.message = "" }) }, } diff --git a/tailbone/subscribers.py b/tailbone/subscribers.py index af88f7a7..3deb9c1e 100644 --- a/tailbone/subscribers.py +++ b/tailbone/subscribers.py @@ -42,6 +42,7 @@ from webhelpers2.html import tags import tailbone from tailbone import helpers from tailbone.db import Session +from tailbone.config import csrf_header_name from tailbone.menus import make_simple_menus @@ -106,6 +107,7 @@ def before_render(event): renderer_globals['datetime'] = datetime renderer_globals['colander'] = colander renderer_globals['deform'] = deform + renderer_globals['csrf_header_name'] = csrf_header_name(request.rattail_config) # theme - we only want do this for classic web app, *not* API # TODO: so, clearly we need a better way to distinguish the two diff --git a/tailbone/templates/formposter.mako b/tailbone/templates/formposter.mako new file mode 100644 index 00000000..47c6ffd3 --- /dev/null +++ b/tailbone/templates/formposter.mako @@ -0,0 +1,42 @@ +## -*- coding: utf-8; -*- + +<%def name="declare_formposter_mixin()"> + + diff --git a/tailbone/templates/themes/falafel/base.mako b/tailbone/templates/themes/falafel/base.mako index 95c5e817..75bb914c 100644 --- a/tailbone/templates/themes/falafel/base.mako +++ b/tailbone/templates/themes/falafel/base.mako @@ -2,6 +2,7 @@ <%namespace file="/grids/nav.mako" import="grid_index_nav" /> <%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" /> <%namespace name="base_meta" file="/base_meta.mako" /> +<%namespace file="/formposter.mako" import="declare_formposter_mixin" /> @@ -487,6 +488,7 @@ <%def name="declare_whole_page_vars()"> + ${declare_formposter_mixin()} ${h.javascript_link(request.static_url('tailbone:static/themes/falafel/js/tailbone.feedback.js') + '?ver={}'.format(tailbone.__version__))}