From e536995768395580441950c5dc3a7f56f0ad6ec0 Mon Sep 17 00:00:00 2001 From: ozgen Date: Mon, 21 Jul 2025 10:56:09 +0200 Subject: [PATCH 1/3] Add: GMP commands for agents and agent groups in GmpNext --- gvm/protocols/gmp/_gmpnext.py | 198 +++++++++++++++++- gvm/protocols/gmp/requests/next/__init__.py | 4 + .../gmp/requests/next/_agent_groups.py | 193 +++++++++++++++++ gvm/protocols/gmp/requests/next/_agents.py | 101 +++++++++ .../gmpnext/entities/agent_groups/__init__.py | 3 + .../agent_groups/test_clone_agent_group.py | 18 ++ .../agent_groups/test_create_agent_group.py | 40 ++++ .../agent_groups/test_delete_agent_group.py | 25 +++ .../agent_groups/test_get_agent_group.py | 18 ++ .../agent_groups/test_get_agent_groups.py | 43 ++++ .../agent_groups/test_modify_agent_group.py | 69 ++++++ .../gmpnext/entities/agents/__init__.py | 3 + .../entities/agents/test_delete_agents.py | 20 ++ .../entities/agents/test_get_agents.py | 32 +++ .../entities/agents/test_modify_agents.py | 41 ++++ .../gmpnext/entities/test_agent_group.py | 48 +++++ .../protocols/gmpnext/entities/test_agents.py | 27 +++ 17 files changed, 882 insertions(+), 1 deletion(-) create mode 100644 gvm/protocols/gmp/requests/next/_agent_groups.py create mode 100644 gvm/protocols/gmp/requests/next/_agents.py create mode 100644 tests/protocols/gmpnext/entities/agent_groups/__init__.py create mode 100644 tests/protocols/gmpnext/entities/agent_groups/test_clone_agent_group.py create mode 100644 tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py create mode 100644 tests/protocols/gmpnext/entities/agent_groups/test_delete_agent_group.py create mode 100644 tests/protocols/gmpnext/entities/agent_groups/test_get_agent_group.py create mode 100644 tests/protocols/gmpnext/entities/agent_groups/test_get_agent_groups.py create mode 100644 tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py create mode 100644 tests/protocols/gmpnext/entities/agents/__init__.py create mode 100644 tests/protocols/gmpnext/entities/agents/test_delete_agents.py create mode 100644 tests/protocols/gmpnext/entities/agents/test_get_agents.py create mode 100644 tests/protocols/gmpnext/entities/agents/test_modify_agents.py create mode 100644 tests/protocols/gmpnext/entities/test_agent_group.py create mode 100644 tests/protocols/gmpnext/entities/test_agents.py diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index 63b27c74..a1166e8d 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -8,7 +8,7 @@ from .._protocol import T from ._gmp227 import GMPv227 -from .requests.next import AgentInstallers +from .requests.next import AgentGroups, AgentInstallers, Agents class GMPNext(GMPv227[T]): @@ -78,3 +78,199 @@ def get_agent_installer_file(self, agent_installer_id: EntityID) -> T: return self._send_request_and_transform_response( AgentInstallers.get_agent_installer_file(agent_installer_id) ) + + def get_agents( + self, + *, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + details: Optional[bool] = None, + ) -> T: + """Request a list of agents. + + Args: + filter_string: Filter term to use for the query. + filter_id: UUID of an existing filter to use for the query. + details: Whether to include detailed agent info. + """ + return self._send_request_and_transform_response( + Agents.get_agents( + filter_string=filter_string, + filter_id=filter_id, + details=details, + ) + ) + + def modify_agents( + self, + agent_ids: list[EntityID], + *, + authorized: Optional[bool] = None, + min_interval: Optional[int] = None, + heartbeat_interval: Optional[int] = None, + schedule: Optional[str] = None, + comment: Optional[str] = None, + ) -> T: + """Modify multiple agents + + Args: + agent_ids: List of agent UUIDs to modify + authorized: Whether the agent is authorized + min_interval: Minimum scan interval + heartbeat_interval: Interval for sending heartbeats + schedule: Cron-style schedule for agent + comment: Comment for the agents + """ + return self._send_request_and_transform_response( + Agents.modify_agents( + agent_ids=agent_ids, + authorized=authorized, + min_interval=min_interval, + heartbeat_interval=heartbeat_interval, + schedule=schedule, + comment=comment, + ) + ) + + def delete_agents(self, agent_ids: list[EntityID]) -> T: + """Delete multiple agents + + Args: + agent_ids: List of agent UUIDs to delete + """ + return self._send_request_and_transform_response( + Agents.delete_agents(agent_ids=agent_ids) + ) + + def get_agent_groups( + self, + *, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + trash: Optional[bool] = None, + ) -> T: + """Request a list of agent groups. + + Args: + filter_string: Filter expression to use. + filter_id: UUID of a predefined filter. + trash: If True, return trashed agent groups. + + Returns: + Request object to fetch agent groups. + """ + return self._send_request_and_transform_response( + AgentGroups.get_agent_groups( + filter_string=filter_string, + filter_id=filter_id, + trash=trash, + ) + ) + + def get_agent_group(self, agent_group_id: EntityID) -> T: + """Request a single agent group by ID. + + Args: + agent_group_id: UUID of the agent group. + + Raises: + RequiredArgument: If agent_group_id is not provided. + + Returns: + Request object to fetch the specific agent group. + """ + return self._send_request_and_transform_response( + AgentGroups.get_agent_group( + agent_group_id=agent_group_id, + ) + ) + + def create_agent_group( + self, + name: str, + *, + agent_ids: list[str], + comment: Optional[str] = None, + ) -> T: + """Create a new agent group. + + Args: + name: Name of the new agent group. + agent_ids: List of agent UUIDs to include in the group (required). + comment: Optional comment for the group. + + Raises: + RequiredArgument: If name or agent_ids is not provided. + """ + return self._send_request_and_transform_response( + AgentGroups.create_agent_group( + name=name, + comment=comment, + agent_ids=agent_ids, + ) + ) + + def modify_agent_group( + self, + agent_group_id: EntityID, + *, + name: Optional[str] = None, + comment: Optional[str] = None, + agent_ids: Optional[list[str]] = None, + ) -> T: + """Modify an existing agent group. + + Args: + agent_group_id: UUID of the group to modify. + name: Optional new name for the group. + comment: Optional comment for the group. + agent_ids: Optional list of agent UUIDs to set for the group. + + Raises: + RequiredArgument: If agent_group_id is not provided. + """ + return self._send_request_and_transform_response( + AgentGroups.modify_agent_group( + agent_group_id=agent_group_id, + name=name, + comment=comment, + agent_ids=agent_ids, + ) + ) + + def delete_agent_group( + self, + agent_group_id: EntityID, + ultimate: bool = False, + ) -> T: + """Delete an existing agent group. + + Args: + agent_group_id: UUID of the group to delete. + ultimate: Whether to permanently delete or move to trashcan. + + Raises: + RequiredArgument: If agent_group_id is not provided. + """ + return self._send_request_and_transform_response( + AgentGroups.delete_agent_group( + agent_group_id=agent_group_id, + ultimate=ultimate, + ) + ) + + def clone_agent_group( + self, + agent_group_id: EntityID, + ) -> T: + """Clone an existing agent group + + Args: + agent_group_id: UUID of an existing agent group to clone from + + Returns: + Request: GMP command to create a new agent group based on a copy + """ + return self._send_request_and_transform_response( + AgentGroups.clone_agent_group(agent_group_id) + ) diff --git a/gvm/protocols/gmp/requests/next/__init__.py b/gvm/protocols/gmp/requests/next/__init__.py index e60c1a17..b42ae874 100644 --- a/gvm/protocols/gmp/requests/next/__init__.py +++ b/gvm/protocols/gmp/requests/next/__init__.py @@ -2,7 +2,9 @@ # # SPDX-License-Identifier: GPL-3.0-or-later +from gvm.protocols.gmp.requests.next._agent_groups import AgentGroups from gvm.protocols.gmp.requests.next._agent_installers import AgentInstallers +from gvm.protocols.gmp.requests.next._agents import Agents from .._entity_id import EntityID from .._version import Version @@ -77,7 +79,9 @@ ) __all__ = ( + "AgentGroups", "AgentInstallers", + "Agents", "Aggregates", "AggregateStatistic", "Alerts", diff --git a/gvm/protocols/gmp/requests/next/_agent_groups.py b/gvm/protocols/gmp/requests/next/_agent_groups.py new file mode 100644 index 00000000..b7ff6fd0 --- /dev/null +++ b/gvm/protocols/gmp/requests/next/_agent_groups.py @@ -0,0 +1,193 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import Optional + +from gvm.errors import RequiredArgument +from gvm.protocols.core import Request +from gvm.protocols.gmp.requests._entity_id import EntityID +from gvm.utils import to_bool +from gvm.xml import XmlCommand + + +class AgentGroups: + @classmethod + def create_agent_group( + cls, + name: str, + *, + agent_ids: list[str], + comment: Optional[str] = None, + ) -> Request: + """Create a new agent group. + + Args: + name: Name of the new agent group. + agent_ids: List of agent UUIDs to include in the group (required). + comment: Optional comment for the group. + + Raises: + RequiredArgument: If name or agent_ids is not provided. + """ + if not name: + raise RequiredArgument( + function=cls.create_agent_group.__name__, argument="name" + ) + + if not agent_ids: + raise RequiredArgument( + function=cls.create_agent_group.__name__, argument="agent_ids" + ) + + cmd = XmlCommand("create_agent_group") + cmd.add_element("name", name) + + if comment: + cmd.add_element("comment", comment) + + agents_element = cmd.add_element("agents") + for agent_id in agent_ids: + agents_element.add_element("agent", attrs={"id": agent_id}) + + return cmd + + @classmethod + def clone_agent_group(cls, agent_group_id: EntityID) -> Request: + """Clone an existing agent group + + Args: + agent_group_id: UUID of an existing agent group to clone from + + Returns: + Request: GMP command to create a new agent group based on a copy + """ + if not agent_group_id: + raise RequiredArgument( + function=cls.clone_agent_group.__name__, + argument="agent_group_id", + ) + + cmd = XmlCommand("create_agent_group") + cmd.add_element("copy", str(agent_group_id)) + + return cmd + + @classmethod + def modify_agent_group( + cls, + agent_group_id: EntityID, + *, + name: Optional[str] = None, + comment: Optional[str] = None, + agent_ids: Optional[list[str]] = None, + ) -> Request: + """Modify an existing agent group. + + Args: + agent_group_id: UUID of the group to modify. + name: Optional new name for the group. + comment: Optional comment for the group. + agent_ids: Optional list of agent UUIDs to set for the group. + + Raises: + RequiredArgument: If agent_group_id is not provided. + """ + if not agent_group_id: + raise RequiredArgument( + function=cls.modify_agent_group.__name__, + argument="agent_group_id", + ) + + cmd = XmlCommand("modify_agent_group") + cmd.set_attribute("agent_group_id", str(agent_group_id)) + + if name: + cmd.add_element("name", name) + + if comment: + cmd.add_element("comment", comment) + + if agent_ids: + agents_element = cmd.add_element("agents") + for agent_id in agent_ids: + agents_element.add_element("agent", attrs={"id": agent_id}) + + return cmd + + @classmethod + def delete_agent_group( + cls, + agent_group_id: EntityID, + *, + ultimate: Optional[bool] = False, + ) -> Request: + """Delete an existing agent group. + + Args: + agent_group_id: UUID of the group to delete. + ultimate: Whether to permanently delete or move to trashcan. + + Raises: + RequiredArgument: If agent_group_id is not provided. + """ + if not agent_group_id: + raise RequiredArgument( + function=cls.delete_agent_group.__name__, + argument="agent_group_id", + ) + + cmd = XmlCommand("delete_agent_group") + cmd.set_attribute("agent_group_id", str(agent_group_id)) + cmd.set_attribute("ultimate", to_bool(ultimate)) + + return cmd + + @classmethod + def get_agent_groups( + cls, + *, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + trash: Optional[bool] = None, + ) -> Request: + """Request a list of agent groups. + + Args: + filter_string: Filter expression to use. + filter_id: UUID of a predefined filter. + trash: If True, return trashed agent groups. + + Returns: + Request object to fetch agent groups. + """ + cmd = XmlCommand("get_agent_groups") + cmd.add_filter(filter_string, filter_id) + + if trash is not None: + cmd.set_attribute("trash", to_bool(trash)) + + return cmd + + @classmethod + def get_agent_group(cls, agent_group_id: EntityID) -> Request: + """Request a single agent group by ID. + + Args: + agent_group_id: UUID of the agent group. + + Raises: + RequiredArgument: If agent_group_id is not provided. + + Returns: + Request object to fetch the specific agent group. + """ + if not agent_group_id: + raise RequiredArgument( + function=cls.get_agent_group.__name__, argument="agent_group_id" + ) + + cmd = XmlCommand("get_agent_groups") + cmd.set_attribute("agent_group_id", str(agent_group_id)) + + return cmd diff --git a/gvm/protocols/gmp/requests/next/_agents.py b/gvm/protocols/gmp/requests/next/_agents.py new file mode 100644 index 00000000..47b7db96 --- /dev/null +++ b/gvm/protocols/gmp/requests/next/_agents.py @@ -0,0 +1,101 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from typing import Optional + +from gvm.errors import RequiredArgument +from gvm.protocols.core import Request +from gvm.protocols.gmp.requests._entity_id import EntityID +from gvm.utils import to_bool +from gvm.xml import XmlCommand + + +class Agents: + @classmethod + def get_agents( + cls, + *, + filter_string: Optional[str] = None, + filter_id: Optional[EntityID] = None, + details: Optional[bool] = None, + ) -> Request: + """Request a list of agents. + + Args: + filter_string: Filter term to use for the query. + filter_id: UUID of an existing filter to use for the query. + details: Whether to include detailed agent info. + """ + cmd = XmlCommand("get_agents") + cmd.add_filter(filter_string, filter_id) + + if details is not None: + cmd.set_attribute("details", to_bool(details)) + + return cmd + + @classmethod + def modify_agents( + cls, + agent_ids: list[EntityID], + *, + authorized: Optional[bool] = None, + min_interval: Optional[int] = None, + heartbeat_interval: Optional[int] = None, + schedule: Optional[str] = None, + comment: Optional[str] = None, + ) -> Request: + """Modify multiple agents + + Args: + agent_ids: List of agent UUIDs to modify + authorized: Whether the agent is authorized + min_interval: Minimum scan interval + heartbeat_interval: Interval for sending heartbeats + schedule: Cron-style schedule for agent + comment: Comment for the agents + """ + if not agent_ids: + raise RequiredArgument( + function=cls.modify_agents.__name__, argument="agent_ids" + ) + + cmd = XmlCommand("modify_agents") + xml_agents = cmd.add_element("agents") + + for agent_id in agent_ids: + xml_agents.add_element("agent", attrs={"id": agent_id}) + + if authorized is not None: + cmd.add_element("authorized", to_bool(authorized)) + if min_interval is not None: + cmd.add_element("min_interval", str(min_interval)) + if heartbeat_interval is not None: + cmd.add_element("heartbeat_interval", str(heartbeat_interval)) + if schedule: + cmd.add_element("schedule", schedule) + if comment: + cmd.add_element("comment", comment) + + return cmd + + @classmethod + def delete_agents(cls, agent_ids: list[EntityID]) -> Request: + """Delete multiple agents + + Args: + agent_ids: List of agent UUIDs to delete + """ + if not agent_ids: + raise RequiredArgument( + function=cls.delete_agents.__name__, argument="agent_ids" + ) + + cmd = XmlCommand("delete_agents") + xml_agents = cmd.add_element("agents") + + for agent_id in agent_ids: + xml_agents.add_element("agent", attrs={"id": agent_id}) + + return cmd diff --git a/tests/protocols/gmpnext/entities/agent_groups/__init__.py b/tests/protocols/gmpnext/entities/agent_groups/__init__.py new file mode 100644 index 00000000..7cb84d4c --- /dev/null +++ b/tests/protocols/gmpnext/entities/agent_groups/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_clone_agent_group.py b/tests/protocols/gmpnext/entities/agent_groups/test_clone_agent_group.py new file mode 100644 index 00000000..21af153f --- /dev/null +++ b/tests/protocols/gmpnext/entities/agent_groups/test_clone_agent_group.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gvm.errors import RequiredArgument + + +class GmpCloneAgentGroupTestMixin: + def test_clone_agent_group(self): + self.gmp.clone_agent_group(agent_group_id="group-1") + + self.connection.send.has_been_called_with( + b"group-1" + ) + + def test_clone_agent_group_without_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.clone_agent_group(agent_group_id=None) diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py b/tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py new file mode 100644 index 00000000..a6b9d1f5 --- /dev/null +++ b/tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py @@ -0,0 +1,40 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gvm.errors import RequiredArgument + + +class GmpCreateAgentGroupTestMixin: + def test_create_agent_group(self): + self.gmp.create_agent_group( + name="ExampleGroup", + agent_ids=["agent-1", "agent-2"], + comment="Sample comment", + ) + + self.connection.send.has_been_called_with( + b"" + b"ExampleGroup" + b"Sample comment" + b'' + b"" + ) + + def test_create_agent_group_without_name(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_agent_group(name="", agent_ids=["agent-1"]) + + def test_create_agent_group_without_agents(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_agent_group(name="Group", agent_ids=[]) + + def test_create_agent_group_without_comment(self): + self.gmp.create_agent_group(name="GroupX", agent_ids=["a1", "a2"]) + + self.connection.send.has_been_called_with( + b"" + b"GroupX" + b'' + b"" + ) diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_delete_agent_group.py b/tests/protocols/gmpnext/entities/agent_groups/test_delete_agent_group.py new file mode 100644 index 00000000..70d9322e --- /dev/null +++ b/tests/protocols/gmpnext/entities/agent_groups/test_delete_agent_group.py @@ -0,0 +1,25 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gvm.errors import RequiredArgument + + +class GmpDeleteAgentGroupTestMixin: + def test_delete_agent_group_soft(self): + self.gmp.delete_agent_group(agent_group_id="group-1", ultimate=False) + + self.connection.send.has_been_called_with( + b'' + ) + + def test_delete_agent_group_hard(self): + self.gmp.delete_agent_group(agent_group_id="group-1", ultimate=True) + + self.connection.send.has_been_called_with( + b'' + ) + + def test_delete_agent_group_without_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.delete_agent_group(agent_group_id=None) diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_get_agent_group.py b/tests/protocols/gmpnext/entities/agent_groups/test_get_agent_group.py new file mode 100644 index 00000000..a066dc1f --- /dev/null +++ b/tests/protocols/gmpnext/entities/agent_groups/test_get_agent_group.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gvm.errors import RequiredArgument + + +class GmpGetAgentGroupTestMixin: + def test_get_agent_group(self): + self.gmp.get_agent_group(agent_group_id="group-1") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_agent_group_without_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.get_agent_group(agent_group_id=None) diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_get_agent_groups.py b/tests/protocols/gmpnext/entities/agent_groups/test_get_agent_groups.py new file mode 100644 index 00000000..203a3417 --- /dev/null +++ b/tests/protocols/gmpnext/entities/agent_groups/test_get_agent_groups.py @@ -0,0 +1,43 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + + +class GmpGetAgentGroupsTestMixin: + def test_get_agent_groups(self): + self.gmp.get_agent_groups() + + self.connection.send.has_been_called_with(b"") + + def test_get_agent_groups_with_filter_string(self): + self.gmp.get_agent_groups(filter_string="name=group") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_agent_groups_with_filter_id(self): + self.gmp.get_agent_groups(filter_id="filter-1") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_agent_groups_with_trash(self): + self.gmp.get_agent_groups(trash=True) + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_agent_groups_with_trash_false(self): + self.gmp.get_agent_groups(trash=False) + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_agent_groups_without_trash(self): + self.gmp.get_agent_groups() + + self.connection.send.has_been_called_with(b"") diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py b/tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py new file mode 100644 index 00000000..83aa3dbd --- /dev/null +++ b/tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gvm.errors import RequiredArgument + + +class GmpModifyAgentGroupTestMixin: + def test_modify_agent_group(self): + self.gmp.modify_agent_group( + agent_group_id="group-1", + name="NewName", + comment="Updated comment", + agent_ids=["agent-1", "agent-2"], + ) + + self.connection.send.has_been_called_with( + b'' + b"NewName" + b"Updated comment" + b'' + b"" + ) + + def test_modify_agent_group_without_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_agent_group(agent_group_id=None) + + def test_modify_agent_group_without_name(self): + self.gmp.modify_agent_group( + agent_group_id="group-1", + comment="Updated comment", + agent_ids=["agent-1", "agent-2"], + ) + + self.connection.send.has_been_called_with( + b'' + b"Updated comment" + b'' + b"" + ) + + def test_modify_agent_group_without_comment(self): + self.gmp.modify_agent_group( + agent_group_id="group-1", + name="NewName", + agent_ids=["agent-1", "agent-2"], + ) + + self.connection.send.has_been_called_with( + b'' + b"NewName" + b'' + b"" + ) + + def test_modify_agent_group_without_agent_ids(self): + self.gmp.modify_agent_group( + agent_group_id="group-1", + name="NewName", + comment="Updated comment", + ) + + self.connection.send.has_been_called_with( + b'' + b"NewName" + b"Updated comment" + b"" + ) diff --git a/tests/protocols/gmpnext/entities/agents/__init__.py b/tests/protocols/gmpnext/entities/agents/__init__.py new file mode 100644 index 00000000..7cb84d4c --- /dev/null +++ b/tests/protocols/gmpnext/entities/agents/__init__.py @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later diff --git a/tests/protocols/gmpnext/entities/agents/test_delete_agents.py b/tests/protocols/gmpnext/entities/agents/test_delete_agents.py new file mode 100644 index 00000000..c829de88 --- /dev/null +++ b/tests/protocols/gmpnext/entities/agents/test_delete_agents.py @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gvm.errors import RequiredArgument + + +class GmpDeleteAgentsTestMixin: + def test_delete_agents(self): + self.gmp.delete_agents(agent_ids=["agent-123", "agent-456"]) + + self.connection.send.has_been_called_with( + b"" + b'' + b"" + ) + + def test_delete_agents_without_ids(self): + with self.assertRaises(RequiredArgument): + self.gmp.delete_agents(agent_ids=[]) diff --git a/tests/protocols/gmpnext/entities/agents/test_get_agents.py b/tests/protocols/gmpnext/entities/agents/test_get_agents.py new file mode 100644 index 00000000..e9a6866c --- /dev/null +++ b/tests/protocols/gmpnext/entities/agents/test_get_agents.py @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + + +class GmpGetAgentsTestMixin: + def test_get_agents(self): + self.gmp.get_agents() + + self.connection.send.has_been_called_with(b"") + + def test_get_agents_with_filter_string(self): + self.gmp.get_agents(filter_string="name=agent") + + self.connection.send.has_been_called_with( + b'' + ) + + def test_get_agents_with_filter_id(self): + self.gmp.get_agents(filter_id="f1") + + self.connection.send.has_been_called_with(b'') + + def test_get_agents_with_details_true(self): + self.gmp.get_agents(details=True) + + self.connection.send.has_been_called_with(b'') + + def test_get_agents_with_details_false(self): + self.gmp.get_agents(details=False) + + self.connection.send.has_been_called_with(b'') diff --git a/tests/protocols/gmpnext/entities/agents/test_modify_agents.py b/tests/protocols/gmpnext/entities/agents/test_modify_agents.py new file mode 100644 index 00000000..50777d93 --- /dev/null +++ b/tests/protocols/gmpnext/entities/agents/test_modify_agents.py @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later + +from gvm.errors import RequiredArgument + + +class GmpModifyAgentsTestMixin: + def test_modify_agents_basic(self): + self.gmp.modify_agents(agent_ids=["agent-123"]) + + self.connection.send.has_been_called_with( + b"" + b'' + b"" + ) + + def test_modify_agents_with_all_fields(self): + self.gmp.modify_agents( + agent_ids=["agent-123", "agent-456"], + authorized=True, + min_interval=300, + heartbeat_interval=600, + schedule="@every 6h", + comment="Updated agents", + ) + + self.connection.send.has_been_called_with( + b"" + b'' + b"1" + b"300" + b"600" + b"@every 6h" + b"Updated agents" + b"" + ) + + def test_modify_agents_without_ids(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_agents(agent_ids=[]) diff --git a/tests/protocols/gmpnext/entities/test_agent_group.py b/tests/protocols/gmpnext/entities/test_agent_group.py new file mode 100644 index 00000000..77f4b9b6 --- /dev/null +++ b/tests/protocols/gmpnext/entities/test_agent_group.py @@ -0,0 +1,48 @@ +# SPDX-FileCopyrightText: 2023-2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from ...gmpnext import GMPTestCase +from .agent_groups.test_clone_agent_group import ( + GmpCloneAgentGroupTestMixin, +) +from .agent_groups.test_create_agent_group import ( + GmpCreateAgentGroupTestMixin, +) +from .agent_groups.test_delete_agent_group import ( + GmpDeleteAgentGroupTestMixin, +) +from .agent_groups.test_get_agent_group import ( + GmpGetAgentGroupTestMixin, +) +from .agent_groups.test_get_agent_groups import ( + GmpGetAgentGroupsTestMixin, +) +from .agent_groups.test_modify_agent_group import ( + GmpModifyAgentGroupTestMixin, +) + + +class GMPGetAgentGroupsTestCase(GmpGetAgentGroupsTestMixin, GMPTestCase): + pass + + +class GMPGetAgentGroupTestCase(GmpGetAgentGroupTestMixin, GMPTestCase): + pass + + +class GMPCreateAgentGroupTestCase(GmpCreateAgentGroupTestMixin, GMPTestCase): + pass + + +class GMPCloneAgentGroupTestCase(GmpCloneAgentGroupTestMixin, GMPTestCase): + pass + + +class GMPModifyAgentGroupTestCase(GmpModifyAgentGroupTestMixin, GMPTestCase): + pass + + +class GMPDeleteAgentGroupTestCase(GmpDeleteAgentGroupTestMixin, GMPTestCase): + pass diff --git a/tests/protocols/gmpnext/entities/test_agents.py b/tests/protocols/gmpnext/entities/test_agents.py new file mode 100644 index 00000000..accf9e5f --- /dev/null +++ b/tests/protocols/gmpnext/entities/test_agents.py @@ -0,0 +1,27 @@ +# SPDX-FileCopyrightText: 2023-2025 Greenbone AG +# +# SPDX-License-Identifier: GPL-3.0-or-later +# + +from ...gmpnext import GMPTestCase +from .agents.test_delete_agents import ( + GmpDeleteAgentsTestMixin, +) +from .agents.test_get_agents import ( + GmpGetAgentsTestMixin, +) +from .agents.test_modify_agents import ( + GmpModifyAgentsTestMixin, +) + + +class GMPGetAgentsTestCase(GmpGetAgentsTestMixin, GMPTestCase): + pass + + +class GMPModifyAgentsTestCase(GmpModifyAgentsTestMixin, GMPTestCase): + pass + + +class GMPDeleteAgentsTestCase(GmpDeleteAgentsTestMixin, GMPTestCase): + pass From bb7a211e0571bf89bebefc9d051341218d423f7d Mon Sep 17 00:00:00 2001 From: ozgen mehmet Date: Wed, 23 Jul 2025 11:03:52 +0200 Subject: [PATCH 2/3] Update gvm/protocols/gmp/requests/next/_agent_groups.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Björn Ricks --- gvm/protocols/gmp/requests/next/_agent_groups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gvm/protocols/gmp/requests/next/_agent_groups.py b/gvm/protocols/gmp/requests/next/_agent_groups.py index b7ff6fd0..0753ef23 100644 --- a/gvm/protocols/gmp/requests/next/_agent_groups.py +++ b/gvm/protocols/gmp/requests/next/_agent_groups.py @@ -16,8 +16,8 @@ class AgentGroups: def create_agent_group( cls, name: str, - *, agent_ids: list[str], + *, comment: Optional[str] = None, ) -> Request: """Create a new agent group. From d0485a90b7ee470521e43f8dfecf4732325fce93 Mon Sep 17 00:00:00 2001 From: ozgen mehmet Date: Wed, 23 Jul 2025 11:03:59 +0200 Subject: [PATCH 3/3] Update gvm/protocols/gmp/_gmpnext.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Björn Ricks --- gvm/protocols/gmp/_gmpnext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index a1166e8d..7584d896 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -188,8 +188,8 @@ def get_agent_group(self, agent_group_id: EntityID) -> T: def create_agent_group( self, name: str, - *, agent_ids: list[str], + *, comment: Optional[str] = None, ) -> T: """Create a new agent group.