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

GmpV214Mixin.create_target()   F

Complexity

Conditions 19

Size

Total Lines 125
Code Lines 72

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 72
nop 19
dl 0
loc 125
rs 0.5999
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.gmpv214.gmpv214.GmpV214Mixin.create_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) 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