Skip to content

Commit 19d9a61

Browse files
authored
Use python-ipware under the hood starting at version 6.0.0. (#106)
Use python-ipware under the hood starting at version 6.0.0. (#106)
1 parent e0aaacd commit 19d9a61

File tree

17 files changed

+101
-909
lines changed

17 files changed

+101
-909
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ jobs:
4646
- name: Coveralls
4747
run: coveralls --service=github
4848
env:
49-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"python.linting.enabled": false,
33
"cSpell.words": [
44
"cmdclass",
5+
"Fastly",
56
"getattr",
67
"ipware",
78
"multicast",

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# 6.0.0
2+
3+
Enhancement: (breaking changes)
4+
5+
- Use python-ipware under the hood
6+
- Minor behavior changes as python-ipware is more accurate
7+
- Use 5.0.2 if you need the old behavior
8+
19
# 5.0.2
210

311
Enhancement:

README.md

Lines changed: 24 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,12 @@
88

99
# Alternative package
1010

11-
If you prefer a python only version that does not integrate with Django, you can use the [python-ipware](https://github.com/un33k/python-ipware) package instead. `django-ipware` will use python-ipware in the future.
11+
If you prefer a python only version that does not integrate with Django directly, but allows for more flexibility and advanced features, you can use the [python-ipware](https://github.com/un33k/python-ipware) package instead. `django-ipware` is a wrapper using [python-ipware](https://github.com/un33k/python-ipware) under the hood staring from version `6.0.0`.
1212

1313
# Overview
1414

1515
**Best attempt** to get client's IP address while keeping it **DRY**.
1616

17-
# Alternative package
18-
19-
If you prefer a python version, you can use the [python-ipware](https://github.com/un33k/python-ipware) package instead. `python-ipware` is a newer package, with more advanced features. While this a Django specific package, `python-ipware` can be used with Django, Flask, etc.
20-
2117
# Notice
2218

2319
There is no perfect `out-of-the-box` solution against fake IP addresses, aka `IP Address Spoofing`.
@@ -56,30 +52,39 @@ Please use ipware `ONLY` as a complement to your `firewall` security measures!
5652
# The client's IP address is publicly routable on the Internet
5753
else:
5854
# The client's IP address is private
59-
60-
# Order of precedence is (Public, Private, Loopback, None)
6155
```
6256

6357
# Advanced users:
6458

6559
- ### Precedence Order
6660

67-
The default meta precedence order is top to bottom. However, you may customize the order
61+
The default meta precedence order is top to bottom. You may customize the order
6862
by providing your own `IPWARE_META_PRECEDENCE_ORDER` by adding it to your project's settings.py
6963

7064
```python
71-
# The default meta precedence order
65+
# The default meta precedence order (update as needed)
7266
IPWARE_META_PRECEDENCE_ORDER = (
73-
'HTTP_X_FORWARDED_FOR', 'X_FORWARDED_FOR', # <client>, <proxy1>, <proxy2>
74-
'HTTP_CLIENT_IP',
75-
'HTTP_X_REAL_IP',
76-
'HTTP_X_FORWARDED',
77-
'HTTP_X_CLUSTER_CLIENT_IP',
78-
'HTTP_FORWARDED_FOR',
79-
'HTTP_FORWARDED',
80-
'HTTP_VIA',
81-
'REMOTE_ADDR',
82-
)
67+
"X_FORWARDED_FOR", # AWS ELB (default client is `left-most` [`<client>, <proxy1>, <proxy2>`])
68+
"HTTP_X_FORWARDED_FOR", # Similar to X_FORWARDED_TO
69+
"HTTP_CLIENT_IP", # Standard headers used by providers such as Amazon EC2, Heroku etc.
70+
"HTTP_X_REAL_IP", # Standard headers used by providers such as Amazon EC2, Heroku etc.
71+
"HTTP_X_FORWARDED", # Squid and others
72+
"HTTP_X_CLUSTER_CLIENT_IP", # Rackspace LB and Riverbed Stingray
73+
"HTTP_FORWARDED_FOR", # RFC 7239
74+
"HTTP_FORWARDED", # RFC 7239
75+
"HTTP_VIA", # Squid and others
76+
"X-CLIENT-IP", # Microsoft Azure
77+
"X-REAL-IP", # NGINX
78+
"X-CLUSTER-CLIENT-IP", # Rackspace Cloud Load Balancers
79+
"X_FORWARDED", # Squid
80+
"FORWARDED_FOR", # RFC 7239
81+
"CF-CONNECTING-IP", # CloudFlare
82+
"TRUE-CLIENT-IP", # CloudFlare Enterprise,
83+
"FASTLY-CLIENT-IP", # Firebase, Fastly
84+
"FORWARDED", # RFC 7239
85+
"CLIENT-IP", # Akamai and Cloudflare: True-Client-IP and Fastly: Fastly-Client-IP
86+
"REMOTE_ADDR", # Default
87+
)
8388
```
8489

8590
**Alternatively**, you can provide your custom _request header meta precedence order_ when calling `get_client_ip()`.
@@ -89,57 +94,6 @@ get_client_ip(request, request_header_order=['X_FORWARDED_FOR'])
8994
get_client_ip(request, request_header_order=['X_FORWARDED_FOR', 'HTTP_X_FORWARDED_FOR'])
9095
```
9196

92-
### Private Prefixes
93-
94-
You may customize the prefixes to indicate an IP address is private. This is done by adding your
95-
own `IPWARE_PRIVATE_IP_PREFIX` to your project's settings.py. IP addresses matching the following
96-
prefixes are considered _private_ & are **not** publicly routable.
97-
98-
```python
99-
# The default private IP prefixes
100-
IPWARE_PRIVATE_IP_PREFIX = getattr(settings,
101-
'IPWARE_PRIVATE_IP_PREFIX', (
102-
'0.', # messages to software
103-
'10.', # class A private block
104-
'100.64.', '100.65.', '100.66.', '100.67.', '100.68.', '100.69.',
105-
'100.70.', '100.71.', '100.72.', '100.73.', '100.74.', '100.75.',
106-
'100.76.', '100.77.', '100.78.', '100.79.', '100.80.', '100.81.',
107-
'100.82.', '100.83.', '100.84.', '100.85.', '100.86.', '100.87.',
108-
'100.88.', '100.89.', '100.90.', '100.91.', '100.92.', '100.93.',
109-
'100.94.', '100.95.', '100.96.', '100.97.', '100.98.', '100.99.',
110-
'100.100.', '100.101.', '100.102.', '100.103.', '100.104.', '100.105.',
111-
'100.106.', '100.107.', '100.108.', '100.109.', '100.110.', '100.111.',
112-
'100.112.', '100.113.', '100.114.', '100.115.', '100.116.', '100.117.',
113-
'100.118.', '100.119.', '100.120.', '100.121.', '100.122.', '100.123.',
114-
'100.124.', '100.125.', '100.126.', '100.127.', # carrier-grade NAT
115-
'169.254.', # link-local block
116-
'172.16.', '172.17.', '172.18.', '172.19.',
117-
'172.20.', '172.21.', '172.22.', '172.23.',
118-
'172.24.', '172.25.', '172.26.', '172.27.',
119-
'172.28.', '172.29.', '172.30.', '172.31.', # class B private blocks
120-
'192.0.0.', # reserved for IANA special purpose address registry
121-
'192.0.2.', # reserved for documentation and example code
122-
'192.168.', # class C private block
123-
'198.18.', '198.19.', # reserved for inter-network communications between two separate subnets
124-
'198.51.100.', # reserved for documentation and example code
125-
'203.0.113.', # reserved for documentation and example code
126-
'224.', '225.', '226.', '227.', '228.', '229.', '230.', '231.', '232.',
127-
'233.', '234.', '235.', '236.', '237.', '238.', '239.', # multicast
128-
'240.', '241.', '242.', '243.', '244.', '245.', '246.', '247.', '248.',
129-
'249.', '250.', '251.', '252.', '253.', '254.', '255.', # reserved
130-
'::', # Unspecified address
131-
'::ffff:', '2001:10:', '2001:20:', # messages to software
132-
'2001::', # TEREDO
133-
'2001:2::', # benchmarking
134-
'2001:db8:', # reserved for documentation and example code
135-
'fc', # IPv6 ULA (RFC4193) - NOTE: Reserved for future use, not currently in widespread use.
136-
'fd', # IPv6 ULA (RFC4193) - Mainly used for private network addressing
137-
'fe80:', # link-local unicast
138-
'ff00:', # IPv6 multicast
139-
)
140-
)
141-
```
142-
14397
### Trusted Proxies
14498

14599
If your Django server is behind one or more known proxy server(s), you can filter out unwanted requests

format.sh

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
11
#!/bin/bash
22

3-
# Ignoring autogenerated files
4-
# -- Migration directories
5-
# Ignoring error codes
6-
# -- E128 continuation line under-indented for visual indent
7-
# -- E225 missing whitespace around operator
8-
# -- E501 line too long
9-
10-
pycodestyle --exclude=migrations,tests --ignore=E128,E225,E241,E501 .
11-
flake8 --exclude=migrations,tests --ignore=E128,E225,E241,E501 .
3+
ruff .

ipware/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
__url__ = 'https://github.com/un33k/django-ipware'
66
__license__ = 'MIT'
77
__copyright__ = 'Copyright 2020 Val Neekman @ Neekware Inc.'
8-
__version__ = '5.0.2'
8+
__version__ = '6.0.0'

ipware/defaults.py

Lines changed: 0 additions & 78 deletions
This file was deleted.

ipware/ip.py

Lines changed: 17 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
from . import defaults as defs
2-
from . import utils as util
1+
from django.conf import settings
2+
from python_ipware import IpWare
33

44

55
def get_client_ip(
@@ -9,53 +9,24 @@ def get_client_ip(
99
proxy_trusted_ips=None,
1010
request_header_order=None,
1111
):
12-
client_ip = None
13-
routable = False
14-
15-
if proxy_count is None:
16-
proxy_count = -1
17-
18-
if proxy_trusted_ips is None:
19-
proxy_trusted_ips = []
20-
21-
if request_header_order is None:
22-
request_header_order = defs.IPWARE_META_PRECEDENCE_ORDER
12+
leftmost = proxy_order == 'left-most'
13+
proxy_count = proxy_count if proxy_count is not None else 0
14+
proxy_list = proxy_trusted_ips if proxy_trusted_ips is not None else []
15+
request_header_order = getattr(settings, 'IPWARE_META_PRECEDENCE_ORDER', request_header_order)
2316

24-
for key in request_header_order:
25-
value = util.get_request_meta(request, key)
26-
if value:
27-
ips, ip_count = util.get_ips_from_string(value)
17+
# Instantiate IpWare with values from the function arguments
18+
ipw = IpWare(precedence=request_header_order,
19+
leftmost=leftmost,
20+
proxy_count=proxy_count,
21+
proxy_list=proxy_list)
2822

29-
if ip_count < 1:
30-
# we are expecting at least one IP address to process
31-
continue
23+
ip, _ = ipw.get_client_ip(request.META, True)
3224

33-
if proxy_count == 0 and ip_count > 1:
34-
# we are not expecting requests via any proxies
35-
continue
36-
37-
if proxy_count > 0 and proxy_count != ip_count - 1:
38-
# we are expecting requests via `proxy_count` number of proxies
39-
continue
40-
41-
if proxy_trusted_ips and ip_count < 2:
42-
# we are expecting requests via at least one trusted proxy
43-
continue
44-
45-
if proxy_order == 'right-most' and ip_count > 1:
46-
# we are expecting requests via proxies to be custom as per `<proxy2>, <proxy1>, <client>`
47-
ips.reverse()
25+
client_ip = None
26+
routable = False
4827

49-
if proxy_trusted_ips:
50-
for proxy in proxy_trusted_ips:
51-
# right most proxy is the most reliable proxy that talks to the django server
52-
if ips[-1].startswith(proxy):
53-
client_ip, routable = util.get_ip_info(ips[0])
54-
if client_ip and routable:
55-
return client_ip, routable
56-
else:
57-
client_ip, routable = util.get_ip_info(util.get_best_ip(client_ip, ips[0]))
58-
if client_ip and routable:
59-
return client_ip, routable
28+
if ip:
29+
client_ip = str(ip)
30+
routable = ip.is_global
6031

6132
return client_ip, routable

ipware/py.typed

Whitespace-only changes.

0 commit comments

Comments
 (0)