Skip to content

Commit f28c8c5

Browse files
Use enums instead of literals
Using enums makes it easier to use the functions in a typesafe way and to validate user input. Fixes: #69
1 parent bcaaedb commit f28c8c5

5 files changed

Lines changed: 135 additions & 117 deletions

File tree

nethsm/__init__.py

Lines changed: 86 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from dataclasses import dataclass
1919
from datetime import datetime
2020
from io import BufferedReader
21-
from typing import TYPE_CHECKING, Any, Iterator, Literal, Mapping, Optional, Union, cast
21+
from typing import TYPE_CHECKING, Any, Iterator, Mapping, Optional, Union, cast
2222
from urllib.parse import urlencode
2323

2424
import urllib3
@@ -76,6 +76,13 @@ class UnattendedBootStatus(enum.Enum):
7676
ON = "on"
7777
OFF = "off"
7878

79+
@staticmethod
80+
def from_string(s: str) -> "UnattendedBootStatus":
81+
for status in UnattendedBootStatus:
82+
if status.value == s:
83+
return status
84+
raise ValueError(f"Unsupported unattended boot status {s}")
85+
7986

8087
class KeyType(enum.Enum):
8188
RSA = "RSA"
@@ -94,11 +101,6 @@ def from_string(s: str) -> "KeyType":
94101
raise ValueError(f"Unsupported key type {s}")
95102

96103

97-
KeyTypeLitteral = Literal[
98-
"RSA", "Curve25519", "EC_P224", "EC_P256", "EC_P384", "EC_P521", "Generic"
99-
]
100-
101-
102104
class KeyMechanism(enum.Enum):
103105
RSA_DECRYPTION_RAW = "RSA_Decryption_RAW"
104106
RSA_DECRYPTION_PKCS1 = "RSA_Decryption_PKCS1"
@@ -120,33 +122,24 @@ class KeyMechanism(enum.Enum):
120122
AES_ENCRYPTION_CBC = "AES_Encryption_CBC"
121123
AES_DECRYPTION_CBC = "AES_Decryption_CBC"
122124

123-
124-
KeyMechanismLiteral = Literal[
125-
"RSA_Decryption_RAW",
126-
"RSA_Decryption_PKCS1",
127-
"RSA_Decryption_OAEP_MD5",
128-
"RSA_Decryption_OAEP_SHA1",
129-
"RSA_Decryption_OAEP_SHA224",
130-
"RSA_Decryption_OAEP_SHA256",
131-
"RSA_Decryption_OAEP_SHA384",
132-
"RSA_Decryption_OAEP_SHA512",
133-
"RSA_Signature_PKCS1",
134-
"RSA_Signature_PSS_MD5",
135-
"RSA_Signature_PSS_SHA1",
136-
"RSA_Signature_PSS_SHA224",
137-
"RSA_Signature_PSS_SHA256",
138-
"RSA_Signature_PSS_SHA384",
139-
"RSA_Signature_PSS_SHA512",
140-
"EdDSA_Signature",
141-
"ECDSA_Signature",
142-
"AES_Encryption_CBC",
143-
"AES_Decryption_CBC",
144-
]
125+
@staticmethod
126+
def from_string(s: str) -> "KeyMechanism":
127+
for key_mechanism in KeyMechanism:
128+
if key_mechanism.value == s:
129+
return key_mechanism
130+
raise ValueError(f"Unsupported key mechanism {s}")
145131

146132

147133
class EncryptMode(enum.Enum):
148134
AES_CBC = "AES_CBC"
149135

136+
@staticmethod
137+
def from_string(s: str) -> "EncryptMode":
138+
for mode in EncryptMode:
139+
if mode.value == s:
140+
return mode
141+
raise ValueError(f"Unsupported encrypt mode {s}")
142+
150143

