Skip to content

Commit

Permalink
Make it possible for forms to be marked as CSRF exempt
Browse files Browse the repository at this point in the history
  • Loading branch information
hedleyroos committed Feb 13, 2017
1 parent 606f241 commit 80095cd
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 30 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

next
----
#. Make it possible for forms to be marked as CSRF exempt.

0.1
---
#. Initial release.

20 changes: 20 additions & 0 deletions formfactory/migrations/0006_form_enable_csrf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.12 on 2017-02-13 13:32
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('formfactory', '0005_added_enum_generic_relation'),
]

operations = [
migrations.AddField(
model_name='form',
name='enable_csrf',
field=models.BooleanField(default=True, help_text='Cross site request forgery protection may not be needed in all cases. Since it incurs a performance penalty you may wish to disable it.', verbose_name='Enable CSRF protection'),
),
]
16 changes: 15 additions & 1 deletion formfactory/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,13 @@ class Form(BaseFormModel):
max_length=64, default="Submit",
help_text="The text you would like on the form submit button."
)
enable_csrf = models.BooleanField(
_("Enable CSRF protection"),
default=True,
help_text=_("""Cross site request forgery protection may not be needed \
in all cases. Since it incurs a performance penalty you may wish to disable \
it.""")
)

class Meta(object):
ordering = ["title"]
Expand All @@ -131,7 +138,14 @@ def __unicode__(self):
return self.title

def get_absolute_url(self):
return reverse("formfactory:form-detail", kwargs={"slug": self.slug})
if self.enable_csrf:
return reverse(
"formfactory:form-detail", kwargs={"slug": self.slug}
)
else:
return reverse(
"formfactory:form-detail-nocsrf", kwargs={"slug": self.slug}
)

@property
def absolute_url(self):
Expand Down
4 changes: 3 additions & 1 deletion formfactory/templates/formfactory/form_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

{% block content %}
<form class="Form" method="POST" action=".">
{% csrf_token %}
{% if view.form_object.enable_csrf %}
{% csrf_token %}
{% endif %}
{{ form.as_p }}
<input type="submit" value="{{ view.form_object.submit_button_text }}" />
</form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<form class="Form" method="POST" action=".">
{% with object.as_form as form %}
{% csrf_token %}
{% if object.enable_csrf %}
{% csrf_token %}
{% endif %}
{{ form.as_p }}
<input type="submit" value="{{ object.submit_button_text }}" />
{% endwith %}
Expand Down
43 changes: 19 additions & 24 deletions formfactory/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ def setUp(self):
}

def test_detail(self):
response = self.client.get(
reverse(
"formfactory:form-detail",
kwargs={"slug": self.simpleform_data["slug"]}
)
)
response = self.client.get(self.simpleform.get_absolute_url())
self.assertEqual(response.status_code, 200)
for field_group in self.simpleform.fieldgroups.all():
for field in field_group.fields.all():
Expand All @@ -53,10 +48,7 @@ def test_detail(self):
self.assertContains(response, choice.value)

response = self.client.post(
reverse(
"formfactory:form-detail",
kwargs={"slug": self.simpleform_data["slug"]}
),
self.simpleform.get_absolute_url(),
data=self.form_postdata, follow=True
)
original_form_field = response.context["form"].fields
Expand All @@ -76,10 +68,7 @@ def test_detail(self):

def test_formfield_group_title_can_be_hidden(self):
response = self.client.get(
reverse(
"formfactory:form-detail",
kwargs={"slug": self.simpleform_data["slug"]}
)
self.simpleform.get_absolute_url()
)
self.assertEqual(response.status_code, 200)
self.failIf("Field Group 1" in response.content)
Expand All @@ -88,6 +77,20 @@ def tearDown(self):
pass


class ViewNoCSRFTestCase(ViewTestCase):
"""Re-use the super class. It tests all the required code paths."""

def setUp(self):
super(ViewNoCSRFTestCase, self).setUp()

# Explicitly enable CSRF checks to confirm the bypass works
self.client = Client(enforce_csrf_checks=True)

# Modify the form
self.simpleform.enable_csrf = False
self.simpleform.save()


class LoginViewDetailTestCase(TestCase):
def setUp(self):
super(LoginViewDetailTestCase, self).setUp()
Expand All @@ -102,23 +105,15 @@ def setUp(self):
}

def test_detail(self):
response = self.client.get(
reverse(
"formfactory:form-detail",
kwargs={"slug": self.loginform_data["slug"]}
)
)
response = self.client.get(self.loginform.get_absolute_url())
self.assertEqual(response.status_code, 200)
for field_group in self.loginform.fieldgroups.all():
self.assertContains(response, field_group.title)
for field in field_group.fields.all():
self.assertContains(response, field.label)

response = self.client.post(
reverse(
"formfactory:form-detail",
kwargs={"slug": self.loginform_data["slug"]}
),
self.loginform.get_absolute_url(),
data=self.loginform_postdata, follow=True
)
self.assertEqual(response.status_code, 200)
Expand Down
9 changes: 8 additions & 1 deletion formfactory/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from django.conf.urls import url
from django.views.decorators.csrf import csrf_exempt

from formfactory.views import FactoryFormView, FactoryWizardView
from formfactory.views import FactoryFormView, FactoryFormNoCSRFView, \
FactoryWizardView


urlpatterns = [
Expand All @@ -10,4 +12,9 @@
name="wizard-detail"
),
url(r"^(?P<slug>[-\w]+)/$", FactoryFormView.as_view(), name="form-detail"),
url(
r"^nocsrf/(?P<slug>[-\w]+)/$",
csrf_exempt(FactoryFormNoCSRFView.as_view()),
name="form-detail-nocsrf"
)
]
7 changes: 7 additions & 0 deletions formfactory/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ def get_success_url(self):
return self.request.path_info


class FactoryFormNoCSRFView(FactoryFormView):
"""csrf_exempt is applied at class level independent of request so a full
class is required for forms that are not subject to CSRF protection."""

pass


class FactoryWizardView(NamedUrlSessionWizardView):
form_list = [EmptyForm, ]
redirect_to = None
Expand Down
4 changes: 2 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ deps =
coverage
-rformfactory/tests/requirements/19.txt
commands =
coverage run manage.py test --nomigrations --settings=formfactory.tests.settings.19
coverage run manage.py test {posargs} --nomigrations --settings=formfactory.tests.settings.19
coverage report -m

[testenv:django110]
deps =
coverage
-rformfactory/tests/requirements/110.txt
commands =
coverage run manage.py test --nomigrations --settings=formfactory.tests.settings.110
coverage run manage.py test {posargs} --nomigrations --settings=formfactory.tests.settings.110
coverage report -m

0 comments on commit 80095cd

Please sign in to comment.