Passed
Push — master ( 8a13b5...2a5f1b )
by Jaspar
84:44 queued 83:23
created

gvm.protocols.gmpv214.gmpv214   F

Complexity

Total Complexity 95

Size/Duplication

Total Lines 723
Duplicated Lines 57.54 %

Importance

Changes 0
Metric Value
eloc 386
dl 416
loc 723
rs 2
c 0
b 0
f 0
wmc 95

8 Methods

Rating   Name   Duplication   Size   Complexity  
A GmpV214Mixin.__init__() 0 10 1
D GmpV214Mixin.create_override() 89 89 12
C GmpV214Mixin.create_note() 73 73 10
D GmpV214Mixin.modify_user() 93 93 13
C GmpV214Mixin.modify_note() 72 72 10
F GmpV214Mixin.modify_target() 0 111 18
F GmpV214Mixin.create_target() 0 125 19
D GmpV214Mixin.modify_override() 89 89 12

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like gvm.protocols.gmpv214.gmpv214 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.

1
# -*- coding: utf-8 -*-
2
# Copyright (C) 2020-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
# pylint: disable=arguments-differ, redefined-builtin, too-many-lines
20
21
"""
22
Module for communication with gvmd in
23
`Greenbone Management Protocol version 21.04`_
24
25
.. _Greenbone Management Protocol version 21.04:
26
    https://docs.greenbone.net/API/GMP/gmp-21.04.html
27
"""
28
29
from typing import Any, List, Optional, Callable
30
import numbers
31
32
from gvm.utils import deprecation
33
from gvm.xml import XmlCommand
34
from gvm.protocols.gmpv7.gmpv7 import _to_comma_list, _to_bool
35
36
from gvm.connections import GvmConnection
37
from gvm.errors import InvalidArgumentType, RequiredArgument
38
39
from gvm.protocols.base import GvmProtocol
40
41
from . import types
42
from .types import *  # pylint: disable=unused-wildcard-import, wildcard-import
43
44
_EMPTY_POLICY_ID = '085569ce-73ed-11df-83c3-002264764cea'
45
46
PROTOCOL_VERSION = (21, 4)
47
48
Severity = numbers.Real
49
50
51
class GmpV214Mixin(GvmProtocol):
52
    types = types
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable types does not seem to be defined.
Loading history...
53
54
    def __init__(
55
        self,
56
        connection: GvmConnection,
57
        *,
58
        transform: Optional[Callable[[str], Any]] = None,
59
    ):
60
        super().__init__(connection, transform=transform)
61
62
        # Is authenticated on gvmd
63
        self._authenticated = False
64
65 View Code Duplication
    def create_note(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
66
        self,
67
        text: str,
68
        nvt_oid: str,
69
        *,
70
        days_active: Optional[int] = None,
71
        hosts: Optional[List[str]] = None,
72
        port: Optional[int] = None,
73
        result_id: Optional[str] = None,
74
        severity: Optional[Severity] = None,
75
        task_id: Optional[str] = None,
76
        threat: Optional[SeverityLevel] = None,
77
    ) -> Any:
78
        """Create a new note
79
80
        Arguments:
81
            text: Text of the new note
82
            nvt_id: OID of the nvt to which note applies
83
            days_active: Days note will be active. -1 on
84
                always, 0 off
85
            hosts: A list of hosts addresses
86
            port: Port to which the note applies
87
            result_id: UUID of a result to which note applies
88
            severity: Severity to which note applies
89
            task_id: UUID of task to which note applies
90
            threat: Severity level to which note applies. Will be converted to
91
                severity.
92
93
        Returns:
94
            The response. See :py:meth:`send_command` for details.
95
        """
96
        if not text:
97
            raise RequiredArgument(
98
                function=self.create_note.__name__, argument='text'
99
            )
100
101
        if not nvt_oid:
102
            raise RequiredArgument(
103
                function=self.create_note.__name__, argument='nvt_oid'
104
            )
105
106
        cmd = XmlCommand("create_note")
107
        cmd.add_element("text", text)
108
        cmd.add_element("nvt", attrs={"oid": nvt_oid})
109
110
        if days_active is not None:
111
            cmd.add_element("active", str(days_active))
112
113
        if hosts:
