TargetsMixin.modify_target()   F
last analyzed

Complexity

Conditions 17

Size

Total Lines 100
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 58
nop 16
dl 0
loc 100
rs 1.8
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like gvm.protocols.gmpv208.entities.targets.TargetsMixin.modify_target() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2021 Greenbone Networks GmbH
3
#
4
# SPDX-License-Identifier: GPL-3.0-or-later
5
#
6
# This program is free software: you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation, either version 3 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
18
19
20
from enum import Enum
21
from typing import Any, List, Optional
22
23
from gvm.errors import RequiredArgument, InvalidArgument, InvalidArgumentType
24
from gvm.utils import add_filter, to_bool, to_comma_list
25
from gvm.xml import XmlCommand
26
27
28
class AliveTest(Enum):
29
    """Enum for choosing an alive test"""
30
31
    SCAN_CONFIG_DEFAULT = 'Scan Config Default'
32
    ICMP_PING = 'ICMP Ping'
33
    TCP_ACK_SERVICE_PING = 'TCP-ACK Service Ping'
34
    TCP_SYN_SERVICE_PING = 'TCP-SYN Service Ping'
35
    ARP_PING = 'ARP Ping'
36
    APR_PING = 'ARP Ping'  # Alias for ARP_PING
37
    ICMP_AND_TCP_ACK_SERVICE_PING = 'ICMP & TCP-ACK Service Ping'
38
    ICMP_AND_ARP_PING = 'ICMP & ARP Ping'
39
    TCP_ACK_SERVICE_AND_ARP_PING = 'TCP-ACK Service & ARP Ping'
40
    ICMP_TCP_ACK_SERVICE_AND_ARP_PING = (  # pylint: disable=invalid-name
41
        'ICMP, TCP-ACK Service & ARP Ping'
42
    )
43
    CONSIDER_ALIVE = 'Consider Alive'
44
45
46
def get_alive_test_from_string(
47
    alive_test: Optional[str],
48
) -> Optional[AliveTest]:
49
    """Convert an alive test string into a AliveTest instance"""
50
    if not alive_test:
51
        return None
52
53
    alive_test = alive_test.lower()
54
55
    try:
56
        return AliveTest[
57
            alive_test.replace(',', '')
58
            .replace(' ', '_')
59
            .replace('-', '_')
60
            .replace('&', 'and')
61
            .upper()
62
        ]
63
    except KeyError:
64
        raise InvalidArgument(
65
            argument='alive_test',
66
            function=get_alive_test_from_string.__name__,
67
        ) from None
68
69
70
class TargetsMixin:
71
    def clone_target(self, target_id: str) -> Any:
72
        """Clone an existing target
73
74
        Arguments:
75
            target_id: UUID of an existing target to clone from
76
77
        Returns:
78
            The response. See :py:meth:`send_command` for details.
79
        """
80
        if not target_id:
81
            raise RequiredArgument(
82
                function=self.clone_target.__name__, argument='target_id'
83
            )
84
85
        cmd = XmlCommand("create_target")
86
        cmd.add_element("copy", target_id)
87
        return self._send_xml_command(cmd)
88
89
    def create_target(
90
        self,
91
        name: str,
92
        *,
93
        asset_hosts_filter: Optional[str] = None,
94
        hosts: Optional[List[str]] = None,
95
        comment: Optional[str] = None,
96
        exclude_hosts: Optional[List[str]] = None,
97
        ssh_credential_id: Optional[str] = None,
98
        ssh_credential_port: Optional[int] = None,
99
        smb_credential_id: Optional[str] = None,
100
        esxi_credential_id: Optional[str] = None,
101
        snmp_credential_id: Optional[str] = None,
102
        alive_test: Optional[AliveTest] = None,
103
        reverse_lookup_only: Optional[bool] = None,
104
        reverse_lookup_unify: Optional[bool] = None,
105
        port_range: Optional[str] = None,
106
        port_list_id: Optional[str] = None,
107
    ) -> Any:
108
        """Create a new target
109
110
        Arguments:
111
            name: Name of the target
112
            asset_hosts_filter: Filter to select target host from assets hosts
113
            hosts: List of hosts addresses to scan
114
            exclude_hosts: List of hosts addresses to exclude from scan
115
            comment: Comment for the target
116
            ssh_credential_id: UUID of a ssh credential to use on target
117
            ssh_credential_port: The port to use for ssh credential
118
            smb_credential_id: UUID of a smb credential to use on target
119
            snmp_credential_id: UUID of a snmp credential to use on target
120
            esxi_credential_id: UUID of a esxi credential to use on target
121
            alive_test: Which alive test to use
122
            reverse_lookup_only: Whether to scan only hosts that have names
123
            reverse_lookup_unify: Whether to scan only one IP when multiple IPs
124
                have the same name.
125
            port_range: Port range for the target
126
            port_list_id: UUID of the port list to use on target
127
128
        Returns:
129
            The response. See :py:meth:`send_command` for details.
130
        """
