Add initial support for CSRF token protection
This commit is contained in:
parent
11e78adaab
commit
ab09314ed3
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
## -*- coding: utf-8 -*-
|
||||
<div class="form">
|
||||
${h.form(form.action_url, id=form.id or None, method='post', enctype='multipart/form-data')}
|
||||
${form.csrf_token()}
|
||||
|
||||
${form.render_fields()|n}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<div class="form">
|
||||
${form.begin(**{'data-ajax': 'false'})}
|
||||
${form.hidden('referrer', value=referrer)}
|
||||
${form.csrf_token()}
|
||||
|
||||
${form.field_div('username', form.text('username'))}
|
||||
${form.field_div('password', form.password('password'))}
|
||||
|
|
|
@ -326,6 +326,7 @@
|
|||
|
||||
<div class="form-wrapper">
|
||||
${form.begin(id='receiving-form')}
|
||||
${form.csrf_token()}
|
||||
${h.hidden('mode')}
|
||||
${h.hidden('expiration_date')}
|
||||
${h.hidden('ordered_product')}
|
||||
|
|
|
@ -101,8 +101,7 @@ class AuthenticationView(View):
|
|||
self.request.session.flash("{} is already logged in".format(self.request.user), 'error')
|
||||
return self.redirect(referrer)
|
||||
|
||||
form = Form(self.request, schema=UserLogin)
|
||||
context = {'form': forms.FormRenderer(form), 'referrer': referrer, 'dialog': mobile}
|
||||
form = forms.SimpleForm(self.request, UserLogin)
|
||||
if form.validate():
|
||||
user = authenticate_user(Session(),
|
||||
form.data['username'],
|
||||
|
@ -115,7 +114,12 @@ class AuthenticationView(View):
|
|||
return self.redirect(referrer, headers=headers)
|
||||
else:
|
||||
self.request.session.flash("Invalid username or password", 'error')
|
||||
return context
|
||||
|
||||
return {
|
||||
'form': forms.FormRenderer(form),
|
||||
'referrer': referrer,
|
||||
'dialog': mobile,
|
||||
}
|
||||
|
||||
def mobile_login(self):
|
||||
return self.login(mobile=True)
|
||||
|
|
Loading…
Reference in a new issue