114
            cmd.add_element("hosts", _to_comma_list(hosts))
115
116
        if port:
117
            cmd.add_element("port", str(port))
118
119
        if result_id:
120
            cmd.add_element("result", attrs={"id": result_id})
121
122
        if severity:
123
            cmd.add_element("severity", str(severity))
124
125
        if task_id:
126
            cmd.add_element("task", attrs={"id": task_id})
127
128
        if threat is not None:
129
            deprecation(
130
                "The threat parameter has been removed in GMP"
131
                " version {}{}".format(
132
                    self.get_protocol_version()[0],
133
                    self.get_protocol_version()[1],
134
                )
135
            )
136
137
        return self._send_xml_command(cmd)
138
139 View Code Duplication
    def create_override(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
140
        self,
141
        text: str,
142
        nvt_oid: str,
143
        *,
144
        days_active: Optional[int] = None,
145
        hosts: Optional[List[str]] = None,
146
        port: Optional[int] = None,
147
        result_id: Optional[str] = None,
148
        severity: Optional[Severity] = None,
149
        new_severity: Optional[Severity] = None,
150
        task_id: Optional[str] = None,
151
        threat: Optional[SeverityLevel] = None,
152
        new_threat: Optional[SeverityLevel] = None,
153
    ) -> Any:
154
        """Create a new override
155
156
        Arguments:
157
            text: Text of the new override
158
            nvt_id: OID of the nvt to which override applies
159
            days_active: Days override will be active. -1 on always, 0 off
160
            hosts: A list of host addresses
161
            port: Port to which the override applies
162
            result_id: UUID of a result to which override applies
163
            severity: Severity to which override applies
164
            new_severity: New severity for result
165
            task_id: UUID of task to which override applies
166
            threat: Severity level to which override applies. Will be converted
167
                to severity.
168
            new_threat: New severity level for results. Will be converted to
169
                new_severity.
170
171
        Returns:
172
            The response. See :py:meth:`send_command` for details.
173
        """
174
        if not text:
175
            raise RequiredArgument(
176
                function=self.create_override.__name__, argument='text'
177
            )
178
179
        if not nvt_oid:
180
            raise RequiredArgument(
181
                function=self.create_override.__name__, argument='nvt_oid'
182
            )
183
184
        cmd = XmlCommand("create_override")
185
        cmd.add_element("text", text)
186
        cmd.add_element("nvt", attrs={"oid": nvt_oid})
187
188
        if days_active is not None:
189
            cmd.add_element("active", str(days_active))
190
191
        if hosts:
192
            cmd.add_element("hosts", _to_comma_list(hosts))
193
194
        if port:
195
            cmd.add_element("port", str(port))
196
197
        if result_id:
198
            cmd.add_element("result", attrs={"id": result_id})
199
200
        if severity:
201
            cmd.add_element("severity", str(severity))
202
203
        if new_severity:
204
            cmd.add_element("new_severity", str(new_severity))
205
206
        if task_id:
207
            cmd.add_element("task", attrs={"id": task_id})
208
209
        if threat is not None:
210
            deprecation(
211
                "The threat parameter has been removed in GMP"
212
                " version {}{}".format(
213
                    self.get_protocol_version()[0],
214
                    self.get_protocol_version()[1],
215
                )
216
            )
217
218
        if new_threat is not None:
219
            deprecation(
220
                "The new_threat parameter has been removed in GMP"
221
                " version {}{}".format(
222
                    self.get_protocol_version()[0],
223
                    self.get_protocol_version()[1],
224
                )
225
            )
226
227
        return self._send_xml_command(cmd)
228
229
    def create_target(
230
        self,
231
        name: str,
232
        *,
233
        make_unique: Optional[bool] = None,
234
        asset_hosts_filter: Optional[str] = None,
235
        hosts: Optional[List[str]] = None,
236
        comment: Optional[str] = None,
237
        exclude_hosts: Optional[List[str]] = None,
238
        ssh_credential_id: Optional[str] = None,
239
        ssh_credential_port: Optional[int] = None,
240
        smb_credential_id: Optional[str] = None,
241
        esxi_credential_id: Optional[str] = None,
242
        snmp_credential_id: Optional[str] = None,
243
        alive_test: Optional[AliveTest] = None,
244
        allow_simultaneous_ips: Optional[bool] = None,
245
        reverse_lookup_only: Optional[bool] = None,
246
        reverse_lookup_unify: Optional[bool] = None,
247
        port_range: Optional[str] = None,
248
        port_list_id: Optional[str] = None,
249
    ) -> Any:
250
        """Create a new target
251
252
        Arguments:
253
            name: Name of the target
254
            make_unique: Append a unique suffix if the name already exists
255
            asset_hosts_filter: Filter to select target host from assets hosts
256
            hosts: List of hosts addresses to scan
257
            exclude_hosts: List of hosts addresses to exclude from scan
258
            comment: Comment for the target
259
            ssh_credential_id: UUID of a ssh credential to use on target
260
            ssh_credential_port: The port to use for ssh credential
261
            smb_credential_id: UUID of a smb credential to use on target
262
            snmp_credential_id: UUID of a snmp credential to use on target
263
            esxi_credential_id: UUID of a esxi credential to use on target
264
            alive_test: Which alive test to use
265
            allow_simultaneous_ips: Whether to scan multiple IPs of the
266
                same host simultaneously
267
            reverse_lookup_only: Whether to scan only hosts that have names
268
            reverse_lookup_unify: Whether to scan only one IP when multiple IPs
269
                have the same name.
270
            port_range: Port range for the target
271
            port_list_id: UUID of the port list to use on target
272
273
        Returns:
274
            The response. See :py:meth:`send_command` for details.
275
        """
276
        if not name:
277
            raise RequiredArgument(
278
                function=self.create_target.__name__, argument='name'
279
            )
280
281
        cmd = XmlCommand("create_target")
282
        _xmlname = cmd.add_element("name", name)
283
284
        if make_unique is not None:
285
            _xmlname.add_element("make_unique", _to_bool(make_unique))
286
287
        if asset_hosts_filter:
288
            cmd.add_element(
289
                "asset_hosts", attrs={"filter": str(asset_hosts_filter)}
290
            )
291
        elif hosts:
292
            cmd.add_element("hosts", _to_comma_list(hosts))
293
        else:
294
            raise RequiredArgument(
295
                function=self.create_target.__name__,
296
                argument='hosts or asset_hosts_filter',
297
            )
298
299
        if comment:
300
            cmd.add_element("comment", comment)
301
302
        if exclude_hosts:
303
            cmd.add_element("exclude_hosts", _to_comma_list(exclude_hosts))
304
305
        if ssh_credential_id:
306
            _xmlssh = cmd.add_element(
307
                "ssh_credential", attrs={"id": ssh_credential_id}
308
            )
309
            if ssh_credential_port:
310
                _xmlssh.add_element("port", str(ssh_credential_port))
311
312
        if smb_credential_id:
313
            cmd.add_element("smb_credential", attrs={"id": smb_credential_id})
314
315
        if esxi_credential_id:
316
            cmd.add_element("esxi_credential", attrs={"id": esxi_credential_id})
317
318
        if snmp_credential_id:
319
            cmd.add_element("snmp_credential", attrs={"id": snmp_credential_id})
320
321
        if alive_test:
322
            if not isinstance(alive_test, AliveTest):
323
                raise InvalidArgumentType(
324
                    function=self.create_target.__name__,
325
                    argument='alive_test',
326
                    arg_type=AliveTest.__name__,
327
                )
328
329
            cmd.add_element("alive_tests", alive_test.value)
330
331
        if allow_simultaneous_ips is not None:
332
            cmd.add_element(
333
                "allow_simultaneous_ips",
334
                _to_bool(allow_simultaneous_ips),
335
            )
336
337
        if reverse_lookup_only is not None:
338
            cmd.add_element(
339
                "reverse_lookup_only", _to_bool(reverse_lookup_only)
340
            )
341
342
        if reverse_lookup_unify is not None:
343
            cmd.add_element(
344
                "reverse_lookup_unify", _to_bool(reverse_lookup_unify)
345
            )
346
347
        if port_range:
348
            cmd.add_element("port_range", port_range)
349
350
        if port_list_id:
351
            cmd.add_element("port_list", attrs={"id": port_list_id})
352
353
        return self._send_xml_command(cmd)
