Skip to content

Commit bd56bcd

Browse files
authored
Allow for proxy_count of exact 0 (#19)
Allow for proxy_count of exact 0
1 parent ddfde3c commit bd56bcd

File tree

7 files changed

+154
-59
lines changed

7 files changed

+154
-59
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
python -m pip install --upgrade pip
3535
pip install -e .[dev]
3636
- name: Run ruff
37-
run: ruff .
37+
run: ruff check .
3838
- name: Run test
3939
run: coverage run --source=python_ipware -m unittest discover
4040
- name: Coveralls

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 2.0.4
2+
3+
Enhance:
4+
- Added `proxy_count=0` as an option (@FraKraBa)
5+
16
## 2.0.3
27

38
Enhance:

README.md

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,23 +185,41 @@ If your python server is behind a `known` number of proxies, but you deploy on m
185185
You can customize the proxy count by providing your `proxy_count` during initialization when calling `IpWare(proxy_count=2)`.
186186

187187
```python
188-
# In the above scenario, the total number of proxies can be used as a way to filter out unwanted requests.
189188
from python_ipware import IpWare
190189

191-
# enforce proxy count
192-
ipw = IpWare(proxy_count=1)
190+
# Enforce proxy count
191+
# proxy_count=0 is valid
192+
# proxy_count=None to disable proxy_count check
193+
ipw = IpWare(proxy_count=2)
193194

194-
# enforce proxy count and trusted proxies
195-
ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
195+
# Example usage in non-strict mode:
196+
# X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
197+
# At least `proxy_count` number of proxies
198+
ip, trusted_route = ipw.get_client_ip(meta=request.META)
196199

200+
# Example usage in strict mode:
201+
# X-Forwarded-For format: <client>, <proxy1>, <proxy2>
202+
# Exact `proxy_count` number of proxies
203+
ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
204+
```
197205

198-
# usage: non-strict mode (X-Forwarded-For: <fake>, <client>, <proxy1>, <proxy2>)
199-
# total number of ip addresses are greater than the total count
200-
ip, trusted_route = ipw.get_client_ip(meta=request.META)
206+
### Proxy Count & Trusted Proxy List Combo
207+
In this example, we utilize the total number of proxies as a method to filter out unwanted requests while verifying the trust proxies.
201208

209+
```python
210+
from python_ipware import IpWare
202211

203-
# usage: strict mode (X-Forwarded-For: <client>, <proxy1>, <proxy2>)
204-
# total number of ip addresses are exactly equal to client ip + proxy_count
212+
# Enforce both proxy count and trusted proxies
213+
ipw = IpWare(proxy_count=1, proxy_list=["198.84.193.157"])
214+
215+
# Example usage in non-strict mode:
216+
# X-Forwarded-For format: <fake>, <client>, <proxy1>, <proxy2>
217+
# At least `proxy_count` number of proxies
218+
ip, trusted_route = ipw.get_client_ip(meta=request.META)
219+
220+
# Example usage in strict mode:
221+
# X-Forwarded-For format: <client>, <proxy1>
222+
# Exact `proxy_count` number of proxies
205223
ip, trusted_route = ipw.get_client_ip(meta=request.META, strict=True)
206224
```
207225

format.sh

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

3-
ruff .
3+
ruff check .

python_ipware/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "2.0.3"
1+
__version__ = "2.0.4"

python_ipware/python_ipware.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,9 @@ class IpWareProxy:
144144

145145
def __init__(
146146
self,
147-
proxy_count: int = 0,
147+
proxy_count: Optional[int] = None,
148148
proxy_list: Optional[List[str]] = None,
149149
) -> None:
150-
if proxy_count is None or proxy_count < 0:
151-
raise ValueError("proxy_count must be a positive integer")
152-
153150
self.proxy_count = proxy_count
154151
self.proxy_list = self._is_valid_proxy_trusted_list(proxy_list or [])
155152

@@ -172,15 +169,14 @@ def is_proxy_count_valid(
172169
"""
173170
Checks if the proxy count is valid
174171
@param ip_list: list of ip addresses
175-
@param strict: if True, we must have exactly proxy_count proxies
172+
@param strict: if True, we must have exactly proxy_count proxies, including `0` proxies
176173
@return: True if the proxy count is valid, False otherwise
177174
"""
178-
if self.proxy_count < 1:
175+
# No proxy count check is required
176+
if self.proxy_count is None:
179177
return True
180178

181179
ip_count: int = len(ip_list)
182-
if ip_count < 1:
183-
return False
184180

185181
if strict:
186182
# our first proxy takes the last ip address and treats it as client ip
@@ -221,10 +217,6 @@ def is_proxy_trusted_list_valid(
221217
if not str(value).startswith(self.proxy_list[index]):
222218
return False
223219

224-
# now all we need is to return the first ip in the list that is not in the trusted proxy list
225-
# best_client_ip_index = proxy_list_count + 1
226-
# best_client_ip = ip_list[-best_client_ip_index]
227-
228220
return True
229221

230222

@@ -237,11 +229,11 @@ def __init__(
237229
self,
238230
precedence: Optional[Tuple[str, ...]] = None,
239231
leftmost: bool = True,
240-
proxy_count: int = 0,
232+
proxy_count: Optional[int] = None,
241233
proxy_list: Optional[List[str]] = None,
242234
) -> None:
243235
IpWareMeta.__init__(self, precedence, leftmost)
244-
IpWareProxy.__init__(self, proxy_count or 0, proxy_list or [])
236+
IpWareProxy.__init__(self, proxy_count, proxy_list)
245237

246238
def get_meta_value(self, meta: Dict[str, str], key: str) -> str:
247239
"""
@@ -257,7 +249,13 @@ def get_meta_values(self, meta: Dict[str, str]) -> List[str]:
257249
Given a list of keys, it returns a list of cleaned up values
258250
@return: a list of values
259251
"""
260-
return [self.get_meta_value(meta, key) for key in self.precedence]
252+
meta_list: List[str] = []
253+
for key in self.precedence:
254+
value = self.get_meta_value(meta, key).strip()
255+
if value:
256+
meta_list.append(value)
257+
258+
return meta_list
261259

262260
def get_client_ip(
263261
self,
@@ -272,8 +270,6 @@ def get_client_ip(
272270
private_list: List[OptionalIpAddressType] = []
273271

274272
for ip_str in self.get_meta_values(meta):
275-
if not ip_str:
276-
continue
277273

278274
ip_list = self.get_ips_from_string(ip_str)
279275
if not ip_list:
@@ -337,7 +333,11 @@ def get_best_ip(
337333
return best_client_ip, True
338334

339335
# the incoming ips match our proxy count
340-
if self.proxy_count > 0 and proxy_count_validated:
336+
if (
337+
self.proxy_count is not None
338+
and self.proxy_count > 0
339+
and proxy_count_validated
340+
):
341341
best_client_ip_index = self.proxy_count + 1
342342
best_client_ip = ip_list[-best_client_ip_index]
343343
return best_client_ip, True

tests/tests_ipv4.py

Lines changed: 100 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -175,39 +175,69 @@ def test_proxy_order_right_most(self):
175175
class TestIPv4ProxyCount(unittest.TestCase):
176176
"""IPv4 Proxy Count Test"""
177177

178-
def setUp(self):
179-
self.ipware = IpWare(proxy_count=1)
180-
181-
def tearDown(self):
182-
self.ipware = None
183-
184-
def test_singleton_proxy_count(self):
178+
def test_proxy_count_one_missing_proxy_fail(self):
179+
ipware = IpWare(proxy_count=1)
185180
meta = {
186181
"HTTP_X_FORWARDED_FOR": "177.139.233.139",
187182
}
188-
r = self.ipware.get_client_ip(meta)
183+
r = ipware.get_client_ip(meta)
184+
189185
self.assertEqual(r, (None, False))
190186

191-
def test_singleton_proxy_count_private(self):
187+
def test_proxy_count_one_at_least_one_proxy_pass(self):
188+
ipware = IpWare(proxy_count=1)
192189
meta = {
193-
"HTTP_X_FORWARDED_FOR": "10.0.0.0",
194-
"HTTP_X_REAL_IP": "177.139.233.139",
190+
"HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
195191
}
196-
r = self.ipware.get_client_ip(meta)
192+
r = ipware.get_client_ip(meta)
193+
self.assertEqual(r, (IPv4Address("198.84.193.157"), True))
194+
195+
def test_proxy_count_one_exactly_one_proxy_fail(self):
196+
ipware = IpWare(proxy_count=1)
197+
meta = {
198+
"HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
199+
}
200+
r = ipware.get_client_ip(meta, strict=True)
197201
self.assertEqual(r, (None, False))
198202

199-
def test_proxy_count_relax(self):
203+
def test_proxy_count_one_exactly_one_proxy_pass(self):
204+
ipware = IpWare(proxy_count=1)
205+
meta = {
206+
"HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157",
207+
}
208+
r = ipware.get_client_ip(meta, strict=True)
209+
self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
210+
211+
def test_proxy_count_one_dont_care_proxy_pass(self):
212+
ipware = IpWare()
200213
meta = {
201214
"HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
202215
}
203-
r = self.ipware.get_client_ip(meta, strict=False)
204-
self.assertEqual(r, (IPv4Address("198.84.193.157"), True))
216+
r = ipware.get_client_ip(meta)
217+
self.assertEqual(r, (IPv4Address("177.139.233.139"), False))
205218

206-
def test_proxy_count_strict(self):
219+
def test_proxy_count_zero_dont_care_proxy_pass(self):
220+
ipware = IpWare(proxy_count=0)
207221
meta = {
208-
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.158",
222+
"HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
209223
}
210-
r = self.ipware.get_client_ip(meta, strict=True)
224+
r = ipware.get_client_ip(meta)
225+
self.assertEqual(r, (IPv4Address("177.139.233.139"), False))
226+
227+
def test_proxy_count_zero_exact_zero_proxy_pass(self):
228+
ipware = IpWare(proxy_count=0)
229+
meta = {
230+
"HTTP_X_FORWARDED_FOR": "177.139.233.139",
231+
}
232+
r = ipware.get_client_ip(meta, strict=True)
233+
self.assertEqual(r, (IPv4Address("177.139.233.139"), False))
234+
235+
def test_proxy_count_zero_exact_zero_proxy_fail(self):
236+
ipware = IpWare(proxy_count=0)
237+
meta = {
238+
"HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
239+
}
240+
r = ipware.get_client_ip(meta, strict=True)
211241
self.assertEqual(r, (None, False))
212242

213243

@@ -245,26 +275,68 @@ def test_proxy_list_success(self):
245275
class TestIPv4ProxyCountProxyList(unittest.TestCase):
246276
"""IPv4 Proxy Count Test"""
247277

248-
def setUp(self):
249-
self.ipware = IpWare(
250-
proxy_count=2, proxy_list=["198.84.193.157", "198.84.193.158"]
251-
)
278+
def test_proxy_list_relax(self):
279+
ipware = IpWare(proxy_list=["198.84.193.157", "198.84.193.158"])
280+
meta = {
281+
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
282+
}
283+
r = ipware.get_client_ip(meta)
284+
self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
252285

253-
def tearDown(self):
254-
self.ipware = None
286+
def test_proxy_list_strict_pass(self):
287+
ipware = IpWare(proxy_list=["198.84.193.157", "198.84.193.158"])
288+
meta = {
289+
"HTTP_X_FORWARDED_FOR": "177.139.233.139, 198.84.193.157, 198.84.193.158",
290+
}
291+
r = ipware.get_client_ip(meta, strict=True)
292+
self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
255293

256-
def test_proxy_list_relax(self):
294+
def test_proxy_list_strict_fail(self):
295+
ipware = IpWare(proxy_list=["198.84.193.157", "198.84.193.158"])
257296
meta = {
258297
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
259298
}
260-
r = self.ipware.get_client_ip(meta)
299+
r = ipware.get_client_ip(meta, strict=True)
300+
self.assertEqual(r, (None, False))
301+
302+
def test_proxy_list_relax_exact_pass(self):
303+
ipware = IpWare(proxy_count=2, proxy_list=["198.84.193.157", "198.84.193.158"])
304+
meta = {
305+
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
306+
}
307+
r = ipware.get_client_ip(meta)
261308
self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
262309

263-
def test_proxy_list_strict(self):
310+
def test_proxy_list_relax_count_under_pass(self):
311+
ipware = IpWare(proxy_count=1, proxy_list=["198.84.193.157", "198.84.193.158"])
264312
meta = {
265313
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
266314
}
267-
r = self.ipware.get_client_ip(meta, strict=True)
315+
r = ipware.get_client_ip(meta)
316+
self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
317+
318+
def test_proxy_list_relax_count_over_pass(self):
319+
ipware = IpWare(proxy_count=5, proxy_list=["198.84.193.157", "198.84.193.158"])
320+
meta = {
321+
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
322+
}
323+
r = ipware.get_client_ip(meta)
324+
self.assertEqual(r, (None, False))
325+
326+
def test_proxy_list_count_exact_pass(self):
327+
ipware = IpWare(proxy_count=2, proxy_list=["198.84.193.157", "198.84.193.158"])
328+
meta = {
329+
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
330+
}
331+
r = ipware.get_client_ip(meta)
332+
self.assertEqual(r, (IPv4Address("177.139.233.139"), True))
333+
334+
def test_proxy_list_count_exact_fail(self):
335+
ipware = IpWare(proxy_count=4, proxy_list=["198.84.193.157", "198.84.193.158"])
336+
meta = {
337+
"HTTP_X_FORWARDED_FOR": "177.139.233.138, 177.139.233.139, 198.84.193.157, 198.84.193.158",
338+
}
339+
r = ipware.get_client_ip(meta)
268340
self.assertEqual(r, (None, False))
269341

270342

0 commit comments

Comments
 (0)