From caff62b5721a5d22c65dfb4c7aa9b61ad938b43e Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Thu, 30 Nov 2023 17:42:45 +0200 Subject: [PATCH 1/6] Add django-honeypot --- pyproject.toml | 1 + requirements-dev.txt | 3 +++ requirements.txt | 3 +++ 3 files changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 0438ec39..7d934fda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,7 @@ dependencies = [ "django-crispy-forms", "django-extensions", "django-flatpickr", + "django-honeypot", "django-recaptcha", "django-registration", "django-storages", diff --git a/requirements-dev.txt b/requirements-dev.txt index f1bd36a2..205af0b4 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -54,6 +54,7 @@ django==4.2.7 # django-extensions # django-filter # django-flatpickr + # django-honeypot # django-modelcluster # django-permissionedforms # django-recaptcha @@ -81,6 +82,8 @@ django-filter==23.3 # via wagtail django-flatpickr==2.0.1 # via Western-Friend-website (pyproject.toml) +django-honeypot==1.0.4 + # via Western-Friend-website (pyproject.toml) django-modelcluster==6.1 # via wagtail django-permissionedforms==0.1 diff --git a/requirements.txt b/requirements.txt index 08795144..452ca648 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,6 +41,7 @@ django==4.2.7 # django-extensions # django-filter # django-flatpickr + # django-honeypot # django-modelcluster # django-permissionedforms # django-recaptcha @@ -64,6 +65,8 @@ django-filter==23.3 # via wagtail django-flatpickr==2.0.1 # via Western-Friend-website (pyproject.toml) +django-honeypot==1.0.4 + # via Western-Friend-website (pyproject.toml) django-modelcluster==6.1 # via wagtail django-permissionedforms==0.1 From 07365b5b27fa2492e3327b7d60ad96bcc291ea26 Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Thu, 30 Nov 2023 17:46:07 +0200 Subject: [PATCH 2/6] Add honeypot; sort --- core/settings.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/core/settings.py b/core/settings.py index a235e6b3..cd62dab9 100644 --- a/core/settings.py +++ b/core/settings.py @@ -128,8 +128,10 @@ # Application definition INSTALLED_APPS = [ # First party (apps from this project) + # keep-sorted start "accounts", "addresses", + "blocks", "cart", "common", "community", @@ -140,54 +142,59 @@ "forms", "home", "library", + "magazine", "memorials", "navigation", "news", "orders", - "payment.apps.PaymentConfig", "pagination", + "payment.apps.PaymentConfig", "paypal", "search", "store", "subscription", - "magazine", "tags", - "blocks", "wf_pages", + # keep-sorted end # Third party (apps that have been installed) - "django_extensions", - "crispy_forms", + # keep-sorted start "crispy_bootstrap5", + "crispy_forms", + "django_extensions", "django_flatpickr", + "django_recaptcha", + "honeypot", "modelcluster", "storages", "taggit", + "wagtail", + "wagtail.admin", "wagtail.contrib.forms", "wagtail.contrib.modeladmin", "wagtail.contrib.redirects", "wagtail.contrib.routable_page", "wagtail.contrib.settings", "wagtail.contrib.styleguide", - "wagtail.embeds", - "wagtail.sites", - "wagtail.users", - "wagtail.snippets", "wagtail.documents", + "wagtail.embeds", "wagtail.images", "wagtail.search", - "wagtail.admin", - "wagtail", + "wagtail.sites", + "wagtail.snippets", + "wagtail.users", "wagtail_color_panel", "wagtailmedia", - "django_recaptcha", + # keep-sorted end # Contrib (apps that are included in Django) + # keep-sorted start "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", - "django.contrib.sessions", "django.contrib.messages", - "django.contrib.staticfiles", + "django.contrib.sessions", "django.contrib.sitemaps", + "django.contrib.staticfiles", + # keep-sorted end ] CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap5" From 929afee4d2329d72dc5eb569d483d957693e30a1 Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Thu, 30 Nov 2023 17:55:16 +0200 Subject: [PATCH 3/6] Add Honeypot settings --- core/settings.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/settings.py b/core/settings.py index cd62dab9..36087971 100644 --- a/core/settings.py +++ b/core/settings.py @@ -209,6 +209,7 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.security.SecurityMiddleware", "wagtail.contrib.redirects.middleware.RedirectMiddleware", + "honeypot.middleware.HoneypotMiddleware", ] X_FRAME_OPTIONS = "SAMEORIGIN" @@ -392,3 +393,7 @@ RECAPTCHA_PUBLIC_KEY = os.environ.get("RECAPTCHA_PUBLIC_KEY", "") RECAPTCHA_PRIVATE_KEY = os.environ.get("RECAPTCHA_PRIVATE_KEY", "") NOCAPTCHA = True + +# Honeypot settings +HONEYPOT_FIELD_NAME = "email2" +HONEYPOT_VALUE = "" From 0ece0f2a4bfd2dbb79ad6fce7e9d3f8eba6bce34 Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Thu, 30 Nov 2023 18:08:48 +0200 Subject: [PATCH 4/6] Ensure honeypot fields render --- accounts/tests.py | 11 +++++++++++ forms/tests.py | 30 +++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/accounts/tests.py b/accounts/tests.py index 0e1dc487..06ea6a47 100644 --- a/accounts/tests.py +++ b/accounts/tests.py @@ -1,5 +1,7 @@ from unittest.mock import PropertyMock, patch +from django.conf import settings from django.test import TestCase +from django.urls import reverse from .models import User from subscription.models import Subscription @@ -87,3 +89,12 @@ def test_user_without_subscription_is_not_subscriber(self) -> None: self.user.refresh_from_db() self.assertFalse(self.user.is_subscriber) + + +class HoneypotTest(TestCase): + def test_register_page_contains_honeypot_field(self): + response = self.client.get(reverse("django_registration_register")) + self.assertContains( + response, + settings.HONEYPOT_FIELD_NAME, + ) diff --git a/forms/tests.py b/forms/tests.py index a39b155a..2fac319e 100644 --- a/forms/tests.py +++ b/forms/tests.py @@ -1 +1,29 @@ -# Create your tests here. +from django.test import TestCase +from django.conf import settings +from wagtail.models import Page, Site + +from home.models import HomePage + +from .models import ContactFormPage + + +class ContactFormPageHoneypotTest(TestCase): + def setUp(self) -> None: + site_root = Page.objects.get(id=2) + + self.home_page = HomePage(title="Home") + site_root.add_child(instance=self.home_page) + + Site.objects.all().update(root_page=self.home_page) + self.contact_form_page = ContactFormPage( + title="Contact Form Page", + slug="contact-form-page", + ) + self.home_page.add_child(instance=self.contact_form_page) + + def test_honeypot_field_is_rendered(self) -> None: + response = self.client.get(self.contact_form_page.url) + self.assertContains( + response, + settings.HONEYPOT_FIELD_NAME, + ) From 345ff2cb29e7d92980b2ec4052045686aa3813bd Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Thu, 30 Nov 2023 18:46:39 +0200 Subject: [PATCH 5/6] Initial working honeypot fields --- accounts/forms.py | 3 +-- .../registration_form.html | 5 ++++ accounts/views.py | 23 +++++++++++++++++++ core/settings.py | 2 -- core/urls.py | 9 +++----- forms/models.py | 11 +++++++++ forms/templates/forms/contact_form_page.html | 7 ++++++ 7 files changed, 50 insertions(+), 10 deletions(-) create mode 100644 accounts/views.py diff --git a/accounts/forms.py b/accounts/forms.py index 92c523fc..f42172df 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -1,6 +1,5 @@ from django import forms from django_registration.forms import RegistrationForm -from django_recaptcha.fields import ReCaptchaField from accounts.models import User @@ -10,7 +9,7 @@ class CustomUserForm(RegistrationForm): first_name = forms.CharField(max_length=30, required=True) last_name = forms.CharField(max_length=30, required=True) - captcha = ReCaptchaField() + # captcha = ReCaptchaField() class Meta(RegistrationForm.Meta): model = User diff --git a/accounts/templates/django_registration/registration_form.html b/accounts/templates/django_registration/registration_form.html index 88dc09a9..992ae71f 100644 --- a/accounts/templates/django_registration/registration_form.html +++ b/accounts/templates/django_registration/registration_form.html @@ -1,5 +1,6 @@ {% extends "base.html" %} {% load crispy_forms_tags %} +{% load honeypot %} {% block content %}

Register

@@ -8,8 +9,12 @@

Register

{% if user.is_authenticated %}

You are already registered.

{% else %} + {{ honeypot_field_name }}
{% csrf_token %} + + {% render_honeypot_field honeypot_field_name %} + {{ form|crispy }} diff --git a/accounts/views.py b/accounts/views.py new file mode 100644 index 00000000..e2020d8b --- /dev/null +++ b/accounts/views.py @@ -0,0 +1,23 @@ +from typing import Any +from django.conf import settings +from django.utils.decorators import method_decorator + +from django_registration.backends.activation.views import RegistrationView # type: ignore +from honeypot.decorators import check_honeypot # type: ignore + +from accounts.forms import CustomUserForm + + +@method_decorator(check_honeypot, name="post") +class CustomRegistrationView(RegistrationView): + form_class = CustomUserForm + success_url = "/" + template_name = "django_registration/registration_form.html" + + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: + """Add honeypot field to context.""" + context = super().get_context_data(**kwargs) + + context["honeypot_field_name"] = settings.HONEYPOT_FIELD_NAME + + return context diff --git a/core/settings.py b/core/settings.py index 36087971..3197b4ff 100644 --- a/core/settings.py +++ b/core/settings.py @@ -209,7 +209,6 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.security.SecurityMiddleware", "wagtail.contrib.redirects.middleware.RedirectMiddleware", - "honeypot.middleware.HoneypotMiddleware", ] X_FRAME_OPTIONS = "SAMEORIGIN" @@ -396,4 +395,3 @@ # Honeypot settings HONEYPOT_FIELD_NAME = "email2" -HONEYPOT_VALUE = "" diff --git a/core/urls.py b/core/urls.py index 9991dc9e..1d7608e7 100644 --- a/core/urls.py +++ b/core/urls.py @@ -3,16 +3,13 @@ from django.contrib import admin from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.urls import include, path -from django_registration.backends.activation.views import RegistrationView + from wagtail import urls as wagtail_urls from wagtail.admin import urls as wagtailadmin_urls from wagtail.contrib.sitemaps.views import sitemap from wagtail.documents import urls as wagtaildocs_urls -# TODO: Change this line to send verification emails when registering users -# Note: this will require two activation email tempates (subject and body) -# from django_registration.backends.activation.views import RegistrationView -from accounts.forms import CustomUserForm +from accounts.views import CustomRegistrationView from magazine import urls as magazine_urls from search import views as search_views @@ -20,7 +17,7 @@ path("django-admin/", admin.site.urls), path( "accounts/register/", - RegistrationView.as_view(form_class=CustomUserForm), + CustomRegistrationView.as_view(), name="django_registration_register", ), path("accounts/", include("django_registration.backends.activation.urls")), diff --git a/forms/models.py b/forms/models.py index 2d00eba1..3310fa51 100644 --- a/forms/models.py +++ b/forms/models.py @@ -1,4 +1,7 @@ +from django.conf import settings from django.db import models +from django.utils.decorators import method_decorator +from honeypot.decorators import check_honeypot from modelcluster.fields import ParentalKey from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel from wagtail.contrib.forms.models import ( @@ -51,6 +54,7 @@ class FormField(AbstractFormField): ) +@method_decorator(check_honeypot, name="serve") class ContactFormPage(WfCaptchaEmailForm): intro = RichTextField(blank=True) thank_you_text = RichTextField(blank=True) @@ -80,3 +84,10 @@ class ContactFormPage(WfCaptchaEmailForm): parent_page_types = ["home.HomePage"] subpage_types: list[str] = [] + + def get_context(self, request, *args, **kwargs): + context = super().get_context(request, *args, **kwargs) + + context["honeypot_field_name"] = settings.HONEYPOT_FIELD_NAME + + return context diff --git a/forms/templates/forms/contact_form_page.html b/forms/templates/forms/contact_form_page.html index 370dc7c4..e5b6c0d8 100644 --- a/forms/templates/forms/contact_form_page.html +++ b/forms/templates/forms/contact_form_page.html @@ -2,6 +2,7 @@ {% load wagtailcore_tags %} {% load crispy_forms_tags %} +{% load honeypot %} {% block title %} @@ -10,10 +11,16 @@ {% block content %}

{{ page.title }}

+ {{ page.intro|richtext }} + {% csrf_token %} + + {% render_honeypot_field honeypot_field_name %} + {{ form | crispy }} +
{% endblock content %} From aebcc3dddb92949f7413c92f794695b07b3e2bef Mon Sep 17 00:00:00 2001 From: Brylie Christopher Oxley Date: Thu, 30 Nov 2023 18:50:53 +0200 Subject: [PATCH 6/6] Cleanup --- accounts/forms.py | 3 ++- accounts/templates/django_registration/registration_form.html | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/accounts/forms.py b/accounts/forms.py index f42172df..3cef588a 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -1,4 +1,5 @@ from django import forms +from django_recaptcha.fields import ReCaptchaField from django_registration.forms import RegistrationForm from accounts.models import User @@ -9,7 +10,7 @@ class CustomUserForm(RegistrationForm): first_name = forms.CharField(max_length=30, required=True) last_name = forms.CharField(max_length=30, required=True) - # captcha = ReCaptchaField() + captcha = ReCaptchaField() class Meta(RegistrationForm.Meta): model = User diff --git a/accounts/templates/django_registration/registration_form.html b/accounts/templates/django_registration/registration_form.html index 992ae71f..9a8d3693 100644 --- a/accounts/templates/django_registration/registration_form.html +++ b/accounts/templates/django_registration/registration_form.html @@ -9,7 +9,6 @@

Register

{% if user.is_authenticated %}

You are already registered.

{% else %} - {{ honeypot_field_name }}
{% csrf_token %}