354
355 View Code Duplication
    def modify_note(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
356
        self,
357
        note_id: str,
358
        text: str,
359
        *,
360
        days_active: Optional[int] = None,
361
        hosts: Optional[List[str]] = None,
362
        port: Optional[int] = None,
363
        result_id: Optional[str] = None,
364
        severity: Optional[Severity] = None,
365
        task_id: Optional[str] = None,
366
        threat: Optional[SeverityLevel] = None,
367
    ) -> Any:
368
        """Modifies an existing note.
369
370
        Arguments:
371
            note_id: UUID of note to modify.
372
            text: The text of the note.
373
            days_active: Days note will be active. -1 on always, 0 off.
374
            hosts: A list of hosts addresses
375
            port: Port to which note applies.
376
            result_id: Result to which note applies.
377
            severity: Severity to which note applies.
378
            task_id: Task to which note applies.
379
            threat: Threat level to which note applies. Will be converted to
380
                severity.
381
382
        Returns:
383
            The response. See :py:meth:`send_command` for details.
384
        """
385
        if not note_id:
386
            raise RequiredArgument(
387
                function=self.modify_note.__name__, argument='note_id'
388
            )
389
390
        if not text:
391
            raise RequiredArgument(
392
                function=self.modify_note.__name__, argument='text'
393
            )
394
395
        cmd = XmlCommand("modify_note")
396
        cmd.set_attribute("note_id", note_id)
397
        cmd.add_element("text", text)
398
399
        if days_active is not None:
400
            cmd.add_element("active", str(days_active))
401
402
        if hosts:
403
            cmd.add_element("hosts", _to_comma_list(hosts))
404
405
        if port:
406
            cmd.add_element("port", str(port))
407
408
        if result_id:
409
            cmd.add_element("result", attrs={"id": result_id})
410
411
        if severity:
412
            cmd.add_element("severity", str(severity))
413
414
        if task_id:
415
            cmd.add_element("task", attrs={"id": task_id})
416
417
        if threat is not None:
418
            deprecation(
419
                "The threat parameter has been removed in GMP"
420
                " version {}{}".format(
421
                    self.get_protocol_version()[0],
422
                    self.get_protocol_version()[1],
423
                )
424
            )
425
426
        return self._send_xml_command(cmd)
427
428 View Code Duplication
    def modify_override(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
429
        self,
430
        override_id: str,
431
        text: str,
432
        *,
433
        days_active: Optional[int] = None,
434
        hosts: Optional[List[str]] = None,
435
        port: Optional[int] = None,
436
        result_id: Optional[str] = None,
437
        severity: Optional[Severity] = None,
438
        new_severity: Optional[Severity] = None,
439
        task_id: Optional[str] = None,
440
        threat: Optional[SeverityLevel] = None,
441
        new_threat: Optional[SeverityLevel] = None,
442
    ) -> Any:
443
        """Modifies an existing override.
444
445
        Arguments:
446
            override_id: UUID of override to modify.
447
            text: The text of the override.
448
            days_active: Days override will be active. -1 on always,
449
                0 off.
450
            hosts: A list of host addresses
451
            port: Port to which override applies.
452
            result_id: Result to which override applies.
453
            severity: Severity to which override applies.
454
            new_severity: New severity score for result.
455
            task_id: Task to which override applies.
456
            threat: Threat level to which override applies.
457
                Will be converted to severity.
458
            new_threat: New threat level for results. Will be converted to
459
                new_severity.
460
461
        Returns:
462
            The response. See :py:meth:`send_command` for details.
463
        """
464
        if not override_id:
465
            raise RequiredArgument(
466
                function=self.modify_override.__name__, argument='override_id'
467
            )
468
        if not text:
469
            raise RequiredArgument(
470
                function=self.modify_override.__name__, argument='text'
471
            )
472
473
        cmd = XmlCommand("modify_override")
474
        cmd.set_attribute("override_id", override_id)
475
        cmd.add_element("text", text)
476
477
        if days_active is not None:
478
            cmd.add_element("active", str(days_active))
479
480
        if hosts:
481
            cmd.add_element("hosts", _to_comma_list(hosts))
482
483
        if port:
484
            cmd.add_element("port", str(port))
485
486
        if result_id:
487
            cmd.add_element("result", attrs={"id": result_id})
488
489
        if severity:
490
            cmd.add_element("severity", str(severity))
491
492
        if new_severity:
493
            cmd.add_element("new_severity", str(new_severity))
494
495
        if task_id:
496
            cmd.add_element("task", attrs={"id": task_id})
497
498
        if threat is not None:
499
            deprecation(
500
                "The threat parameter has been removed in GMP"
501
                " version {}{}".format(
502
                    self.get_protocol_version()[0],
503
                    self.get_protocol_version()[1],
504
                )
505
            )
506
507
        if new_threat is not None:
508
            deprecation(
509
                "The new_threat parameter has been removed in GMP"
510
                " version {}{}".format(
511
                    self.get_protocol_version()[0],
512
                    self.get_protocol_version()[1],
513
                )
514
            )
515
516
        return self._send_xml_command(cmd)
517
518
    def modify_target(
519
        self,
520
        target_id: str,
521
        *,
522
        name: Optional[str] = None,
523
        comment: Optional[str] = None,
524
        hosts: Optional[List[str]] = None,
525
        exclude_hosts: Optional[List[str]] = None,
526
        ssh_credential_id: Optional[str] = None,
527
        ssh_credential_port: Optional[bool] = None,
528
        smb_credential_id: Optional[str] = None,
529
        esxi_credential_id: Optional[str] = None,
530
        snmp_credential_id: Optional[str] = None,
531
        alive_test: Optional[AliveTest] = None,
532
        allow_simultaneous_ips: Optional[bool] = None,
533
        reverse_lookup_only: Optional[bool] = None,
534
        reverse_lookup_unify: Optional[bool] = None,
535
        port_list_id: Optional[str] = None,
536
    ) -> Any:
537
        """Modifies an existing target.
538
539
        Arguments:
540
            target_id: ID of target to modify.
541
            comment: Comment on target.
542
            name: Name of target.
543
            hosts: List of target hosts.
544
            exclude_hosts: A list of hosts to exclude.
545
            ssh_credential_id: UUID of SSH credential to use on target.
546
            ssh_credential_port: The port to use for ssh credential
547
            smb_credential_id: UUID of SMB credential to use on target.
548
            esxi_credential_id: UUID of ESXi credential to use on target.
549
            snmp_credential_id: UUID of SNMP credential to use on target.
550
            port_list_id: UUID of port list describing ports to scan.
551
            alive_test: Which alive tests to use.
552
            allow_simultaneous_ips: Whether to scan multiple IPs of the
553
                same host simultaneously
554
            reverse_lookup_only: Whether to scan only hosts that have names.
555
            reverse_lookup_unify: Whether to scan only one IP when multiple IPs
556
                have the same name.
557
558
        Returns:
559
            The response. See :py:meth:`send_command` for details.
560
        """
561
        if not target_id:
562
            raise RequiredArgument(
563
                function=self.modify_target.__name__, argument='target_id'
564
            )
565
566
        cmd = XmlCommand("modify_target")
567
        cmd.set_attribute("target_id", target_id)
568
569
        if comment:
570
            cmd.add_element("comment", comment)
571
572
        if name:
573
            cmd.add_element("name", name)
574
575
        if hosts:
576
            cmd.add_element("hosts", _to_comma_list(hosts))
577
            if exclude_hosts is None:
578
                exclude_hosts = ['']
579
580
        if exclude_hosts:
581
            cmd.add_element("exclude_hosts", _to_comma_list(exclude_hosts))
582
583
        if alive_test:
584
            if not isinstance(alive_test, AliveTest):
585
                raise InvalidArgumentType(
586
                    function=self.modify_target.__name__,
587
                    argument='alive_test',
588
                    arg_type=AliveTest.__name__,
589
                )
590
            cmd.add_element("alive_tests", alive_test.value)
591
592
        if ssh_credential_id:
593
            _xmlssh = cmd.add_element(
594
                "ssh_credential", attrs={"id": ssh_credential_id}
595
            )
596
597
            if ssh_credential_port:
598
                _xmlssh.add_element("port", str(ssh_credential_port))
599
600
        if smb_credential_id:
601
            cmd.add_element("smb_credential", attrs={"id": smb_credential_id})
602
603
        if esxi_credential_id:
604
            cmd.add_element("esxi_credential", attrs={"id": esxi_credential_id})
