Completed
Push — master ( 12b62e...11d6ab )
by
unknown
17s queued 11s
created

gvm.protocols.gmpv8.Gmp.modify_ticket()   C

Complexity

Conditions 10

Size

Total Lines 60
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 32
nop 7
dl 0
loc 60
rs 5.9999
c 0
b 0
f 0

How to fix   Long Method    Complexity   

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.gmpv8.Gmp.modify_ticket() 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) 2018 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
20
21
"""
22
Module for communication with gvmd in `Greenbone Management Protocol version 8`_
23
24
.. _Greenbone Management Protocol version 8:
25
    https://docs.greenbone.net/API/GMP/gmp-8.0.html
26
"""
27
from enum import Enum
28
29
from gvm.errors import InvalidArgument, RequiredArgument
30
from gvm.utils import get_version_string
31
from gvm.xml import XmlCommand
32
33
from .gmpv7 import Gmp as Gmpv7, _to_bool, _add_filter
34
35
PROTOCOL_VERSION = (8,)
36
37
38
class CredentialType(Enum):
39
    CLIENT_CERTIFICATE = 'cc'
40
    SNMP = 'snmp'
41
    USERNAME_PASSWORD = 'up'
42
    USERNAME_SSH_KEY = 'usk'
43
    SMIME_CERTIFICATE = 'smime'
44
    PGP_ENCRYPTION_KEY = 'pgp'
45
46
47
class TicketStatus(Enum):
48
    OPEN = 'Open'
49
    FIXED = 'Fixed'
50
    CLOSED = 'Closed'
51
52
53
class Gmp(Gmpv7):
54
    @staticmethod
55
    def get_protocol_version():
56
        """Determine the Greenbone Management Protocol version.
57
58
        Returns:
59
            str: Implemented version of the Greenbone Management Protocol
60
        """
61
        return get_version_string(PROTOCOL_VERSION)
62
63
    def create_credential(
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (19/15).
Loading history...
64
        self,
65
        name,
66
        credential_type,
67
        *,
68
        comment=None,
69
        allow_insecure=None,
70
        certificate=None,
71
        key_phrase=None,
72
        private_key=None,
73
        login=None,
74
        password=None,
75
        auth_algorithm=None,
76
        community=None,
77
        privacy_algorithm=None,
78
        privacy_password=None,
79
        public_key=None
80
    ):
81
        """Create a new credential
82
83
        Create a new credential e.g. to be used in the method of an alert.
84
85
        Currently the following credential types are supported:
86
87
            - Username + Password
88
            - Username + private SSH-Key
89
            - Client Certificates
90
            - SNMPv1 or SNMPv2c protocol
91
            - S/MIME Certificate
92
            - OpenPGP Key
93
            - Password only
94
95
        Arguments:
96
            name (str): Name of the new credential
97
            credential_type (CredentialType): The credential type.
98
            comment (str, optional): Comment for the credential
99
            allow_insecure (boolean, optional): Whether to allow insecure use of
100
                the credential
101
            certificate (str, optional): Certificate for the credential.
102
                Required for cc and smime credential types.
103
            key_phrase (str, optional): Key passphrase for the private key.
104
                Used for the usk credential type.
105
            private_key (str, optional): Private key to use for login. Required
106
                for usk credential type. Also used for the cc credential type.
107
                The supported key types (dsa, rsa, ecdsa, ...) and formats (PEM,
108
                PKC#12, OpenSSL, ...) depend on your installed GnuTLS version.
109
            login (str, optional): Username for the credential. Required for
110
                up, usk and snmp credential type.
111
            password (str, optional): Password for the credential. Used for
112
                up and snmp credential types.
113
            community (str, optional): The SNMP community
114
            auth_algorithm (str, optional): The SNMP authentication algorithm.
115
                Either 'md5' or 'sha1'. Required for snmp credential type.
116
            privacy_algorithm (str, optional): The SNMP privacy algorithm,
117
                either aes or des.
118
            privacy_password (str, optional): The SNMP privacy password
119
            public_key: (str, optional): PGP public key in *armor* plain text
120
                format. Required for pgp credential type.
121
122
        Examples:
123
            Creating a Username + Password credential
124
125
            .. code-block:: python
126
127
                gmp.create_credential(
128
                    name='UP Credential',
129
                    credential_type=CredentialType.USERNAME_PASSWORD,
130
                    login='foo',
131
                    password='bar',
132
                );
133
134
            Creating a Username + SSH Key credential
135
136
            .. code-block:: python
137
138
                with open('path/to/private-ssh-key') as f:
139
                    key = f.read()
140
141
                gmp.create_credential(
142
                    name='USK Credential',
143
                    credential_type=CredentialType.USERNAME_SSH_KEY,
144
                    login='foo',
145
                    key_phrase='foobar',
146
                    private_key=key,
147
                )
148
149
            Creating a PGP credential
150
151
            .. note::
152
153
                A compatible public pgp key file can be exported with GnuPG via
154
                ::
155
156
                    $ gpg --armor --export [email protected] > alice.asc
157
158
            .. code-block:: python
159
160
                with open('path/to/pgp.key.asc') as f:
161
                    key = f.read()
162
163
                gmp.create_credential(
164
                    name='PGP Credential',
165
                    credential_type=CredentialType.PGP_ENCRYPTION_KEY,
166
                    public_key=key,
167
                )
168
169
            Creating a S/MIME credential
170
171
            .. code-block:: python
172
173
                with open('path/to/smime-cert') as f:
174
                    cert = f.read()
175
176
                gmp.create_credential(
177
                    name='SMIME Credential',
178
                    credential_type=CredentialType.SMIME_CERTIFICATE,
179
                    certificate=cert,
180
                )
181
182
        Returns:
183
            The response. See :py:meth:`send_command` for details.
184
        """
185
        if not name:
186
            raise RequiredArgument("create_credential requires name argument")
187
188
        if not isinstance(credential_type, CredentialType):
189
            raise InvalidArgument(
190
                "create_credential requires type to be a CredentialType "
191
                "instance"
192
            )
193
194
        cmd = XmlCommand("create_credential")
195
        cmd.add_element("name", name)
196
197
        cmd.add_element("type", credential_type.value)
198
199
        if comment:
200
            cmd.add_element("comment", comment)
201
202
        if allow_insecure is not None:
203
            cmd.add_element("allow_insecure", _to_bool(allow_insecure))
204
205
        if (
206
            credential_type == CredentialType.CLIENT_CERTIFICATE
0 ignored issues
show
Unused Code introduced by
Consider merging these comparisons with "in" to 'credential_type in (CredentialType.CLIENT_CERTIFICATE, CredentialType.SMIME_CERTIFICATE)'
Loading history...
207
            or credential_type == CredentialType.SMIME_CERTIFICATE
208
        ):
209
            if not certificate:
210
                raise RequiredArgument(
211
                    "create_credential requires certificate argument for "
212
                    "credential_type {0}".format(credential_type.name)
213
                )
214
215
            cmd.add_element("certificate", certificate)
216
217
        if (
218
            credential_type == CredentialType.USERNAME_PASSWORD
0 ignored issues
show
Unused Code introduced by
Consider merging these comparisons with "in" to 'credential_type in (CredentialType.USERNAME_PASSWORD, CredentialType.USERNAME_SSH_KEY, CredentialType.SNMP)'
Loading history...
219
            or credential_type == CredentialType.USERNAME_SSH_KEY
220
            or credential_type == CredentialType.SNMP
221
        ):
222
            if not login:
223
                raise RequiredArgument(
224
                    "create_credential requires login argument for "
225
                    "credential_type {0}".format(credential_type.name)
226
                )
227
228
            cmd.add_element("login", login)
229
230
        if (
231
            credential_type == CredentialType.USERNAME_PASSWORD
0 ignored issues
show
Unused Code introduced by
Consider merging these comparisons with "in" to 'credential_type in (CredentialType.USERNAME_PASSWORD, CredentialType.SNMP)'
Loading history...
232
            or credential_type == CredentialType.SNMP
233
        ) and password:
234
            cmd.add_element("password", password)
235
236
        if credential_type == CredentialType.USERNAME_SSH_KEY:
237
            if not private_key:
238
                raise RequiredArgument(
239
                    "create_credential requires certificate argument for "
240
                    "credential_type {0}".format(credential_type.name)
241
                )
242
243
            _xmlkey = cmd.add_element("key")
244
            _xmlkey.add_element("private", private_key)
245
246
            if key_phrase:
247
                _xmlkey.add_element("phrase", key_phrase)
248
249
        if credential_type == CredentialType.CLIENT_CERTIFICATE and private_key:
250
            _xmlkey = cmd.add_element("key")
251
            _xmlkey.add_element("private", private_key)
252
253 View Code Duplication
        if credential_type == CredentialType.SNMP:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
254
            if auth_algorithm not in ("md5", "sha1"):
255
                raise InvalidArgument(
256
                    "create_credential requires auth_algorithm to be either "
257
                    "md5 or sha1"
258
                )
259
260
            cmd.add_element("auth_algorithm", auth_algorithm)
261
262
            if community:
263
                cmd.add_element("community", community)
264
265
            if privacy_algorithm is not None or privacy_password:
266
                _xmlprivacy = cmd.add_element("privacy")
267
268
                if privacy_algorithm is not None:
269
                    if privacy_algorithm not in ("aes", "des"):
270
                        raise InvalidArgument(
271
                            "create_credential requires algorithm to be either "
272
                            "aes or des"
273
                        )
274
275
                    _xmlprivacy.add_element("algorithm", privacy_algorithm)
276
277
                if privacy_password:
278
                    _xmlprivacy.add_element("password", privacy_password)
279
280
        if credential_type == CredentialType.PGP_ENCRYPTION_KEY:
281
            if not public_key:
282
                raise RequiredArgument(
283
                    "Creating a pgp credential requires a public_key argument"
284
                )
285
286
            _xmlkey = cmd.add_element("key")
287
            _xmlkey.add_element("public", public_key)
288
289
        return self._send_xml_command(cmd)
290
291
    def modify_credential(
0 ignored issues
show
Comprehensibility introduced by
This function exceeds the maximum number of variables (20/15).
Loading history...
292
        self,
293
        credential_id,
294
        *,
295
        name=None,
296
        comment=None,
297
        allow_insecure=None,
298
        certificate=None,
299
        key_phrase=None,
300
        private_key=None,
301
        login=None,
302
        password=None,
303
        auth_algorithm=None,
304
        community=None,
305
        privacy_algorithm=None,
306
        privacy_password=None,
307
        credential_type=None,
308
        public_key=None
309
    ):
310
        """Modifies an existing credential.
311
312
        Arguments:
313
            credential_id (str): UUID of the credential
314
            name (str, optional): Name of the credential
315
            comment (str, optional): Comment for the credential
316
            allow_insecure (boolean, optional): Whether to allow insecure use of
317
                 the credential
318
            certificate (str, optional): Certificate for the credential
319
            key_phrase (str, optional): Key passphrase for the private key
320
            private_key (str, optional): Private key to use for login
321
            login (str, optional): Username for the credential
322
            password (str, optional): Password for the credential
323
            auth_algorithm (str, optional): The auth_algorithm,
324
                either md5 or sha1.
325
            community (str, optional): The SNMP community
326
            privacy_algorithm (str, optional): The SNMP privacy algorithm,
327
                either aes or des.
328
            privacy_password (str, optional): The SNMP privacy password
329
            credential_type (CredentialType, optional): The credential type.
330
            public_key: (str, optional): PGP public key in *armor* plain text
331
                format
332
333
        Returns:
334
            The response. See :py:meth:`send_command` for details.
335
        """
336
        if not credential_id:
337
            raise RequiredArgument(
338
                "modify_credential requires " "a credential_id attribute"
339
            )
340
341
        cmd = XmlCommand("modify_credential")
342
        cmd.set_attribute("credential_id", credential_id)
343
344
        if credential_type:
345
            if not isinstance(credential_type, CredentialType):
346
                raise InvalidArgument(
347
                    "modify_credential requires type to be a CredentialType "
348
                    "instance"
349
                )
350
            cmd.add_element("type", credential_type.value)
351
352
        if comment:
353
            cmd.add_element("comment", comment)
354
355
        if name:
356
            cmd.add_element("name", name)
357
358
        if allow_insecure is not None:
359
            cmd.add_element("allow_insecure", _to_bool(allow_insecure))
360
361
        if certificate:
362
            cmd.add_element("certificate", certificate)
363
364
        if key_phrase or private_key:
365
            if not key_phrase or not private_key:
366
                raise RequiredArgument(
367
                    "modify_credential requires "
368
                    "a key_phrase and private_key arguments"
369
                )
370
            _xmlkey = cmd.add_element("key")
371
            _xmlkey.add_element("phrase", key_phrase)
372
            _xmlkey.add_element("private", private_key)
373
374
        if login:
375
            cmd.add_element("login", login)
376
377
        if password:
378
            cmd.add_element("password", password)
379
380
        if auth_algorithm:
381
            if auth_algorithm not in ("md5", "sha1"):
382
                raise InvalidArgument(
383
                    "modify_credential requires "
384
                    "auth_algorithm to be either "
385
                    "md5 or sha1"
386
                )
387
            cmd.add_element("auth_algorithm", auth_algorithm)
388
389
        if community:
390
            cmd.add_element("community", community)
391
392
        if privacy_algorithm is not None or privacy_password is not None:
393
            _xmlprivacy = cmd.add_element("privacy")
394
395
            if privacy_algorithm is not None:
396
                if privacy_algorithm not in ("aes", "des"):
397
                    raise InvalidArgument(
398
                        "modify_credential requires privacy_algorithm to be "
399
                        "either aes or des"
400
                    )
401
402
                _xmlprivacy.add_element("algorithm", privacy_algorithm)
403
404
            if privacy_password is not None:
405
                _xmlprivacy.add_element("password", privacy_password)
406
407
        if public_key:
408
            _xmlkey = cmd.add_element("key")
409
            _xmlkey.add_element("public", public_key)
410
411
        return self._send_xml_command(cmd)
412
413
    def create_tag(
414
        self,
415
        name,
416
        resource_type,
417
        *,
418
        resource_filter=None,
419
        resource_ids=None,
420
        value=None,
421
        comment=None,
422
        active=None
423
    ):
424
        """Create a tag.
425
426
        Arguments:
427
            name (str): Name of the tag. A full tag name consisting of
428
                namespace and predicate e.g. `foo:bar`.
429
            resource_type (str): Entity type the tag is to be attached
430
                to.
431
            resource_filter (str, optional) Filter term to select
432
                resources the tag is to be attached to. Only one of
433
                resource_filter or resource_ids can be provided.
434
            resource_ids (list, optional): IDs of the resources the
435
                tag is to be attached to. Only one of resource_filter or
436
                resource_ids can be provided.
437
            value (str, optional): Value associated with the tag.
438
            comment (str, optional): Comment for the tag.
439
            active (boolean, optional): Whether the tag should be
440
                active.
441
442
        Returns:
443
            The response. See :py:meth:`send_command` for details.
444
        """
445
        if not name:
446
            raise RequiredArgument("create_tag requires name argument")
447
448
        if resource_filter and resource_ids:
449
            raise InvalidArgument(
450
                "create_tag accepts either resource_filter or resource_ids "
451
                "argument"
452
            )
453
454
        if not resource_type:
455
            raise RequiredArgument("create_tag requires resource_type argument")
456
457
        cmd = XmlCommand('create_tag')
458
        cmd.add_element('name', name)
459
460
        _xmlresources = cmd.add_element("resources")
461
        if resource_filter is not None:
462
            _xmlresources.set_attribute("filter", resource_filter)
463
464
        for resource_id in resource_ids or []:
465
            _xmlresources.add_element(
466
                "resource", attrs={"id": str(resource_id)}
467
            )
468
469
        _xmlresources.add_element("type", resource_type)
470
471
        if comment:
472
            cmd.add_element("comment", comment)
473
474
        if value:
475
            cmd.add_element("value", value)
476
477
        if active is not None:
478
            if active:
479
                cmd.add_element("active", "1")
480
            else:
481
                cmd.add_element("active", "0")
482
483
        return self._send_xml_command(cmd)
484
485
    def modify_tag(
486
        self,
487
        tag_id,
488
        *,
489
        comment=None,
490
        name=None,
491
        value=None,
492
        active=None,
493
        resource_action=None,
494
        resource_type=None,
495
        resource_filter=None,
496
        resource_ids=None
497
    ):
498
        """Modifies an existing tag.
499
500
        Arguments:
501
            tag_id (str): UUID of the tag.
502
            comment (str, optional): Comment to add to the tag.
503
            name (str, optional): Name of the tag.
504
            value (str, optional): Value of the tag.
505
            active (boolean, optional): Whether the tag is active.
506
            resource_action (str, optional) Whether to add or remove
507
                resources instead of overwriting. One of '', 'add',
508
                'set' or 'remove'.
509
            resource_type (str, optional): Type of the resources to
510
                which to attach the tag. Required if resource_filter
511
                is set.
512
            resource_filter (str, optional) Filter term to select
513
                resources the tag is to be attached to.
514
            resource_ids (list, optional): IDs of the resources to
515
                which to attach the tag.
516
517
        Returns:
518
            The response. See :py:meth:`send_command` for details.
519
        """
520
        if not tag_id:
521
            raise RequiredArgument("modify_tag requires a tag_id element")
522
523
        cmd = XmlCommand("modify_tag")
524
        cmd.set_attribute("tag_id", str(tag_id))
525
526
        if comment:
527
            cmd.add_element("comment", comment)
528
529
        if name:
530
            cmd.add_element("name", name)
531
532
        if value:
533
            cmd.add_element("value", value)
534
535
        if active is not None:
536
            cmd.add_element("active", _to_bool(active))
537
538
        if resource_action or resource_filter or resource_ids or resource_type:
539
            if resource_filter and not resource_type:
540
                raise RequiredArgument(
541
                    "modify_tag requires resource_type argument when "
542
                    "resource_filter is set"
543
                )
544
545
            _xmlresources = cmd.add_element("resources")
546
            if resource_action is not None:
547
                _xmlresources.set_attribute("action", resource_action)
548
549
            if resource_filter is not None:
550
                _xmlresources.set_attribute("filter", resource_filter)
551
552
            for resource_id in resource_ids or []:
553
                _xmlresources.add_element(
554
                    "resource", attrs={"id": str(resource_id)}
555
                )
556
557
            if resource_type is not None:
558
                _xmlresources.add_element("type", resource_type)
559
560
        return self._send_xml_command(cmd)
561
562
    def clone_ticket(self, ticket_id):
563
        """Clone an existing ticket
564
565
        Arguments:
566
            ticket_id (str): UUID of an existing ticket to clone from
567
568
        Returns:
569
            The response. See :py:meth:`send_command` for details.
570
        """
571
        if not ticket_id:
572
            raise RequiredArgument("clone_ticket requires a ticket_id argument")
573
574
        cmd = XmlCommand("create_ticket")
575
576
        _copy = cmd.add_element("copy", ticket_id)
577
578
        return self._send_xml_command(cmd)
579
580
    def create_ticket(
581
        self, *, result_id, assigned_to_user_id, note, comment=None
582
    ):
583
        """Create a new ticket
584
585
        Arguments:
586
            result_id (str): UUID of the result the ticket applies to
587
            assigned_to_user_id (str): UUID of a user the ticket should be
588
                assigned to
589
            note (str): A note about opening the ticket
590
            comment (str, optional): Comment for the ticket
591
592
        Returns:
593
            The response. See :py:meth:`send_command` for details.
594
        """
595
        if not result_id:
596
            raise RequiredArgument(
597
                "create_ticket requires a result_id argument"
598
            )
599
600
        if not assigned_to_user_id:
601
            raise RequiredArgument(
602
                "create_ticket requires a assigned_to_user_id argument"
603
            )
604
605
        if not note:
606
            raise RequiredArgument("create_ticket requires a note argument")
607
608
        cmd = XmlCommand("create_ticket")
609
610
        _result = cmd.add_element("result")
611
        _result.set_attribute("id", result_id)
612
613
        _assigned = cmd.add_element("assigned_to")
614
        _user = _assigned.add_element("user")
615
        _user.set_attribute("id", assigned_to_user_id)
616
617
        _note = cmd.add_element("open_note", note)
618
619
        if comment:
620
            cmd.add_element("comment", comment)
621
622
        return self._send_xml_command(cmd)
623
624
    def delete_ticket(self, ticket_id, *, ultimate=False):
625
        """Deletes an existing ticket
626
627
        Arguments:
628
            ticket_id (str) UUID of the ticket to be deleted.
629
            ultimate (boolean, optional): Whether to remove entirely,
630
                or to the trashcan.
631
        """
632
        if not ticket_id:
633
            raise RequiredArgument(
634
                "delete_ticket requires a " "ticket_id argument"
635
            )
636
637
        cmd = XmlCommand("delete_ticket")
638
        cmd.set_attribute("ticket_id", ticket_id)
639
        cmd.set_attribute("ultimate", _to_bool(ultimate))
640
641
        return self._send_xml_command(cmd)
642
643
    def get_tickets(self, *, trash=None, filter=None, filter_id=None):
644
        """Request a list of tickets
645
646
        Arguments:
647
            filter (str, optional): Filter term to use for the query
648
            filter_id (str, optional): UUID of an existing filter to use for
649
                the query
650
            trash (boolean, optional): True to request the tickets in the
651
                trashcan
652
        Returns:
653
            The response. See :py:meth:`send_command` for details.
654
        """
655
        cmd = XmlCommand("get_tickets")
656
657
        _add_filter(cmd, filter, filter_id)
658
659
        if not trash is None:
660
            cmd.set_attribute("trash", _to_bool(trash))
661
662
        return self._send_xml_command(cmd)
663
664
    def get_ticket(self, ticket_id):
665
        """Request a single ticket
666
667
        Arguments:
668
            ticket_id (str): UUID of an existing ticket
669
670
        Returns:
671
            The response. See :py:meth:`send_command` for details.
672
        """
673
        if not ticket_id:
674
            raise RequiredArgument("get_ticket requires a ticket_id argument")
675
676
        cmd = XmlCommand("get_tickets")
677
        cmd.set_attribute("ticket_id", ticket_id)
678
        return self._send_xml_command(cmd)
679
680
    def get_vulnerabilities(self, *, filter=None, filter_id=None):
681
        """Request a list of vulnerabilities
682
683
        Arguments:
684
            filter (str, optional): Filter term to use for the query
685
            filter_id (str, optional): UUID of an existing filter to use for
686
                the query
687
        Returns:
688
            The response. See :py:meth:`send_command` for details.
689
        """
690
        cmd = XmlCommand("get_vulns")
691
692
        _add_filter(cmd, filter, filter_id)
693
694
        return self._send_xml_command(cmd)
695
696
    def get_vulnerability(self, vulnerability_id):
697
        """Request a single vulnerability
698
699
        Arguments:
700
            vulnerability_id (str): UUID of an existing vulnerability
701
702
        Returns:
703
            The response. See :py:meth:`send_command` for details.
704
        """
705
        if not vulnerability_id:
706
            raise RequiredArgument(
707
                "get_vulnerability requires a vulnerability_id argument"
708
            )
709
710
        cmd = XmlCommand("get_vulns")
711
        cmd.set_attribute("vuln_id", vulnerability_id)
712
        return self._send_xml_command(cmd)
713
714
    def modify_ticket(
715
        self,
716
        ticket_id,
717
        *,
718
        status=None,
719
        note=None,
720
        assigned_to_user_id=None,
721
        comment=None
722
    ):
723
        """Modify a single ticket
724
725
        Arguments:
726
            ticket_id (str): UUID of an existing ticket
727
            status (TicketStatus, optional): New status for the ticket
728
            note (str, optional): Note for the status change. Required if status
729
                is set.
730
            assigned_to_user_id (str, optional): UUID of the user the ticket
731
                should be assigned to
732
            comment (str, optional): Comment for the ticket
733
734
        Returns:
735
            The response. See :py:meth:`send_command` for details.
736
        """
737
        if not ticket_id:
738
            raise RequiredArgument(
739
                "modify_ticket requires a ticket_id argument"
740
            )
741
742
        if status and not note:
743
            raise RequiredArgument(
744
                "setting a status in modify_ticket requires a note argument"
745
            )
746
747
        if note and not status:
748
            raise RequiredArgument(
749
                "setting a note in modify_ticket requires a status argument"
750
            )
751
752
        cmd = XmlCommand("modify_ticket")
753
        cmd.set_attribute("ticket_id", ticket_id)
754
755
        if assigned_to_user_id:
756
            _assigned = cmd.add_element("assigned_to")
757
            _user = _assigned.add_element("user")
758
            _user.set_attribute("id", assigned_to_user_id)
759
760
        if status:
761
            if not isinstance(status, TicketStatus):
762
                raise InvalidArgument(
763
                    "status argument of modify_ticket needs to be a "
764
                    "TicketStatus"
765
                )
766
767
            cmd.add_element('status', status.value)
768
            cmd.add_element('{}_note'.format(status.name.lower()), note)
769
770
        if comment:
771
            cmd.add_element("comment", comment)
772
773
        return self._send_xml_command(cmd)
774