mirror of
https://github.com/sabnzbd/sabnzbd.git
synced 2025-12-24 00:00:12 -05:00
227 lines
14 KiB
Python
227 lines
14 KiB
Python
#!/usr/bin/python3 -OO
|
|
# Copyright 2007-2025 by The SABnzbd-Team (sabnzbd.org)
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
"""
|
|
tests.test_interface - Testing functions in interface.py
|
|
"""
|
|
import cherrypy
|
|
|
|
from sabnzbd import interface
|
|
from sabnzbd.misc import is_local_addr, is_loopback_addr
|
|
|
|
from tests.testhelper import *
|
|
|
|
|
|
class TestInterfaceFunctions:
|
|
@pytest.mark.parametrize(
|
|
"remote_ip, local_ranges, xff_header, result_with_xff",
|
|
[
|
|
("10.11.12.13", None, None, True),
|
|
("10.11.12.13", None, "127.0.0.1", True),
|
|
("10.11.12.13", None, "127.1.2.3", True),
|
|
("10.11.12.13", None, "127.0.0.1:8080", False), # Port number in XFF
|
|
("10.11.12.13", None, "::1", True),
|
|
("10.11.12.13", None, "[::1]", True),
|
|
("10.11.12.13", None, "[::1]:8080", False), # Port number in XFF
|
|
("10.11.12.13", None, "localhost", False), # Hostname in XFF
|
|
("10.11.12.13", None, "example.org", False), # Hostname in XFF
|
|
("10.11.12.13", None, "192.168.1.1", True),
|
|
("10.11.12.13", None, "10.11.12.99", True),
|
|
("10.11.12.13", None, "8.7.6.5", False), # XFF IP isn't local
|
|
("10.11.12.13", None, "192.168.1.1, 10.11.12.13", True),
|
|
("10.11.12.13", None, "192.168.1.1, 10.11.12.13, 9.8.7.6", False), # Last XFF IP isn't local
|
|
("10.11.12.13", None, "192.168.1.1, 10.11.12.13, ::1", True),
|
|
("10.11.12.13", None, "192.168.1.1, 10.11.12.13, sabrules.example.org", False), # Hostname in XFF
|
|
("10.11.12.13", "192.168.1.0/24", None, False), # Remote IP not part of local ranges
|
|
("10.11.12.13", "192.168.1.0/24", "192.168.1.23", False),
|
|
("10.11.12.13", "192.168.1.0/24", "192.168.1.23, 10.11.12.1", False),
|
|
("10.11.12.13", "192.168.1.0/24, 10.0.0.0/8", "192.168.1.23", True),
|
|
("10.11.12.13", "192.168.2.0/24, 10.0.0.0/8", "192.168.1.23", False),
|
|
("10.11.12.13", "192.168.1.0/24, 10.0.0.0/24", "192.168.1.23", False),
|
|
("10.11.12.13", "10.11.12.0/24", "192.168.1.23", False),
|
|
("10.11.12.13", "2001:ffff::/64", None, False),
|
|
("10.11.12.13", "2001:ffff::/64, 192.168.1.0/24", None, False),
|
|
("13.12.11.10", None, None, False), # Public remote IP doesn't have access, XFF ignored altogether
|
|
("13.12.11.10", None, "127.0.0.1", False),
|
|
("13.12.11.10", None, "127.1.2.3", False),
|
|
("13.12.11.10", None, "::1", False),
|
|
("13.12.11.10", None, "[::1]", False),
|
|
("13.12.11.10", None, "localhost", False),
|
|
("13.12.11.10", None, "192.168.1.1", False),
|
|
("13.12.11.10", None, "192.168.1.1, 13.12.11.10", False),
|
|
("13.12.11.10", None, "192.168.1.1, 13.12.11.10, ::1", False),
|
|
("13.12.11.10", None, "2001::/16", False),
|
|
("13.12.11.10", None, "2001::/16, 13.12.11.10", False),
|
|
("13.12.11.10", None, "2001::/16, 13.0.0.0/9", False),
|
|
("13.12.11.10", "13.12.11.10", None, True), # Local ranges include a public IP
|
|
("13.12.11.10", "13.12.11.10, 192.168.255.0/24", None, True),
|
|
("13.12.11.10", "13.12.11.10", "192.168.1.1", False), # XFF not in local ranges
|
|
("13.12.11.10", "13.12.11.10, 192.168.255.0/24", "192.168.1.1", False),
|
|
("13.12.11.10", "13.12.11.10", "192.168.1.1, 9.8.7.6", False),
|
|
("13.12.11.10", "13.12.11.10, 192.168.255.0/24", "192.168.1.1, 9.8.7.6", False),
|
|
("13.12.11.10", "13.0.0.0/12", None, True),
|
|
("13.12.11.10", "13.0.0.0/12, 192.168.255.0/24", None, True),
|
|
("13.12.11.10", "13.0.0.0/12", "192.168.1.1", False), # XFF not in local ranges
|
|
("13.12.11.10", "13.0.0.0/12, 192.168.255.0/24", "192.168.1.1", False),
|
|
("13.12.11.10", "13.0.0.0/12", "192.168.1.1, 9.8.7.6", False),
|
|
("13.12.11.10", "13.0.0.0/12, 192.168.255.0/24", "192.168.1.1, 9.8.7.6", False),
|
|
("127.6.6.6", None, None, True),
|
|
("127.6.6.6", None, "127.0.0.1", True),
|
|
("127.6.6.6", None, "127.1.2.3", True),
|
|
("127.6.6.6", None, "127.0.0.1:8080", False), # Port number in XFF
|
|
("127.6.6.6", None, "::1", True),
|
|
("127.6.6.6", None, "[::1]", True),
|
|
("127.6.6.6", None, "[::1]:8080", False), # Port number in XFF
|
|
("127.6.6.6", None, "localhost", False), # Hostname in XFF
|
|
("127.6.6.6", None, "example.org", False), # Hostname in XFF
|
|
("127.6.6.6", None, "192.168.1.1", True),
|
|
("127.6.6.6", None, "10.11.12.99", True),
|
|
("127.6.6.6", None, "8.7.6.5", False), # XFF IP isn't local
|
|
("127.6.6.6", None, "192.168.1.1, 127.6.6.6", True),
|
|
("127.6.6.6", None, "192.168.1.1, 127.6.6.6, 9.8.7.6", False), # Last XFF IP isn't local
|
|
("127.6.6.6", None, "192.168.1.1, 127.6.6.6, ::1", True),
|
|
("127.6.6.6", None, "192.168.1.1, 127.6.6.6, sabrules.example.org", False), # Hostname in XFF
|
|
("127.6.6.6", "192.168.1.0/24", None, True), # Remote IP is loopback, local ranges be damned
|
|
("127.6.6.6", "192.168.1.0/24", "192.168.1.23", True),
|
|
("127.6.6.6", "192.168.1.0/24", "192.168.1.23, 127.0.0.1", True),
|
|
("127.6.6.6", "192.168.1.0/24, 127.0.0.0/8", "192.168.1.23", True),
|
|
("127.6.6.6", "192.168.2.0/24, 127.0.0.0/8", "192.168.1.23", False), # Access denied by XFF
|
|
("127.6.6.6", "192.168.2.0/24, 127.0.0.0/8", "5.6.7.8", False), # Idem
|
|
("127.6.6.6", "192.168.1.0/24, 127.0.0.0/8", "192.168.1.23, 5.6.7.8", False), # Idem
|
|
("127.6.6.6", "192.168.1.0/24, 10.0.0.0/24", "::1", True),
|
|
("127.6.6.6", "127.6.6.0/24", "192.168.1.23", False), # Access denied by XFF
|
|
("127.6.6.6", "2001:ffff::/32", None, True),
|
|
("127.6.6.6", "2001:ffff::/32, 192.168.1.0/24", None, True),
|
|
("127.6.6.6", "2001:ffff::/32", "2001:ffff:a:b:c:d:e:f", True),
|
|
("127.6.6.6", "2001:ffff::/32, 192.168.1.0/24", "2001:ffff:a:b:c:d:e:f, 192.168.1.1", True),
|
|
("127.6.6.6", "2001:ffff::/32", "666:ffff:a:b:c:d:e:f", False), # Access denied by XFF
|
|
("127.6.6.6", "2001:ffff::/32, 192.168.1.0/24", "666:ffff:a:b:c:d:e:f, 192.168.1.1", False), # Idem
|
|
("DEAD:BEEF:2023:007::1", None, None, False), # Back to ignoring XFF altogether
|
|
("DEAD:BEEF:2023:007::1", None, "127.0.0.1", False), # XFF is loopback
|
|
("DEAD:BEEF:2023:007::1", None, "127.1.2.3", False),
|
|
("DEAD:BEEF:2023:007::1", None, "::1", False),
|
|
("DEAD:BEEF:2023:007::1", None, "[::1]", False),
|
|
("DEAD:BEEF:2023:007::1", None, "localhost", False), # Hostname in XFF
|
|
("DEAD:BEEF:2023:007::1", None, "192.168.1.1", False),
|
|
("DEAD:BEEF:2023:007::1", None, "192.168.1.1, DEAD:BEEF:2023:0007::1", False),
|
|
("DEAD:BEEF:2023:007::1", None, "192.168.1.1, DEAD:BEEF:2023:0007::1, ::1", False),
|
|
("DEAD:BEEF:2023:007::1", None, "2001::/16", False),
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", None, True), # Local ranges include a public IPv6
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "127.0.0.1", True), # XFF is loopback
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "127.1.2.3", True),
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "::1", True),
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "[::1]", True),
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "localhost", False), # Hostname in XFF
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "192.168.1.1", False),
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "192.168.1.1, DEAD:BEEF:2023:0007::1", False),
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "192.168.1.1, DEAD:BEEF:2023:0007::1, ::1", False),
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "DEAD::/16", False), # Netmask in XFF
|
|
("DEAD:BEEF:2023:007::1", "dead:beef::/32", "DEAD:BEEF:2023:7::42", True), # XFF in local ranges
|
|
],
|
|
)
|
|
@pytest.mark.parametrize("access_type", [1, 2, 3, 4, 5, 6])
|
|
@pytest.mark.parametrize("inet_exposure", [0, 1, 2, 3, 4, 5])
|
|
@pytest.mark.parametrize("verify_xff_header", [False, True])
|
|
def test_check_access(
|
|
self, access_type, inet_exposure, local_ranges, remote_ip, xff_header, verify_xff_header, result_with_xff
|
|
):
|
|
@set_config(
|
|
{
|
|
"local_ranges": local_ranges,
|
|
"inet_exposure": inet_exposure,
|
|
"verify_xff_header": verify_xff_header,
|
|
}
|
|
)
|
|
def _func():
|
|
# Insert fake request data
|
|
cherrypy.request.remote.ip = remote_ip
|
|
cherrypy.request.headers.update({"X-Forwarded-For": xff_header})
|
|
|
|
if verify_xff_header:
|
|
result = result_with_xff
|
|
else:
|
|
# Without XFF, only the remote IP and the local ranges setting matter
|
|
result = is_loopback_addr(remote_ip) or is_local_addr(remote_ip)
|
|
|
|
if access_type <= inet_exposure:
|
|
assert interface.check_access(access_type) is True
|
|
else:
|
|
assert interface.check_access(access_type) is result
|
|
|
|
_func()
|
|
|
|
@pytest.mark.parametrize(
|
|
"local_ranges, xff_ips, expected_result",
|
|
[
|
|
([], ["4.3.2.1"], "4.3.2.1"), # Standard situation, single non-local XFF IP
|
|
([], ["42:1b5::beef"], "42:1b5::beef"),
|
|
([], ["10.10.10.10"], "10.10.10.10"), # Only local XFF IPs, first entry wins
|
|
([], ["::1"], "::1"),
|
|
([], ["127.0.0.1"], "127.0.0.1"),
|
|
([], ["10.10.10.10", "192.168.0.1"], "10.10.10.10"), # Only local XFF IPs, first entry wins
|
|
([], ["10.10.10.10", "192.168.0.1", "192.168.1.2"], "10.10.10.10"), # Only local XFF IPs, first entry wins
|
|
([], ["4.3.2.1", "10.10.10.10"], "4.3.2.1"), # First non-local entry wins
|
|
([], ["4.3.2.1", "192.168.0.1", "10.10.10.10"], "4.3.2.1"),
|
|
([], ["127.0.0.1", "4.3.2.1", "192.168.0.1", "10.10.10.10"], "4.3.2.1"),
|
|
([], ["4.3.2.1", "192.168.0.1", "10.10.10.10", "127.0.0.1"], "4.3.2.1"),
|
|
([], ["666::1", "4.3.2.1", "10.10.10.10"], "4.3.2.1"),
|
|
([], ["4.3.2.1", "666::1", "192.168.0.1", "10.10.10.10"], "666::1"),
|
|
([], ["127.0.0.1", "4.3.2.1", "666::1", "10.10.10.10"], "666::1"),
|
|
([], ["4.3.2.1", "192.168.0.1", "10.10.10.10", "127.0.0.1", "666::1"], "666::1"),
|
|
([], ["10.10.10.10", "4.3.2.1"], "4.3.2.1"),
|
|
([], ["192.168.0.1", "4.3.2.1", "10.10.10.10"], "4.3.2.1"),
|
|
([], ["127.0.0.1", "192.168.0.1", "4.3.2.1", "10.10.10.10"], "4.3.2.1"),
|
|
([], ["192.168.0.1", "4.3.2.1", "10.10.10.10", "127.0.0.1"], "4.3.2.1"),
|
|
(["4.3.2.0/24"], ["4.3.2.1"], "4.3.2.1"), # Only local IPs due to local_ranges, first entry wins
|
|
(["666::/48"], ["666::1"], "666::1"),
|
|
(["192.168.0.0/16", "4.3.2.0/24"], ["4.3.2.1"], "4.3.2.1"),
|
|
(["666::/48", "192.168.0.0/16", "4.3.2.0/24"], ["4.3.2.1"], "4.3.2.1"),
|
|
(["666::/48", "192.168.0.0/16", "4.3.2.0/24"], ["666::1"], "666::1"),
|
|
(["192.168.0.0/16", "4.3.2.0/24"], ["10.10.10.10"], "10.10.10.10"), # 10.x wins, outside local_ranges
|
|
(["192.168.0.0/16", "4.3.2.0/24"], ["4.3.2.1", "10.10.10.10"], "10.10.10.10"),
|
|
(["192.168.0.0/16", "4.3.2.0/24"], ["10.10.10.10", "4.3.2.1"], "10.10.10.10"),
|
|
(["666::/48", "192.168.0.0/16", "4.3.2.0/24"], ["10.10.10.10"], "10.10.10.10"),
|
|
(["666::/48", "192.168.0.0/16", "4.3.2.0/24"], ["4.3.2.1", "10.10.10.10"], "10.10.10.10"),
|
|
(["666::/48", "192.168.0.0/16", "4.3.2.0/24"], ["10.10.10.10", "4.3.2.1"], "10.10.10.10"),
|
|
(["8.8.8.8", "4.3.2.1"], ["4.3.2.1", "192.168.0.1", "10.10.10.10"], "10.10.10.10"),
|
|
(["8.8.8.8", "4.3.2.1"], ["192.168.0.1", "4.3.2.1", "10.10.10.10"], "10.10.10.10"),
|
|
(["8.8.8.8", "4.3.2.1"], ["192.168.0.1", "10.10.10.10", "4.3.2.1"], "10.10.10.10"),
|
|
(["8.8.8.8"], ["192.168.0.1", "10.10.10.10", "4.3.2.1"], "4.3.2.1"), # All XFF IPs non-local, last wins
|
|
(["8.8.8.8"], ["4.3.2.1", "10.10.10.10", "192.168.0.1"], "192.168.0.1"),
|
|
(["8.8.8.8"], ["4.3.2.1", "192.168.0.1", "10.10.10.10"], "10.10.10.10"),
|
|
(["8.8.8.8"], ["127.0.0.1", "4.3.2.1", "10.10.10.10", "192.168.0.1"], "192.168.0.1"),
|
|
(["666::/48"], ["192.168.0.1", "10.10.10.10", "4.3.2.1"], "4.3.2.1"),
|
|
(["666::/48"], ["4.3.2.1", "10.10.10.10", "192.168.0.1"], "192.168.0.1"),
|
|
(["666::/48"], ["4.3.2.1", "192.168.0.1", "10.10.10.10"], "10.10.10.10"),
|
|
(["666::/48"], ["127.0.0.1", "4.3.2.1", "10.10.10.10", "192.168.0.1"], "192.168.0.1"),
|
|
(["8.8.8.8"], ["4.3.2.1", "192.168.0.1", "10.10.10.10", "127.0.0.1"], "10.10.10.10"), # Loopback as last
|
|
(["666::/48"], ["4.3.2.1", "192.168.0.1", "10.10.10.10", "127.0.0.1"], "10.10.10.10"),
|
|
(["8.8.8.8"], ["4.3.2.1", "192.168.0.1", "10.10.10.10", "::1"], "10.10.10.10"),
|
|
(["666::/48"], ["4.3.2.1", "192.168.0.1", "10.10.10.10", "::1"], "10.10.10.10"),
|
|
([], ["4.3.2.1:56789"], "4.3.2.1:56789"), # Garbage in, garbage out.
|
|
],
|
|
)
|
|
def test_remote_ip_from_xff(self, local_ranges, xff_ips, expected_result):
|
|
@set_config({"local_ranges": local_ranges})
|
|
def _func():
|
|
# Insert fake request data; should *not* influence the results of the tested function
|
|
cherrypy.request.remote.ip = "6.6.6.6"
|
|
assert xff_ips
|
|
assert interface.remote_ip_from_xff(xff_ips) is expected_result
|
|
|
|
_func()
|