From 09f2c077d5e86ad2bed2af32661fc6f23aa5e96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=9A=82=E8=9A=81=E4=BA=91?= Date: Mon, 14 Sep 2026 13:04:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 1 - tests/unit/utils/test_ip.py | 33 +++++++++++ tests/unit/utils/test_ip_trusted_proxy.py | 70 ----------------------- 3 files changed, 33 insertions(+), 71 deletions(-) create mode 100644 tests/unit/utils/test_ip.py delete mode 100644 tests/unit/utils/test_ip_trusted_proxy.py diff --git a/.env b/.env index 527e132..11ec88f 100644 --- a/.env +++ b/.env @@ -137,7 +137,6 @@ CAPTCHA_NOISE_DOTS=30 CORS_ALLOWED_ORIGINS=http://localhost:8001 - # ============================================================ # 邮件配置 # ============================================================ diff --git a/tests/unit/utils/test_ip.py b/tests/unit/utils/test_ip.py new file mode 100644 index 0000000..f898357 --- /dev/null +++ b/tests/unit/utils/test_ip.py @@ -0,0 +1,33 @@ +"""utils/ip.py 客户端IP获取测试。""" +import types + +from utils.ip import get_client_ip + + +def _make_request(client_host, xff=None): + """构造模拟 Request 对象。""" + req = types.SimpleNamespace() + req.client = types.SimpleNamespace(host=client_host) + req.headers = {'X-Forwarded-For': xff} if xff else {} + return req + + +def test_xff_single_ip(): + """XFF 只有一个 IP → 直接返回""" + assert get_client_ip(_make_request('10.0.0.1', xff='1.2.3.4')) == '1.2.3.4' + + +def test_xff_multiple_ips_returns_leftmost(): + """XFF 多个 IP → 取最左侧(真实客户端 IP)""" + assert get_client_ip(_make_request('10.0.0.1', xff='1.2.3.4, 10.0.0.1')) == '1.2.3.4' + + +def test_no_xff_falls_back_to_client_host(): + """无 XFF 头 → 回退到 request.client.host""" + assert get_client_ip(_make_request('203.0.113.9')) == '203.0.113.9' + + +def test_no_client_falls_back_to_localhost(): + """client 为 None → 回退到 127.0.0.1""" + req = types.SimpleNamespace(client=None, headers={}) + assert get_client_ip(req) == '127.0.0.1' diff --git a/tests/unit/utils/test_ip_trusted_proxy.py b/tests/unit/utils/test_ip_trusted_proxy.py deleted file mode 100644 index 8b723bd..0000000 --- a/tests/unit/utils/test_ip_trusted_proxy.py +++ /dev/null @@ -1,70 +0,0 @@ -"""utils/ip.py 可信代理白名单:仅直连对端命中白名单才信任 X-Forwarded-For。""" -import types - -from utils import ip as ip_mod -from utils.ip import get_client_ip - - -def _make_request(client_host, xff=None): - req = types.SimpleNamespace() - req.client = types.SimpleNamespace(host=client_host) - req.headers = {'X-Forwarded-For': xff} if xff else {} - return req - - -def test_proxy_count_zero_ignores_xff(monkeypatch): - # TRUSTED_PROXY_COUNT=0:即使带 XFF 头也不信任,直接返回 TCP 层 IP - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 0) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset({'127.0.0.1'})) - assert get_client_ip(_make_request('203.0.113.9', xff='1.2.3.4')) == '203.0.113.9' - - -def test_proxy_count_positive_but_no_whitelist_is_fail_closed(monkeypatch): - # 配了 TRUSTED_PROXY_COUNT 但未配 TRUSTED_PROXY_IPS → 不信任 XFF(fail-closed) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 1) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset()) - assert get_client_ip(_make_request('203.0.113.9', xff='1.2.3.4')) == '203.0.113.9' - - -def test_peer_not_in_whitelist_rejects_xff(monkeypatch): - # 直连对端不是可信代理(应用被直接暴露,如 Docker bridge 映射端口) - # → 客户端伪造的 XFF 被忽略,返回真实对端 IP - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 1) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset({'10.0.0.1'})) - assert get_client_ip(_make_request('203.0.113.9', xff='1.2.3.4')) == '203.0.113.9' - - -def test_peer_in_whitelist_trusts_xff(monkeypatch): - # 单层代理:XFF 仅含客户端 IP(由可信代理追加),跳过最右侧 1 个代理条目即客户端 - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 1) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset({'10.0.0.1'})) - assert get_client_ip(_make_request('10.0.0.1', xff='1.2.3.4')) == '1.2.3.4' - - -def test_peer_in_whitelist_spoofed_left_entry_ignored(monkeypatch): - # 客户端伪造条目只会出现在 XFF 左侧(由可信代理追加的在其右侧),伪造 IP 被忽略 - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 1) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset({'10.0.0.1'})) - assert get_client_ip(_make_request('10.0.0.1', xff='6.6.6.6, 1.2.3.4')) == '1.2.3.4' - - -def test_peer_in_whitelist_fewer_entries_than_proxies(monkeypatch): - # 代理条目少于层数(部分代理未追加 XFF)→ 回退到最右侧条目,避免拿到代理 IP - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 2) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset({'10.0.0.2'})) - assert get_client_ip(_make_request('10.0.0.2', xff='1.2.3.4')) == '1.2.3.4' - - -def test_peer_in_whitelist_multiple_proxy_layers(monkeypatch): - # 两层代理:XFF=[客户端, 外层代理],最内层可信代理是直连对端(不在 XFF 内), - # 跳过最右侧 2 个代理条目取真实客户端 - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 2) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset({'10.0.0.2'})) - assert get_client_ip(_make_request('10.0.0.2', xff='1.2.3.4, 10.0.0.1')) == '1.2.3.4' - - -def test_no_client_falls_back_to_localhost(monkeypatch): - req = types.SimpleNamespace(client=None, headers={}) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_COUNT', 1) - monkeypatch.setattr(ip_mod, 'TRUSTED_PROXY_IPS', frozenset({'10.0.0.1'})) - assert get_client_ip(req) == '127.0.0.1'