Completed
Push — master ( b519bb...486e1a )
by Jaspar
25s queued 15s
created

CredentialsMixin.modify_credential()   F

Complexity

Conditions 23

Size

Total Lines 111
Code Lines 68

Duplication

Lines 15
Ratio 13.51 %

Importance

Changes 0
Metric Value
cc 23
eloc 68
nop 16
dl 15
loc 111
rs 0
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.credentials.CredentialsMixin.modify_credential() 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
# pylint:  disable=redefined-builtin
20
21
from enum import Enum
22
from typing import Any, Optional
23
24
from gvm.errors import InvalidArgument, InvalidArgumentType, RequiredArgument
25
from gvm.utils import add_filter, to_bool
26
from gvm.xml import XmlCommand
27
28
29
class CredentialFormat(Enum):
30
    """Enum for credential format"""
31
32
    KEY = 'key'
33
    RPM = 'rpm'
34
    DEB = 'deb'
35
    EXE = 'exe'
36
    PEM = 'pem'
37
38
39
def get_credential_format_from_string(
40
    credential_format: Optional[str],
41
) -> Optional[CredentialFormat]:
42
    if not credential_format:
43
        return None
44
45
    try:
46
        return CredentialFormat[credential_format.upper()]
47
    except KeyError:
48
        raise InvalidArgument(
49
            argument='credential_format',
50
            function=get_credential_format_from_string.__name__,
51
        ) from None
52
53
54
class CredentialType(Enum):
55
    """Enum for credential types"""
56
57
    CLIENT_CERTIFICATE = 'cc'
58
    SNMP = 'snmp'
59
    USERNAME_PASSWORD = 'up'
60
    USERNAME_SSH_KEY = 'usk'
61
    SMIME_CERTIFICATE = 'smime'
62
    PGP_ENCRYPTION_KEY = 'pgp'
63
    PASSWORD_ONLY = 'pw'
64
65
66
def get_credential_type_from_string(
67
    credential_type: Optional[str],
68
) -> Optional[CredentialType]:
69
    """Convert a credential type string into a CredentialType instance"""
70
    if not credential_type:
71
        return None
72
73
    try:
74
        return CredentialType[credential_type.upper()]
75
    except KeyError:
76
        raise InvalidArgument(
77
            argument='credential_type',
78
            function=get_credential_type_from_string.__name__,
79
        ) from None
80
81
82
class SnmpAuthAlgorithm(Enum):
83
    """Enum for SNMP auth algorithm"""
84
85
    SHA1 = 'sha1'
86
    MD5 = 'md5'
87
88
89
def get_snmp_auth_algorithm_from_string(
90
    algorithm: Optional[str],
91
) -> Optional[SnmpAuthAlgorithm]:
92
    """Convert a SNMP auth algorithm string into a SnmpAuthAlgorithm instance"""
93
    if not algorithm:
94
        return None
95
96
    try:
97
        return SnmpAuthAlgorithm[algorithm.upper()]
98
    except KeyError:
99
        raise InvalidArgument(
100
            argument='algorithm',
101
            function=get_snmp_auth_algorithm_from_string.__name__,
102
        ) from None
103
104
105
class SnmpPrivacyAlgorithm(Enum):
106
    """Enum for SNMP privacy algorithm"""
107
108
    AES = 'aes'
109
    DES = 'des'
110
111
112
def get_snmp_privacy_algorithm_from_string(
113
    algorithm: Optional[str],
114
) -> Optional[SnmpPrivacyAlgorithm]:
115
    """Convert a SNMP privacy algorithm string into a SnmpPrivacyAlgorithm
116
    instance
117
    """
118
    if not algorithm:
119
        return None
120
121
    try:
122
        return SnmpPrivacyAlgorithm[algorithm.upper()]
123
    except KeyError:
124
        raise InvalidArgument(
125
            argument='algorithm',
126
            function=get_snmp_privacy_algorithm_from_string.__name__,
127
        ) from None
128
129
130
class CredentialsMixin:
131
    def clone_credential(self, credential_id: str) -> Any:
