Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrejZbin committed Jul 2, 2020
0 parents commit 92e46e8
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.idea
*.pyc
*.egg-info
build
11 changes: 11 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Copyright 2020 Andrej Zbín

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include LICENSE
include README.rst
recursive-include hcaptcha/templates *
recursive-include docs *
71 changes: 71 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
===============
Django hCaptcha
===============

Django hCaptcha provides a simple way to protect your django forms using `hCaptcha <https://www.hcaptcha.com/>`_.

Configuration
-------------

Add "django-hcaptcha" to your INSTALLED_APPS setting like this::

INSTALLED_APPS = [
...
'hcaptcha',
]

For development purposes no further configuration is required. By default, django-hCaptcha will use dummy keys.

For production, you'll need to obtain your hCaptcha site key and secret key and add them to you settings::

HCAPTCHA_SITEKEY = '<your sitekey>'
HCAPTCHA_SECRET = '<your secret key>'


You can also configure your hCaptcha widget globally (`see all options <https://docs.hcaptcha.com/configuration>`_)::

HCAPTCHA_DEFAULT_CONFIG = {
'onload': 'name_of_js_function',
'render': 'explicit',
'theme': 'dark', # do not use data- prefix
'size': 'compact', # do not use data- prefix
...
}

If you need to, you can also override default hcaptcha endpoints::


HCAPTCHA_JS_API_URL = 'https://hcaptcha.com/1/api.js'
HCAPTCHA_VERIFY_URL = 'https://hcaptcha.com/siteverify'

Use proxies::

HCAPTCHA_PROXIES = {
'http': 'http://127.0.0.1:8000',
}

Change default verification timeout::

HCAPTCHA_TIMEOUT = 5



Usage
-----------

Simply add hCaptchaField to your forms::

from hcaptcha.fields import hCaptchaField

class Forms(forms.Form):
....
hcaptcha = hCaptchaField()
....

You can override default config by passing additional arguments::

class Forms(forms.Form):
....
hcaptcha = hCaptchaField(theme='dark', size='compact')
....

Empty file added hcaptcha/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions hcaptcha/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class HcaptchaConfig(AppConfig):
name = 'hcaptcha'
56 changes: 56 additions & 0 deletions hcaptcha/fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import json
from urllib.error import HTTPError
from urllib.parse import urlencode
from urllib.request import build_opener, Request, ProxyHandler

from django import forms
from django.utils.translation import ugettext_lazy as _

from hcaptcha.settings import DEFAULT_CONFIG, PROXIES, SECRET, TIMEOUT, VERIFY_URL
from hcaptcha.widgets import hCaptchaWidget


class hCaptchaField(forms.Field):
widget = hCaptchaWidget
default_error_messages = {
'error_hcaptcha': _('hCaptcha could not be verified.'),
'invalid_hcaptcha': _('hCaptcha could not be verified.'),
'required': _('Please prove you are a human.'),
}

def __init__(self, required=True, **config):
widget_settings = DEFAULT_CONFIG.copy()
widget_settings.update(config)
widget_url_settings = {}
for prop in filter(lambda p: p in widget_settings, ('onload', 'render', 'hl')):
widget_url_settings[prop] = widget_settings[prop]
del widget_settings[prop]
self.widget_settings = widget_settings

super().__init__(label='', help_text='', required=required)

self.widget.extra_url = widget_url_settings

def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
for key, value in self.widget_settings.items():
attrs['data-%s' % key] = value
return attrs

def validate(self, value):
super().validate(value)
opener = build_opener(ProxyHandler(PROXIES))
post_data = urlencode({
'secret': SECRET,
'response': value,
}).encode()
request = Request(VERIFY_URL, post_data)
try:
response = opener.open(request, timeout=TIMEOUT)
except HTTPError:
raise forms.ValidationError(self.error_messages['error_hcaptcha'], code='error_hcaptcha')

response_data = json.loads(response.read().decode("utf-8"))

if not response_data.get('success'):
raise forms.ValidationError(self.error_messages['invalid_hcaptcha'], code='invalid_hcaptcha')
Empty file added hcaptcha/migrations/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions hcaptcha/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.conf import settings

JS_API_URL = getattr(settings, 'HCAPTCHA_JS_API_URL', 'https://hcaptcha.com/1/api.js')
VERIFY_URL = getattr(settings, 'HCAPTCHA_VERIFY_URL', 'https://hcaptcha.com/siteverify')
SITEKEY = getattr(settings, 'HCAPTCHA_SITEKEY', '10000000-ffff-ffff-ffff-000000000001')
SECRET = getattr(settings, 'HCAPTCHA_SECRET', '0x0000000000000000000000000000000000000000')
TIMEOUT = getattr(settings, 'HCAPTCHA_TIMEOUT', 5)
DEFAULT_CONFIG = getattr(settings, 'HCAPTCHA_DEFAULT_CONFIG', {})
PROXIES = getattr(settings, 'HCAPTCHA_PROXIES', {})
2 changes: 2 additions & 0 deletions hcaptcha/templates/hcaptcha_widget.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<script src="{{ api_url }}" async defer></script>
<div class="h-captcha" {% include "django/forms/widgets/attrs.html" %}></div>
3 changes: 3 additions & 0 deletions hcaptcha/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.shortcuts import render

# Create your views here.
28 changes: 28 additions & 0 deletions hcaptcha/widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from urllib.parse import urlencode

from django import forms

from hcaptcha.settings import JS_API_URL, SITEKEY


class hCaptchaWidget(forms.Widget):
template_name = 'hcaptcha_widget.html'

def __init__(self, *args, **kwargs):
self.extra_url = {}
super().__init__(*args, **kwargs)

def value_from_datadict(self, data, files, name):
return data.get('h-captcha-response')

def build_attrs(self, base_attrs, extra_attrs=None):
attrs = super().build_attrs(base_attrs, extra_attrs)
attrs['data-sitekey'] = SITEKEY
return attrs

def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
context['api_url'] = JS_API_URL
if self.extra_url:
context['api_url'] += '?' + urlencode(self.extra_url)
return context
28 changes: 28 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[metadata]
name = django-hCaptcha
version = 0.1.0
description = Django hCaptcha provides a simple way to protect your django forms using hCaptcha
long_description = file: README.rst
url = https://github.com/AndrejZbin
author = Andrej Zbín
author_email = [email protected]
license = BSD-3-Clause
classifiers =
Environment :: Web Environment
Framework :: Django
Framework :: Django :: 2.2
Intended Audience :: Developers
License :: OSI Approved :: BSD License
Operating System :: OS Independent
Programming Language :: Python
Programming Language :: Python :: 3
Programming Language :: Python :: 3 :: Only
Programming Language :: Python :: 3.6
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Topic :: Internet :: WWW/HTTP
Topic :: Internet :: WWW/HTTP :: Dynamic Content

[options]
include_package_data = true
packages = find:
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from setuptools import setup

setup()

0 comments on commit 92e46e8

Please sign in to comment.