Files
sabnzbd/tests/test_interface.py

166 lines
9.8 KiB
Python

#!/usr/bin/python3 -OO
# Copyright 2007-2023 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()