132
        """Clone an existing credential
133
134
        Arguments:
135
            credential_id: UUID of an existing credential to clone from
136
137
        Returns:
138
            The response. See :py:meth:`send_command` for details.
139
        """
140
        if not credential_id:
141
            raise RequiredArgument(
142
                function=self.clone_credential.__name__,
143
                argument='credential_id',
144
            )
145
146
        cmd = XmlCommand("create_credential")
147
        cmd.add_element("copy", credential_id)
148
        return self._send_xml_command(cmd)
149
150
    def create_credential(
151
        self,
152
        name: str,
153
        credential_type: CredentialType,
154
        *,
155
        comment: Optional[str] = None,
156
        allow_insecure: Optional[bool] = None,
157
        certificate: Optional[str] = None,
158
        key_phrase: Optional[str] = None,
159
        private_key: Optional[str] = None,
160
        login: Optional[str] = None,
161
        password: Optional[str] = None,
162
        auth_algorithm: Optional[SnmpAuthAlgorithm] = None,
163
        community: Optional[str] = None,
164
        privacy_algorithm: Optional[SnmpPrivacyAlgorithm] = None,
165
        privacy_password: Optional[str] = None,
166
        public_key: Optional[str] = None,
167
    ) -> Any:
168
        """Create a new credential
169
170
        Create a new credential e.g. to be used in the method of an alert.
171
172
        Currently the following credential types are supported:
173
174
            - Username + Password
175
            - Username + SSH-Key
176
            - Client Certificates
177
            - SNMPv1 or SNMPv2c protocol
178
            - S/MIME Certificate
179
            - OpenPGP Key
180
            - Password only
181
182
        Arguments:
183
            name: Name of the new credential
184
            credential_type: The credential type.
185
            comment: Comment for the credential
186
            allow_insecure: Whether to allow insecure use of the credential
187
            certificate: Certificate for the credential.
188
                Required for client-certificate and smime credential types.
189
            key_phrase: Key passphrase for the private key.
190
                Used for the username+ssh-key credential type.
191
            private_key: Private key to use for login. Required
192
                for usk credential type. Also used for the cc credential type.
193
                The supported key types (dsa, rsa, ecdsa, ...) and formats (PEM,
194
                PKC#12, OpenSSL, ...) depend on your installed GnuTLS version.
195
            login: Username for the credential. Required for username+password,
196
                username+ssh-key and snmp credential type.
197
            password: Password for the credential. Used for username+password
198
                and snmp credential types.
199
            community: The SNMP community
200
            auth_algorithm: The SNMP authentication algorithm. Required for snmp
201
                credential type.
202
            privacy_algorithm: The SNMP privacy algorithm
203
            privacy_password: The SNMP privacy password
204
            public_key: PGP public key in *armor* plain text format. Required
205
                for pgp credential type.
206
207
        Examples:
208
            Creating a Username + Password credential
209
210
            .. code-block:: python
211
212
                gmp.create_credential(
213
                    name='UP Credential',
214
                    credential_type=CredentialType.USERNAME_PASSWORD,
215
                    login='foo',
216
                    password='bar',
217
                )
218
219
            Creating a Username + SSH Key credential
220
221
            .. code-block:: python
222
223
                with open('path/to/private-ssh-key') as f:
224
                    key = f.read()
225
226
                gmp.create_credential(
227
                    name='USK Credential',
228
                    credential_type=CredentialType.USERNAME_SSH_KEY,
229
                    login='foo',
230
                    key_phrase='foobar',
231
                    private_key=key,
232
                )
233
234
            Creating a PGP credential
235
236
            .. note::
237
238
                A compatible public pgp key file can be exported with GnuPG via
239
                ::
240
241
                    $ gpg --armor --export [email protected] > alice.asc
242
243
            .. code-block:: python
244
245
                with open('path/to/pgp.key.asc') as f:
246
                    key = f.read()
247
248
                gmp.create_credential(
249
                    name='PGP Credential',
250
                    credential_type=CredentialType.PGP_ENCRYPTION_KEY,
251
                    public_key=key,
252
                )
253
254
            Creating a S/MIME credential
255
256
            .. code-block:: python
257
258
                with open('path/to/smime-cert') as f:
259
                    cert = f.read()
260
261
                gmp.create_credential(
262
                    name='SMIME Credential',
263
                    credential_type=CredentialType.SMIME_CERTIFICATE,
264
                    certificate=cert,
265
                )
266
267
            Creating a Password-Only credential
268
269
            .. code-block:: python
270
271
                gmp.create_credential(
272
                    name='Password-Only Credential',
273
                    credential_type=CredentialType.PASSWORD_ONLY,
274
                    password='foo',
275
                )
276
        Returns:
277
            The response. See :py:meth:`send_command` for details.
278
        """
