mirror of
https://github.com/peeringdb/peeringdb.git
synced 2024-05-11 05:55:09 +00:00
Captcha fallback for where re-captcha is unavailable (#38)
This commit is contained in:
1
Pipfile
1
Pipfile
@@ -51,6 +51,7 @@ django = "==1.11.20"
|
||||
uwsgi = "==2.0.14"
|
||||
markdown = "==2.6.7"
|
||||
"twentyc.rpc" = "==0.3.5"
|
||||
django-simple-captcha = "==0.5.11"
|
||||
unidecode = "==1.0.23"
|
||||
|
||||
[requires]
|
||||
|
||||
@@ -63,6 +63,7 @@ INSTALLED_APPS += [
|
||||
'oauth2_provider',
|
||||
'peeringdb_server',
|
||||
'reversion',
|
||||
'captcha',
|
||||
]
|
||||
|
||||
# django_peeringdb settings
|
||||
|
||||
@@ -29,6 +29,11 @@ urlpatterns = [
|
||||
]
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
|
||||
urlpatterns += [
|
||||
url(r'^captcha/', include('captcha.urls')),
|
||||
]
|
||||
|
||||
urlpatterns += peeringdb_server.urls.urlpatterns
|
||||
|
||||
handler_404 = 'peeringdb_server.views.view_http_error_404'
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import re
|
||||
from peeringdb_server.models import User, Organization
|
||||
import requests
|
||||
|
||||
from django.contrib.auth import forms as auth_forms
|
||||
from django import forms
|
||||
from django.utils import timezone
|
||||
from django_namespace_perms.constants import *
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings as dj_settings
|
||||
|
||||
from captcha.fields import CaptchaField
|
||||
from captcha.models import CaptchaStore
|
||||
|
||||
from peeringdb_server.models import User, Organization
|
||||
from peeringdb_server.inet import get_client_ip
|
||||
|
||||
|
||||
class OrgAdminUserPermissionForm(forms.Form):
|
||||
@@ -90,6 +99,10 @@ class UsernameRetrieveForm(forms.Form):
|
||||
|
||||
|
||||
class UserCreationForm(auth_forms.UserCreationForm):
|
||||
recaptcha = forms.CharField(required=False)
|
||||
captcha = forms.CharField(required=False)
|
||||
captcha_generator = CaptchaField(required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = (
|
||||
@@ -100,6 +113,39 @@ class UserCreationForm(auth_forms.UserCreationForm):
|
||||
)
|
||||
|
||||
|
||||
def clean(self):
|
||||
super(UserCreationForm, self).clean()
|
||||
recaptcha = self.cleaned_data.get("recaptcha", "")
|
||||
captcha = self.cleaned_data.get("captcha", "")
|
||||
|
||||
if not recaptcha and not captcha:
|
||||
raise forms.ValidationError(_("Please fill out the anti-spam challenge (captcha) field"))
|
||||
|
||||
elif recaptcha:
|
||||
cpt_params = {
|
||||
"secret": dj_settings.RECAPTCHA_SECRET_KEY,
|
||||
"response": recaptcha,
|
||||
"remoteip": get_client_ip(self.request)
|
||||
}
|
||||
cpt_response = requests.post(dj_settings.RECAPTCHA_VERIFY_URL,
|
||||
params=cpt_params).json()
|
||||
if not cpt_response.get("success"):
|
||||
raise forms.ValidationError(_("reCAPTCHA invalid"))
|
||||
else:
|
||||
try:
|
||||
hashkey, value = captcha.split(":")
|
||||
self.captcha_object = CaptchaStore.objects.get(response=value,
|
||||
hashkey=hashkey,
|
||||
expiration__gt=timezone.now())
|
||||
except CaptchaStore.DoesNotExist:
|
||||
raise forms.ValidationError(_("captcha invalid"))
|
||||
|
||||
def delete_captcha(self):
|
||||
captcha_object = getattr(self, "captcha_object", None)
|
||||
if captcha_object:
|
||||
captcha_object.delete()
|
||||
|
||||
|
||||
class UserLocaleForm(forms.Form):
|
||||
locale = forms.CharField()
|
||||
|
||||
|
||||
@@ -205,3 +205,14 @@ def get_prefix_protocol(prefix):
|
||||
return "IPv6"
|
||||
except ipaddress.AddressValueError:
|
||||
raise ValueError("Prefix invalid")
|
||||
|
||||
|
||||
def get_client_ip(request):
|
||||
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(",")[0]
|
||||
else:
|
||||
ip = request.META.get("REMOTE_ADDR")
|
||||
return ip
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ PeeringDB = {
|
||||
fac : {},
|
||||
ix : {}
|
||||
},
|
||||
recaptcha_loaded : function() {
|
||||
return (typeof grecaptcha == "object");
|
||||
},
|
||||
init : function() {
|
||||
this.InlineSearch.init_search();
|
||||
|
||||
@@ -25,8 +28,25 @@ PeeringDB = {
|
||||
});
|
||||
|
||||
$('#form-create-account').on('export', function(e, data) {
|
||||
if(this.recaptcha_loaded()){
|
||||
data.recaptcha = grecaptcha.getResponse();
|
||||
});
|
||||
} else {
|
||||
data.captcha = $('#id_captcha_generator_0').val()+":"+$('#id_captcha_generator_1').val()
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
setTimeout(function() {
|
||||
var fallback = $('#form-create-account').find('.fallback');
|
||||
if(!this.recaptcha_loaded()) {
|
||||
console.log("re-captcha could not be loaded, falling back to internal captcha");
|
||||
$('#form-create-account').find('.recaptcha').hide();
|
||||
fallback.show();
|
||||
} else {
|
||||
// since re-captcha loaded successfully we don't need a requirement
|
||||
// on the fallback input
|
||||
fallback.find('#id_captcha_0').attr('required', false);
|
||||
}
|
||||
}.bind(this), 3000);
|
||||
|
||||
this.fix_list_offsets();
|
||||
|
||||
|
||||
@@ -1288,3 +1288,37 @@ div[data-edit-name=ipaddr6]
|
||||
.ixf-errors-overview small {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/**
|
||||
* captcha
|
||||
*/
|
||||
|
||||
div.captcha {
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
}
|
||||
|
||||
div.captcha .loading {
|
||||
padding-top: 5px;
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
div.captcha .captcha-container {
|
||||
background-color: #f1f1f1;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
div.captcha .g-recaptcha {
|
||||
}
|
||||
|
||||
div.captcha .fallback {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,19 @@
|
||||
data-edit-name="last_name"
|
||||
data-edit-type="string" />
|
||||
|
||||
<div class="captcha">
|
||||
<div class="loading center"><img src="{{ STATIC_URL }}loading.gif"> {% trans "Loading anti-spam challenge" %} ...</div>
|
||||
<div class="captcha-container">
|
||||
<div class="g-recaptcha" data-sitekey="{{ RECAPTCHA_PUBLIC_KEY }}"></div>
|
||||
<div class="fallback">
|
||||
{% for field in register_form.visible_fields %}
|
||||
{% if field.name == "captcha_generator" %}
|
||||
{{ field }}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-default" data-edit-action="submit">{% trans "Create" %}</a>
|
||||
</div>
|
||||
@@ -86,6 +98,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script language="javascript" type="text/javascript">
|
||||
$('#form-create-account').each(function(idx) {
|
||||
$(this).on("action-success:submit", function(ev, data) {
|
||||
@@ -95,7 +108,9 @@ $('#form-create-account').each(function(idx) {
|
||||
window.document.location.href = "/verify"
|
||||
})
|
||||
$(this).on("action-error:submit", function(ev, data) {
|
||||
if(PeeringDB.recaptcha_loaded()) {
|
||||
grecaptcha.reset()
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -122,14 +122,6 @@ def make_env(**data):
|
||||
return env
|
||||
|
||||
|
||||
def get_client_ip(request):
|
||||
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
||||
if x_forwarded_for:
|
||||
ip = x_forwarded_for.split(",")[0]
|
||||
else:
|
||||
ip = request.META.get("REMOTE_ADDR")
|
||||
return ip
|
||||
|
||||
|
||||
def view_http_error_404(request):
|
||||
template = loader.get_template('site/error_404.html')
|
||||
@@ -661,27 +653,18 @@ def view_registration(request):
|
||||
env = BASE_ENV.copy()
|
||||
env.update({
|
||||
'global_stats': global_stats(),
|
||||
'register_form' : UserCreationForm(),
|
||||
})
|
||||
return HttpResponse(template.render(env, request))
|
||||
|
||||
elif request.method == "POST":
|
||||
form = UserCreationForm(request.POST)
|
||||
form.request = request
|
||||
|
||||
if not form.is_valid():
|
||||
return JsonResponse(form.errors, status=400)
|
||||
|
||||
cpt = request.POST.get("recaptcha", "")
|
||||
cpt_params = {
|
||||
"secret": dj_settings.RECAPTCHA_SECRET_KEY,
|
||||
"response": cpt,
|
||||
"remoteip": get_client_ip(request)
|
||||
}
|
||||
cpt_response = requests.post(dj_settings.RECAPTCHA_VERIFY_URL,
|
||||
params=cpt_params).json()
|
||||
if not cpt_response.get("success"):
|
||||
return JsonResponse({
|
||||
"non_field_errors": [_("reCAPTCHA invalid")]
|
||||
}, status=400)
|
||||
errors = form.errors
|
||||
errors["non_field_errors"] = errors.get("__all__",[])
|
||||
return JsonResponse(errors, status=400)
|
||||
|
||||
email = form.cleaned_data["email"]
|
||||
if EmailAddress.objects.filter(email=email).count() > 0:
|
||||
@@ -708,6 +691,8 @@ def view_registration(request):
|
||||
|
||||
user.send_email_confirmation(signup=True, request=request)
|
||||
|
||||
form.delete_captcha()
|
||||
|
||||
return JsonResponse({"status": "ok"})
|
||||
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ django-tables2==1.0.4
|
||||
django-vanilla-views==1.0.2
|
||||
django-cors-headers==2.1.0
|
||||
django-ratelimit==1.1.0
|
||||
django-simple-captcha==0.5.11
|
||||
|
||||
# issue #95
|
||||
passlib==1.7.1
|
||||
|
||||
@@ -27,6 +27,7 @@ settings.configure(
|
||||
'dal',
|
||||
'dal_select2',
|
||||
'corsheaders',
|
||||
'captcha',
|
||||
],
|
||||
CACHES={
|
||||
"default": {
|
||||
@@ -159,6 +160,8 @@ settings.configure(
|
||||
DATA_QUALITY_MIN_PREFIXLEN_V6 = 64,
|
||||
DATA_QUALITY_MAX_PREFIXLEN_V6 = 116,
|
||||
TUTORIAL_MODE=False,
|
||||
CAPTCHA_TEST_MODE=True,
|
||||
SITE_ID=1,
|
||||
RATELIMITS={
|
||||
"view_affiliate_to_org_POST": "100/m",
|
||||
"resend_confirmation_mail": "2/m",
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import pytest
|
||||
import json
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from django.test import Client, TestCase, RequestFactory
|
||||
from django.contrib.auth.models import Group
|
||||
|
||||
from captcha.models import CaptchaStore
|
||||
|
||||
import peeringdb_server.models as models
|
||||
import peeringdb_server.views as views
|
||||
|
||||
@@ -271,3 +275,29 @@ class UserTests(TestCase):
|
||||
|
||||
with self.assertRaises(KeyError):
|
||||
email = c.session["username_retrieve_email"]
|
||||
|
||||
|
||||
def test_signup(self):
|
||||
"""
|
||||
test user signup with captcha fallback
|
||||
"""
|
||||
|
||||
c = Client()
|
||||
response = c.get("/register")
|
||||
self.assertGreater(response.content.find('name="captcha_generator_0"'), -1)
|
||||
m = re.search('name="captcha_generator_0" value="([^"]+)"', response.content)
|
||||
|
||||
captcha_obj = CaptchaStore.objects.get(hashkey=m.group(1))
|
||||
|
||||
response = c.post("/register", {
|
||||
"username": "signuptest",
|
||||
"password1": "signuptest_123",
|
||||
"password2": "signuptest_123",
|
||||
"email": "signuptest@localhost",
|
||||
"captcha": "{}:{}".format(captcha_obj.hashkey, captcha_obj.response)
|
||||
})
|
||||
|
||||
self.assertEqual( json.loads(response.content), {"status":"ok"})
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user