151144
class DecryptMode(enum.Enum):
152145
RAW = "RAW"
@@ -159,6 +152,13 @@ class DecryptMode(enum.Enum):
159152
OAEP_SHA512 = "OAEP_SHA512"
160153
AES_CBC = "AES_CBC"
161154

155+
@staticmethod
156+
def from_string(s: str) -> "DecryptMode":
157+
for mode in DecryptMode:
158+
if mode.value == s:
159+
return mode
160+
raise ValueError(f"Unsupported decrypt mode {s}")
161+
162162

163163
class SignMode(enum.Enum):
164164
PKCS1 = "PKCS1"
@@ -171,6 +171,13 @@ class SignMode(enum.Enum):
171171
EDDSA = "EdDSA"
172172
ECDSA = "ECDSA"
173173

174+
@staticmethod
175+
def from_string(s: str) -> "SignMode":
176+
for mode in SignMode:
177+
if mode.value == s:
178+
return mode
179+
raise ValueError(f"Unsupported sign mode {s}")
180+
174181

175182
class TlsKeyType(enum.Enum):
176183
RSA = "RSA"
@@ -180,6 +187,13 @@ class TlsKeyType(enum.Enum):
180187
EC_P384 = "EC_P384"
181188
EC_P521 = "EC_P521"
182189

190+
@staticmethod
191+
def from_string(s: str) -> "TlsKeyType":
192+
for key_type in TlsKeyType:
193+
if key_type.value == s:
194+
return key_type
195+
raise ValueError(f"Unsupported TLS key type {s}")
196+
183197