279
        if not name:
280
            raise RequiredArgument(
281
                function=self.create_credential.__name__, argument='name'
282
            )
283
284
        if not isinstance(credential_type, CredentialType):
285
            raise InvalidArgumentType(
286
                function=self.create_credential.__name__,
287
                argument='credential_type',
288
                arg_type=CredentialType.__name__,
289
            )
290
291
        cmd = XmlCommand("create_credential")
292
        cmd.add_element("name", name)
293
294
        cmd.add_element("type", credential_type.value)
295
296
        if comment:
297
            cmd.add_element("comment", comment)
298
299
        if allow_insecure is not None:
300
            cmd.add_element("allow_insecure", to_bool(allow_insecure))
301
302
        if (
303
            credential_type == CredentialType.CLIENT_CERTIFICATE
304
            or credential_type == CredentialType.SMIME_CERTIFICATE
305
        ):
306
            if not certificate:
307
                raise RequiredArgument(
308
                    function=self.create_credential.__name__,
309
                    argument='certificate',
310
                )
311
312
            cmd.add_element("certificate", certificate)
313
314
        if (
315
            credential_type == CredentialType.USERNAME_PASSWORD
316
            or credential_type == CredentialType.USERNAME_SSH_KEY
317
            or credential_type == CredentialType.SNMP
318
        ):
319
            if not login:
320
                raise RequiredArgument(
321
                    function=self.create_credential.__name__, argument='login'
322
                )
323
324
            cmd.add_element("login", login)
325
326
        if credential_type == CredentialType.PASSWORD_ONLY and not password:
327
            raise RequiredArgument(
328
                function=self.create_credential.__name__, argument='password'
329
            )
330
331
        if (
332
            credential_type == CredentialType.USERNAME_PASSWORD
333
            or credential_type == CredentialType.SNMP
334
            or credential_type == CredentialType.PASSWORD_ONLY
335
        ) and password:
336
            cmd.add_element("password", password)
337
338
        if credential_type == CredentialType.USERNAME_SSH_KEY:
339
            if not private_key:
340
                raise RequiredArgument(
341
                    function=self.create_credential.__name__,
342
                    argument='private_key',
343
                )
344
345
            _xmlkey = cmd.add_element("key")
346
            _xmlkey.add_element("private", private_key)
347
348
            if key_phrase:
349
                _xmlkey.add_element("phrase", key_phrase)
350
351
        if credential_type == CredentialType.CLIENT_CERTIFICATE and private_key:
352
            _xmlkey = cmd.add_element("key")
353
            _xmlkey.add_element("private", private_key)
354
355
        if credential_type == CredentialType.SNMP:
356
            if not isinstance(auth_algorithm, SnmpAuthAlgorithm):
357
                raise InvalidArgumentType(
358
                    function=self.create_credential.__name__,
359
                    argument='auth_algorithm',
360
                    arg_type=SnmpAuthAlgorithm.__name__,
361
                )
362
363
            cmd.add_element("auth_algorithm", auth_algorithm.value)
364
365
            if community:
366
                cmd.add_element("community", community)
367
368 View Code Duplication
            if privacy_algorithm is not None or privacy_password:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
369
                _xmlprivacy = cmd.add_element("privacy")
370
371
                if privacy_algorithm is not None:
372
                    if not isinstance(privacy_algorithm, SnmpPrivacyAlgorithm):
373
                        raise InvalidArgumentType(
374
                            function=self.create_credential.__name__,
375
                            argument='privacy_algorithm',
376
                            arg_type=SnmpPrivacyAlgorithm.__name__,
377
                        )
378
379
                    _xmlprivacy.add_element(
380
                        "algorithm", privacy_algorithm.value
381
                    )
382
383
                if privacy_password:
384
                    _xmlprivacy.add_element("password", privacy_password)
385
386
        if credential_type == CredentialType.PGP_ENCRYPTION_KEY:
387
            if not public_key:
388
                raise RequiredArgument(
389
                    function=self.create_credential.__name__,
390
                    argument='public_key',
391
                )
392
393
            _xmlkey = cmd.add_element("key")
394
            _xmlkey.add_element("public", public_key)
395
396
        return self._send_xml_command(cmd)
397
398
    def delete_credential(
399
        self, credential_id: str, *, ultimate: Optional[bool] = False
400
    ) -> Any:
401
        """Deletes an existing credential
402
403
        Arguments:
404
            credential_id: UUID of the credential to be deleted.
405
            ultimate: Whether to remove entirely, or to the trashcan.
406
        """
407
        if not credential_id:
408
            raise RequiredArgument(
409
                function=self.delete_credential.__name__,
410
                argument='credential_id',
411
            )
412
413
        cmd = XmlCommand("delete_credential")
414
        cmd.set_attribute("credential_id", credential_id)
415
        cmd.set_attribute("ultimate", to_bool(ultimate))
416
417
        return self._send_xml_command(cmd)
418
419
    def get_credentials(
420
        self,
421
        *,
422
        filter: Optional[str] = None,
423
        filter_id: Optional[str] = None,
424
        scanners: Optional[bool] = None,
425
        trash: Optional[bool] = None,
426
        targets: Optional[bool] = None,
427
    ) -> Any:
428
        """Request a list of credentials
429
430
        Arguments:
431
            filter: Filter term to use for the query
432
            filter_id: UUID of an existing filter to use for the query
433
            scanners: Whether to include a list of scanners using the
434
                credentials
435
            trash: Whether to get the trashcan credentials instead
436
            targets: Whether to include a list of targets using the credentials
437
438
        Returns:
439
            The response. See :py:meth:`send_command` for details.
440
        """
441
        cmd = XmlCommand("get_credentials")
442
443
        add_filter(cmd, filter, filter_id)
444
445
        if scanners is not None:
446
            cmd.set_attribute("scanners", to_bool(scanners))
447
448
        if trash is not None:
449
            cmd.set_attribute("trash", to_bool(trash))
450
451
        if targets is not None:
452
            cmd.set_attribute("targets", to_bool(targets))
453
454
        return self._send_xml_command(cmd)
455
456
    def get_credential(
457
        self,
458
        credential_id: str,
459
        *,
460
        scanners: Optional[bool] = None,
461
        targets: Optional[bool] = None,
462
        credential_format: Optional[CredentialFormat] = None,
463
    ) -> Any:
464
        """Request a single credential
465
466
        Arguments:
467
            credential_id: UUID of an existing credential
468
            scanners: Whether to include a list of scanners using the
469
                credentials
470
            targets: Whether to include a list of targets using the credentials
471
            credential_format: One of "key", "rpm", "deb", "exe" or "pem"
472
473
        Returns:
474
            The response. See :py:meth:`send_command` for details.
475
        """
476
        if not credential_id:
477
            raise RequiredArgument(
478
                function=self.get_credential.__name__, argument='credential_id'
479
            )
480
481
        cmd = XmlCommand("get_credentials")
482
        cmd.set_attribute("credential_id", credential_id)
483
484
        if credential_format:
485
            if not isinstance(credential_format, CredentialFormat):
486
                raise InvalidArgumentType(
487
                    function=self.get_credential.__name__,
488
                    argument='credential_format',
489
                    arg_type=CredentialFormat.__name__,
490
                )
491
492
            cmd.set_attribute("format", credential_format.value)
493
494
        if scanners is not None:
495
            cmd.set_attribute("scanners", to_bool(scanners))
496
497
        if targets is not None:
498
            cmd.set_attribute("targets", to_bool(targets))
499
500
        return self._send_xml_command(cmd)
501
502
    def modify_credential(
503
        self,
504
        credential_id: str,
505
        *,
506
        name: Optional[str] = None,
507
        comment: Optional[str] = None,
508
        allow_insecure: Optional[bool] = None,
509
        certificate: Optional[str] = None,
510
        key_phrase: Optional[str] = None,
511
        private_key: Optional[str] = None,
512
        login: Optional[str] = None,
513
        password: Optional[str] = None,
514
        auth_algorithm: Optional[SnmpAuthAlgorithm] = None,
515
        community: Optional[str] = None,
516
        privacy_algorithm: Optional[SnmpPrivacyAlgorithm] = None,
517
        privacy_password: Optional[str] = None,
518
        public_key: Optional[str] = None,
519
    ) -> Any:
520
        """Modifies an existing credential.
521
522
        Arguments:
523
            credential_id: UUID of the credential
524
            name: Name of the credential
525
            comment: Comment for the credential
526
            allow_insecure: Whether to allow insecure use of the credential
527
            certificate: Certificate for the credential
528
            key_phrase: Key passphrase for the private key
529
            private_key: Private key to use for login
530
            login: Username for the credential
531
            password: Password for the credential
532
            auth_algorithm: The authentication algorithm for SNMP
533
            community: The SNMP community
534
            privacy_algorithm: The privacy algorithm for SNMP
535
            privacy_password: The SNMP privacy password
536
            public_key: PGP public key in *armor* plain text format
537
538
        Returns:
539
            The response. See :py:meth:`send_command` for details.
540
        """
541
        if not credential_id:
542
            raise RequiredArgument(
543
                function=self.modify_credential.__name__,
544
                argument='credential_id',
545
            )
546
547
        cmd = XmlCommand("modify_credential")
548
        cmd.set_attribute("credential_id", credential_id)
549
550
        if comment:
551
            cmd.add_element("comment", comment)
552
553
        if name:
554
            cmd.add_element("name", name)
555
556
        if allow_insecure is not None:
557
            cmd.add_element("allow_insecure", to_bool(allow_insecure))
558
559
        if certificate:
560
            cmd.add_element("certificate", certificate)
561
562
        if key_phrase and private_key:
563
            _xmlkey = cmd.add_element("key")
564
            _xmlkey.add_element("phrase", key_phrase)
565
            _xmlkey.add_element("private", private_key)
566
        elif (not key_phrase and private_key) or (
567
            key_phrase and not private_key
568
        ):
569
            raise RequiredArgument(
570
                function=self.modify_credential.__name__,
571
                argument='key_phrase and private_key',
572
            )
573
574
        if login:
575
            cmd.add_element("login", login)
576
577
        if password:
578
            cmd.add_element("password", password)
579
580
        if auth_algorithm:
581
            if not isinstance(auth_algorithm, SnmpAuthAlgorithm):
582
                raise InvalidArgumentType(
583
                    function=self.modify_credential.__name__,
584
                    argument='auth_algorithm',
585
                    arg_type=SnmpAuthAlgorithm.__name__,
586
                )
587
            cmd.add_element("auth_algorithm", auth_algorithm.value)
588
589
        if community:
590
            cmd.add_element("community", community)
591
592 View Code Duplication
        if privacy_algorithm is not None or privacy_password is not None:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
593
            _xmlprivacy = cmd.add_element("privacy")
594
595
            if privacy_algorithm is not None:
596
                if not isinstance(privacy_algorithm, SnmpPrivacyAlgorithm):
597
                    raise InvalidArgumentType(
598
                        function=self.modify_credential.__name__,
599
                        argument='privacy_algorithm',
600
                        arg_type=SnmpPrivacyAlgorithm.__name__,
601
                    )
602
603
                _xmlprivacy.add_element("algorithm", privacy_algorithm.value)
604
605
            if privacy_password is not None:
606
                _xmlprivacy.add_element("password", privacy_password)
607
608
        if public_key:
609
            _xmlkey = cmd.add_element("key")
610
            _xmlkey.add_element("public", public_key)
611
612
        return self._send_xml_command(cmd)
613