Add CAPTCHA to registration/password reset forms

Extend and override the default views so we can extend and override the
default forms to add a CAPTCHA field. This should prevent the automated
account creation requests we've been seeing on layers.openembedded.org
(luckily failing anyway due to bad domain names), but in any case this
also improves security by making it harder to do user enumeration.

For the registration page in particular, because Django's forms logic
tries to be helpful by showing all errors at once, we need to change it
so that if there's an error for the CAPTCHA then you only see that error
and no other - in particular you won't see "that username already
exists" if that is the case.

Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
This commit is contained in:
Paul Eggleton 2018-10-15 15:59:13 +13:00
parent 5d7cf9e5ae
commit 23194fc5d4
4 changed files with 74 additions and 1 deletions

16
layerindex/auth_forms.py Normal file
View File

@ -0,0 +1,16 @@
# layerindex-web - extended authentication forms
#
# Copyright (C) 2018 Intel Corporation
#
# Licensed under the MIT license, see COPYING.MIT for details
from captcha.fields import CaptchaField
from registration.forms import RegistrationForm
from django.contrib.auth.forms import PasswordResetForm
class CaptchaRegistrationForm(RegistrationForm):
captcha = CaptchaField(label='Verification', help_text='Please enter the letters displayed for verification purposes', error_messages={'invalid':'Incorrect entry, please try again'})
class CaptchaPasswordResetForm(PasswordResetForm):
captcha = CaptchaField(label='Verification', help_text='Please enter the letters displayed for verification purposes', error_messages={'invalid':'Incorrect entry, please try again'})

30
layerindex/auth_views.py Normal file
View File

@ -0,0 +1,30 @@
# layerindex-web - extended authentication views
#
# Copyright (C) 2018 Intel Corporation
#
# Licensed under the MIT license, see COPYING.MIT for details
from registration.backends.model_activation.views import RegistrationView
from django.contrib.auth.views import PasswordResetView
from layerindex.auth_forms import CaptchaRegistrationForm, CaptchaPasswordResetForm
class CaptchaRegistrationView(RegistrationView):
form_class = CaptchaRegistrationForm
def get_context_data(self, **kwargs):
context = super(CaptchaRegistrationView, self).get_context_data(**kwargs)
form = context['form']
# Prepare a list of fields with errors
# We do this so that if there's a problem with the captcha, that's the only error shown
# (since we have a username field, we want to make user enumeration difficult)
if 'captcha' in form.errors:
error_fields = ['captcha']
else:
error_fields = form.errors.keys()
context['error_fields'] = error_fields
return context
class CaptchaPasswordResetView(PasswordResetView):
form_class = CaptchaPasswordResetForm

View File

@ -3,7 +3,25 @@
{% block content %}
<form id="registration_form" method="post" action=".">
{{ form.as_p }}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in form.visible_fields %}
{% if field.name in error_fields %}
<div class="form-group alert alert-danger">
{{ field.errors }}
{% else %}
<div class="form-group">
{% endif %}
<div class="control-label {% if field.required %}requiredlabel{% endif %}">
{{ field.label_tag }}
</div>
<div class="controls">
{{ field }}
</div>
</div>
{% endfor %}
<input type="submit" class="btn btn-default" value="{% trans 'Submit' %}" />
{% csrf_token %}

View File

@ -6,7 +6,9 @@
# All rights reserved.
from django.conf.urls import include, url
from django.core.urlresolvers import reverse_lazy
from django.views.generic import RedirectView
from layerindex.auth_views import CaptchaRegistrationView, CaptchaPasswordResetView
from django.contrib import admin
admin.autodiscover()
@ -16,6 +18,13 @@ import settings
urlpatterns = [
url(r'^layerindex/', include('layerindex.urls')),
url(r'^admin/', include(admin.site.urls)),
url(r'^accounts/password/reset/$',
CaptchaPasswordResetView.as_view(
email_template_name='registration/password_reset_email.txt',
success_url=reverse_lazy('auth_password_reset_done')),
name='auth_password_reset'),
url(r'^accounts/register/$', CaptchaRegistrationView.as_view(),
name='registration_register'),
url(r'^accounts/', include('registration.backends.default.urls')),
url(r'^captcha/', include('captcha.urls')),
]