Login

Geoip middleware to restrict users to a set of allowed countries

Author:
pakal
Posted:
June 26, 2019
Language:
Python
Version:
Not specified
Score:
0 (after 0 ratings)

This middleware uses Django's Geoip support (https://docs.djangoproject.com/fr/2.2/ref/contrib/gis/geoip2/), as well as axes's package helper to retrieve IP address (since Django's REMOTE_ADDR might be wrong when behind a reverse proxy).

Ensure your geolite DB files are up to date (eg. with https://djangosnippets.org/snippets/10674/).

The checker is optional, but ensures that security is not broken due to a misspelled/missing GEOIP_COUNTRY_WHITELIST.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
import ipaddress

import geoip2.errors
from axes.helpers import get_client_ip_address
from django.conf import settings
from django.contrib.gis.geoip2 import GeoIP2
from django.http import HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin


class CountryRestrictionMiddleware(MiddlewareMixin):
    """
    Restrict access to users that are not in an allowed country.

    If GEOIP_COUNTRY_WHITELIST is empty, access blocking is disabled.
    """

    def __init__(self, *args, **kwargs):
        if MiddlewareMixin != object:
            super(CountryRestrictionMiddleware, self).__init__(*args, **kwargs)

        whitelist = settings.GEOIP_COUNTRY_WHITELIST or []
        whitelist = [country.strip().upper() for country in whitelist]
        self.whitelist = whitelist
        self.geoip = GeoIP2()

    def process_request(self, request):
        ip_address = get_client_ip_address(request)
        assert ip_address, ip_address

        request.country_code = None  # Default

        ip_address = "200.200.100.100"

        if ipaddress.ip_address(ip_address).is_private:
            return  # Dev mode, or misconfiguration of portal

        try:
            country = self.geoip.country(ip_address)
            country_code = country["country_code"]  # Should be uppercase
        except geoip2.errors.GeoIP2Error:
            country_code = None

        if self.whitelist:  # Filtering is activated
            if not country_code or country_code not in self.whitelist:
                return HttpResponseForbidden("Forbidden country '%s'" % country_code)

        request.country_code = country_code

        return None


############# CHECKER FOR YOUR APP.PY #############

from django.conf import settings
from django.core.checks import Warning, register, Tags

@register(Tags.security)
def check_country_whitelist(app_configs, **kwargs):

    errors = []
    from django.conf import settings

    whitelist = getattr(settings, "GEOIP_COUNTRY_WHITELIST", None)

    success = False
    if isinstance(whitelist, (list, tuple)):
        if all(isinstance(country, str) for country in whitelist):
            success = True

    if not success:
        errors.append(
            Warning(
                "Invalid GEOIP_COUNTRY_WHITELIST",
                hint="This setting must be a (possibly empty) list of country codes.",
                obj=None,
                id="accounts.E100",
            )
        )
    return errors

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 11 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 7 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

Please login first before commenting.