Skip to content

Commit f31bb3a

Browse files
committed
[fix] Added CIDR and subnet support to FREERADIUS_ALLOWED_HOSTS #229
Closes #229 Added strict=False to ipaddress.ip_network parsing and strip() to string inputs to allow networks with host bits and spaces to be correctly parsed and authenticated.
1 parent eef0edf commit f31bb3a

3 files changed

Lines changed: 72 additions & 4 deletions

File tree

openwisp_radius/api/freeradius_views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ def _check_client_ip_and_return(self, request, uuid):
105105

106106
for ip in ip_list:
107107
try:
108-
if ipaddress.ip_address(client_ip) in ipaddress.ip_network(ip):
108+
if ipaddress.ip_address(client_ip) in ipaddress.ip_network(
109+
(ip or "").strip(), strict=False
110+
):
109111
return (AnonymousUser(), uuid)
110112
except ValueError:
111113
invalid_addr_message = _(

openwisp_radius/base/models.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,8 +1338,11 @@ def __str__(self):
13381338
@property
13391339
def freeradius_allowed_hosts_list(self):
13401340
addresses = []
1341-
if self.freeradius_allowed_hosts:
1342-
addresses = self.freeradius_allowed_hosts.split(",")
1341+
addresses = [
1342+
(ip or "").strip()
1343+
for ip in (self.freeradius_allowed_hosts or "").split(",")
1344+
if (ip or "").strip()
1345+
]
13431346
return addresses
13441347

13451348
@property
@@ -1383,7 +1386,9 @@ def _clean_freeradius_allowed_hosts(self):
13831386
else:
13841387
try:
13851388
for ip_address in allowed_hosts_set:
1386-
ipaddress.ip_network(ip_address)
1389+
ip_str = (ip_address or "").strip()
1390+
if ip_str:
1391+
ipaddress.ip_network(ip_str, strict=False)
13871392
except ValueError:
13881393
raise ValidationError(
13891394
{

openwisp_radius/tests/test_api/test_freeradius_api.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,6 +2385,67 @@ def test_ip_from_radsetting_valid(self):
23852385
self.assertEqual(response.status_code, 200)
23862386
self.assertEqual(response.data, _AUTH_TYPE_ACCEPT_RESPONSE)
23872387

2388+
def test_ip_from_radsetting_cidr_range_valid(self):
2389+
with mock.patch(self.freeradius_hosts_path, []):
2390+
radsetting = OrganizationRadiusSettings.objects.get(
2391+
organization=self._get_org()
2392+
)
2393+
radsetting.freeradius_allowed_hosts = "172.18.0.0/16"
2394+
radsetting.save()
2395+
2396+
with mock.patch(
2397+
"openwisp_radius.api.freeradius_views.get_client_ip",
2398+
return_value=("172.18.0.10", True),
2399+
):
2400+
response = self.client.post(reverse("radius:authorize"), self.params)
2401+
2402+
self.assertEqual(response.status_code, 200)
2403+
self.assertEqual(response.data, _AUTH_TYPE_ACCEPT_RESPONSE)
2404+
2405+
def test_ip_from_radsetting_spaces_and_host_bits_valid(self):
2406+
org = self._get_org()
2407+
with mock.patch(self.freeradius_hosts_path, []):
2408+
radsetting = OrganizationRadiusSettings.objects.get(organization=org)
2409+
2410+
radsetting.freeradius_allowed_hosts = "127.0.0.1, 172.18.0.5/16"
2411+
radsetting.save()
2412+
2413+
self.assertEqual(
2414+
cache.get(f"ip-{org.pk}"),
2415+
["127.0.0.1", "172.18.0.5/16"],
2416+
)
2417+
2418+
with mock.patch(
2419+
"openwisp_radius.api.freeradius_views.get_client_ip",
2420+
return_value=("172.18.0.10", True),
2421+
):
2422+
response = self.client.post(reverse("radius:authorize"), self.params)
2423+
2424+
self.assertEqual(response.status_code, 200)
2425+
self.assertEqual(response.data, _AUTH_TYPE_ACCEPT_RESPONSE)
2426+
2427+
@capture_any_output()
2428+
def test_ip_outside_cidr_range_rejected(self):
2429+
with mock.patch(self.freeradius_hosts_path, []):
2430+
radsetting = OrganizationRadiusSettings.objects.get(
2431+
organization=self._get_org()
2432+
)
2433+
radsetting.freeradius_allowed_hosts = "172.18.0.0/16"
2434+
radsetting.save()
2435+
2436+
with mock.patch(
2437+
"openwisp_radius.api.freeradius_views.get_client_ip",
2438+
return_value=("10.0.0.5", True),
2439+
):
2440+
response = self.client.post(reverse("radius:authorize"), self.params)
2441+
2442+
self.assertEqual(response.status_code, 403)
2443+
self.assertEqual(
2444+
response.data["detail"],
2445+
"Request rejected: Client IP address (10.0.0.5) is not in "
2446+
"the list of IP addresses allowed to consume the freeradius API.",
2447+
)
2448+
23882449
def test_ip_from_setting_valid(self):
23892450
response = self.client.post(reverse("radius:authorize"), self.params)
23902451
self.assertEqual(response.status_code, 200)

0 commit comments

Comments
 (0)