131
132
        cmd = XmlCommand("create_target")
133
        _xmlname = cmd.add_element("name", name)
134
135
        if not name:
136
            raise RequiredArgument(
137
                function=self.create_target.__name__, argument='name'
138
            )
139
140
        if asset_hosts_filter:
141
            cmd.add_element(
142
                "asset_hosts", attrs={"filter": str(asset_hosts_filter)}
143
            )
144
        elif hosts:
145
            cmd.add_element("hosts", to_comma_list(hosts))
146
        else:
147
            raise RequiredArgument(
148
                function=self.create_target.__name__,
149
                argument='hosts or asset_hosts_filter',
150
            )
151
152
        if comment:
153
            cmd.add_element("comment", comment)
154
155
        if exclude_hosts:
156
            cmd.add_element("exclude_hosts", to_comma_list(exclude_hosts))
157
158
        if ssh_credential_id:
159
            _xmlssh = cmd.add_element(
160
                "ssh_credential", attrs={"id": ssh_credential_id}
161
            )
162
            if ssh_credential_port:
163
                _xmlssh.add_element("port", str(ssh_credential_port))
164
165
        if smb_credential_id:
166
            cmd.add_element("smb_credential", attrs={"id": smb_credential_id})
167
168
        if esxi_credential_id:
169
            cmd.add_element("esxi_credential", attrs={"id": esxi_credential_id})
170
171
        if snmp_credential_id:
172
            cmd.add_element("snmp_credential", attrs={"id": snmp_credential_id})
173
174
        if alive_test:
175
            if not isinstance(alive_test, AliveTest):
176
                raise InvalidArgumentType(
177
                    function=self.create_target.__name__,
178
                    argument='alive_test',
179
                    arg_type=AliveTest.__name__,
180
                )
181
182
            cmd.add_element("alive_tests", alive_test.value)
183
184
        if reverse_lookup_only is not None:
185
            cmd.add_element("reverse_lookup_only", to_bool(reverse_lookup_only))
186
187
        if reverse_lookup_unify is not None:
188
            cmd.add_element(
189
                "reverse_lookup_unify", to_bool(reverse_lookup_unify)
190
            )
191
192
        # since 20.08 one of port_range or port_list_id is required!
193
        if not port_range and not port_list_id:
194
            raise RequiredArgument(
195
                function=self.create_target.__name__,
196
                argument='port_range or port_list_id',
197
            )
198
199
        if port_range:
200
            cmd.add_element("port_range", port_range)
201
202
        if port_list_id:
203
            cmd.add_element("port_list", attrs={"id": port_list_id})
204
205
        return self._send_xml_command(cmd)
206
207
    def delete_target(
208
        self, target_id: str, *, ultimate: Optional[bool] = False
209
    ) -> Any:
210
        """Deletes an existing target
211
212
        Arguments:
213
            target_id: UUID of the target to be deleted.
214
            ultimate: Whether to remove entirely, or to the trashcan.
215
        """
216
        if not target_id:
217
            raise RequiredArgument(
218
                function=self.delete_target.__name__, argument='target_id'
219
            )
220
221
        cmd = XmlCommand("delete_target")
222
        cmd.set_attribute("target_id", target_id)
223
        cmd.set_attribute("ultimate", to_bool(ultimate))
224
225
        return self._send_xml_command(cmd)
226
227
    def get_target(
228
        self, target_id: str, *, tasks: Optional[bool] = None
229
    ) -> Any:
230
        """Request a single target
231
232
        Arguments:
233
            target_id: UUID of an existing target
234
            tasks: Whether to include list of tasks that use the target
235
236
        Returns:
237
            The response. See :py:meth:`send_command` for details.
238
        """
239
        cmd = XmlCommand("get_targets")
240
241
        if not target_id:
242
            raise RequiredArgument(
243
                function=self.get_target.__name__, argument='target_id'
244
            )
245
246
        cmd.set_attribute("target_id", target_id)
247
248
        if tasks is not None:
249
            cmd.set_attribute("tasks", to_bool(tasks))
250
251
        return self._send_xml_command(cmd)
252
253 View Code Duplication
    def get_targets(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
254
        self,
255
        *,
256
        filter_string: Optional[str] = None,
257
        filter_id: Optional[str] = None,
258
        trash: Optional[bool] = None,
259
        tasks: Optional[bool] = None,
260
    ) -> Any:
261
        """Request a list of targets
262
263
        Arguments:
264
            filter_string: Filter term to use for the query
265
            filter_id: UUID of an existing filter to use for the query
266
            trash: Whether to get the trashcan targets instead
267
            tasks: Whether to include list of tasks that use the target
268
269
        Returns:
270
            The response. See :py:meth:`send_command` for details.
271
        """
272
        cmd = XmlCommand("get_targets")
273
274
        add_filter(cmd, filter_string, filter_id)
275
276
        if trash is not None:
277
            cmd.set_attribute("trash", to_bool(trash))
278
279
        if tasks is not None:
280
            cmd.set_attribute("tasks", to_bool(tasks))
281
282
        return self._send_xml_command(cmd)
283
284
    def modify_target(
285
        self,
286
        target_id: str,
287
        *,
288
        name: Optional[str] = None,
289
        comment: Optional[str] = None,
290
        hosts: Optional[List[str]] = None,
291
        exclude_hosts: Optional[List[str]] = None,
292
        ssh_credential_id: Optional[str] = None,
293
        ssh_credential_port: Optional[bool] = None,
294
        smb_credential_id: Optional[str] = None,
295
        esxi_credential_id: Optional[str] = None,
296
        snmp_credential_id: Optional[str] = None,
297
        alive_test: Optional[AliveTest] = None,
298
        reverse_lookup_only: Optional[bool] = None,
299
        reverse_lookup_unify: Optional[bool] = None,
300
        port_list_id: Optional[str] = None,
301
    ) -> Any:
302
        """Modifies an existing target.
303
304
        Arguments:
305
            target_id: ID of target to modify.
306
            comment: Comment on target.
307
            name: Name of target.
308
            hosts: List of target hosts.
309
            exclude_hosts: A list of hosts to exclude.
310
            ssh_credential_id: UUID of SSH credential to use on target.
311
            ssh_credential_port: The port to use for ssh credential
312
            smb_credential_id: UUID of SMB credential to use on target.
313
            esxi_credential_id: UUID of ESXi credential to use on target.
314
            snmp_credential_id: UUID of SNMP credential to use on target.
315
            port_list_id: UUID of port list describing ports to scan.
316
            alive_test: Which alive tests to use.
317
            reverse_lookup_only: Whether to scan only hosts that have names.
318
            reverse_lookup_unify: Whether to scan only one IP when multiple IPs
319
                have the same name.
320
321
        Returns:
322
            The response. See :py:meth:`send_command` for details.
323
        """
324
        if not target_id:
325
            raise RequiredArgument(
326
                function=self.modify_target.__name__, argument='target_id'
327
            )
328
329
        cmd = XmlCommand("modify_target")
330
        cmd.set_attribute("target_id", target_id)
331
332
        if comment:
333
            cmd.add_element("comment", comment)
334
335
        if name:
336
            cmd.add_element("name", name)
337
338
        if hosts:
339
            cmd.add_element("hosts", to_comma_list(hosts))
340
            if exclude_hosts is None:
341
                exclude_hosts = ['']
342
343
        if exclude_hosts:
344
            cmd.add_element("exclude_hosts", to_comma_list(exclude_hosts))
345
346
        if alive_test:
347
            if not isinstance(alive_test, AliveTest):
348
                raise InvalidArgumentType(
349
                    function=self.modify_target.__name__,
350
                    argument='alive_test',
351
                    arg_type=AliveTest.__name__,
352
                )
353
            cmd.add_element("alive_tests", alive_test.value)
354
355
        if ssh_credential_id:
356
            _xmlssh = cmd.add_element(
357
                "ssh_credential", attrs={"id": ssh_credential_id}
358
            )
359
360
            if ssh_credential_port:
361
                _xmlssh.add_element("port", str(ssh_credential_port))
362
363
        if smb_credential_id:
364
            cmd.add_element("smb_credential", attrs={"id": smb_credential_id})
365
366
        if esxi_credential_id:
367
            cmd.add_element("esxi_credential", attrs={"id": esxi_credential_id})
368
369
        if snmp_credential_id:
370
            cmd.add_element("snmp_credential", attrs={"id": snmp_credential_id})
371
372
        if reverse_lookup_only is not None:
373
            cmd.add_element("reverse_lookup_only", to_bool(reverse_lookup_only))
374
375
        if reverse_lookup_unify is not None:
376
            cmd.add_element(
377
                "reverse_lookup_unify", to_bool(reverse_lookup_unify)
378
            )
379
380
        if port_list_id:
381
            cmd.add_element("port_list", attrs={"id": port_list_id})
382
383
        return self._send_xml_command(cmd)
384