diff --git a/.env.dist b/.env.dist index 93d6bb1..0eef409 100644 --- a/.env.dist +++ b/.env.dist @@ -1,3 +1,4 @@ DJANGO_ALLOWED_HOSTS= DJANGO_DEBUG=True DJANGO_SECRET_KEY=example-secret-key +REQUIRE_IPV6_DISABLE= diff --git a/require_ipv6/__init__.py b/require_ipv6/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/require_ipv6/apps.py b/require_ipv6/apps.py new file mode 100644 index 0000000..e398fe7 --- /dev/null +++ b/require_ipv6/apps.py @@ -0,0 +1,10 @@ +""" +Configuration of the require_ipv6 Django app +""" + +from django.apps import AppConfig + + +class RequireIpv6Config(AppConfig): + name = "require_ipv6" + verbose_name = "Require IPv6" diff --git a/require_ipv6/middleware.py b/require_ipv6/middleware.py new file mode 100644 index 0000000..906f622 --- /dev/null +++ b/require_ipv6/middleware.py @@ -0,0 +1,46 @@ +""" +Middlewares of the require_ipv6 Django app +""" + +from django.conf import settings +from django.core.exceptions import MiddlewareNotUsed, SuspiciousOperation +from django.shortcuts import render +from ipware import get_client_ip +import ipaddress +import logging + +logger = logging.getLogger(__name__) + + +class RequireIPv6Middleware: + """ + A middleware that responds with an error if request is not done via IPv6 + """ + + def __init__(self, get_response): + # Requirement on IPv6 can be disabled by explicitly using a `True` value + # in setting `REQUIRE_IPV6_DISABLE` + should_disable = getattr(settings, "REQUIRE_IPV6_DISABLE", False) is True + + if should_disable: + logger.warning("RequireIPv6 Middleware is disabled per settings.") + raise MiddlewareNotUsed + + self.get_response = get_response + + def __call__(self, request): + client_ip_address_as_string = get_client_ip(request)[0] + + try: + client_ip_address = ipaddress.ip_address(client_ip_address_as_string) + except ValueError: + raise SuspiciousOperation( + "Invalid client IP address: %s" % client_ip_address_as_string + ) + + if not isinstance(client_ip_address, ipaddress.IPv6Address): + # Client does not use IPv6, render appropriate error page + return render(request, "require_ipv6/error.html", status=400) + + # Client uses IPv6, continue processing request + return self.get_response(request) diff --git a/require_ipv6/templates/require_ipv6/error.html b/require_ipv6/templates/require_ipv6/error.html new file mode 100644 index 0000000..09fa218 --- /dev/null +++ b/require_ipv6/templates/require_ipv6/error.html @@ -0,0 +1,6 @@ + + + + Sorry, only reachable by IPv6 + + diff --git a/requirements.txt b/requirements.txt index d60d36f..6d8532b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ asgiref==3.2.7 # via django django-environ==0.4.5 # via ungleich_screening_task (setup.py) +django-ipware==2.1.0 # via ungleich_screening_task (setup.py) django==3.0.6 # via ungleich_screening_task (setup.py) pytz==2020.1 # via django sqlparse==0.3.1 # via django diff --git a/setup.py b/setup.py index 032cd17..0cc8bcb 100644 --- a/setup.py +++ b/setup.py @@ -3,6 +3,7 @@ from setuptools import setup requirements = [ "django", "django-environ", + "django-ipware", ] dev_requirements = [ diff --git a/ungleich_screening_task/settings.py b/ungleich_screening_task/settings.py index 7dd0bea..9c5dc72 100644 --- a/ungleich_screening_task/settings.py +++ b/ungleich_screening_task/settings.py @@ -49,6 +49,7 @@ INSTALLED_APPS = [ "django.contrib.messages", "django.contrib.staticfiles", "friendly_cat", + "require_ipv6", ] MIDDLEWARE = [ @@ -56,6 +57,7 @@ MIDDLEWARE = [ "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", + "require_ipv6.middleware.RequireIPv6Middleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", @@ -124,3 +126,6 @@ USE_TZ = True # https://docs.djangoproject.com/en/3.0/howto/static-files/ STATIC_URL = "/static/" + +# Allow settings of the “Require IPv6” application to be set as environment variables +REQUIRE_IPV6_DISABLE = env.bool("REQUIRE_IPV6_DISABLE", False)