|
30 | 30 | RadiusAccounting = load_model("RadiusAccounting") |
31 | 31 | RadiusPostAuth = load_model("RadiusPostAuth") |
32 | 32 | RadiusGroupReply = load_model("RadiusGroupReply") |
| 33 | +RadiusGroupCheck = load_model("RadiusGroupCheck") |
| 34 | +RadiusGroup = load_model("RadiusGroup") |
33 | 35 | RegisteredUser = load_model("RegisteredUser") |
34 | 36 | OrganizationRadiusSettings = load_model("OrganizationRadiusSettings") |
35 | 37 | Organization = swapper.load_model("openwisp_users", "Organization") |
@@ -1344,7 +1346,7 @@ def test_authorize_counters_reply_interaction(self): |
1344 | 1346 |
|
1345 | 1347 | with self.subTest("Counters disabled"): |
1346 | 1348 | with mock.patch.object(app_settings, "COUNTERS", []): |
1347 | | - with self.assertNumQueries(6): |
| 1349 | + with self.assertNumQueries(7): |
1348 | 1350 | response = self._authorize_user(auth_header=self.auth_header) |
1349 | 1351 | self.assertEqual(response.status_code, 200) |
1350 | 1352 | expected = { |
@@ -1437,6 +1439,139 @@ def test_authorize_counters_exception_handling(self): |
1437 | 1439 | self.assertEqual(response.status_code, 200) |
1438 | 1440 | self.assertEqual(response.data, truncated_accept_response) |
1439 | 1441 |
|
| 1442 | + @mock.patch.object(app_settings, "SIMULTANEOUS_USE_ENABLED", True) |
| 1443 | + def test_authorize_simultaneous_use(self): |
| 1444 | + user = self._get_org_user().user |
| 1445 | + group = user.radiususergroup_set.first().group |
| 1446 | + self._create_radius_groupreply( |
| 1447 | + group=group, |
| 1448 | + groupname=group.name, |
| 1449 | + attribute="Idle-Timeout", |
| 1450 | + op="=", |
| 1451 | + value="240", |
| 1452 | + ) |
| 1453 | + org2 = self._create_org(name="org2") |
| 1454 | + self._create_org_user(organization=org2, user=user) |
| 1455 | + |
| 1456 | + with self.subTest("Authorization within simultaneous use limit"): |
| 1457 | + simultaneous_use_check = self._create_radius_groupcheck( |
| 1458 | + group=group, |
| 1459 | + groupname=group.name, |
| 1460 | + attribute="Simultaneous-Use", |
| 1461 | + op=":=", |
| 1462 | + value="2", |
| 1463 | + ) |
| 1464 | + |
| 1465 | + # Create one open session (within limit of 2) |
| 1466 | + acct_data = self.acct_post_data |
| 1467 | + acct_data.update( |
| 1468 | + { |
| 1469 | + "username": user.username, |
| 1470 | + "unique_id": "test_session_1", |
| 1471 | + "stop_time": None, |
| 1472 | + } |
| 1473 | + ) |
| 1474 | + self._create_radius_accounting(**acct_data) |
| 1475 | + |
| 1476 | + # Authorization should succeed |
| 1477 | + response = self._authorize_user(auth_header=self.auth_header) |
| 1478 | + self.assertEqual(response.status_code, 200) |
| 1479 | + self.assertEqual(response.data["control:Auth-Type"], "Accept") |
| 1480 | + |
| 1481 | + with self.subTest("Authorization exceeding simultaneous use limit"): |
| 1482 | + RadiusGroupCheck.objects.filter(id=simultaneous_use_check.id).update( |
| 1483 | + value="1" |
| 1484 | + ) |
| 1485 | + |
| 1486 | + # Already have one open session (at the limit of 1) |
| 1487 | + response = self._authorize_user(auth_header=self.auth_header) |
| 1488 | + self.assertEqual(response.status_code, 401) |
| 1489 | + self.assertEqual(response.data["control:Auth-Type"], "Reject") |
| 1490 | + self.assertEqual( |
| 1491 | + response.data["Reply-Message"], |
| 1492 | + "You are already logged in - access denied", |
| 1493 | + ) |
| 1494 | + self.assertEqual( |
| 1495 | + response.data["Idle-Timeout"], |
| 1496 | + {"op": "=", "value": "240"}, |
| 1497 | + ) |
| 1498 | + |
| 1499 | + with self.subTest("Closed sessions are ignored"): |
| 1500 | + # Keep limit at 1 and Close all previously open sessions |
| 1501 | + RadiusAccounting.objects.filter(stop_time=None).update( |
| 1502 | + stop_time="2025-08-12T23:00:24.020460+01:00" |
| 1503 | + ) |
| 1504 | + |
| 1505 | + # Authorization should succeed (closed sessions don't count) |
| 1506 | + response = self._authorize_user(auth_header=self.auth_header) |
| 1507 | + self.assertEqual(response.status_code, 200) |
| 1508 | + self.assertEqual(response.data["control:Auth-Type"], "Accept") |
| 1509 | + |
| 1510 | + with self.subTest("Open session in different org is ignored"): |
| 1511 | + # Keep limit at 1 and create an open session for the user in org2 |
| 1512 | + self._create_radius_accounting( |
| 1513 | + **{ |
| 1514 | + **self.acct_post_data, |
| 1515 | + "username": user.username, |
| 1516 | + "unique_id": "test_session_org2", |
| 1517 | + "stop_time": None, |
| 1518 | + "organization": org2, |
| 1519 | + } |
| 1520 | + ) |
| 1521 | + |
| 1522 | + # Authorization should succeed (session in different org doesn't count) |
| 1523 | + response = self._authorize_user(auth_header=self.auth_header) |
| 1524 | + self.assertEqual(response.status_code, 200) |
| 1525 | + self.assertEqual(response.data["control:Auth-Type"], "Accept") |
| 1526 | + |
| 1527 | + with self.subTest("Zero limit allows unlimited simultaneous sessions"): |
| 1528 | + RadiusGroupCheck.objects.filter(id=simultaneous_use_check.id).update( |
| 1529 | + value="0" |
| 1530 | + ) |
| 1531 | + |
| 1532 | + # Create multiple open sessions |
| 1533 | + acct_data.update( |
| 1534 | + { |
| 1535 | + "username": user.username, |
| 1536 | + "stop_time": None, |
| 1537 | + "session_time": 1, |
| 1538 | + "input_octets": 0, |
| 1539 | + "output_octets": 0, |
| 1540 | + } |
| 1541 | + ) |
| 1542 | + for i in range(2, 4): # Create 2 more sessions (total 3) |
| 1543 | + acct_data["unique_id"] = f"test_session_{i}" |
| 1544 | + self._create_radius_accounting(**acct_data) |
| 1545 | + |
| 1546 | + # Authorization should still succeed with unlimited sessions |
| 1547 | + response = self._authorize_user(auth_header=self.auth_header) |
| 1548 | + self.assertEqual(response.status_code, 200) |
| 1549 | + self.assertEqual(response.data["control:Auth-Type"], "Accept") |
| 1550 | + |
| 1551 | + @mock.patch.object(app_settings, "SIMULTANEOUS_USE_ENABLED", False) |
| 1552 | + @mock.patch("openwisp_radius.api.freeradius_views.RadiusAccounting.objects.count") |
| 1553 | + def test_authorize_simultaneous_use_disabled_in_settings(self, mocked_count): |
| 1554 | + user = self._get_org_user().user |
| 1555 | + group = user.radiususergroup_set.first().group |
| 1556 | + self._create_radius_groupcheck( |
| 1557 | + group=group, |
| 1558 | + groupname=group.name, |
| 1559 | + attribute="Simultaneous-Use", |
| 1560 | + op=":=", |
| 1561 | + value="1", |
| 1562 | + ) |
| 1563 | + |
| 1564 | + # Create an open session that would normally exceed limit |
| 1565 | + acct_data = self.acct_post_data |
| 1566 | + acct_data.update({"username": user.username, "stop_time": None}) # Open session |
| 1567 | + self._create_radius_accounting(**acct_data) |
| 1568 | + |
| 1569 | + # Authorization should succeed (check is disabled) |
| 1570 | + response = self._authorize_user(auth_header=self.auth_header) |
| 1571 | + self.assertEqual(response.status_code, 200) |
| 1572 | + self.assertEqual(response.data["control:Auth-Type"], "Accept") |
| 1573 | + mocked_count.assert_not_called() |
| 1574 | + |
1440 | 1575 | def test_authorize_radius_token_200(self): |
1441 | 1576 | self._get_org_user() |
1442 | 1577 | rad_token = self._login_and_obtain_auth_token() |
|
0 commit comments