605
606
        if snmp_credential_id:
607
            cmd.add_element("snmp_credential", attrs={"id": snmp_credential_id})
608
609
        if allow_simultaneous_ips is not None:
610
            cmd.add_element(
611
                "allow_simultaneous_ips",
612
                _to_bool(allow_simultaneous_ips),
613
            )
614
615
        if reverse_lookup_only is not None:
616
            cmd.add_element(
617
                "reverse_lookup_only", _to_bool(reverse_lookup_only)
618
            )
619
620
        if reverse_lookup_unify is not None:
621
            cmd.add_element(
622
                "reverse_lookup_unify", _to_bool(reverse_lookup_unify)
623
            )
624
625
        if port_list_id:
626
            cmd.add_element("port_list", attrs={"id": port_list_id})
627
628
        return self._send_xml_command(cmd)
629
630 View Code Duplication
    def modify_user(
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
631
        self,
632
        user_id: str = None,
633
        *,
634
        name: Optional[str] = None,
635
        comment: Optional[str] = None,
636
        password: Optional[str] = None,
637
        auth_source: Optional[UserAuthType] = None,
638
        role_ids: Optional[List[str]] = None,
639
        hosts: Optional[List[str]] = None,
640
        hosts_allow: Optional[bool] = False,
641
        ifaces: Optional[List[str]] = None,
642
        ifaces_allow: Optional[bool] = False,
643
        group_ids: Optional[List[str]] = None,
644
    ) -> Any:
645
646
        """Modifies an existing user. Most of the fields need to be supplied
647
        for changing a single field even if no change is wanted for those.
648
        Else empty values are inserted for the missing fields instead.
649
650
        Arguments:
651
            user_id: UUID of the user to be modified.
652
            name: The new name for the user.
653
            comment: Comment on the user.
654
            password: The password for the user.
655
            auth_source: Source allowed for authentication for this user.
656
            roles_id: List of roles UUIDs for the user.
657
            hosts: User access rules: List of hosts.
658
            hosts_allow: Defines how the hosts list is to be interpreted.
659
                If False (default) the list is treated as a deny list.
660
                All hosts are allowed by default except those provided by
661
                the hosts parameter. If True the list is treated as a
662
                allow list. All hosts are denied by default except those
663
                provided by the hosts parameter.
664
            ifaces: User access rules: List of ifaces.
665
            ifaces_allow: Defines how the ifaces list is to be interpreted.
666
                If False (default) the list is treated as a deny list.
667
                All ifaces are allowed by default except those provided by
668
                the ifaces parameter. If True the list is treated as a
669
                allow list. All ifaces are denied by default except those
670
                provided by the ifaces parameter.
671
            group_ids: List of group UUIDs for the user.
672
673
        Returns:
674
            The response. See :py:meth:`send_command` for details.
675
        """
676
        if not user_id:
677
            raise RequiredArgument(
678
                function=self.modify_user.__name__, argument='user_id'
679
            )
680
681
        cmd = XmlCommand("modify_user")
682
683
        if user_id:
684
            cmd.set_attribute("user_id", user_id)
685
686
        if name:
687
            cmd.add_element("new_name", name)
688
689
        if role_ids:
690
            for role in role_ids:
691
                cmd.add_element("role", attrs={"id": role})
692
693
        if hosts:
694
            cmd.add_element(
695
                "hosts",
696
                _to_comma_list(hosts),
697
                attrs={"allow": _to_bool(hosts_allow)},
698
            )
699
700
        if ifaces:
701
            cmd.add_element(
702
                "ifaces",
703
                _to_comma_list(ifaces),
704
                attrs={"allow": _to_bool(ifaces_allow)},
705
            )
706
707
        if comment:
708
            cmd.add_element("comment", comment)
709
710
        if password:
711
            cmd.add_element("password", password)
712
713
        if auth_source:
714
            _xmlauthsrc = cmd.add_element("sources")
715
            _xmlauthsrc.add_element("source", auth_source.value)
716
717
        if group_ids:
718
            _xmlgroups = cmd.add_element("groups")
719
            for group_id in group_ids:
720
                _xmlgroups.add_element("group", attrs={"id": group_id})
721
722
        return self._send_xml_command(cmd)
723