diff --git a/tailbone/forms/__init__.py b/tailbone/forms/__init__.py index e24af4c3..cbdc493e 100644 --- a/tailbone/forms/__init__.py +++ b/tailbone/forms/__init__.py @@ -28,7 +28,7 @@ from __future__ import unicode_literals, absolute_import from formencode import Schema -from .core import Form, Field, FieldSet, GenericFieldSet +from .core import Form, Field, FieldSet, GenericFieldSet, invalid_csrf_token from .simpleform import SimpleForm, FormRenderer from .alchemy import AlchemyForm from .fields import AssociationProxyField diff --git a/tailbone/forms/alchemy.py b/tailbone/forms/alchemy.py index 143193b0..874eecc4 100644 --- a/tailbone/forms/alchemy.py +++ b/tailbone/forms/alchemy.py @@ -30,8 +30,10 @@ from rattail.core import Object import formalchemy as fa from pyramid.renderers import render +from webhelpers.html import HTML, tags from tailbone.db import Session +from tailbone.forms import invalid_csrf_token class TemplateEngine(fa.templates.TemplateEngine): @@ -54,11 +56,12 @@ class AlchemyForm(Object): allow_successive_creates = False - def __init__(self, request, fieldset, session=None, **kwargs): + def __init__(self, request, fieldset, session=None, csrf_field='_csrf', **kwargs): super(AlchemyForm, self).__init__(**kwargs) self.request = request self.fieldset = fieldset self.session = session + self.csrf_field = csrf_field def _get_readonly(self): return self.fieldset.readonly @@ -70,6 +73,31 @@ class AlchemyForm(Object): def successive_create_label(self): return "%s and continue" % self.create_label + def csrf(self, name=None): + """ + NOTE: this method was copied from `pyramid_simpleform.FormRenderer` + + Returns the CSRF hidden input. Creates new CSRF token + if none has been assigned yet. + + The name of the hidden field is **_csrf** by default. + """ + name = name or self.csrf_field + + token = self.request.session.get_csrf_token() + if token is None: + token = self.request.session.new_csrf_token() + + return tags.hidden(name, value=token) + + def csrf_token(self, name=None): + """ + NOTE: this method was copied from `pyramid_simpleform.FormRenderer` + + Convenience function. Returns CSRF hidden tag inside hidden DIV. + """ + return HTML.tag("div", self.csrf(name), style="display:none;") + def render(self, **kwargs): kwargs['form'] = self if self.readonly: @@ -86,5 +114,8 @@ class AlchemyForm(Object): self.session.flush() def validate(self): + if invalid_csrf_token(self.request): + self.request.session.flash("Invalid CSRF token", 'error') + return False self.fieldset.rebind(data=self.request.params) return self.fieldset.validate() diff --git a/tailbone/forms/core.py b/tailbone/forms/core.py index 0d2728cd..19187bbb 100644 --- a/tailbone/forms/core.py +++ b/tailbone/forms/core.py @@ -33,6 +33,17 @@ from formalchemy.helpers import content_tag from pyramid.renderers import render +def invalid_csrf_token(request): + """ + Returns boolean indicating whether the given request has an *invalid* CSRF token. + """ + if request.method == 'POST': + csrf_token = request.session.get_csrf_token() + if request.POST.get('_csrf') != csrf_token: + return True + return False + + class Form(object): """ Base class for all forms. diff --git a/tailbone/forms/simpleform.py b/tailbone/forms/simpleform.py index fa82b0b0..2fd481c3 100644 --- a/tailbone/forms/simpleform.py +++ b/tailbone/forms/simpleform.py @@ -33,7 +33,7 @@ from pyramid_simpleform import renderers from webhelpers.html import tags from webhelpers.html import HTML -from tailbone.forms import Form +from tailbone.forms import Form, invalid_csrf_token class SimpleForm(Form): @@ -52,6 +52,12 @@ class SimpleForm(Form): kwargs['form'] = FormRenderer(self) return super(SimpleForm, self).render(**kwargs) + def validate(self): + if invalid_csrf_token(self.request): + self.request.session.flash("Invalid CSRF token", 'error') + return False + return self._form.validate() + class FormRenderer(renderers.FormRenderer): """ diff --git a/tailbone/templates/forms/form.mako b/tailbone/templates/forms/form.mako index bf68a515..3baf812a 100644 --- a/tailbone/templates/forms/form.mako +++ b/tailbone/templates/forms/form.mako @@ -1,6 +1,7 @@ ## -*- coding: utf-8 -*-