diff --git a/CHANGELOG.md b/CHANGELOG.md index bc3a3f80b..844f15968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Split getting a single preference by name from `get_preferences` method into `get_preference` [PR 85](https://github.com/greenbone/python-gvm/pull/85) * Added an explicit `create_container_task` method [PR 108](https://github.com/greenbone/python-gvm/pull/108) +* Added Gmpv8 version of create_tag with resource_filter parameter and + plural resource_ids parameter [PR 115](https://github.com/greenbone/python-gvm/pull/115) +* Added Gmpv8 version of modify_tag with resource_action parameter, + resource_filter parameter, plural resource_ids parameter [PR 115](https://github.com/greenbone/python-gvm/pull/115) ### Changed * Aligned ALIVE_TESTS declaration with list from GSA [PR 93](https://github.com/greenbone/python-gvm/pull/93) diff --git a/gvm/protocols/gmpv7.py b/gvm/protocols/gmpv7.py index d6eb7ac03..c01249654 100644 --- a/gvm/protocols/gmpv7.py +++ b/gvm/protocols/gmpv7.py @@ -5269,7 +5269,7 @@ def modify_tag( name (str, optional): Name of the tag. value (str, optional): Value of the tag. active (boolean, optional): Whether the tag is active. - resource_id (str, optional): IDs of the resource to which to + resource_id (str, optional): ID of the resource to which to attach the tag. Required if resource_type is set. resource_type (str, optional): Type of the resource to which to attach the tag. Required if resource_id is set. diff --git a/gvm/protocols/gmpv8.py b/gvm/protocols/gmpv8.py index 1a74adc90..ce7aa10d8 100644 --- a/gvm/protocols/gmpv8.py +++ b/gvm/protocols/gmpv8.py @@ -392,3 +392,159 @@ def modify_credential( _xmlkey.add_element("public", public_key) return self._send_xml_command(cmd) + + def create_tag( + self, + name, + resource_type, + *, + resource_filter=None, + resource_ids=None, + value=None, + comment=None, + active=None + ): + """Create a tag. + + Arguments: + name (str): Name of the tag. A full tag name consisting of + namespace and predicate e.g. `foo:bar`. + resource_type (str): Entity type the tag is to be attached + to. + resource_filter (str, optional) Filter term to select + resources the tag is to be attached to. Either + resource_filter or resource_ids must be provided. + resource_ids (list, optional): IDs of the resources the + tag is to be attached to. Either resource_filter or + resource_ids must be provided. + value (str, optional): Value associated with the tag. + comment (str, optional): Comment for the tag. + active (boolean, optional): Whether the tag should be + active. + + Returns: + The response. See :py:meth:`send_command` for details. + """ + if not name: + raise RequiredArgument("create_tag requires name argument") + + if not resource_filter and not resource_ids: + raise RequiredArgument( + "create_tag requires resource_filter or resource_ids argument" + ) + + if not resource_type: + raise RequiredArgument("create_tag requires resource_type argument") + + cmd = XmlCommand('create_tag') + cmd.add_element('name', name) + + _xmlresources = cmd.add_element("resources") + if resource_filter is not None: + _xmlresources.set_attribute("filter", resource_filter) + + for resource_id in resource_ids or []: + _xmlresources.add_element( + "resource", attrs={"id": str(resource_id)} + ) + + _xmlresources.add_element("type", resource_type) + + if comment: + cmd.add_element("comment", comment) + + if value: + cmd.add_element("value", value) + + if active is not None: + if active: + cmd.add_element("active", "1") + else: + cmd.add_element("active", "0") + + return self._send_xml_command(cmd) + + def modify_tag( + self, + tag_id, + *, + comment=None, + name=None, + value=None, + active=None, + resource_action=None, + resource_type=None, + resource_filter=None, + resource_ids=None + ): + """Modifies an existing tag. + + Arguments: + tag_id (str): UUID of the tag. + comment (str, optional): Comment to add to the tag. + name (str, optional): Name of the tag. + value (str, optional): Value of the tag. + active (boolean, optional): Whether the tag is active. + resource_action (str, optional) Whether to add or remove + resources instead of overwriting. One of '', 'add', + 'set' or 'remove'. + resource_type (str, optional): Type of the resources to + which to attach the tag. Required if resource_filter + or resource_ids is set. + resource_filter (str, optional) Filter term to select + resources the tag is to be attached to. Required if + resource_type is set unless resource_ids is set. + resource_ids (list, optional): IDs of the resources to + which to attach the tag. Required if resource_type is + set unless resource_filter is set. + + Returns: + The response. See :py:meth:`send_command` for details. + """ + if not tag_id: + raise RequiredArgument("modify_tag requires a tag_id element") + + cmd = XmlCommand("modify_tag") + cmd.set_attribute("tag_id", str(tag_id)) + + if comment: + cmd.add_element("comment", comment) + + if name: + cmd.add_element("name", name) + + if value: + cmd.add_element("value", value) + + if active is not None: + cmd.add_element("active", _to_bool(active)) + + if resource_action or resource_filter or resource_ids or resource_type: + if not resource_filter and not resource_ids: + raise RequiredArgument( + "modify_tag requires resource_filter or resource_ids " + "argument when resource_action or resource_type is set" + ) + + if not resource_type: + raise RequiredArgument( + "modify_tag requires resource_type argument when " + "resource_action or resource_filter or resource_ids " + "is set" + ) + + _xmlresources = cmd.add_element("resources") + if resource_action is not None: + _xmlresources.set_attribute("action", resource_action) + + if resource_filter is not None: + _xmlresources.set_attribute("filter", resource_filter) + + for resource_id in resource_ids or []: + _xmlresources.add_element( + "resource", attrs={"id": str(resource_id)} + ) + + _xmlresources.add_element("type", resource_type) + + return self._send_xml_command(cmd) diff --git a/tests/protocols/gmpv8/test_create_tag.py b/tests/protocols/gmpv8/test_create_tag.py new file mode 100644 index 000000000..622776542 --- /dev/null +++ b/tests/protocols/gmpv8/test_create_tag.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2018 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from gvm.errors import RequiredArgument +from gvm.protocols.gmpv8 import Gmp + +from .. import MockConnection + + +class GmpCreateTagTestCase(unittest.TestCase): + def setUp(self): + self.connection = MockConnection() + self.gmp = Gmp(self.connection) + + def test_create_tag_missing_name(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name=None, resource_ids=['foo'], resource_type='task' + ) + + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name='', resource_ids=['foo'], resource_type='task' + ) + + def test_create_tag_missing_resource_filter_and_ids(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name='foo', + resource_type='task', + resource_filter=None, + resource_ids=None + ) + + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name='foo', + resource_type='task', + resource_filter=None, + resource_ids=[] + ) + + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name='foo', + resource_type='task' + ) + + def test_create_tag_missing_resource_type(self): + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name='foo', + resource_type=None, + resource_filter=None, + resource_ids=['foo'] + ) + + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name='foo', + resource_type=None, + resource_filter="name=foo", + resource_ids=None + ) + + with self.assertRaises(RequiredArgument): + self.gmp.create_tag( + name='foo', + resource_type='', + resource_ids=['foo'] + ) + + def test_create_tag_with_resource_filter(self): + self.gmp.create_tag( + name='foo', resource_filter='name=foo', resource_type='task' + ) + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + 'task' + '' + '' + ) + + def test_create_tag_with_resource_ids(self): + self.gmp.create_tag( + name='foo', resource_ids=['foo'], resource_type='task' + ) + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + '' + 'task' + '' + '' + ) + + self.gmp.create_tag( + name='foo', resource_ids=['foo', 'bar'], resource_type='task' + ) + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + '' + '' + 'task' + '' + '' + ) + + def test_create_tag_with_comment(self): + self.gmp.create_tag( + name='foo', + resource_ids=['foo'], + resource_type='task', + comment='bar' + ) + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + '' + 'task' + '' + 'bar' + '' + ) + + def test_create_tag_with_value(self): + self.gmp.create_tag( + name='foo', resource_ids=['foo'], resource_type='task', value='bar' + ) + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + '' + 'task' + '' + 'bar' + '' + ) + + def test_create_tag_with_active(self): + self.gmp.create_tag( + name='foo', resource_ids=['foo'], resource_type='task', active=True + ) + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + '' + 'task' + '' + '1' + '' + ) + + self.gmp.create_tag( + name='foo', resource_ids=['foo'], resource_type='task', active=False + ) + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + '' + 'task' + '' + '0' + '' + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/protocols/gmpv8/test_modify_tag.py b/tests/protocols/gmpv8/test_modify_tag.py new file mode 100644 index 000000000..8029b4fd6 --- /dev/null +++ b/tests/protocols/gmpv8/test_modify_tag.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2018 Greenbone Networks GmbH +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest + +from gvm.errors import RequiredArgument +from gvm.protocols.gmpv8 import Gmp + +from .. import MockConnection + + +class GmpModifyTagTestCase(unittest.TestCase): + def setUp(self): + self.connection = MockConnection() + self.gmp = Gmp(self.connection) + + def test_modify_tag(self): + self.gmp.modify_tag(tag_id='t1') + + self.connection.send.has_been_called_with('') + + def test_modify_tag_missing_tag_id(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_tag(tag_id=None) + + with self.assertRaises(RequiredArgument): + self.gmp.modify_tag(tag_id='') + + def test_modify_tag_with_comment(self): + self.gmp.modify_tag(tag_id='t1', comment='foo') + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + ) + + def test_modify_tag_with_value(self): + self.gmp.modify_tag(tag_id='t1', value='foo') + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + ) + + def test_modify_tag_with_name(self): + self.gmp.modify_tag(tag_id='t1', name='foo') + + self.connection.send.has_been_called_with( + '' + 'foo' + '' + ) + + def test_modify_tag_with_active(self): + self.gmp.modify_tag(tag_id='t1', active=True) + + self.connection.send.has_been_called_with( + '' + '1' + '' + ) + + self.gmp.modify_tag(tag_id='t1', active=False) + + self.connection.send.has_been_called_with( + '' + '0' + '' + ) + + def test_modify_tag_with_resource_filter_and_type(self): + self.gmp.modify_tag( + tag_id='t1', resource_filter='name=foo', resource_type='task' + ) + + self.connection.send.has_been_called_with( + '' + '' + 'task' + '' + '' + ) + + def test_modify_tag_with_resource_action_filter_and_type(self): + self.gmp.modify_tag( + tag_id='t1', + resource_action='set', + resource_filter='name=foo', + resource_type='task' + ) + + self.connection.send.has_been_called_with( + '' + '' + 'task' + '' + '' + ) + + def test_modify_tag_with_resource_ids_and_type(self): + self.gmp.modify_tag( + tag_id='t1', + resource_ids=['r1'], + resource_type='task' + ) + + self.connection.send.has_been_called_with( + '' + '' + '' + 'task' + '' + '' + ) + + def test_modify_tag_with_resource_action_ids_and_type(self): + self.gmp.modify_tag( + tag_id='t1', + resource_action="set", + resource_ids=['r1'], + resource_type='task' + ) + + self.connection.send.has_been_called_with( + '' + '' + '' + 'task' + '' + '' + ) + + def test_modify_tag_with_missing_resource_filter_or_ids_andtype(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_tag(tag_id='t1', resource_action='add') + + def test_modify_tag_with_missing_resource_type(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_tag(tag_id='t1', resource_ids=['r1']) + + with self.assertRaises(RequiredArgument): + self.gmp.modify_tag(tag_id='t1', resource_filter='name=foo') + + def test_modify_tag_with_missing_resource_filter_and_ids(self): + with self.assertRaises(RequiredArgument): + self.gmp.modify_tag(tag_id='t1', resource_type='task') + + +if __name__ == '__main__': + unittest.main()