Add common "form poster" logic, to make CSRF token/header names configurable

also refactor the Feedback logic to use it
This commit is contained in:
Lance Edgar 2020-12-16 14:28:41 -06:00
parent a801672821
commit cc833c52b6
6 changed files with 66 additions and 29 deletions

View file

@ -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')

View file

@ -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')

View file

@ -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 = ""
})
},
}

View file

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

View file

@ -0,0 +1,42 @@
## -*- coding: utf-8; -*-
<%def name="declare_formposter_mixin()">
<script type="text/javascript">
let FormPosterMixin = {
methods: {
submitForm(action, params, success) {
let csrftoken = ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n}
let headers = {
'${csrf_header_name}': csrftoken,
}
this.$http.post(action, params, {headers: headers}).then(response => {
if (response.data.ok) {
success(response)
} else {
this.$buefy.toast.open({
message: "Failed to send feedback: " + response.data.error,
type: 'is-danger',
duration: 4000, // 4 seconds
})
}
}, response => {
this.$buefy.toast.open({
message: "Failed to submit form! (unknown server error)",
type: 'is-danger',
duration: 4000, // 4 seconds
})
})
},
},
}
</script>
</%def>

View file

@ -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" />
<!DOCTYPE html>
<html lang="en">
<head>
@ -487,6 +488,7 @@
</%def>
<%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__))}
<script type="text/javascript">
@ -523,7 +525,6 @@
<%def name="modify_whole_page_vars()">
<script type="text/javascript">
FeedbackFormData.csrftoken = ${json.dumps(request.session.get_csrf_token() or request.session.new_csrf_token())|n}
FeedbackFormData.referrer = location.href
% if request.user: