Make ipware an optional dependency

Relates to #1038
This commit is contained in:
Aleksi Häkli 2023-04-27 22:53:22 +03:00
parent 700ea46607
commit cd950ddfef
5 changed files with 50 additions and 14 deletions

View file

@ -5,7 +5,6 @@ from string import Template
from typing import Callable, Optional, Type, Union
from urllib.parse import urlencode
import ipware.ip
from django.core.cache import BaseCache, caches
from django.http import HttpRequest, HttpResponse, JsonResponse, QueryDict
from django.shortcuts import redirect, render
@ -16,6 +15,13 @@ from axes.models import AccessBase
log = getLogger(__name__)
try:
import ipware.ip
IPWARE_INSTALLED = True
except ImportError:
IPWARE_INSTALLED = False
def get_cache() -> BaseCache:
"""
@ -148,20 +154,24 @@ def get_client_username(
return request_data.get(settings.AXES_USERNAME_FORM_FIELD, None)
def get_client_ip_address(request: HttpRequest) -> str:
def get_client_ip_address(
request: HttpRequest,
use_ipware: Optional[bool] = None,
) -> Optional[str]:
"""
Get client IP address as configured by the user.
The order of preference for address resolution is as follows:
1. If configured, use ``AXES_CLIENT_IP_CALLABLE``, and supply ``request`` as argument
2. Use django-ipware package (parameters can be configured in the Axes package)
2. If available, use django-ipware package (parameters can be configured in the Axes package)
3. Use ``request.META.get('REMOTE_ADDR', None)`` as a fallback
:param request: incoming Django ``HttpRequest`` or similar object from authentication backend or other source
"""
if settings.AXES_CLIENT_IP_CALLABLE:
log.debug("Using settings.AXES_CLIENT_IP_CALLABLE to get username")
log.debug("Using settings.AXES_CLIENT_IP_CALLABLE to get client IP address")
if callable(settings.AXES_CLIENT_IP_CALLABLE):
return settings.AXES_CLIENT_IP_CALLABLE( # pylint: disable=not-callable
@ -173,15 +183,26 @@ def get_client_ip_address(request: HttpRequest) -> str:
"settings.AXES_CLIENT_IP_CALLABLE needs to be a string, callable, or None."
)
client_ip_address, _ = ipware.ip.get_client_ip(
request,
proxy_order=settings.AXES_PROXY_ORDER,
proxy_count=settings.AXES_PROXY_COUNT,
proxy_trusted_ips=settings.AXES_PROXY_TRUSTED_IPS,
request_header_order=settings.AXES_META_PRECEDENCE_ORDER,
)
# Resolve using django-ipware from a configuration flag that can be set to False to explicitly disable
# this is added to both enable or disable the branch when ipware is installed in the test environment
if use_ipware is None:
use_ipware = IPWARE_INSTALLED
if use_ipware:
log.debug("Using django-ipware to get client IP address")
return client_ip_address
client_ip_address, _ = ipware.ip.get_client_ip(
request,
proxy_order=settings.AXES_PROXY_ORDER,
proxy_count=settings.AXES_PROXY_COUNT,
proxy_trusted_ips=settings.AXES_PROXY_TRUSTED_IPS,
request_header_order=settings.AXES_META_PRECEDENCE_ORDER,
)
return client_ip_address
log.debug(
"Using request.META.get('REMOTE_ADDR', None) fallback method to get client IP address"
)
return request.META.get("REMOTE_ADDR", None)
def get_client_user_agent(request: HttpRequest) -> str:

View file

@ -5,7 +5,8 @@ Installation
Axes is easy to install from the PyPI package::
$ pip install django-axes
$ pip install django-axes[ipware] # use django-ipware for resolving client IP addresses OR
$ pip install django-axes # implement and configure custom AXES_CLIENT_IP_CALLABLE
After installing the package, the project settings need to be configured.

View file

@ -1,4 +1,5 @@
-e .
django-ipware>=3
coverage==7.2.3
pytest==7.3.1
pytest-cov==4.0.0

View file

@ -36,7 +36,10 @@ setup(
use_scm_version=True,
setup_requires=["setuptools_scm"],
python_requires=">=3.7",
install_requires=["django>=3.2", "django-ipware>=3", "setuptools"],
install_requires=["django>=3.2", "setuptools"],
extras_require={
"ipware": "django-ipware>=3",
},
include_package_data=True,
packages=find_packages(exclude=["tests"]),
classifiers=[

View file

@ -614,6 +614,16 @@ class ClientIpAddressTestCase(AxesTestCase):
with self.assertRaises(TypeError):
get_client_ip_address(HttpRequest())
def test_get_client_ip_address_with_ipware(self):
request = HttpRequest()
request.META["REMOTE_ADDR"] = "127.0.0.2"
self.assertEqual(get_client_ip_address(request, use_ipware=True), "127.0.0.2")
def test_get_client_ip_address_without_ipware(self):
request = HttpRequest()
request.META["REMOTE_ADDR"] = "127.0.0.3"
self.assertEqual(get_client_ip_address(request, use_ipware=False), "127.0.0.3")
class IPWhitelistTestCase(AxesTestCase):
def setUp(self):