python-ipware 2.0.3__py3-none-any.whl → 2.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python_ipware/__version__.py +1 -1
- python_ipware/python_ipware.py +177 -188
- {python_ipware-2.0.3.dist-info → python_ipware-2.0.5.dist-info}/METADATA +29 -11
- python_ipware-2.0.5.dist-info/RECORD +9 -0
- python_ipware-2.0.3.dist-info/RECORD +0 -9
- {python_ipware-2.0.3.dist-info → python_ipware-2.0.5.dist-info}/LICENSE +0 -0
- {python_ipware-2.0.3.dist-info → python_ipware-2.0.5.dist-info}/WHEEL +0 -0
- {python_ipware-2.0.3.dist-info → python_ipware-2.0.5.dist-info}/top_level.txt +0 -0
python_ipware/__version__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "2.0.
|
|
1
|
+
__version__ = "2.0.5"
|
python_ipware/python_ipware.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ipaddress
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import Dict, List, Optional, Tuple, Union
|
|
5
5
|
|
|
6
6
|
IpAddressType = Union[ipaddress.IPv4Address, ipaddress.IPv6Address]
|
|
7
7
|
OptionalIpAddressType = Optional[IpAddressType]
|
|
@@ -11,125 +11,130 @@ logger = logging.getLogger(__name__)
|
|
|
11
11
|
|
|
12
12
|
class IpWareMeta:
|
|
13
13
|
"""
|
|
14
|
-
A class
|
|
14
|
+
A class to manage metadata extracted from HTTP request headers, primarily for identifying
|
|
15
|
+
the client's IP address behind load balancers and proxies.
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
def __init__(
|
|
18
|
-
self,
|
|
19
|
-
precedence: Optional[Tuple[str, ...]] = None,
|
|
20
|
-
leftmost: bool = True,
|
|
19
|
+
self, precedence: Optional[Tuple[str, ...]] = None, leftmost: bool = True
|
|
21
20
|
) -> None:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
21
|
+
"""
|
|
22
|
+
Initializes the metadata handler with a precedence list of HTTP headers and the position of the client IP.
|
|
23
|
+
|
|
24
|
+
:param precedence: A tuple of header names that define the order to check for a valid IP.
|
|
25
|
+
If not provided, a default list of commonly used headers is applied.
|
|
26
|
+
:param leftmost: A boolean that indicates whether to use the left-most IP in the header
|
|
27
|
+
when multiple IPs are listed (typically the case with proxies).
|
|
28
|
+
"""
|
|
29
|
+
# Default precedence list of headers if none is provided
|
|
30
|
+
if precedence is None:
|
|
31
|
+
precedence = (
|
|
32
|
+
"X_FORWARDED_FOR", # Common header for proxies, load balancers, like AWS ELB. Default to the left-most IP.
|
|
33
|
+
"HTTP_X_FORWARDED_FOR", # Alternative header similar to `X_FORWARDED_FOR`.
|
|
34
|
+
"HTTP_CLIENT_IP", # Header used by some providers like Amazon EC2, Heroku.
|
|
35
|
+
"HTTP_X_REAL_IP", # Header used by some providers like Amazon EC2, Heroku.
|
|
36
|
+
"HTTP_X_FORWARDED", # Used by Squid and similar software.
|
|
37
|
+
"HTTP_X_CLUSTER_CLIENT_IP", # Used by Rackspace LB, Riverbed Stingray.
|
|
38
|
+
"HTTP_FORWARDED_FOR", # Standard header defined by RFC 7239.
|
|
39
|
+
"HTTP_FORWARDED", # Standard header defined by RFC 7239.
|
|
40
|
+
"HTTP_CF_CONNECTING_IP", # Used by CloudFlare.
|
|
41
|
+
"X-CLIENT-IP", # Used by Microsoft Azure.
|
|
42
|
+
"X-REAL-IP", # Commonly used by NGINX.
|
|
43
|
+
"X-CLUSTER-CLIENT-IP", # Used by Rackspace Cloud Load Balancers.
|
|
44
|
+
"X_FORWARDED", # Used by Squid.
|
|
45
|
+
"FORWARDED_FOR", # Standard header defined by RFC 7239.
|
|
46
|
+
"CF-CONNECTING-IP", # Used by CloudFlare.
|
|
47
|
+
"TRUE-CLIENT-IP", # Header for CloudFlare Enterprise.
|
|
48
|
+
"FASTLY-CLIENT-IP", # Used by Fastly, Firebase.
|
|
49
|
+
"FORWARDED", # Standard header defined by RFC 7239.
|
|
50
|
+
"CLIENT-IP", # Used by Akamai, Cloudflare's True-Client-IP, and Fastly's Fastly-Client-IP.
|
|
51
|
+
"REMOTE_ADDR", # The default IP address header (direct connection).
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
self.precedence = precedence
|
|
44
55
|
self.leftmost = leftmost
|
|
45
56
|
|
|
46
57
|
|
|
47
58
|
class IpWareIpAddress:
|
|
48
59
|
"""
|
|
49
|
-
A class
|
|
60
|
+
A class for handling and parsing IP address data from HTTP requests.
|
|
50
61
|
"""
|
|
51
62
|
|
|
52
|
-
def
|
|
53
|
-
"""
|
|
54
|
-
Given an IPv4 address or address:port, it extracts the IP address
|
|
55
|
-
@param ip_str: IP address or address:port
|
|
56
|
-
@return: IP address
|
|
63
|
+
def extract_ipv4(self, ip_address: str) -> str:
|
|
57
64
|
"""
|
|
65
|
+
Extracts the IPv4 address from a given string that may include a port number.
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if ":" in ip_address:
|
|
62
|
-
ip_address = ip_address.split(":")[0]
|
|
63
|
-
return ip_address.strip()
|
|
64
|
-
|
|
65
|
-
return ip_address.strip()
|
|
67
|
+
Args:
|
|
68
|
+
ip_address (str): An IPv4 address possibly including a port (e.g., "192.168.1.1:8080").
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def extract_ipv6_only(self, ip_address: str) -> str:
|
|
70
|
-
"""
|
|
71
|
-
Given an IPv6 address or address:port, it extracts the IP address
|
|
72
|
-
@param ip_str: IP address or address:port
|
|
73
|
-
@return: IP address
|
|
70
|
+
Returns:
|
|
71
|
+
str: The IPv4 address without the port. Returns an empty string if input is None.
|
|
74
72
|
"""
|
|
75
|
-
|
|
76
73
|
if ip_address:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
ip_address = ip_address.split("]:")[0]
|
|
80
|
-
ip_address = ip_address.replace("[", "")
|
|
81
|
-
return ip_address.strip()
|
|
74
|
+
return ip_address.split(":")[0].strip()
|
|
75
|
+
return ""
|
|
82
76
|
|
|
83
|
-
|
|
77
|
+
def extract_ipv6(self, ip_address: str) -> str:
|
|
78
|
+
"""
|
|
79
|
+
Extracts the IPv6 address from a given string that may include a port number.
|
|
84
80
|
|
|
85
|
-
|
|
81
|
+
Args:
|
|
82
|
+
ip_address (str): An IPv6 address possibly including a port (e.g., "[2001:db8::1]:8080").
|
|
86
83
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
ip_str: str,
|
|
90
|
-
) -> OptionalIpAddressType:
|
|
84
|
+
Returns:
|
|
85
|
+
str: The IPv6 address without brackets or port. Returns an empty string if input is None.
|
|
91
86
|
"""
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
87
|
+
if ip_address and "]:" in ip_address:
|
|
88
|
+
return ip_address.split("]:")[0].replace("[", "").strip()
|
|
89
|
+
return ip_address.strip() if ip_address else ""
|
|
90
|
+
|
|
91
|
+
def parse_ip_address(self, ip_str: str):
|
|
95
92
|
"""
|
|
93
|
+
Parses the given IP address string to an IPv4Address or IPv6Address object.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
ip_str (str): An IP address possibly including a port.
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
Returns:
|
|
99
|
+
ipaddress.IPv4Address|ipaddress.IPv6Address|None: The parsed IP object or None if the address is invalid.
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
# First, try to parse as IPv6.
|
|
103
|
+
ipv6 = self.extract_ipv6(ip_str)
|
|
104
|
+
ip = ipaddress.IPv6Address(ipv6)
|
|
105
|
+
return ip.ipv4_mapped or ip
|
|
106
|
+
except ipaddress.AddressValueError:
|
|
99
107
|
try:
|
|
100
|
-
# try to parse as
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
ip = ip.ipv4_mapped or ip
|
|
108
|
+
# If IPv6 parsing fails, try to parse as IPv4.
|
|
109
|
+
ipv4 = self.extract_ipv4(ip_str)
|
|
110
|
+
return ipaddress.IPv4Address(ipv4)
|
|
104
111
|
except ipaddress.AddressValueError:
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
ip = ipaddress.IPv4Address(ipv4)
|
|
109
|
-
except ipaddress.AddressValueError:
|
|
110
|
-
# not a valid IP address, return None
|
|
111
|
-
logger.info("Invalid ip address. {0}".format(ip_str))
|
|
112
|
-
ip = None
|
|
113
|
-
return ip
|
|
112
|
+
# Log error if IP is invalid
|
|
113
|
+
# print(f"Invalid IP address: {ip_str}")
|
|
114
|
+
return None
|
|
114
115
|
|
|
115
116
|
def get_ips_from_string(
|
|
116
117
|
self,
|
|
117
118
|
ip_str: str,
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Given a comma separated list of IP addresses or address:port, it parses the IP addresses
|
|
121
|
-
@param ip_str: comma separated list of IP addresses or address:port
|
|
122
|
-
@return: list of IP addresses of type IPv4Address or IPv6Address
|
|
119
|
+
strict: bool = False,
|
|
120
|
+
) -> Optional[List[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]]:
|
|
123
121
|
"""
|
|
124
|
-
|
|
122
|
+
Parses a comma-separated list of IP addresses, each possibly including a port.
|
|
125
123
|
|
|
124
|
+
Args:
|
|
125
|
+
ip_str (str): A comma-separated list of IP addresses or address:port entries.
|
|
126
|
+
strict (bool): True to bail out on first invalid ip, False to skip invalid ip
|
|
127
|
+
Returns:
|
|
128
|
+
List[ipaddress.IPv4Address|ipaddress.IPv6Address]|None: A list of IP address objects or None if any IP is invalid.
|
|
129
|
+
"""
|
|
130
|
+
ip_list = []
|
|
126
131
|
for ip_address in ip_str.split(","):
|
|
127
|
-
ip = self.
|
|
132
|
+
ip = self.parse_ip_address(ip_address.strip())
|
|
128
133
|
if ip:
|
|
129
134
|
ip_list.append(ip)
|
|
130
135
|
else:
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
if strict:
|
|
137
|
+
return None
|
|
133
138
|
|
|
134
139
|
if not self.leftmost:
|
|
135
140
|
ip_list.reverse()
|
|
@@ -139,125 +144,133 @@ class IpWareIpAddress:
|
|
|
139
144
|
|
|
140
145
|
class IpWareProxy:
|
|
141
146
|
"""
|
|
142
|
-
A class
|
|
147
|
+
A class to handle proxy data from an HTTP request, including validating
|
|
148
|
+
proxy counts and trusted proxy lists.
|
|
143
149
|
"""
|
|
144
150
|
|
|
145
151
|
def __init__(
|
|
146
|
-
self,
|
|
147
|
-
proxy_count: int = 0,
|
|
148
|
-
proxy_list: Optional[List[str]] = None,
|
|
152
|
+
self, proxy_count: Optional[int] = None, proxy_list: Optional[List[str]] = None
|
|
149
153
|
) -> None:
|
|
150
|
-
|
|
151
|
-
|
|
154
|
+
"""
|
|
155
|
+
Initialize the IpWareProxy class with optional proxy count and proxy list.
|
|
152
156
|
|
|
157
|
+
Args:
|
|
158
|
+
proxy_count: The expected number of proxies, can be None if no specific count is enforced.
|
|
159
|
+
proxy_list: A list of partial IP addresses as trusted proxies, can be None.
|
|
160
|
+
"""
|
|
161
|
+
if proxy_count is not None and proxy_count < 0:
|
|
162
|
+
raise ValueError("proxy_count must be non-negative")
|
|
153
163
|
self.proxy_count = proxy_count
|
|
154
|
-
self.proxy_list = self.
|
|
164
|
+
self.proxy_list = self._validate_proxy_list(proxy_list or [])
|
|
155
165
|
|
|
156
|
-
def
|
|
157
|
-
"""
|
|
158
|
-
Checks if the proxy list is a valid list of strings
|
|
159
|
-
@return: proxy list or raises an exception
|
|
166
|
+
def _validate_proxy_list(self, proxy_list: List[str]) -> List[str]:
|
|
160
167
|
"""
|
|
168
|
+
Validates that the proxy list contains only strings.
|
|
161
169
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
if not all(isinstance(x, str) for x in proxy_list):
|
|
165
|
-
raise ValueError("All elements in list must be strings")
|
|
170
|
+
Args:
|
|
171
|
+
proxy_list: A list of proxies.
|
|
166
172
|
|
|
167
|
-
|
|
173
|
+
Returns:
|
|
174
|
+
The proxy list if all items are valid strings.
|
|
168
175
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
) -> bool:
|
|
176
|
+
Raises:
|
|
177
|
+
ValueError: If the proxy list is not a list of strings.
|
|
172
178
|
"""
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
179
|
+
if not all(isinstance(ip, str) for ip in proxy_list):
|
|
180
|
+
raise ValueError("All elements in the proxy list must be strings.")
|
|
181
|
+
return proxy_list
|
|
182
|
+
|
|
183
|
+
def is_proxy_count_valid(self, ip_list: List[str], strict: bool = False) -> bool:
|
|
177
184
|
"""
|
|
178
|
-
|
|
179
|
-
return True
|
|
185
|
+
Validates the proxy count against a list of IP addresses.
|
|
180
186
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
Args:
|
|
188
|
+
ip_list: A list of IP addresses from the request headers.
|
|
189
|
+
strict: If True, the number of proxies must exactly match the proxy_count.
|
|
184
190
|
|
|
191
|
+
Returns:
|
|
192
|
+
True if the proxy count is valid, False otherwise.
|
|
193
|
+
"""
|
|
194
|
+
if self.proxy_count is None:
|
|
195
|
+
return True # No proxy count specified, so always valid.
|
|
196
|
+
|
|
197
|
+
ip_count = len(ip_list)
|
|
185
198
|
if strict:
|
|
186
|
-
|
|
187
|
-
|
|
199
|
+
return ip_count - 1 == self.proxy_count
|
|
200
|
+
# Exact match required, excluding client's own IP.
|
|
188
201
|
|
|
189
|
-
|
|
190
|
-
#
|
|
191
|
-
return ip_count - 1 > self.proxy_count
|
|
202
|
+
return ip_count - 1 >= self.proxy_count
|
|
203
|
+
# Allow more proxies than the count, excluding client's IP.
|
|
192
204
|
|
|
193
205
|
def is_proxy_trusted_list_valid(
|
|
194
|
-
self,
|
|
195
|
-
ip_list: List[IpAddressType],
|
|
196
|
-
strict: bool = False,
|
|
206
|
+
self, ip_list: List[str], strict: bool = False
|
|
197
207
|
) -> bool:
|
|
198
208
|
"""
|
|
199
|
-
Checks if the
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
209
|
+
Checks if all proxies in the incoming list are trusted based on the proxy_list.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
ip_list: A list of IP addresses from the request headers.
|
|
213
|
+
strict: If True, the number of proxies must exactly match the length of proxy_list.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
True if all proxies are trusted, False otherwise.
|
|
203
217
|
"""
|
|
204
218
|
if not self.proxy_list:
|
|
205
|
-
return True
|
|
219
|
+
return True # No specific proxies to trust, so always valid.
|
|
206
220
|
|
|
207
221
|
ip_count = len(ip_list)
|
|
208
222
|
proxy_list_count = len(self.proxy_list)
|
|
209
223
|
|
|
210
|
-
# in strict mode, total ip count must be 1 more than proxy count
|
|
211
224
|
if strict and ip_count - 1 != proxy_list_count:
|
|
212
|
-
return False
|
|
225
|
+
return False # Strict mode: Exact count match required.
|
|
213
226
|
|
|
214
|
-
# total ip count (client + proxies) must be more than proxy count
|
|
215
227
|
if ip_count - 1 < proxy_list_count:
|
|
216
|
-
return False
|
|
217
|
-
|
|
218
|
-
# start from the end, slice the incoming ip list to the same length as the trusted proxy list
|
|
219
|
-
ip_list_slice = ip_list[-proxy_list_count:]
|
|
220
|
-
for index, value in enumerate(ip_list_slice):
|
|
221
|
-
if not str(value).startswith(self.proxy_list[index]):
|
|
222
|
-
return False
|
|
228
|
+
return False # Not enough IPs to match the trusted proxies.
|
|
223
229
|
|
|
224
|
-
#
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
230
|
+
# Verify each proxy against the trusted list by comparing each corresponding element.
|
|
231
|
+
return all(
|
|
232
|
+
str(ip).startswith(trusted_proxy_pattern)
|
|
233
|
+
for ip, trusted_proxy_pattern in zip(
|
|
234
|
+
ip_list[-proxy_list_count:], self.proxy_list
|
|
235
|
+
)
|
|
236
|
+
)
|
|
229
237
|
|
|
230
238
|
|
|
231
239
|
class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
|
|
232
240
|
"""
|
|
233
|
-
A class that makes best effort to determine the client's IP address.
|
|
241
|
+
A class that makes a best effort to determine the client's IP address.
|
|
234
242
|
"""
|
|
235
243
|
|
|
236
244
|
def __init__(
|
|
237
245
|
self,
|
|
238
246
|
precedence: Optional[Tuple[str, ...]] = None,
|
|
239
247
|
leftmost: bool = True,
|
|
240
|
-
proxy_count: int =
|
|
248
|
+
proxy_count: Optional[int] = None,
|
|
241
249
|
proxy_list: Optional[List[str]] = None,
|
|
242
250
|
) -> None:
|
|
243
251
|
IpWareMeta.__init__(self, precedence, leftmost)
|
|
244
|
-
IpWareProxy.__init__(self, proxy_count
|
|
252
|
+
IpWareProxy.__init__(self, proxy_count, proxy_list)
|
|
245
253
|
|
|
246
254
|
def get_meta_value(self, meta: Dict[str, str], key: str) -> str:
|
|
247
255
|
"""
|
|
248
|
-
|
|
249
|
-
@param key:
|
|
250
|
-
@return:
|
|
256
|
+
Returns a cleaned up version of the value for a given key.
|
|
257
|
+
@param key: The key to look up.
|
|
258
|
+
@return: The value of the key or an empty string if the key is not found.
|
|
251
259
|
"""
|
|
252
260
|
meta = meta or {}
|
|
253
261
|
return meta.get(key, meta.get(key.replace("_", "-"), "")).strip()
|
|
254
262
|
|
|
255
263
|
def get_meta_values(self, meta: Dict[str, str]) -> List[str]:
|
|
256
264
|
"""
|
|
257
|
-
|
|
258
|
-
@return:
|
|
265
|
+
Returns a list of cleaned up values for the keys defined in 'precedence'.
|
|
266
|
+
@return: A list of values.
|
|
259
267
|
"""
|
|
260
|
-
|
|
268
|
+
meta_list: List[str] = []
|
|
269
|
+
for key in self.precedence:
|
|
270
|
+
value = self.get_meta_value(meta, key).strip()
|
|
271
|
+
if value:
|
|
272
|
+
meta_list.append(value)
|
|
273
|
+
return meta_list
|
|
261
274
|
|
|
262
275
|
def get_client_ip(
|
|
263
276
|
self,
|
|
@@ -267,15 +280,11 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
|
|
|
267
280
|
"""
|
|
268
281
|
Returns the client's IP address.
|
|
269
282
|
"""
|
|
270
|
-
|
|
271
283
|
loopback_list: List[IpAddressType] = []
|
|
272
284
|
private_list: List[OptionalIpAddressType] = []
|
|
273
285
|
|
|
274
286
|
for ip_str in self.get_meta_values(meta):
|
|
275
|
-
|
|
276
|
-
continue
|
|
277
|
-
|
|
278
|
-
ip_list = self.get_ips_from_string(ip_str)
|
|
287
|
+
ip_list = self.get_ips_from_string(ip_str, strict)
|
|
279
288
|
if not ip_list:
|
|
280
289
|
continue
|
|
281
290
|
|
|
@@ -287,60 +296,40 @@ class IpWare(IpWareMeta, IpWareProxy, IpWareIpAddress):
|
|
|
287
296
|
if not proxy_list_validated:
|
|
288
297
|
continue
|
|
289
298
|
|
|
290
|
-
client_ip, trusted_route = self.get_best_ip(
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
# we found a private ip, save it
|
|
299
|
-
if client_ip is not None and client_ip.is_loopback:
|
|
300
|
-
loopback_list.append(client_ip)
|
|
301
|
-
else:
|
|
302
|
-
# if not global (public) or loopback (local), we treat it asd private
|
|
303
|
-
private_list.append(client_ip)
|
|
299
|
+
client_ip, trusted_route = self.get_best_ip(ip_list)
|
|
300
|
+
if client_ip is not None:
|
|
301
|
+
if client_ip.is_global:
|
|
302
|
+
return client_ip, trusted_route
|
|
303
|
+
if client_ip.is_loopback:
|
|
304
|
+
loopback_list.append(client_ip)
|
|
305
|
+
else:
|
|
306
|
+
private_list.append(client_ip)
|
|
304
307
|
|
|
305
|
-
# we have not been able to locate a global ip
|
|
306
|
-
# it could be the server is running on the intranet
|
|
307
|
-
# we will return the first private ip we found
|
|
308
308
|
if private_list:
|
|
309
309
|
return private_list[0], False
|
|
310
|
-
|
|
311
|
-
# we have not been able to locate a global ip, nor a private ip
|
|
312
|
-
# it could be the server is running on a loopback address serving local requests
|
|
313
310
|
if loopback_list:
|
|
314
311
|
return loopback_list[0], False
|
|
315
|
-
|
|
316
|
-
# we were unable to find any ip address
|
|
317
312
|
return None, False
|
|
318
313
|
|
|
319
314
|
def get_best_ip(
|
|
320
315
|
self,
|
|
321
316
|
ip_list: List[IpAddressType],
|
|
322
|
-
proxy_count_validated: bool = True,
|
|
323
|
-
proxy_list_validated: bool = True,
|
|
324
317
|
) -> Tuple[OptionalIpAddressType, bool]:
|
|
325
318
|
"""
|
|
326
|
-
|
|
319
|
+
Determines the best possible IP address for the client from a list of IP addresses.
|
|
327
320
|
"""
|
|
328
|
-
|
|
329
321
|
if not ip_list:
|
|
330
|
-
logger.warning("Invalid
|
|
322
|
+
logger.warning("Invalid IP list provided.")
|
|
331
323
|
return None, False
|
|
332
324
|
|
|
333
|
-
|
|
334
|
-
if len(self.proxy_list) > 0 and proxy_list_validated:
|
|
325
|
+
if self.proxy_list and len(self.proxy_list) > 0:
|
|
335
326
|
best_client_ip_index = len(self.proxy_list) + 1
|
|
336
327
|
best_client_ip = ip_list[-best_client_ip_index]
|
|
337
328
|
return best_client_ip, True
|
|
338
329
|
|
|
339
|
-
|
|
340
|
-
if self.proxy_count > 0 and proxy_count_validated:
|
|
330
|
+
if self.proxy_count is not None:
|
|
341
331
|
best_client_ip_index = self.proxy_count + 1
|
|
342
332
|
best_client_ip = ip_list[-best_client_ip_index]
|
|
343
333
|
return best_client_ip, True
|
|
344
334
|
|
|
345
|
-
# we don't track proxy related info, so we just return the first ip
|
|
346
335
|
return ip_list[0], False
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: python-ipware
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.5
|
|
4
4
|
Summary: A Python package to retrieve user's IP address
|
|
5
5
|
Author-email: Val Neekman <info@neekware.com>
|
|
6
6
|
License: MIT
|
|
@@ -216,23 +216,41 @@ If your python server is behind a `known` number of proxies, but you deploy on m
|
|
|
216
216
|
You can customize the proxy count by providing your `proxy_count` during initialization when calling `IpWare(proxy_count=2)`.
|
|
217
217
|
|
|
218
218
|
```python
|
|
219
|
-
# In the above scenario, the total number of proxies can be used as a way to filter out unwanted requests.
|
|
220
219
|
from python_ipware import IpWare
|
|
221
220
|
|
|
222
|
-
#
|
|
223
|
-
|
|
221
|
+
# Enforce proxy count
|
|
222
|
+
# proxy_count=0 is valid
|
|
223
|
+
# proxy_count=None to disable proxy_count check
|
|
224
|
+
ipw = IpWare(proxy_count=2)
|
|
224
225
|
|
|
225
|
-
#
|
|
226
|
-
|
|
226
|
+
# Example usage in non-strict mode:
|
|
227
|
+
# X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
|
|
228
|
+
# At least `proxy_count` number of proxies
|
|
229
|
+
ip, trusted_route = ipw.get_client_ip(meta=request.META)
|
|
227
230
|
|
|
231
|
+
# Example usage in strict mode:
|
|
232
|
+
# X-Forwarded-For format: <client>, <proxy1>, <proxy2>
|
|
233
|
+
# Exact `proxy_count` number of proxies
|
|
234
|
+
ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
|
|
235
|
+
```
|
|
228
236
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
ip, trusted_route = ipw.get_client_ip(meta=request.META)
|
|
237
|
+
### Proxy Count & Trusted Proxy List Combo
|
|
238
|
+
In this example, we utilize the total number of proxies as a method to filter out unwanted requests while verifying the trust proxies.
|
|
232
239
|
|
|
240
|
+
```python
|
|
241
|
+
from python_ipware import IpWare
|
|
233
242
|
|
|
234
|
-
#
|
|
235
|
-
|
|
243
|
+
# Enforce both proxy count and trusted proxies
|
|
244
|
+
ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
|
|
245
|
+
|
|
246
|
+
# Example usage in non-strict mode:
|
|
247
|
+
# X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
|
|
248
|
+
# At least `proxy_count` number of proxies
|
|
249
|
+
ip, trusted_route = ipw.get_client_ip(meta=request.META)
|
|
250
|
+
|
|
251
|
+
# Example usage in strict mode:
|
|
252
|
+
# X-Forwarded-For format: <client>, <proxy1>
|
|
253
|
+
# Exact `proxy_count` number of proxies
|
|
236
254
|
ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
|
|
237
255
|
```
|
|
238
256
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
python_ipware/__init__.py,sha256=TCZx4mt5Gxr2D_zNU6j0ynopdsAN_v4xeiTWz1OXSpY,87
|
|
2
|
+
python_ipware/__version__.py,sha256=xEb7Z4b8xalXXExBg42XPAhbJKniHzcsEPjp-6S3ppg,22
|
|
3
|
+
python_ipware/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
python_ipware/python_ipware.py,sha256=IotMZQIGSOu2XCbmdYH4WuhBvXm-lJVJHe3FXylSMvc,12725
|
|
5
|
+
python_ipware-2.0.5.dist-info/LICENSE,sha256=MLpNxpqfTc4TLdcDk3x6k7Vz4lJGBNLV-SxQZlFMDU8,1103
|
|
6
|
+
python_ipware-2.0.5.dist-info/METADATA,sha256=3Lsxn8HubcN0KnuwggZLcDu5h67rZc5YOn5ad-YNNdY,15420
|
|
7
|
+
python_ipware-2.0.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
8
|
+
python_ipware-2.0.5.dist-info/top_level.txt,sha256=URE4dvjpReJtRHyQpJqoBow5EWt_RqVXFn85B9Tq5Xw,14
|
|
9
|
+
python_ipware-2.0.5.dist-info/RECORD,,
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
python_ipware/__init__.py,sha256=TCZx4mt5Gxr2D_zNU6j0ynopdsAN_v4xeiTWz1OXSpY,87
|
|
2
|
-
python_ipware/__version__.py,sha256=_GEKEa6BYjBV34SZkSlAR87aCM5Y9G0aSI0LXL52iJg,22
|
|
3
|
-
python_ipware/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
python_ipware/python_ipware.py,sha256=kAoCyLbJLxN-zocHB9Ps-AdZYWMUQ8KasHscg6Qcc3Q,12119
|
|
5
|
-
python_ipware-2.0.3.dist-info/LICENSE,sha256=MLpNxpqfTc4TLdcDk3x6k7Vz4lJGBNLV-SxQZlFMDU8,1103
|
|
6
|
-
python_ipware-2.0.3.dist-info/METADATA,sha256=cwAXk8j8o_cfJUfZT9tQ3sCwjRa6OQRVVX89juDwJgA,14857
|
|
7
|
-
python_ipware-2.0.3.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
8
|
-
python_ipware-2.0.3.dist-info/top_level.txt,sha256=URE4dvjpReJtRHyQpJqoBow5EWt_RqVXFn85B9Tq5Xw,14
|
|
9
|
-
python_ipware-2.0.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|