diff options
| author | Simeon Simeonov | 2022-03-21 13:25:26 +0100 |
|---|---|---|
| committer | Simeon Simeonov | 2022-03-21 13:25:26 +0100 |
| commit | 12c5e38691b24d12a8fc9605e3b93fac86d461ae (patch) | |
| tree | e27a66b696bf42b61fe2c27e864e9a73c9b57d4a /otp2289/server.py | |
| parent | 9179abf6c4b3e3eb2c991cbbd74738e160b910cc (diff) | |
Reformat the code with annotations and new code style
Diffstat (limited to 'otp2289/server.py')
| -rw-r--r-- | otp2289/server.py | 120 |
1 files changed, 73 insertions, 47 deletions
diff --git a/otp2289/server.py b/otp2289/server.py index d0c3e01..7f742cd 100644 --- a/otp2289/server.py +++ b/otp2289/server.py | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
| 2 | # SPDX-License-Identifier: BSD-2-Clause-FreeBSD | 2 | # SPDX-License-Identifier: BSD-2-Clause-FreeBSD |
| 3 | # | 3 | # |
| 4 | # Copyright (c) 2020, Simeon Simeonov | 4 | # Copyright (c) 2020-2022 Simeon Simeonov |
| 5 | # All rights reserved. | 5 | # All rights reserved. |
| 6 | # | 6 | # |
| 7 | # Redistribution and use in source and binary forms, with or without | 7 | # Redistribution and use in source and binary forms, with or without |
| @@ -27,9 +27,7 @@ | |||
| 27 | import binascii | 27 | import binascii |
| 28 | import hashlib | 28 | import hashlib |
| 29 | 29 | ||
| 30 | from .generator import (OTP_ALGO_MD5, | 30 | from .generator import OTP_ALGO_MD5, OTPGenerator, OTPGeneratorException |
| 31 | OTPGenerator, | ||
| 32 | OTPGeneratorException) | ||
| 33 | 31 | ||
| 34 | 32 | ||
| 35 | class OTPStateException(Exception): | 33 | class OTPStateException(Exception): |
| @@ -53,7 +51,13 @@ class OTPState: | |||
| 53 | - validate the corresponding generated response from the generator | 51 | - validate the corresponding generated response from the generator |
| 54 | """ | 52 | """ |
| 55 | 53 | ||
| 56 | def __init__(self, ot_hex, current_step, seed, hash_algo=OTP_ALGO_MD5): | 54 | def __init__( |
| 55 | self, | ||
| 56 | ot_hex: str, | ||
| 57 | current_step: int, | ||
| 58 | seed: str, | ||
| 59 | hash_algo=OTP_ALGO_MD5, | ||
| 60 | ): | ||
| 57 | """ | 61 | """ |
| 58 | Constructs an OTPState object with the given arguments. | 62 | Constructs an OTPState object with the given arguments. |
| 59 | 63 | ||
| @@ -87,45 +91,47 @@ class OTPState: | |||
| 87 | 91 | ||
| 88 | def __repr__(self): | 92 | def __repr__(self): |
| 89 | """repr implementation""" | 93 | """repr implementation""" |
| 90 | return (f'{self.__class__} at {id(self)} ' | 94 | return ( |
| 91 | f'(ot_hex={self._current_digest}, current_step={self._step}, ' | 95 | f'{self.__class__} at {id(self)} ' |
| 92 | f'seed={self._seed}, ' | 96 | f'(ot_hex={self._current_digest}, current_step={self._step}, ' |
| 93 | f'hash_algo={self._hash_algo})') | 97 | f'seed={self._seed}, ' |
| 98 | f'hash_algo={self._hash_algo})' | ||
| 99 | ) | ||
| 94 | 100 | ||
| 95 | @property | 101 | @property |
| 96 | def challenge_string(self): | 102 | def challenge_string(self) -> str: |
| 97 | """challenge_string-property""" | 103 | """challenge_string-property""" |
| 98 | # RFC-2289: "...the entire challenge string MUST be | 104 | # RFC-2289: "...the entire challenge string MUST be |
| 99 | # terminated with either a space or a new line." | 105 | # terminated with either a space or a new line." |
| 100 | return f'otp-{self._hash_algo} {self._step} {self._seed} ' | 106 | return f'otp-{self._hash_algo} {self._step} {self._seed} ' |
| 101 | 107 | ||
| 102 | @property | 108 | @property |
| 103 | def current_digest(self): | 109 | def current_digest(self) -> bytes: |
| 104 | """current_digest-property""" | 110 | """current_digest-property""" |
| 105 | return self._current_digest | 111 | return self._current_digest |
| 106 | 112 | ||
| 107 | @property | 113 | @property |
| 108 | def hash_algo(self): | 114 | def hash_algo(self) -> str: |
| 109 | """hash_algo-property""" | 115 | """hash_algo-property""" |
| 110 | return self._hash_algo | 116 | return self._hash_algo |
| 111 | 117 | ||
| 112 | @property | 118 | @property |
| 113 | def seed(self): | 119 | def seed(self) -> str: |
| 114 | """seed-property""" | 120 | """seed-property""" |
| 115 | return self._seed | 121 | return self._seed |
| 116 | 122 | ||
| 117 | @property | 123 | @property |
| 118 | def step(self): | 124 | def step(self) -> int: |
| 119 | """step-property""" | 125 | """step-property""" |
| 120 | return self._step | 126 | return self._step |
| 121 | 127 | ||
| 122 | @property | 128 | @property |
| 123 | def validated(self): | 129 | def validated(self) -> bool: |
| 124 | """validated-property""" | 130 | """validated-property""" |
| 125 | return bool(self._new_digest_hex) | 131 | return bool(self._new_digest_hex) |
| 126 | 132 | ||
| 127 | @classmethod | 133 | @classmethod |
| 128 | def from_dict(cls, dict_obj): | 134 | def from_dict(cls, dict_obj: dict): |
| 129 | """ | 135 | """ |
| 130 | Returns an OTPState object from the dict-object | 136 | Returns an OTPState object from the dict-object |
| 131 | 137 | ||
| @@ -138,7 +144,7 @@ class OTPState: | |||
| 138 | return cls(**dict_obj) | 144 | return cls(**dict_obj) |
| 139 | 145 | ||
| 140 | @staticmethod | 146 | @staticmethod |
| 141 | def response_to_bytes(response): | 147 | def response_to_bytes(response: str) -> bytes: |
| 142 | """ | 148 | """ |
| 143 | A wrapper that handles/validates the response as specified by RFC-2289. | 149 | A wrapper that handles/validates the response as specified by RFC-2289. |
| 144 | 150 | ||
| @@ -166,10 +172,11 @@ class OTPState: | |||
| 166 | return OTPState.validate_hex(response) | 172 | return OTPState.validate_hex(response) |
| 167 | except OTPStateException: | 173 | except OTPStateException: |
| 168 | raise OTPInvalidResponse( | 174 | raise OTPInvalidResponse( |
| 169 | 'The response is neither a valid token or hex') from None | 175 | 'The response is neither a valid token or hex' |
| 176 | ) from None | ||
| 170 | 177 | ||
| 171 | @staticmethod | 178 | @staticmethod |
| 172 | def validate_hex(ot_hex): | 179 | def validate_hex(ot_hex: str) -> bytes: |
| 173 | """ | 180 | """ |
| 174 | Validates the provided hexidigest. | 181 | Validates the provided hexidigest. |
| 175 | 182 | ||
| @@ -187,8 +194,10 @@ class OTPState: | |||
| 187 | ot_hex = ot_hex[2:] | 194 | ot_hex = ot_hex[2:] |
| 188 | ot_hex = ot_hex.strip().lower() | 195 | ot_hex = ot_hex.strip().lower() |
| 189 | if len(ot_hex) != 16: | 196 | if len(ot_hex) != 16: |
| 190 | raise OTPStateException('The length of the hex should be 16 ' | 197 | raise OTPStateException( |
| 191 | '(representing 64 bits digest)') | 198 | 'The length of the hex should be 16 ' |
| 199 | '(representing 64 bits digest)' | ||
| 200 | ) | ||
| 192 | try: | 201 | try: |
| 193 | return binascii.unhexlify(ot_hex) | 202 | return binascii.unhexlify(ot_hex) |
| 194 | except binascii.Error: | 203 | except binascii.Error: |
| @@ -206,12 +215,18 @@ class OTPState: | |||
| 206 | """ | 215 | """ |
| 207 | if self._new_digest_hex is None: | 216 | if self._new_digest_hex is None: |
| 208 | return None | 217 | return None |
| 209 | return OTPState(self._new_digest_hex, | 218 | return OTPState( |
| 210 | self._step - 1, | 219 | self._new_digest_hex, |
| 211 | self._seed, | 220 | self._step - 1, |
| 212 | self._hash_algo) | 221 | self._seed, |
| 222 | self._hash_algo, | ||
| 223 | ) | ||
| 213 | 224 | ||
| 214 | def response_validates(self, response, store_valid_response=True): | 225 | def response_validates( |
| 226 | self, | ||
| 227 | response: str, | ||
| 228 | store_valid_response: str = True, | ||
| 229 | ) -> bool: | ||
| 215 | """ | 230 | """ |
| 216 | Validates the incoming response as specified by RFC-2289. | 231 | Validates the incoming response as specified by RFC-2289. |
| 217 | 232 | ||
| @@ -233,32 +248,35 @@ class OTPState: | |||
| 233 | if self._hash_algo == 'md5': | 248 | if self._hash_algo == 'md5': |
| 234 | digest = hashlib.md5(response_bytes).digest() | 249 | digest = hashlib.md5(response_bytes).digest() |
| 235 | if ( | 250 | if ( |
| 236 | self._current_digest is None or | 251 | self._current_digest is None |
| 237 | OTPGenerator.strxor(digest[0:8], digest[8:]) == | 252 | or OTPGenerator.strxor(digest[0:8], digest[8:]) |
| 238 | self._current_digest | 253 | == self._current_digest |
| 239 | ): | 254 | ): |
| 240 | if store_valid_response: | 255 | if store_valid_response: |
| 241 | self._new_digest_hex = binascii.hexlify( | 256 | self._new_digest_hex = binascii.hexlify( |
| 242 | response_bytes).decode() | 257 | response_bytes |
| 258 | ).decode() | ||
| 243 | return True | 259 | return True |
| 244 | return False | 260 | return False |
| 245 | if self._hash_algo == 'sha1': | 261 | if self._hash_algo == 'sha1': |
| 246 | digest = hashlib.sha1(response_bytes).digest() | 262 | digest = hashlib.sha1(response_bytes).digest() |
| 247 | if ( | 263 | if ( |
| 248 | self._current_digest is None or | 264 | self._current_digest is None |
| 249 | OTPGenerator.sha1_digest_folding( | 265 | or OTPGenerator.sha1_digest_folding( |
| 250 | hashlib.sha1( | 266 | hashlib.sha1(response_bytes).digest() |
| 251 | response_bytes).digest()) == self._current_digest | 267 | ) |
| 268 | == self._current_digest | ||
| 252 | ): | 269 | ): |
| 253 | if store_valid_response: | 270 | if store_valid_response: |
| 254 | self._new_digest_hex = binascii.hexlify( | 271 | self._new_digest_hex = binascii.hexlify( |
| 255 | response_bytes).decode() | 272 | response_bytes |
| 273 | ).decode() | ||
| 256 | return True | 274 | return True |
| 257 | return False | 275 | return False |
| 258 | # this should not happen since the hash_algo is validated by the caller | 276 | # this should not happen since the hash_algo is validated by the caller |
| 259 | raise OTPInvalidResponse(f'Ivalid hash_algo: {self._hash_algo}') | 277 | raise OTPInvalidResponse(f'Ivalid hash_algo: {self._hash_algo}') |
| 260 | 278 | ||
| 261 | def to_dict(self): | 279 | def to_dict(self) -> dict: |
| 262 | """ | 280 | """ |
| 263 | Returns a dict representation of the object. | 281 | Returns a dict representation of the object. |
| 264 | 282 | ||
| @@ -270,10 +288,12 @@ class OTPState: | |||
| 270 | ot_hex = self._current_digest | 288 | ot_hex = self._current_digest |
| 271 | if ot_hex is not None: | 289 | if ot_hex is not None: |
| 272 | ot_hex = binascii.hexlify(self._current_digest).decode() | 290 | ot_hex = binascii.hexlify(self._current_digest).decode() |
| 273 | return {'ot_hex': ot_hex, | 291 | return { |
| 274 | 'current_step': self._step, | 292 | 'ot_hex': ot_hex, |
| 275 | 'seed': self._seed, | 293 | 'current_step': self._step, |
| 276 | 'hash_algo': self._hash_algo} | 294 | 'seed': self._seed, |
| 295 | 'hash_algo': self._hash_algo, | ||
| 296 | } | ||
| 277 | 297 | ||
| 278 | 298 | ||
| 279 | class OTPStore: | 299 | class OTPStore: |
| @@ -285,6 +305,7 @@ class OTPStore: | |||
| 285 | 305 | ||
| 286 | The class could serve as a base class when implementing store backends. | 306 | The class could serve as a base class when implementing store backends. |
| 287 | """ | 307 | """ |
| 308 | |||
| 288 | def __init__(self, data=None): | 309 | def __init__(self, data=None): |
| 289 | """ | 310 | """ |
| 290 | Constructs an OTPStore object from data | 311 | Constructs an OTPStore object from data |
| @@ -310,7 +331,7 @@ class OTPStore: | |||
| 310 | return len(self._data) | 331 | return len(self._data) |
| 311 | 332 | ||
| 312 | @property | 333 | @property |
| 313 | def data(self): | 334 | def data(self) -> dict: |
| 314 | """ | 335 | """ |
| 315 | data-property | 336 | data-property |
| 316 | 337 | ||
| @@ -320,7 +341,7 @@ class OTPStore: | |||
| 320 | return self._data | 341 | return self._data |
| 321 | 342 | ||
| 322 | @property | 343 | @property |
| 323 | def states(self): | 344 | def states(self) -> dict: |
| 324 | """ | 345 | """ |
| 325 | states-property | 346 | states-property |
| 326 | 347 | ||
| @@ -329,7 +350,7 @@ class OTPStore: | |||
| 329 | """ | 350 | """ |
| 330 | return self._states | 351 | return self._states |
| 331 | 352 | ||
| 332 | def add_state(self, key, state): | 353 | def add_state(self, key: str, state: OTPState): |
| 333 | """ | 354 | """ |
| 334 | Adds an OTPState object with a given key. | 355 | Adds an OTPState object with a given key. |
| 335 | 356 | ||
| @@ -356,7 +377,7 @@ class OTPStore: | |||
| 356 | """A wrapper for dict.items""" | 377 | """A wrapper for dict.items""" |
| 357 | return self._data.items() | 378 | return self._data.items() |
| 358 | 379 | ||
| 359 | def pop_state(self, key): | 380 | def pop_state(self, key: str) -> OTPState: |
| 360 | """ | 381 | """ |
| 361 | Removes specified key and returns the corresponding OTPState-object. | 382 | Removes specified key and returns the corresponding OTPState-object. |
| 362 | 383 | ||
| @@ -376,7 +397,12 @@ class OTPStore: | |||
| 376 | self._states.pop(state) | 397 | self._states.pop(state) |
| 377 | return state | 398 | return state |
| 378 | 399 | ||
| 379 | def response_validates(self, key, response, store_valid_response=True): | 400 | def response_validates( |
| 401 | self, | ||
| 402 | key: str, | ||
| 403 | response: str, | ||
| 404 | store_valid_response: bool = True, | ||
| 405 | ) -> bool: | ||
| 380 | """ | 406 | """ |
| 381 | A method that wraps around OTPState.response_validates and | 407 | A method that wraps around OTPState.response_validates and |
| 382 | OTPState.get_next_state. | 408 | OTPState.get_next_state. |
| @@ -411,7 +437,7 @@ class OTPStore: | |||
| 411 | self._states.pop(state) | 437 | self._states.pop(state) |
| 412 | return rvalue | 438 | return rvalue |
| 413 | 439 | ||
| 414 | def to_dict(self): | 440 | def to_dict(self) -> dict: |
| 415 | """ | 441 | """ |
| 416 | Returns a dict representation of the object. | 442 | Returns a dict representation of the object. |
| 417 | 443 | ||
| @@ -422,7 +448,7 @@ class OTPStore: | |||
| 422 | """ | 448 | """ |
| 423 | return {key: state.to_dict() for key, state in self._data.items()} | 449 | return {key: state.to_dict() for key, state in self._data.items()} |
| 424 | 450 | ||
| 425 | def _add_data(self, dict_obj): | 451 | def _add_data(self, dict_obj: dict) -> dict: |
| 426 | """ | 452 | """ |
| 427 | Adds data from a dict object (dict_obj). | 453 | Adds data from a dict object (dict_obj). |
| 428 | 454 | ||