184198
@dataclass
185199
class SystemInfo:
@@ -199,7 +213,7 @@ class User:
199213
@dataclass
200214
class Key:
201215
key_id: str
202-
mechanisms: list[str]
216+
mechanisms: list[KeyMechanism]
203217
type: KeyType
204218
operations: int
205219
tags: Optional[list[str]]
@@ -521,7 +535,7 @@ def get_user(self, user_id: str) -> User:
521535
def add_user(
522536
self,
523537
real_name: str,
524-
role: Literal["Administrator", "Operator", "Metrics", "Backup"],
538+
role: Role,
525539
passphrase: str,
526540
user_id: Optional[str] = None,
527541
) -> str:
@@ -530,7 +544,7 @@ def add_user(
530544

531545
body = UserPostDataDict(
532546
realName=real_name,
533-
role=role,
547+
role=role.value,
534548
passphrase=passphrase,
535549
)
536550
try:
@@ -769,7 +783,9 @@ def get_key(self, key_id: str) -> Key:
769783
)
770784
return Key(
771785
key_id=key_id,
772-
mechanisms=[mechanism for mechanism in key.mechanisms],
786+
mechanisms=[
787+
KeyMechanism.from_string(mechanism) for mechanism in key.mechanisms
788+
],
773789
type=KeyType.from_string(key.type),
774790
operations=key.operations,
775791
tags=[str(tag) for tag in cast(list[str], key.restrictions["tags"])]
@@ -805,14 +821,15 @@ def get_key_public_key(self, key_id: str) -> str:
805821
def add_key(
806822
self,
807823
key_id: str,
808-
type: KeyTypeLitteral,
809-
mechanisms: list[KeyMechanismLiteral],
824+
type: KeyType,
825+
mechanisms: list[KeyMechanism],
810826
tags: list[str],
811827
prime_p: Optional[str],
812828
prime_q: Optional[str],
813829
public_exponent: Optional[str],
814830
data: Optional[str],
815831
) -> str:
832+
from .client.components.schema.key_mechanisms import KeyMechanismsTupleInput
816833
from .client.components.schema.key_private_data import KeyPrivateDataDict
817834
from .client.components.schema.key_restrictions import KeyRestrictionsDict
818835
from .client.components.schema.private_key import PrivateKeyDict
@@ -821,7 +838,7 @@ def add_key(
821838
# To do: split into different methods for RSA and other key types, or
822839
# at least change typing accordingly
823840

824-
if type == "RSA":
841+
if type == KeyType.RSA:
825842
assert prime_p
826843
assert prime_q
827844
assert public_exponent
@@ -834,19 +851,23 @@ def add_key(
834851
assert data
835852
key_data = KeyPrivateDataDict(data=data)
836853

854+
mechanism_tuple: KeyMechanismsTupleInput = [
855+
mechanism.value for mechanism in mechanisms
856+
]
857+
837858
if tags:
838859
body = PrivateKeyDict(
839-
type=type,
840-
mechanisms=mechanisms,
860+
type=type.value,
861+
mechanisms=mechanism_tuple,
841862
key=key_data,
842863
restrictions=KeyRestrictionsDict(
843864
tags=TagListTuple([tag for tag in tags])
844865
),
845866
)
846867
else:
847868
body = PrivateKeyDict(
848-
type=type,
849-
mechanisms=mechanisms,
869+
type=type.value,
870+
mechanisms=mechanism_tuple,
850871
key=key_data,
851872
)
852873

@@ -899,27 +920,31 @@ def delete_key(self, key_id: str) -> None:
899920

900921
def generate_key(
901922
self,
902-
type: KeyTypeLitteral,
903-
mechanisms: tuple[KeyMechanismLiteral],
923+
type: KeyType,
924+
mechanisms: list[KeyMechanism],
904925
length: int,
905926
key_id: Optional[str] = None,
906927
) -> str:
907928
from .client.components.schema.key_generate_request_data import (
908929
KeyGenerateRequestDataDict,
909930
)
910-
from .client.components.schema.key_mechanisms import KeyMechanismsTuple
931+
from .client.components.schema.key_mechanisms import KeyMechanismsTupleInput
932+
933+
mechanism_tuple: KeyMechanismsTupleInput = [
934+
mechanism.value for mechanism in mechanisms
935+
]
911936

912937
if key_id:
913938
body = KeyGenerateRequestDataDict(
914-
type=type,
915-
mechanisms=KeyMechanismsTuple(mechanisms),
939+
type=type.value,
940+
mechanisms=mechanism_tuple,
916941
length=length,
917942
id=key_id,
918943
)
919944
else:
920945
body = KeyGenerateRequestDataDict(
921-
type=type,
922-
mechanisms=KeyMechanismsTuple(mechanisms),
946+
type=type.value,
947+
mechanisms=mechanism_tuple,
923948
length=length,
924949
)
925950
try:
@@ -1101,7 +1126,7 @@ def csr(
11011126

11021127
def generate_tls_key(
11031128
self,
1104-
type: Literal["RSA", "Curve25519", "EC_P224", "EC_P256", "EC_P384", "EC_P521"],
1129+
type: TlsKeyType,
11051130
length: Optional[int] = None,
11061131
) -> None:
11071132
from .client.components.schema.tls_key_generate_request_data import (
@@ -1110,7 +1135,7 @@ def generate_tls_key(
11101135
from .client.schemas import Unset
11111136

11121137
body = TlsKeyGenerateRequestDataDict(
1113-
type=type,
1138+
type=type.value,
11141139
length=length if length is not None else Unset(),
11151140
)
11161141

@@ -1207,11 +1232,13 @@ def set_logging_config(
12071232
self,
12081233
ip_address: str,
12091234
port: int,
1210-
log_level: Literal["debug", "info", "warning", "error"],
1235+
log_level: LogLevel,
12111236
) -> None:
12121237
from .client.components.schema.logging_config import LoggingConfigDict
12131238

1214-
body = LoggingConfigDict(ipAddress=ip_address, port=port, logLevel=log_level)
1239+
body = LoggingConfigDict(
1240+
ipAddress=ip_address, port=port, logLevel=log_level.value
1241+
)
12151242
try:
12161243
self.get_api().config_logging_put(body=body)
12171244
except Exception as e:
@@ -1256,12 +1283,12 @@ def set_time(self, time: Union[str, datetime]) -> None:
12561283
},
12571284
)
12581285

1259-
def set_unattended_boot(self, status: Literal["on", "off"]) -> None:
1286+
def set_unattended_boot(self, status: UnattendedBootStatus) -> None:
12601287
from .client.components.schema.unattended_boot_config import (
12611288
UnattendedBootConfigDict,
12621289
)
12631290

1264-
body = UnattendedBootConfigDict(status=status)
1291+
body = UnattendedBootConfigDict(status=status.value)
12651292
try:
12661293
self.get_api().config_unattended_boot_put(body=body)
12671294
except Exception as e:
@@ -1402,7 +1429,7 @@ def factory_reset(self) -> None:
14021429
)
14031430

14041431
def encrypt(
1405-
self, key_id: str, data: str, mode: Literal["AES_CBC"], iv: str
1432+
self, key_id: str, data: str, mode: EncryptMode, iv: str
14061433
) -> tuple[str, str]:
14071434
from .client.components.schema.encrypt_request_data import (
14081435
EncryptRequestDataDict,
@@ -1412,7 +1439,7 @@ def encrypt(
14121439
)
14131440

14141441
path_params = PathParametersDict(KeyID=key_id)
1415-
body = EncryptRequestDataDict(message=data, mode=mode, iv=iv)
1442+
body = EncryptRequestDataDict(message=data, mode=mode.value, iv=iv)
14161443
try:
14171444
response = self.get_api().keys_key_id_encrypt_post(
14181445
path_params=path_params, body=body
@@ -1433,17 +1460,7 @@ def decrypt(
14331460
self,
14341461
key_id: str,
14351462
data: str,
1436-
mode: Literal[
1437-
"RAW",
1438-
"PKCS1",
1439-
"OAEP_MD5",
1440-
"OAEP_SHA1",
1441-
"OAEP_SHA224",
1442-
"OAEP_SHA256",
1443-
"OAEP_SHA384",
1444-
"OAEP_SHA512",
1445-
"AES_CBC",
1446-
],
1463+
mode: DecryptMode,
14471464
iv: str,
14481465
) -> str:
14491466
from .client.components.schema.decrypt_request_data import (
@@ -1453,10 +1470,10 @@ def decrypt(
14531470
PathParametersDict,
14541471
)
14551472

1456-
body = DecryptRequestDataDict(encrypted=data, mode=mode, iv=iv)
1473+
body = DecryptRequestDataDict(encrypted=data, mode=mode.value, iv=iv)
14571474

14581475
if len(iv) == 0:
1459-
body = DecryptRequestDataDict(encrypted=data, mode=mode)
1476+
body = DecryptRequestDataDict(encrypted=data, mode=mode.value)
14601477

14611478
path_params = PathParametersDict(KeyID=key_id)
14621479
try:
@@ -1479,25 +1496,15 @@ def sign(
14791496
self,
14801497
key_id: str,
14811498
data: str,
1482-
mode: Literal[
1483-
"PKCS1",
1484-
"PSS_MD5",
1485-
"PSS_SHA1",
1486-
"PSS_SHA224",
1487-
"PSS_SHA256",
1488-
"PSS_SHA384",
1489-
"PSS_SHA512",
1490-
"EdDSA",
1491-
"ECDSA",
1492-
],
1499+
mode: SignMode,
14931500
) -> str:
14941501
from .client.components.schema.sign_request_data import SignRequestDataDict
14951502
from .client.paths.keys_key_id_sign.post.path_parameters import (
14961503
PathParametersDict,
14971504
)
14981505

14991506
path_params = PathParametersDict(KeyID=key_id)
1500-
body = SignRequestDataDict(message=data, mode=mode)
1507+
body = SignRequestDataDict(message=data, mode=mode.value)
15011508
try:
15021509
response = self.get_api().keys_key_id_sign_post(
15031510
path_params=path_params, body=body

0 commit comments

Comments
 (0)