From ade60c75d86c3250ba207ee777a9d7cbf1188b9e Mon Sep 17 00:00:00 2001 From: Ecnama Date: Fri, 18 Oct 2024 20:09:58 +0200 Subject: [PATCH 1/8] Add Netcontrol class with request methods --- backend/langate/modules/netcontrol.py | 150 ++++++++++++++++++-------- 1 file changed, 106 insertions(+), 44 deletions(-) diff --git a/backend/langate/modules/netcontrol.py b/backend/langate/modules/netcontrol.py index 4e44480..0c52042 100644 --- a/backend/langate/modules/netcontrol.py +++ b/backend/langate/modules/netcontrol.py @@ -1,52 +1,114 @@ -import socket, struct -import pickle -import threading -from ..settings import NETCONTROL_SOCKET_FILE +import requests +import subprocess +import logging -lock = threading.Lock() +GET_REQUESTS = ["get_mac", "get_ip"] +POST_REQUESTS = ["connect_user"] +DELETE_REQUESTS = ["disconnect_user"] +PUT_REQUESTS = ["set_mark"] -class NetworkDaemonError(RuntimeError): - """ every error originating from netcontrol raise this exception """ +class RequestError(Exception): + """ + Exception raised when a request to the netcontrol API fails. + """ + def __init__(self, message): + self.message = message + super().__init__(self.message) -def _send(sock, data): - pack = struct.pack('>I', len(data)) + data - sock.sendall(pack) +class NetControl: + """ + Class which interacts with the netcontrol API. + """ + def __init__(self): + """ + Initialize HOST_IP to the docker's default route and check the connection with the netcontrol API. + """ + self.HOST_IP = subprocess.run(["/sbin/ip", "route"], capture_output=True).stdout.decode("utf-8").split()[2] + self.logger = logging.getLogger(__name__) -def _recv_bytes(sock, size): - data = b'' - while len(data) < size: - r = sock.recv(size - len(data)) - if not r: - return None - data += r - return data + try: + self.logger.info("Checking connection with the netcontrol API...") + if requests.get(f"http://{self.HOST_IP}:6784/") != "netcontrol is running": + raise RequestError("Netcontrol is not running.") + except requests.exceptions.ConnectionError: + self.logger.info("Could not connect to the netcontrol API.") + except RequestError as e: + self.logger.info(e.message) -def _recv(sock): - data_length_r = _recv_bytes(sock, 4) + def request(self, endpoint='', args={}): + """ + Make a given request to the netcontrol API. + """ + # Construct the data to be sent in the request + if len(args) == 1: + data = '/'+args[0] + elif len(args) > 1: + data = '?' + "&".join([f"{key}={value}" for key, value in args.items()]) + else: + data = '' - if not data_length_r: - return None + # Make the request + try: + # Check the type of request + if endpoint in GET_REQUESTS: + response = requests.get(f"http://{self.HOST_IP}:6784/{endpoint}{data}") + elif endpoint in POST_REQUESTS: + response = requests.post(f"http://{self.HOST_IP}:6784/{endpoint}{data}") + elif endpoint in DELETE_REQUESTS: + response = requests.delete(f"http://{self.HOST_IP}:6784/{endpoint}{data}") + elif endpoint in PUT_REQUESTS: + response = requests.put(f"http://{self.HOST_IP}:6784/{endpoint}{data}") + + # Check for errors in the response + if "error" in response.json().keys(): + raise RequestError(response.json()["error"]) + return response.json() + + # Handle exceptions + except requests.exceptions.ConnectionError: + self.logger.info("Could not connect to the netcontrol API.") + except RequestError as e: + self.logger.info(e.message) - data_length = struct.unpack('>I', data_length_r)[0] - return _recv_bytes(sock, data_length) + def check_api(self): + """ + Check if the netcontrol API is running. + """ + self.logger.info("Checking connection with the netcontrol API...") + return self.request() - -def communicate(payload): - with lock: - with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: - sock.connect(NETCONTROL_SOCKET_FILE) - r = pickle.dumps(payload) - _send(sock, r) - - response_r = _recv(sock) - response = pickle.loads(response_r) - - if response["success"]: - return response - - else: - raise NetworkDaemonError(response["message"]) - -def query(q, opts = {}): - b = { "query": q } - return communicate({ **b, **opts }) + def get_mac(self, ip: str): + """ + Get the MAC address of the device with the given IP address. + """ + self.logger.info(f"Getting MAC address of {ip}...") + return self.request("get_mac", {"ip": ip})["mac"] + + def get_ip(self, mac: str): + """ + Get the IP address of the device with the given MAC address. + """ + self.logger.info(f"Getting IP address of {mac}...") + return self.request("get_ip", {"mac": mac})["ip"] + + def connect_user(self, mac: str, mark: int, name: str): + """ + Connect the user with the given MAC address. + """ + self.logger.info(f"Connecting user with MAC address {mac} ({name})...") + return self.request("connect_user", {"mac": mac, "mark": mark, "name": name}) + + def disconnect_user(self, mac: str): + """ + Disconnect the user with the given MAC address. + """ + self.logger.info(f"Disconnecting user with MAC address {mac}...") + return self.request("disconnect_user", {"mac": mac}) + + def set_mark(self, mac: str, mark: int): + """ + Set the mark of the user with the given MAC address. + """ + self.logger.info(f"Setting mark of user with MAC address {mac} to {mark}...") + return self.request("set_mark", {"mac": mac, "mark": mark}) + From a4784d7d8fc2eab5d9f43792a52bbed9de64cf36 Mon Sep 17 00:00:00 2001 From: Ecnama Date: Fri, 18 Oct 2024 21:20:31 +0200 Subject: [PATCH 2/8] Changed backend to use new Netcontrol class --- backend/langate/modules/netcontrol.py | 4 ++-- backend/langate/network/apps.py | 20 ++++++++--------- backend/langate/network/models.py | 32 ++++++++++++--------------- backend/langate/settings.py | 4 ++++ backend/langate/user/views.py | 6 ++--- 5 files changed, 33 insertions(+), 33 deletions(-) diff --git a/backend/langate/modules/netcontrol.py b/backend/langate/modules/netcontrol.py index 0c52042..a500394 100644 --- a/backend/langate/modules/netcontrol.py +++ b/backend/langate/modules/netcontrol.py @@ -15,7 +15,7 @@ def __init__(self, message): self.message = message super().__init__(self.message) -class NetControl: +class Netcontrol: """ Class which interacts with the netcontrol API. """ @@ -60,7 +60,7 @@ def request(self, endpoint='', args={}): response = requests.put(f"http://{self.HOST_IP}:6784/{endpoint}{data}") # Check for errors in the response - if "error" in response.json().keys(): + if response.json()["error"]: raise RequestError(response.json()["error"]) return response.json() diff --git a/backend/langate/network/apps.py b/backend/langate/network/apps.py index 0636531..1a73277 100644 --- a/backend/langate/network/apps.py +++ b/backend/langate/network/apps.py @@ -8,7 +8,7 @@ from django.apps import AppConfig from django.utils.translation import gettext_lazy as _ -from ..modules import netcontrol +from langate.settings import netcontrol from langate.settings import SETTINGS logger = logging.getLogger(__name__) @@ -50,14 +50,14 @@ def ready(self): for dev in Device.objects.all(): userdevice = UserDevice.objects.filter(mac=dev.mac).first() if userdevice is not None: - connect_res = netcontrol.query("connect_user", {"mac": userdevice.mac, "name": userdevice.user.username}) + connect_res = netcontrol.connect_user(userdevice.mac, userdevice.mark, userdevice.user.username) else: - connect_res = netcontrol.query("connect_user", {"mac": dev.mac, "name": dev.name}) - if not connect_res["success"]: + connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name) + if connect_res["error"]: logger.info("[PortalConfig] Could not connect device %s", dev.mac) - mark_res = netcontrol.query("set_mark", {"mac": dev.mac, "mark": dev.mark}) - if not mark_res["success"]: + mark_res = netcontrol.set_mark(dev.mac, dev.mark) + if mark_res["error"]: logger.info("[PortalConfig] Could not set mark for device %s", dev.name) logger.info(_("[PortalConfig] Add default whitelist devices to the ipset")) @@ -76,12 +76,12 @@ def ready(self): dev.whitelisted = True dev.save() - connect_res = netcontrol.query("connect_user", {"mac": dev.mac, "name": dev.name}) - if not connect_res["success"]: + connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name) + if connect_res["error"]: logger.info("[PortalConfig] Could not connect device %s", dev.mac) - mark_res = netcontrol.query("set_mark", {"mac": dev.mac, "mark": mark}) - if not mark_res["success"]: + mark_res = netcontrol.set_mark(dev.mac, mark) + if mark_res["error"]: logger.info("[PortalConfig] Could not set mark for device %s", dev.name) else: logger.error("[PortalConfig] Invalid line in whitelist.txt: %s", line) diff --git a/backend/langate/network/models.py b/backend/langate/network/models.py index 57f4a66..2e45a05 100644 --- a/backend/langate/network/models.py +++ b/backend/langate/network/models.py @@ -8,7 +8,7 @@ from django.core.exceptions import ValidationError from langate.user.models import User -from langate.modules import netcontrol +from langate.settings import netcontrol from langate.settings import SETTINGS from .utils import generate_dev_name, get_mark @@ -64,8 +64,7 @@ def create_device(mac, name, whitelisted=False, mark=None): # Validate the MAC address validate_mac(mac) - netcontrol.query("connect", { "mac": mac, "name": name }) - netcontrol.query("set_mark", { "mac": mac, "mark": mark }) + netcontrol.connect_user(mac, mark, name) logger.info("Connected device %s (the mac %s has been connected)", name, mac) try: @@ -73,7 +72,7 @@ def create_device(mac, name, whitelisted=False, mark=None): device.save() return device except Exception as e: - netcontrol.query("disconnect_user", { "mac": mac }) + netcontrol.disconnect_user(mac) raise ValidationError( _("There was an error creating the device. Please try again.") ) from e @@ -83,7 +82,7 @@ def delete_device(mac): """ Delete a device with the given mac address """ - netcontrol.query("disconnect_user", { "mac": mac }) + netcontrol.disconnect_user(mac) logger.info("Disconnected device %s from the internet.", mac) device = Device.objects.get(mac=mac) @@ -98,16 +97,14 @@ def create_user_device(user: User, ip, name=None): if not name: name = generate_dev_name() - r = netcontrol.query("get_mac", { "ip": ip }) + r = netcontrol.get_mac(ip) mac = r["mac"] # Validate the MAC address validate_mac(mac) - netcontrol.query("connect_user", { "mac": mac, "name": user.username }) - mark = get_mark(user) - netcontrol.query("set_mark", { "mac": mac, "mark": mark }) + netcontrol.connect_user(mac, mark, user.username) logger.info( "Connected device %s (owned by %s) at %s to the internet.", @@ -121,7 +118,7 @@ def create_user_device(user: User, ip, name=None): device.save() return device except Exception as e: - netcontrol.query("disconnect_user", { "mac": mac }) + netcontrol.disconnect_user(mac) raise ValidationError( _("There was an error creating the device. Please try again.") ) from e @@ -142,19 +139,18 @@ def edit_device(device: Device, mac, name, mark=None): if name and name != device.name: device.name = name if mac and mac != device.mac: - validate_mac(mac) - # Disconnect the old MAC - netcontrol.query("disconnect_user", { "mac": device.mac }) - - # Connect the new MAC - netcontrol.query("connect", { "mac": mac, "name": device.name }) - device.mac = mac + validate_mac(mac) + # Disconnect the old MAC + netcontrol.disconnect_user(device.mac) + # Connect the new MAC + netcontrol.connect_user(mac, device.mark, device.name) + device.mac = mac if mark and mark != device.mark: # Check if the mark is valid if mark not in [m["value"] for m in SETTINGS["marks"]]: raise ValidationError(_("Invalid mark")) device.mark = mark - netcontrol.query("set_mark", { "mac": device.mac, "mark": mark }) + netcontrol.set_mark(device.mac, mark) try: device.save() diff --git a/backend/langate/settings.py b/backend/langate/settings.py index 08261f7..516d79e 100644 --- a/backend/langate/settings.py +++ b/backend/langate/settings.py @@ -16,6 +16,7 @@ from sys import argv import json import logging +from langate.modules.netcontrol import Netcontrol from django.utils.translation import gettext_lazy as _ from langate.network.utils import validate_marks, validate_games @@ -254,3 +255,6 @@ SETTINGS["games"] = {} NETCONTROL_SOCKET_FILE = getenv("NETCONTROL_SOCKET_FILE", "/var/run/langate3000-netcontrol.sock") + +# Netcontrol interface +netcontrol = Netcontrol() \ No newline at end of file diff --git a/backend/langate/user/views.py b/backend/langate/user/views.py index 9b19fc9..21744a7 100644 --- a/backend/langate/user/views.py +++ b/backend/langate/user/views.py @@ -26,7 +26,7 @@ ) from .models import User, Role -from langate.modules import netcontrol +from langate.settings import netcontrol from langate.network.models import UserDevice, Device, DeviceManager from langate.network.serializers import UserDeviceSerializer @@ -104,7 +104,7 @@ def get(self, request): if not user_devices.filter(ip=client_ip).exists(): # If the user is still logged in but the device is not registered on the network, # we register it. - r = netcontrol.query("get_mac", { "ip": client_ip }) + r = netcontrol.get_mac(client_ip) client_mac = r["mac"] try: # The following should never raise a MultipleObjectsReturned exception @@ -228,7 +228,7 @@ def post(self, request): # If this device is not registered on the network, we register it. if not user_devices.filter(ip=client_ip).exists(): - r = netcontrol.query("get_mac", { "ip": client_ip }) + r = netcontrol.get_mac(client_ip) client_mac = r["mac"] try: # The following should never raise a MultipleObjectsReturned exception From 9de4c7bec4bfb2081018e9a390336e82c3e8fcf0 Mon Sep 17 00:00:00 2001 From: Ecnama Date: Fri, 18 Oct 2024 22:35:16 +0200 Subject: [PATCH 3/8] Modified tests to match new methods --- backend/langate/network/tests.py | 160 +++++++++++++------------------ 1 file changed, 66 insertions(+), 94 deletions(-) diff --git a/backend/langate/network/tests.py b/backend/langate/network/tests.py index ab8156d..d9d163b 100644 --- a/backend/langate/network/tests.py +++ b/backend/langate/network/tests.py @@ -58,17 +58,17 @@ def setUp(self): password="password" ) - def test_create_base_device_valid(self): + @patch('langate.network.models.netcontrol.connect_user', return_value = None) + @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) + def test_create_base_device_valid(self, mock_connect_user, mock_disconnect_user): """ Test the creation of a base device with valid parameters """ - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = None - device = DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice") - self.assertIsNotNone(device) - self.assertEqual(device.mac, "00:11:22:33:44:55") - self.assertEqual(device.name, "TestDevice") - self.assertFalse(device.whitelisted) + device = DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice") + self.assertIsNotNone(device) + self.assertEqual(device.mac, "00:11:22:33:44:55") + self.assertEqual(device.name, "TestDevice") + self.assertFalse(device.whitelisted) def test_create_device_invalid_mac(self): """ @@ -77,28 +77,28 @@ def test_create_device_invalid_mac(self): with self.assertRaises(ValidationError): DeviceManager.create_device(mac="invalid_mac", name="TestDevice") - def test_create_device_no_name(self): + @patch('langate.network.models.generate_dev_name', return_value="GeneratedName") + @patch('langate.network.models.netcontrol.connect_user', return_value = None) + @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) + def test_create_device_no_name(self, mock_gen_name, mock_connect_user, mock_disconnect_user): """ Test the creation of a device with no name """ - with patch('langate.network.models.netcontrol.query') as mock_query, \ - patch('langate.network.models.generate_dev_name', return_value="GeneratedName") as mock_gen_name: - mock_query.return_value = None - device = DeviceManager.create_device(mac="00:11:22:33:44:55", name=None) - mock_gen_name.assert_called_once() - self.assertEqual(device.name, "GeneratedName") + device = DeviceManager.create_device(mac="00:11:22:33:44:55", name=None) + mock_gen_name.assert_called_once() + self.assertEqual(device.name, "GeneratedName") + @patch('langate.network.models.netcontrol.connect_user', return_value = None) + @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) def test_create_whitelist_device_valid(self): """ Test the creation of a whitelisted device with valid parameters """ - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = None - device = DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice", whitelisted=True) - self.assertIsNotNone(device) - self.assertEqual(device.mac, "00:11:22:33:44:55") - self.assertEqual(device.name, "TestDevice") - self.assertTrue(device.whitelisted) + device = DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice", whitelisted=True) + self.assertIsNotNone(device) + self.assertEqual(device.mac, "00:11:22:33:44:55") + self.assertEqual(device.name, "TestDevice") + self.assertTrue(device.whitelisted) def test_create_whitelist_device_invalid_mac(self): """ @@ -107,78 +107,62 @@ def test_create_whitelist_device_invalid_mac(self): with self.assertRaises(ValidationError): DeviceManager.create_device(mac="invalid_mac", name="TestDevice", whitelisted=True) - def test_create_whitelist_device_no_name(self): + @patch('langate.network.models.generate_dev_name', return_value="GeneratedName") + @patch('langate.network.models.netcontrol.connect_user', return_value = None) + @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) + def test_create_whitelist_device_no_name(self, mock_gen_name , mock_connect_user, mock_disconnect_user): """ Test the creation of a whitelisted device with no name """ - with patch('langate.network.models.netcontrol.query') as mock_query, \ - patch('langate.network.models.generate_dev_name', return_value="GeneratedName") as mock_gen_name: - mock_query.return_value = None - device = DeviceManager.create_device(mac="00:11:22:33:44:55", name=None, whitelisted=True) - mock_gen_name.assert_called_once() - self.assertEqual(device.name, "GeneratedName") + device = DeviceManager.create_device(mac="00:11:22:33:44:55", name=None, whitelisted=True) + mock_gen_name.assert_called_once() + self.assertEqual(device.name, "GeneratedName") - def test_create_user_device_valid(self): + @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") + def test_create_user_device_valid(self, mock_get_mac): """ Test the creation of a user device with valid parameters """ - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } - device = DeviceManager.create_user_device( - user=self.user, ip="123.123.123.123", name="TestDevice" - ) - self.assertIsNotNone(device) - self.assertEqual(device.mac, "00:11:22:33:44:55") - self.assertEqual(device.name, "TestDevice") - self.assertFalse(device.whitelisted) - self.assertEqual(device.user, self.user) - self.assertEqual(device.ip, "123.123.123.123") + device = DeviceManager.create_user_device( + user=self.user, ip="123.123.123.123", name="TestDevice" + ) + self.assertIsNotNone(device) + self.assertEqual(device.mac, "00:11:22:33:44:55") + self.assertEqual(device.name, "TestDevice") + self.assertFalse(device.whitelisted) + self.assertEqual(device.user, self.user) + self.assertEqual(device.ip, "123.123.123.123") - def test_create_user_device_invalid_ip(self): + @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") + def test_create_user_device_invalid_ip(self, mock_get_mac): """ Test the creation of a user device with an invalid MAC address """ with self.assertRaises(ValidationError): - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } - DeviceManager.create_user_device( - user=self.user, ip="123.123.123.823", name="TestDevice" - ) + DeviceManager.create_user_device( + user=self.user, ip="123.123.123.823", name="TestDevice" + ) - def test_create_user_device_no_name(self): + @patch('langate.network.models.generate_dev_name', return_value="GeneratedName") + @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") + def test_create_user_device_no_name(self, mock_gen_name, mock_get_mac): """ Test the creation of a user device with no name """ - with patch('langate.network.models.netcontrol.query') as mock_query, \ - patch('langate.network.models.generate_dev_name', return_value="GeneratedName") as mock_gen_name: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } - device = DeviceManager.create_user_device( - user=self.user, ip="123.123.123.123", name=None - ) - mock_gen_name.assert_called_once() - self.assertEqual(device.name, "GeneratedName") + device = DeviceManager.create_user_device( + user=self.user, ip="123.123.123.123", name=None + ) + mock_gen_name.assert_called_once() + self.assertEqual(device.name, "GeneratedName") - def test_create_device_duplicate_mac(self): + @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") + def test_create_device_duplicate_mac(self, mock_get_mac): """ Test the creation of a device with a duplicate MAC address """ - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } - with self.assertRaises(ValidationError): - DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice") - DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice2") + with self.assertRaises(ValidationError): + DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice") + DeviceManager.create_device(mac="00:11:22:33:44:55", name="TestDevice2") class TestNetworkAPI(TestCase): """ @@ -286,11 +270,8 @@ def test_delete_device_success(self): """ self.client.force_authenticate(user=self.user) - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": self.device.mac, - } + with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: + mock_get_mac.return_value = self.device.mac response = self.client.delete(reverse('device-detail', args=[self.user_device.pk])) self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) @@ -333,11 +314,8 @@ def test_patch_device_success(self): 'mac': '00:11:22:33:44:57', 'name': 'new_name' } - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": new_data['mac'], - } + with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: + mock_get_mac.return_value = new_data['mac'] response = self.client.patch(reverse('device-detail', args=[self.device.pk]), new_data) self.device.refresh_from_db() self.assertEqual(response.status_code, status.HTTP_200_OK) @@ -510,11 +488,8 @@ def test_post_device_success(self): 'mac': '00:11:22:33:44:57', 'name': 'new_name' } - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": new_data['mac'], - } + with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: + mock_get_mac.return_value = new_data['mac'] response = self.client.post(reverse('device-list'), new_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -533,11 +508,8 @@ def test_post_user_device_success(self): 'user': self.user.id, } - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:57", - } + with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: + mock_get_mac.return_value = "00:11:22:33:44:57" response = self.client.post(reverse('device-list'), new_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) From d886470234d92e58f55f49e6c4fbfc83b2c7d09b Mon Sep 17 00:00:00 2001 From: Ecnama Date: Sat, 19 Oct 2024 21:19:52 +0200 Subject: [PATCH 4/8] Change request errors handling --- backend/langate/modules/netcontrol.py | 2 ++ backend/langate/network/apps.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/langate/modules/netcontrol.py b/backend/langate/modules/netcontrol.py index a500394..7608450 100644 --- a/backend/langate/modules/netcontrol.py +++ b/backend/langate/modules/netcontrol.py @@ -67,8 +67,10 @@ def request(self, endpoint='', args={}): # Handle exceptions except requests.exceptions.ConnectionError: self.logger.info("Could not connect to the netcontrol API.") + return {"error": "Could not connect to the netcontrol API."} except RequestError as e: self.logger.info(e.message) + return response.json() def check_api(self): """ diff --git a/backend/langate/network/apps.py b/backend/langate/network/apps.py index 1a73277..1eec928 100644 --- a/backend/langate/network/apps.py +++ b/backend/langate/network/apps.py @@ -54,11 +54,11 @@ def ready(self): else: connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name) if connect_res["error"]: - logger.info("[PortalConfig] Could not connect device %s", dev.mac) + logger.info("[PortalConfig] %s", connect_res["error"]) mark_res = netcontrol.set_mark(dev.mac, dev.mark) if mark_res["error"]: - logger.info("[PortalConfig] Could not set mark for device %s", dev.name) + logger.info("[PortalConfig] %s", connect_res["error"]) logger.info(_("[PortalConfig] Add default whitelist devices to the ipset")) if os.path.exists("assets/misc/whitelist.txt"): From 99055e63d03ed8643e620ac408a7e75476dc4e99 Mon Sep 17 00:00:00 2001 From: Ecnama Date: Sun, 20 Oct 2024 01:19:52 +0200 Subject: [PATCH 5/8] Use HTTPExceptions --- backend/langate/modules/netcontrol.py | 68 ++++++++++++--------------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/backend/langate/modules/netcontrol.py b/backend/langate/modules/netcontrol.py index 7608450..0823466 100644 --- a/backend/langate/modules/netcontrol.py +++ b/backend/langate/modules/netcontrol.py @@ -1,39 +1,37 @@ import requests import subprocess import logging +from fastapi import HTTPException GET_REQUESTS = ["get_mac", "get_ip"] POST_REQUESTS = ["connect_user"] DELETE_REQUESTS = ["disconnect_user"] PUT_REQUESTS = ["set_mark"] -class RequestError(Exception): - """ - Exception raised when a request to the netcontrol API fails. - """ - def __init__(self, message): - self.message = message - super().__init__(self.message) - class Netcontrol: """ Class which interacts with the netcontrol API. """ def __init__(self): """ - Initialize HOST_IP to the docker's default route and check the connection with the netcontrol API. + Initialize HOST_IP to the docker's default route, set up REQUEST_URL and check the connection with the netcontrol API. """ self.HOST_IP = subprocess.run(["/sbin/ip", "route"], capture_output=True).stdout.decode("utf-8").split()[2] + self.REQUEST_URL = f"http://{self.HOST_IP}:6784/" + self.logger = logging.getLogger(__name__) + # Check the connection with the netcontrol API try: - self.logger.info("Checking connection with the netcontrol API...") - if requests.get(f"http://{self.HOST_IP}:6784/") != "netcontrol is running": - raise RequestError("Netcontrol is not running.") - except requests.exceptions.ConnectionError: - self.logger.info("Could not connect to the netcontrol API.") - except RequestError as e: - self.logger.info(e.message) + try: + self.logger.info("Checking connection with the netcontrol API...") + if requests.get(self.REQUEST_URL) != "netcontrol is running": + raise HTTPException(status_code=404, detail="Netcontrol is not running.") + + except requests.exceptions.ConnectionError: + raise HTTPException(status_code=408, detail="Could not connect to the netcontrol API.") + except HTTPException as e: + self.logger.info(e.detail) def request(self, endpoint='', args={}): """ @@ -49,28 +47,24 @@ def request(self, endpoint='', args={}): # Make the request try: - # Check the type of request - if endpoint in GET_REQUESTS: - response = requests.get(f"http://{self.HOST_IP}:6784/{endpoint}{data}") - elif endpoint in POST_REQUESTS: - response = requests.post(f"http://{self.HOST_IP}:6784/{endpoint}{data}") - elif endpoint in DELETE_REQUESTS: - response = requests.delete(f"http://{self.HOST_IP}:6784/{endpoint}{data}") - elif endpoint in PUT_REQUESTS: - response = requests.put(f"http://{self.HOST_IP}:6784/{endpoint}{data}") + try: + # Check the type of request + if endpoint in GET_REQUESTS: + response = requests.get(self.REQUEST_URL + endpoint + data) + elif endpoint in POST_REQUESTS: + response = requests.post(self.REQUEST_URL + endpoint + data) + elif endpoint in DELETE_REQUESTS: + response = requests.delete(self.REQUEST_URL + endpoint + data) + elif endpoint in PUT_REQUESTS: + response = requests.put(self.REQUEST_URL + endpoint + data) + + response.raise_for_status() + return response.json() - # Check for errors in the response - if response.json()["error"]: - raise RequestError(response.json()["error"]) - return response.json() - - # Handle exceptions - except requests.exceptions.ConnectionError: - self.logger.info("Could not connect to the netcontrol API.") - return {"error": "Could not connect to the netcontrol API."} - except RequestError as e: - self.logger.info(e.message) - return response.json() + except requests.exceptions.ConnectionError: + raise HTTPException(status_code=408, detail="Could not connect to the netcontrol API.") + except HTTPException as e: + self.logger.info(e.detail) # Handle HTTP exception (TODO) def check_api(self): """ From a63a49c72f1ffa1812a9ff8f726a66b6b15c6b1a Mon Sep 17 00:00:00 2001 From: Ecnama Date: Sun, 20 Oct 2024 11:12:51 +0200 Subject: [PATCH 6/8] Fixed confusion between HTTPException and HTTPError --- backend/langate/modules/netcontrol.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/backend/langate/modules/netcontrol.py b/backend/langate/modules/netcontrol.py index 0823466..52e8527 100644 --- a/backend/langate/modules/netcontrol.py +++ b/backend/langate/modules/netcontrol.py @@ -1,7 +1,6 @@ import requests import subprocess import logging -from fastapi import HTTPException GET_REQUESTS = ["get_mac", "get_ip"] POST_REQUESTS = ["connect_user"] @@ -21,17 +20,7 @@ def __init__(self): self.logger = logging.getLogger(__name__) - # Check the connection with the netcontrol API - try: - try: - self.logger.info("Checking connection with the netcontrol API...") - if requests.get(self.REQUEST_URL) != "netcontrol is running": - raise HTTPException(status_code=404, detail="Netcontrol is not running.") - - except requests.exceptions.ConnectionError: - raise HTTPException(status_code=408, detail="Could not connect to the netcontrol API.") - except HTTPException as e: - self.logger.info(e.detail) + self.check_api() def request(self, endpoint='', args={}): """ @@ -62,9 +51,9 @@ def request(self, endpoint='', args={}): return response.json() except requests.exceptions.ConnectionError: - raise HTTPException(status_code=408, detail="Could not connect to the netcontrol API.") - except HTTPException as e: - self.logger.info(e.detail) # Handle HTTP exception (TODO) + raise requests.HTTPError("Could not connect to the netcontrol API.") + except requests.HTTPError as e: + self.logger.info(e) # Handle HTTP exception (TODO) def check_api(self): """ From 4fd2d23d0684838cefd78779b1f09919a2e5702f Mon Sep 17 00:00:00 2001 From: Ecnama Date: Sun, 20 Oct 2024 19:42:49 +0200 Subject: [PATCH 7/8] Fix tests and error handling --- backend/langate/modules/netcontrol.py | 60 ++++++++-------- backend/langate/network/apps.py | 36 +++++----- backend/langate/network/models.py | 80 ++++++++++++++++------ backend/langate/network/tests.py | 99 +++++++++++++++------------ backend/langate/user/tests.py | 63 +++++++---------- backend/langate/user/views.py | 78 ++++++++++++--------- 6 files changed, 235 insertions(+), 181 deletions(-) diff --git a/backend/langate/modules/netcontrol.py b/backend/langate/modules/netcontrol.py index 52e8527..1df26da 100644 --- a/backend/langate/modules/netcontrol.py +++ b/backend/langate/modules/netcontrol.py @@ -2,7 +2,7 @@ import subprocess import logging -GET_REQUESTS = ["get_mac", "get_ip"] +GET_REQUESTS = ["get_mac", "get_ip", ''] POST_REQUESTS = ["connect_user"] DELETE_REQUESTS = ["disconnect_user"] PUT_REQUESTS = ["set_mark"] @@ -11,24 +11,15 @@ class Netcontrol: """ Class which interacts with the netcontrol API. """ - def __init__(self): - """ - Initialize HOST_IP to the docker's default route, set up REQUEST_URL and check the connection with the netcontrol API. - """ - self.HOST_IP = subprocess.run(["/sbin/ip", "route"], capture_output=True).stdout.decode("utf-8").split()[2] - self.REQUEST_URL = f"http://{self.HOST_IP}:6784/" - - self.logger = logging.getLogger(__name__) - - self.check_api() - def request(self, endpoint='', args={}): """ Make a given request to the netcontrol API. """ + response = None + # Construct the data to be sent in the request if len(args) == 1: - data = '/'+args[0] + data = '/' + next(iter(args.items()))[1] elif len(args) > 1: data = '?' + "&".join([f"{key}={value}" for key, value in args.items()]) else: @@ -36,24 +27,23 @@ def request(self, endpoint='', args={}): # Make the request try: - try: - # Check the type of request - if endpoint in GET_REQUESTS: - response = requests.get(self.REQUEST_URL + endpoint + data) - elif endpoint in POST_REQUESTS: - response = requests.post(self.REQUEST_URL + endpoint + data) - elif endpoint in DELETE_REQUESTS: - response = requests.delete(self.REQUEST_URL + endpoint + data) - elif endpoint in PUT_REQUESTS: - response = requests.put(self.REQUEST_URL + endpoint + data) - - response.raise_for_status() - return response.json() + # Check the type of request + if endpoint in GET_REQUESTS: + response = requests.get(self.REQUEST_URL + endpoint + data) + elif endpoint in POST_REQUESTS: + response = requests.post(self.REQUEST_URL + endpoint + data) + elif endpoint in DELETE_REQUESTS: + response = requests.delete(self.REQUEST_URL + endpoint + data) + elif endpoint in PUT_REQUESTS: + response = requests.put(self.REQUEST_URL + endpoint + data) - except requests.exceptions.ConnectionError: - raise requests.HTTPError("Could not connect to the netcontrol API.") - except requests.HTTPError as e: - self.logger.info(e) # Handle HTTP exception (TODO) + response.raise_for_status() + return response.json() + + except requests.exceptions.ConnectionError: + raise requests.HTTPError("Could not connect to the netcontrol API.") + except requests.exceptions.Timeout: + raise requests.HTTPError("The request to the netcontrol API timed out.") def check_api(self): """ @@ -97,3 +87,13 @@ def set_mark(self, mac: str, mark: int): self.logger.info(f"Setting mark of user with MAC address {mac} to {mark}...") return self.request("set_mark", {"mac": mac, "mark": mark}) + def __init__(self): + """ + Initialize HOST_IP to the docker's default route, set up REQUEST_URL and check the connection with the netcontrol API. + """ + self.HOST_IP = subprocess.run(["/sbin/ip", "route"], capture_output=True).stdout.decode("utf-8").split()[2] + self.REQUEST_URL = f"http://{self.HOST_IP}:6784/" + + self.logger = logging.getLogger(__name__) + + #self.check_api() diff --git a/backend/langate/network/apps.py b/backend/langate/network/apps.py index 1eec928..b4a4d17 100644 --- a/backend/langate/network/apps.py +++ b/backend/langate/network/apps.py @@ -2,6 +2,7 @@ Network module. This module is responsible for the device and user connexion management. """ +import requests import sys, logging import os @@ -49,16 +50,18 @@ def ready(self): for dev in Device.objects.all(): userdevice = UserDevice.objects.filter(mac=dev.mac).first() - if userdevice is not None: - connect_res = netcontrol.connect_user(userdevice.mac, userdevice.mark, userdevice.user.username) - else: - connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name) - if connect_res["error"]: - logger.info("[PortalConfig] %s", connect_res["error"]) + try: + if userdevice is not None: + connect_res = netcontrol.connect_user(userdevice.mac, userdevice.mark, userdevice.user.username) + else: + connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name) + except requests.HTTPError as e: + logger.info("[PortalConfig] {e}") - mark_res = netcontrol.set_mark(dev.mac, dev.mark) - if mark_res["error"]: - logger.info("[PortalConfig] %s", connect_res["error"]) + try: + mark_res = netcontrol.set_mark(dev.mac, dev.mark) + except requests.HTTPError as e: + logger.info("[PortalConfig] {e}") logger.info(_("[PortalConfig] Add default whitelist devices to the ipset")) if os.path.exists("assets/misc/whitelist.txt"): @@ -75,13 +78,14 @@ def ready(self): else: dev.whitelisted = True dev.save() + try: + connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name) + except requests.HTTPError as e: + logger.info("[PortalConfig] {e}") - connect_res = netcontrol.connect_user(dev.mac, dev.mark, dev.name) - if connect_res["error"]: - logger.info("[PortalConfig] Could not connect device %s", dev.mac) - - mark_res = netcontrol.set_mark(dev.mac, mark) - if mark_res["error"]: - logger.info("[PortalConfig] Could not set mark for device %s", dev.name) + try: + mark_res = netcontrol.set_mark(dev.mac, mark) + except requests.HTTPError as e: + logger.info("[PortalConfig] {e}") else: logger.error("[PortalConfig] Invalid line in whitelist.txt: %s", line) diff --git a/backend/langate/network/models.py b/backend/langate/network/models.py index 2e45a05..e103d91 100644 --- a/backend/langate/network/models.py +++ b/backend/langate/network/models.py @@ -1,3 +1,4 @@ +import requests import random, logging import re @@ -64,15 +65,25 @@ def create_device(mac, name, whitelisted=False, mark=None): # Validate the MAC address validate_mac(mac) - netcontrol.connect_user(mac, mark, name) - logger.info("Connected device %s (the mac %s has been connected)", name, mac) + try: + netcontrol.connect_user(mac, mark, name) + logger.info("Connected device %s (the mac %s has been connected)", name, mac) + except requests.HTTPError as e: + raise ValidationError( + _("Could not connect user.") + ) from e try: device = Device.objects.create(mac=mac, name=name, whitelisted=whitelisted, mark=mark) device.save() return device except Exception as e: - netcontrol.disconnect_user(mac) + try: + netcontrol.disconnect_user(mac) + except requests.HTTPError as e: + raise ValidationError( + _("Could not disconnect user.") + ) from e raise ValidationError( _("There was an error creating the device. Please try again.") ) from e @@ -82,8 +93,13 @@ def delete_device(mac): """ Delete a device with the given mac address """ - netcontrol.disconnect_user(mac) - logger.info("Disconnected device %s from the internet.", mac) + try: + netcontrol.disconnect_user(mac) + logger.info("Disconnected device %s from the internet.", mac) + except requests.HTTPError as e: + raise ValidationError( + _("Could not disconnect user.") + ) from e device = Device.objects.get(mac=mac) device.delete() @@ -97,28 +113,42 @@ def create_user_device(user: User, ip, name=None): if not name: name = generate_dev_name() - r = netcontrol.get_mac(ip) - mac = r["mac"] + try: + mac = netcontrol.get_mac(ip) + except requests.HTTPError as e: + raise ValidationError( + _("Could not get MAC address.") + ) from e # Validate the MAC address validate_mac(mac) mark = get_mark(user) - netcontrol.connect_user(mac, mark, user.username) - logger.info( - "Connected device %s (owned by %s) at %s to the internet.", - mac, - user.username, - ip - ) + try: + netcontrol.connect_user(mac, mark, user.username) + logger.info( + "Connected device %s (owned by %s) at %s to the internet.", + mac, + user.username, + ip + ) + except requests.HTTPError as e: + raise ValidationError( + _("Could not connect user.") + ) from e try: device = UserDevice.objects.create(mac=mac, name=name, user=user, ip=ip, mark=mark) device.save() return device except Exception as e: - netcontrol.disconnect_user(mac) + try: + netcontrol.disconnect_user(mac) + except requests.HTTPError as e: + raise ValidationError( + _("Could not disconnect user.") + ) from e raise ValidationError( _("There was an error creating the device. Please try again.") ) from e @@ -141,16 +171,26 @@ def edit_device(device: Device, mac, name, mark=None): if mac and mac != device.mac: validate_mac(mac) # Disconnect the old MAC - netcontrol.disconnect_user(device.mac) - # Connect the new MAC - netcontrol.connect_user(mac, device.mark, device.name) - device.mac = mac + try: + netcontrol.disconnect_user(device.mac) + # Connect the new MAC + netcontrol.connect_user(mac, device.mark, device.name) + device.mac = mac + except requests.HTTPError as e: + raise ValidationError( + _("Could not connect user") + ) from e if mark and mark != device.mark: # Check if the mark is valid if mark not in [m["value"] for m in SETTINGS["marks"]]: raise ValidationError(_("Invalid mark")) device.mark = mark - netcontrol.set_mark(device.mac, mark) + try: + netcontrol.set_mark(device.mac, mark) + except requests.HTTPError as e: + raise ValidationError( + _("Could not set mark") + ) from e try: device.save() diff --git a/backend/langate/network/tests.py b/backend/langate/network/tests.py index d9d163b..fc06b95 100644 --- a/backend/langate/network/tests.py +++ b/backend/langate/network/tests.py @@ -58,9 +58,9 @@ def setUp(self): password="password" ) - @patch('langate.network.models.netcontrol.connect_user', return_value = None) - @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) - def test_create_base_device_valid(self, mock_connect_user, mock_disconnect_user): + @patch('langate.settings.netcontrol.connect_user', return_value = None) + @patch('langate.settings.netcontrol.disconnect_user', return_value = None) + def test_create_base_device_valid(self, mock_disconnect_user, mock_connect_user): """ Test the creation of a base device with valid parameters """ @@ -70,7 +70,9 @@ def test_create_base_device_valid(self, mock_connect_user, mock_disconnect_user) self.assertEqual(device.name, "TestDevice") self.assertFalse(device.whitelisted) - def test_create_device_invalid_mac(self): + @patch('langate.settings.netcontrol.connect_user', return_value = None) + @patch('langate.settings.netcontrol.disconnect_user', return_value = None) + def test_create_device_invalid_mac(self, mock_disconnect_user, mock_connect_user): """ Test the creation of a device with an invalid MAC address """ @@ -78,9 +80,9 @@ def test_create_device_invalid_mac(self): DeviceManager.create_device(mac="invalid_mac", name="TestDevice") @patch('langate.network.models.generate_dev_name', return_value="GeneratedName") - @patch('langate.network.models.netcontrol.connect_user', return_value = None) - @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) - def test_create_device_no_name(self, mock_gen_name, mock_connect_user, mock_disconnect_user): + @patch('langate.settings.netcontrol.connect_user', return_value = None) + @patch('langate.settings.netcontrol.disconnect_user', return_value = None) + def test_create_device_no_name(self, mock_disconnect_user, mock_connect_user, mock_gen_name): """ Test the creation of a device with no name """ @@ -88,9 +90,9 @@ def test_create_device_no_name(self, mock_gen_name, mock_connect_user, mock_disc mock_gen_name.assert_called_once() self.assertEqual(device.name, "GeneratedName") - @patch('langate.network.models.netcontrol.connect_user', return_value = None) - @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) - def test_create_whitelist_device_valid(self): + @patch('langate.settings.netcontrol.connect_user', return_value = None) + @patch('langate.settings.netcontrol.disconnect_user', return_value = None) + def test_create_whitelist_device_valid(self, mock_disconnect_user, mock_connect_user): """ Test the creation of a whitelisted device with valid parameters """ @@ -108,9 +110,9 @@ def test_create_whitelist_device_invalid_mac(self): DeviceManager.create_device(mac="invalid_mac", name="TestDevice", whitelisted=True) @patch('langate.network.models.generate_dev_name', return_value="GeneratedName") - @patch('langate.network.models.netcontrol.connect_user', return_value = None) - @patch('langate.network.models.netcontrol.disconnect_user', return_value = None) - def test_create_whitelist_device_no_name(self, mock_gen_name , mock_connect_user, mock_disconnect_user): + @patch('langate.settings.netcontrol.connect_user', return_value = None) + @patch('langate.settings.netcontrol.disconnect_user', return_value = None) + def test_create_whitelist_device_no_name(self, mock_disconnect_user, mock_connect_user, mock_gen_name): """ Test the creation of a whitelisted device with no name """ @@ -118,8 +120,9 @@ def test_create_whitelist_device_no_name(self, mock_gen_name , mock_connect_user mock_gen_name.assert_called_once() self.assertEqual(device.name, "GeneratedName") - @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") - def test_create_user_device_valid(self, mock_get_mac): + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:55") + @patch('langate.settings.netcontrol.connect_user', return_value = None) + def test_create_user_device_valid(self, mock_connect_user, mock_get_mac): """ Test the creation of a user device with valid parameters """ @@ -133,8 +136,9 @@ def test_create_user_device_valid(self, mock_get_mac): self.assertEqual(device.user, self.user) self.assertEqual(device.ip, "123.123.123.123") - @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") - def test_create_user_device_invalid_ip(self, mock_get_mac): + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:55") + @patch('langate.settings.netcontrol.connect_user', return_value = None) + def test_create_user_device_invalid_ip(self, mock_connect_user, mock_get_mac): """ Test the creation of a user device with an invalid MAC address """ @@ -144,8 +148,9 @@ def test_create_user_device_invalid_ip(self, mock_get_mac): ) @patch('langate.network.models.generate_dev_name', return_value="GeneratedName") - @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") - def test_create_user_device_no_name(self, mock_gen_name, mock_get_mac): + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:55") + @patch('langate.settings.netcontrol.connect_user', return_value = None) + def test_create_user_device_no_name(self, mock_connect_user, mock_get_mac, mock_gen_name): """ Test the creation of a user device with no name """ @@ -155,8 +160,9 @@ def test_create_user_device_no_name(self, mock_gen_name, mock_get_mac): mock_gen_name.assert_called_once() self.assertEqual(device.name, "GeneratedName") - @patch('langate.network.models.netcontrol.get_mac', return_value="00:11:22:33:44:55") - def test_create_device_duplicate_mac(self, mock_get_mac): + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:55") + @patch('langate.settings.netcontrol.connect_user', return_value = None) + def test_create_device_duplicate_mac(self, mock_connect_user, mock_get_mac): """ Test the creation of a device with a duplicate MAC address """ @@ -264,21 +270,21 @@ def test_get_device_unauthorized(self): response = self.client.get(reverse('device-detail', args=[self.device.pk])) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_delete_device_success(self): + @patch('langate.settings.netcontrol.disconnect_user', return_value=None) + def test_delete_device_success(self, mock_disconnect_user): """ Test the deletion of a device """ self.client.force_authenticate(user=self.user) - with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: - mock_get_mac.return_value = self.device.mac - response = self.client.delete(reverse('device-detail', args=[self.user_device.pk])) - self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) + response = self.client.delete(reverse('device-detail', args=[self.user_device.pk])) + self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT) - self.assertFalse(Device.objects.filter(pk=self.user_device.pk).exists()) - self.assertFalse(UserDevice.objects.filter(pk=self.user_device.pk).exists()) + self.assertFalse(Device.objects.filter(pk=self.user_device.pk).exists()) + self.assertFalse(UserDevice.objects.filter(pk=self.user_device.pk).exists()) - def test_delete_device_not_found(self): + @patch('langate.settings.netcontrol.disconnect_user', return_value=None) + def test_delete_device_not_found(self, mock_disconnect_user): """ Test the deletion of a non-existent device """ @@ -288,14 +294,16 @@ def test_delete_device_not_found(self): response = self.client.delete(url) self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND) - def test_delete_device_no_auth(self): + @patch('langate.settings.netcontrol.disconnect_user', return_value=None) + def test_delete_device_no_auth(self, mock_disconnect_user): """ Test the deletion of a device without authentication """ response = self.client.delete(reverse('device-detail', args=[self.device.pk])) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_delete_device_unauthorized(self): + @patch('langate.settings.netcontrol.disconnect_user', return_value=None) + def test_delete_device_unauthorized(self, mock_disconnect_user): """ Test the deletion of a device by an unauthorized user """ @@ -304,7 +312,10 @@ def test_delete_device_unauthorized(self): response = self.client.delete(reverse('device-detail', args=[self.device.pk])) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_patch_device_success(self): + @patch('langate.settings.netcontrol.disconnect_user', return_value=None) + @patch('langate.settings.netcontrol.connect_user', return_value=None) + @patch('langate.settings.netcontrol.set_mark', return_value=None) + def test_patch_device_success(self, mock_set_mark, mock_connect_user, mock_disconnect_user): """ Test the update of a device """ @@ -314,7 +325,7 @@ def test_patch_device_success(self): 'mac': '00:11:22:33:44:57', 'name': 'new_name' } - with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: + with patch('langate.settings.netcontrol.get_mac') as mock_get_mac: mock_get_mac.return_value = new_data['mac'] response = self.client.patch(reverse('device-detail', args=[self.device.pk]), new_data) self.device.refresh_from_db() @@ -478,7 +489,8 @@ def test_get_user_device_list_unauthorized(self): response = self.client.get(reverse('user-devices')) self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN) - def test_post_device_success(self): + @patch('langate.settings.netcontrol.connect_user', return_value=None) + def test_post_device_success(self, mock_connect_user): """ Test the creation of a device """ @@ -488,7 +500,7 @@ def test_post_device_success(self): 'mac': '00:11:22:33:44:57', 'name': 'new_name' } - with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: + with patch('langate.settings.netcontrol.get_mac') as mock_get_mac: mock_get_mac.return_value = new_data['mac'] response = self.client.post(reverse('device-list'), new_data) self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -496,7 +508,11 @@ def test_post_device_success(self): device = Device.objects.get(mac='00:11:22:33:44:57') self.assertEqual(device.name, 'new_name') - def test_post_user_device_success(self): + @patch('langate.settings.netcontrol.disconnect_user', return_value=None) + @patch('langate.settings.netcontrol.connect_user', return_value=None) + @patch('langate.settings.netcontrol.set_mark', return_value=None) + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:57") + def test_post_user_device_success(self, mock_get_mac, mock_set_mark, mock_connect_user, mock_disconnect_user): """ Test the creation of a user device """ @@ -507,14 +523,11 @@ def test_post_user_device_success(self): 'name': 'new_name', 'user': self.user.id, } + response = self.client.post(reverse('device-list'), new_data) + self.assertEqual(response.status_code, status.HTTP_201_CREATED) - with patch('langate.network.models.netcontrol.get_mac') as mock_get_mac: - mock_get_mac.return_value = "00:11:22:33:44:57" - response = self.client.post(reverse('device-list'), new_data) - self.assertEqual(response.status_code, status.HTTP_201_CREATED) - - device = UserDevice.objects.get(mac='00:11:22:33:44:57') - self.assertEqual(device.name, 'new_name') + device = UserDevice.objects.get(mac='00:11:22:33:44:57') + self.assertEqual(device.name, 'new_name') def test_post_device_invalid_data(self): """ diff --git a/backend/langate/user/tests.py b/backend/langate/user/tests.py index e394df6..50d1e7d 100644 --- a/backend/langate/user/tests.py +++ b/backend/langate/user/tests.py @@ -110,7 +110,9 @@ def send_valid_data(data): } ) - def test_login_account(self): + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:55") + @patch('langate.settings.netcontrol.connect_user', return_value = None) + def test_login_account(self, mock_connect_user, mock_get_mac): """ Test that when everything is ok, an user is able to login """ @@ -123,20 +125,15 @@ def test_login_account(self): def send_valid_data(data): # Add HTTP_X_FORWARDED_FOR to simulate a request from a client - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } - request = self.client.post( - "/user/login/", - data, - format="json", - HTTP_X_FORWARDED_FOR="127.0.0.1" - ) - - self.assertEqual(request.status_code, 200) - self.assertTrue("sessionid" in self.client.cookies) + request = self.client.post( + "/user/login/", + data, + format="json", + HTTP_X_FORWARDED_FOR="127.0.0.1" + ) + + self.assertEqual(request.status_code, 200) + self.assertTrue("sessionid" in self.client.cookies) send_valid_data( { @@ -171,43 +168,33 @@ def setUp(self): self.admin.role = Role.ADMIN self.admin.save() - def test_get_user(self): + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:55") + @patch('langate.settings.netcontrol.connect_user', return_value = None) + def test_get_user(self, mock_connect_user, mock_get_mac): """ Test that the user can get his own information """ - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } - self.client.force_authenticate(user=User.objects.get(username="randomplayer")) - request = self.client.get("/user/me/", HTTP_X_FORWARDED_FOR="127.0.0.1") + self.client.force_authenticate(user=User.objects.get(username="randomplayer")) + request = self.client.get("/user/me/", HTTP_X_FORWARDED_FOR="127.0.0.1") - self.assertEqual(request.status_code, 200) - self.assertEqual(request.data["username"], "randomplayer") + self.assertEqual(request.status_code, 200) + self.assertEqual(request.data["username"], "randomplayer") - def test_get_user_not_logged_in(self): + @patch('langate.settings.netcontrol.get_mac', return_value="00:11:22:33:44:55") + def test_get_user_not_logged_in(self, mock_get_mac): """ Test that the user can't get his own information if he's not logged in """ - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } - request = self.client.get("/user/me/") + request = self.client.get("/user/me/") - self.assertEqual(request.status_code, 403) + self.assertEqual(request.status_code, 403) def test_get_user_not_found(self): """ Test that the user can't get his own information if he's not logged in """ - with patch('langate.network.models.netcontrol.query') as mock_query: - mock_query.return_value = { - "success": True, - "mac": "00:11:22:33:44:55", - } + with patch('langate.settings.netcontrol.get_mac') as mock_get_mac: + mock_get_mac.return_value = "00:11:22:33:44:55" self.client.force_authenticate(user=User.objects.get(username="randomplayer")) request = self.client.get("/user/unknown/") diff --git a/backend/langate/user/views.py b/backend/langate/user/views.py index 21744a7..2a7f15e 100644 --- a/backend/langate/user/views.py +++ b/backend/langate/user/views.py @@ -1,4 +1,6 @@ """User module API Endpoints""" +import requests + from functools import reduce from operator import or_ @@ -102,38 +104,42 @@ def get(self, request): client_ip = request.META.get('HTTP_X_FORWARDED_FOR') if not user_devices.filter(ip=client_ip).exists(): - # If the user is still logged in but the device is not registered on the network, - # we register it. - r = netcontrol.get_mac(client_ip) - client_mac = r["mac"] - try: - # The following should never raise a MultipleObjectsReturned exception - # because it would mean that there are more than one devices - # already registered with the same MAC. - - device = UserDevice.objects.get(mac=client_mac) - # If the device MAC is already registered on the network but with a different IP, - # This could happen if the DHCP has changed the IP of the client. - - # * If the registered device is owned by another user, we delete the old device and we register the new one. - if device.user != request.user: - if user_devices.count() >= request.user.max_device_nb: - user["too_many_devices"] = True - return Response(user, status=status.HTTP_200_OK) - DeviceManager.delete_user_device(device) - - DeviceManager.create_user_device(request.user, client_ip) - # * If the registered device is owned by the requesting user, we change the IP of the registered device. - else: - device.ip = client_ip - device.save() - - except UserDevice.DoesNotExist: - # If the device is not registered on the network, we register it. - if user_devices.count() >= request.user.max_device_nb: - user["too_many_devices"] = True - return Response(user, status=status.HTTP_200_OK) - DeviceManager.create_user_device(request.user, client_ip) + # If the user is still logged in but the device is not registered on the network, + # we register it. + try: + client_mac = netcontrol.get_mac(client_ip) + except requests.HTTPError as e: + raise ValidationError( + _("Could not get MAC address") + ) from e + try: + # The following should never raise a MultipleObjectsReturned exception + # because it would mean that there are more than one devices + # already registered with the same MAC. + + device = UserDevice.objects.get(mac=client_mac) + # If the device MAC is already registered on the network but with a different IP, + # This could happen if the DHCP has changed the IP of the client. + + # * If the registered device is owned by another user, we delete the old device and we register the new one. + if device.user != request.user: + if user_devices.count() >= request.user.max_device_nb: + user["too_many_devices"] = True + return Response(user, status=status.HTTP_200_OK) + DeviceManager.delete_user_device(device) + + DeviceManager.create_user_device(request.user, client_ip) + # * If the registered device is owned by the requesting user, we change the IP of the registered device. + else: + device.ip = client_ip + device.save() + + except UserDevice.DoesNotExist: + # If the device is not registered on the network, we register it. + if user_devices.count() >= request.user.max_device_nb: + user["too_many_devices"] = True + return Response(user, status=status.HTTP_200_OK) + DeviceManager.create_user_device(request.user, client_ip) user_devices = UserDevice.objects.filter(user=request.user) @@ -228,8 +234,12 @@ def post(self, request): # If this device is not registered on the network, we register it. if not user_devices.filter(ip=client_ip).exists(): - r = netcontrol.get_mac(client_ip) - client_mac = r["mac"] + try: + client_mac = netcontrol.get_mac(client_ip) + except requests.HTTPError as e: + raise ValidationError( + _("Could not get MAC address") + ) from e try: # The following should never raise a MultipleObjectsReturned exception # because it would mean that there are more than one devices From f71f477ddd3823f8d0a6ad5d3467cce10f467e26 Mon Sep 17 00:00:00 2001 From: Ecnama Date: Mon, 21 Oct 2024 08:32:56 +0200 Subject: [PATCH 8/8] Change args in Netcontrol.request --- backend/langate/modules/netcontrol.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/backend/langate/modules/netcontrol.py b/backend/langate/modules/netcontrol.py index 1df26da..f40d34f 100644 --- a/backend/langate/modules/netcontrol.py +++ b/backend/langate/modules/netcontrol.py @@ -17,25 +17,17 @@ def request(self, endpoint='', args={}): """ response = None - # Construct the data to be sent in the request - if len(args) == 1: - data = '/' + next(iter(args.items()))[1] - elif len(args) > 1: - data = '?' + "&".join([f"{key}={value}" for key, value in args.items()]) - else: - data = '' - # Make the request try: # Check the type of request if endpoint in GET_REQUESTS: - response = requests.get(self.REQUEST_URL + endpoint + data) + response = requests.get(self.REQUEST_URL + endpoint, params=args) elif endpoint in POST_REQUESTS: - response = requests.post(self.REQUEST_URL + endpoint + data) + response = requests.post(self.REQUEST_URL + endpoint, params=args) elif endpoint in DELETE_REQUESTS: - response = requests.delete(self.REQUEST_URL + endpoint + data) + response = requests.delete(self.REQUEST_URL + endpoint, params=args) elif endpoint in PUT_REQUESTS: - response = requests.put(self.REQUEST_URL + endpoint + data) + response = requests.put(self.REQUEST_URL + endpoint, params=args) response.raise_for_status() return response.json()