diff --git a/monkey/monkey_island/cc/models/__init__.py b/monkey/monkey_island/cc/models/__init__.py index 5ed812b7d..319b7b34f 100644 --- a/monkey/monkey_island/cc/models/__init__.py +++ b/monkey/monkey_island/cc/models/__init__.py @@ -9,3 +9,4 @@ from .pba_results import PbaResults from monkey_island.cc.models.report.report import Report from .simulation import Simulation, SimulationSchema, IslandMode from .user_credentials import UserCredentials +from .machine import Machine diff --git a/monkey/monkey_island/cc/models/machine.py b/monkey/monkey_island/cc/models/machine.py new file mode 100644 index 000000000..c06c6b086 --- /dev/null +++ b/monkey/monkey_island/cc/models/machine.py @@ -0,0 +1,17 @@ +from ipaddress import IPv4Interface +from typing import Optional, Sequence + +from pydantic import Field, PositiveInt + +from common import OperatingSystems + +from .base_models import MutableBaseModel + + +class Machine(MutableBaseModel): + id: PositiveInt = Field(..., allow_mutation=False) + node_id: Optional[PositiveInt] + network_interfaces: Sequence[IPv4Interface] + operating_system: OperatingSystems + operating_system_version: str + hostname: str diff --git a/monkey/tests/unit_tests/monkey_island/cc/models/test_machine.py b/monkey/tests/unit_tests/monkey_island/cc/models/test_machine.py new file mode 100644 index 000000000..dc0ccd45d --- /dev/null +++ b/monkey/tests/unit_tests/monkey_island/cc/models/test_machine.py @@ -0,0 +1,153 @@ +import uuid +from ipaddress import IPv4Interface +from types import MappingProxyType + +import pytest + +from common import OperatingSystems +from monkey_island.cc.models import Machine + +MACHINE_OBJECT_DICT = MappingProxyType( + { + "id": 1, + "node_id": uuid.getnode(), + "network_interfaces": [IPv4Interface("10.0.0.1/24"), IPv4Interface("192.168.5.32/16")], + "operating_system": OperatingSystems.WINDOWS, + "operating_system_version": "eXtra Problems", + "hostname": "my.host", + } +) + +MACHINE_SIMPLE_DICT = MappingProxyType( + { + "id": 1, + "node_id": uuid.getnode(), + "network_interfaces": ["10.0.0.1/24", "192.168.5.32/16"], + "operating_system": "windows", + "operating_system_version": "eXtra Problems", + "hostname": "my.host", + } +) + + +def test_constructor(): + # Raises exception_on_failure + Machine(**MACHINE_OBJECT_DICT) + + +def test_from_dict(): + # Raises exception_on_failure + Machine(**MACHINE_SIMPLE_DICT) + + +def test_to_dict(): + m = Machine(**MACHINE_OBJECT_DICT) + + assert m.dict(simplify=True) == dict(MACHINE_SIMPLE_DICT) + + +@pytest.mark.parametrize( + "key, value", + [ + ("id", "not-an-int"), + ("node_id", "not-an-int"), + ("network_interfaces", "not-a-list"), + ("operating_system", 2.1), + ("operating_system", "bsd"), + ("operating_system_version", {}), + ("hostname", []), + ], +) +def test_construct_invalid_field__type_error(key, value): + invalid_type_dict = MACHINE_SIMPLE_DICT.copy() + invalid_type_dict[key] = value + + with pytest.raises(TypeError): + Machine(**invalid_type_dict) + + +@pytest.mark.parametrize( + "key, value", + [ + ("id", -1), + ("node_id", 0), + ("network_interfaces", [1, "stuff", 3]), + ("network_interfaces", ["10.0.0.1/16", 2, []]), + ], +) +def test_construct_invalid_field__value_error(key, value): + invalid_type_dict = MACHINE_SIMPLE_DICT.copy() + invalid_type_dict[key] = value + + with pytest.raises(ValueError): + Machine(**invalid_type_dict) + + +def test_construct__extra_fields_forbidden(): + extra_field_dict = MACHINE_SIMPLE_DICT.copy() + extra_field_dict["extra_field"] = 99 # red balloons + + with pytest.raises(ValueError): + Machine(**extra_field_dict) + + +def test_id_immutable(): + m = Machine(**MACHINE_OBJECT_DICT) + with pytest.raises(TypeError): + m.id = 2 + + +@pytest.mark.parametrize("node_id", [None, 1, 100]) +def test_node_id_set_valid_value(node_id): + m = Machine(**MACHINE_OBJECT_DICT) + + # Raises exception_on_failure + m.node_id = node_id + + +def test_node_id_validate_on_set(): + m = Machine(**MACHINE_OBJECT_DICT) + with pytest.raises(ValueError): + m.node_id = -50 + + +def test_network_interfaces_set_valid_value(): + m = Machine(**MACHINE_OBJECT_DICT) + + # Raises exception_on_failure + m.network_interfaces = [IPv4Interface("172.1.2.3/24")] + + +def test_network_interfaces_set_invalid_value(): + m = Machine(**MACHINE_OBJECT_DICT) + + with pytest.raises(ValueError): + m.network_interfaces = [IPv4Interface("172.1.2.3/24"), None] + + +def test_operating_system_set_valid_value(): + m = Machine(**MACHINE_OBJECT_DICT) + + # Raises exception_on_failure + m.operating_system = OperatingSystems.LINUX + + +def test_operating_system_set_invalid_value(): + m = Machine(**MACHINE_OBJECT_DICT) + + with pytest.raises(ValueError): + m.operating_system = "MacOS" + + +def test_set_operating_system_version(): + m = Machine(**MACHINE_OBJECT_DICT) + + # Raises exception_on_failure + m.operating_system_version = "1234" + + +def test_set_hostname(): + m = Machine(**MACHINE_OBJECT_DICT) + + # Raises exception_on_failure + m.operating_system_version = "wopr" diff --git a/vulture_allowlist.py b/vulture_allowlist.py index 70f620492..312022dd3 100644 --- a/vulture_allowlist.py +++ b/vulture_allowlist.py @@ -209,6 +209,7 @@ _serialize_credentials # unused method (monkey/common/credentials/credentials:6 # Models _make_simulation # unused method (monkey/monkey_island/cc/models/simulation.py:19 +operating_system_version # TODO DELETE AFTER RESOURCE REFACTORING