diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index bfb8063..3a9b717 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,8 +3,6 @@ name: docs on: push: branches: [ master ] - pull_request: - branches: [ master ] jobs: deploy: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c4352a2..5e4aedc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -51,7 +51,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - python -m pip install -e . + sh build.sh pip install tox tox-gh-actions - name: Run tests using tox run: tox -e ${{ matrix.env }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index f3c3919..40e7d85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,16 @@ +# caches .idea .tox .pytest_cache *.egg-info __pycache__ +# docs docs/node_modules docs/package-lock.json docs/.vitepress/cache docs/.vitepress/dist + +# build +build +dist diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..518140c --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include src/django_forbid/templates/*.html \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..b7ad47e --- /dev/null +++ b/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# last version of `build` supporting Python 3.6 +pip install build==0.9.0 + +# build the wheel and install it +WHEEL_NAME=$(python -m build | grep -Po "django_forbid-.*\.whl" | tail -n 1) +pip install dist/$WHEEL_NAME \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index 8ff549f..e9723da 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,7 +26,7 @@ keywords = detection django-forbid license = MIT -license_file = LICENSE +license_files = LICENSE platforms = unix, linux, osx, win32 classifiers = Operating System :: OS Independent @@ -50,6 +50,7 @@ classifiers = [options] packages = django_forbid + django_forbid.skills install_requires = Django>=2.1 geoip2 diff --git a/src/django_forbid/__init__.py b/src/django_forbid/__init__.py index 3dc1f76..b3f4756 100644 --- a/src/django_forbid/__init__.py +++ b/src/django_forbid/__init__.py @@ -1 +1 @@ -__version__ = "0.1.0" +__version__ = "0.1.2" diff --git a/src/django_forbid/middleware.py b/src/django_forbid/middleware.py index 9cc319c..d874000 100644 --- a/src/django_forbid/middleware.py +++ b/src/django_forbid/middleware.py @@ -3,9 +3,9 @@ from .skills.forbid_network import ForbidNetworkMiddleware __skills__ = ( - ForbidDeviceMiddleware, - ForbidLocationMiddleware, ForbidNetworkMiddleware, + ForbidLocationMiddleware, + ForbidDeviceMiddleware, ) diff --git a/src/django_forbid/skills/forbid_network.py b/src/django_forbid/skills/forbid_network.py index e32c900..0f98b38 100644 --- a/src/django_forbid/skills/forbid_network.py +++ b/src/django_forbid/skills/forbid_network.py @@ -22,7 +22,17 @@ def erase_response_attributes(): for attr in response_attributes: request.session.pop(attr) + def forbidden_page(): + # Redirects to the FORBIDDEN_NET URL if set. + if Settings.has("OPTIONS.URL.FORBIDDEN_NET"): + return redirect(Settings.get("OPTIONS.URL.FORBIDDEN_NET")) + return HttpResponseForbidden() + + geoip2_tz = request.session.get("GEOIP2_TZ") + verified_tz = request.session.get("VERIFIED_TZ", "") + if any([ + verified_tz == geoip2_tz, # Checks if VPN is False or not set. not Settings.get("OPTIONS.VPN", False), # Checks if the request is an AJAX request. @@ -33,19 +43,19 @@ def erase_response_attributes(): ]): return self.get_response(request) + # Checks if GEOIP2_TZ and VERIFIED_TZ don't exist. + if all([verified_tz, geoip2_tz != "N/A"]): + return forbidden_page() + if all(map(request.session.has_key, ("GEOIP2_TZ", *response_attributes))): # Handles if the user's timezone differs from the # one determined by GeoIP API. If so, VPN is used. - verified_tz = request.session.get("VERIFIED_TZ") - geoip2_tz = request.session.get("GEOIP2_TZ") client_tz = request.POST.get("CLIENT_TZ", verified_tz) if geoip2_tz != "N/A" and client_tz != geoip2_tz: + request.session["VERIFIED_TZ"] = "" erase_response_attributes() - # Redirects to the FORBIDDEN_NET URL if set. - if Settings.has("OPTIONS.URL.FORBIDDEN_NET"): - return redirect(Settings.get("OPTIONS.URL.FORBIDDEN_NET")) - return HttpResponseForbidden() + return forbidden_page() # Restores the response from the session. response = HttpResponse(**{ diff --git a/tests/test_network_middleware.py b/tests/test_network_middleware.py index e859825..e2557ac 100644 --- a/tests/test_network_middleware.py +++ b/tests/test_network_middleware.py @@ -12,19 +12,16 @@ def skips(get_response, ip_address, ajax=False): return response.status_code == 200 -def forbids(get_response, ip_address): - detector = Detector(get_response) +def forbids_shared_session(detector, ip_address): response = detector.request_resource(ip_address) - assert response.status_code == 302 - response = detector.request_access() + if response.status_code == 302: + response = detector.request_access(ip_address) return response.status_code == 403 -def forbids_shared_session(detector, ip_address): - response = detector.request_resource(ip_address) - assert response.status_code == 302 - response = detector.request_access() - return response.status_code == 403 +def forbids(get_response, ip_address): + detector = Detector(get_response) + return forbids_shared_session(detector, ip_address) class Detector: @@ -36,13 +33,15 @@ def request_resource(self, ip_address=""): """Sends a request to the server to access a resource""" request = self.request.get() request.META["HTTP_X_FORWARDED_FOR"] = ip_address - get_response = ForbidLocationMiddleware(self.get_response) - return ForbidNetworkMiddleware(get_response)(request) + get_response = ForbidNetworkMiddleware(self.get_response) + return ForbidLocationMiddleware(get_response)(request) - def request_access(self): + def request_access(self, ip_address=""): """Simulates the request sent by the user browser to the server""" request = self.request.post({"CLIENT_TZ": "Europe/London"}) - return ForbidNetworkMiddleware(self.get_response)(request) + request.META["HTTP_X_FORWARDED_FOR"] = ip_address + get_response = ForbidNetworkMiddleware(self.get_response) + return ForbidLocationMiddleware(get_response)(request) def test_should_allow_all_when_no_config_provided(get_response): diff --git a/tests/test_primary_middleware.py b/tests/test_primary_middleware.py index 697401c..360e724 100644 --- a/tests/test_primary_middleware.py +++ b/tests/test_primary_middleware.py @@ -11,8 +11,10 @@ def forbids(get_response, request): response = ForbidMiddleware(get_response)(request) + client_ip = request.META["HTTP_X_FORWARDED_FOR"] if response.status_code == 302: request = wsgi.post({"CLIENT_TZ": "Europe/London"}) + request.META["HTTP_X_FORWARDED_FOR"] = client_ip response = ForbidMiddleware(get_response)(request) return response.status_code == 403 @@ -49,9 +51,32 @@ def test_should_forbid_users_when_country_in_territories_blacklist(get_response) @override_settings(DJANGO_FORBID={"COUNTRIES": ["GB"], "OPTIONS": {"VPN": True}}) def test_should_allow_users_when_country_in_countries_whitelist(get_response): + """Should allow access to users from countries in whitelist.""" for ip_address in IP.all: request.META["HTTP_X_FORWARDED_FOR"] = ip_address if ip_address == IP.ip_london: assert not forbids(get_response, request) continue assert forbids(get_response, request) + + +@override_settings(DJANGO_FORBID={"OPTIONS": {"VPN": True}}) +def test_should_allow_users_only_from_great_britain_with_shared_session(get_response): + """It should give access to the user from Great Britain when session is shared""" + # Get access from London + request.META["HTTP_X_FORWARDED_FOR"] = IP.ip_london + assert not forbids(get_response, request) + # Turn on VPN temporary + request.META["HTTP_X_FORWARDED_FOR"] = IP.ip_zurich + assert forbids(get_response, request) + request.META["HTTP_X_FORWARDED_FOR"] = IP.ip_cobain + assert forbids(get_response, request) + # Turn off VPN - back to London + request.META["HTTP_X_FORWARDED_FOR"] = IP.ip_london + assert not forbids(get_response, request) + # Turn on VPN temporary + request.META["HTTP_X_FORWARDED_FOR"] = IP.ip_cobain + assert forbids(get_response, request) + # Turn off VPN - back to London + request.META["HTTP_X_FORWARDED_FOR"] = IP.ip_london + assert not forbids(get_response, request) diff --git a/tox.ini b/tox.ini index 9e52bed..38ef4eb 100644 --- a/tox.ini +++ b/tox.ini @@ -14,6 +14,7 @@ deps = django32: django<3.3 django21: django==2.1 -r{toxinidir}/tests/requirements.txt +allowlist_externals = sh commands = - pip install -e . + sh build.sh pytest \ No newline at end of file