eventsourcing 9.3.5__py3-none-any.whl → 9.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of eventsourcing might be problematic. Click here for more details.

eventsourcing/cipher.py CHANGED
@@ -5,18 +5,20 @@ from base64 import b64decode, b64encode
5
5
  from typing import TYPE_CHECKING
6
6
 
7
7
  from Crypto.Cipher import AES
8
- from Crypto.Cipher._mode_gcm import GcmMode
8
+ from Crypto.Cipher._mode_gcm import (
9
+ GcmMode, # pyright: ignore [reportPrivateImportUsage]
10
+ )
9
11
  from Crypto.Cipher.AES import key_size
10
12
 
11
13
  from eventsourcing.persistence import Cipher
12
14
 
13
- if TYPE_CHECKING: # pragma: nocover
15
+ if TYPE_CHECKING:
14
16
  from eventsourcing.utils import Environment
15
17
 
16
18
 
17
19
  class AESCipher(Cipher):
18
- """
19
- Cipher strategy that uses AES cipher in GCM mode.
20
+ """Cipher strategy that uses AES cipher (in GCM mode)
21
+ from the Python pycryptodome package.
20
22
  """
21
23
 
22
24
  CIPHER_KEY = "CIPHER_KEY"
@@ -24,8 +26,7 @@ class AESCipher(Cipher):
24
26
 
25
27
  @staticmethod
26
28
  def create_key(num_bytes: int) -> str:
27
- """
28
- Creates AES cipher key, with length num_bytes.
29
+ """Creates AES cipher key, with length num_bytes.
29
30
 
30
31
  :param num_bytes: An int value, either 16, 24, or 32.
31
32
 
@@ -44,8 +45,7 @@ class AESCipher(Cipher):
44
45
  return os.urandom(num_bytes)
45
46
 
46
47
  def __init__(self, environment: Environment):
47
- """
48
- Initialises AES cipher with ``cipher_key``.
48
+ """Initialises AES cipher with ``cipher_key``.
49
49
 
50
50
  :param str cipher_key: 16, 24, or 32 bytes encoded as base64
51
51
  """
@@ -59,7 +59,6 @@ class AESCipher(Cipher):
59
59
 
60
60
  def encrypt(self, plaintext: bytes) -> bytes:
61
61
  """Return ciphertext for given plaintext."""
62
-
63
62
  # Construct AES-GCM cipher, with 96-bit nonce.
64
63
  nonce = AESCipher.random_bytes(12)
65
64
  cipher = self.construct_cipher(nonce)
@@ -71,6 +70,7 @@ class AESCipher(Cipher):
71
70
 
72
71
  # Return ciphertext.
73
72
  return nonce + tag + encrypted
73
+ # return nonce + tag + encrypted
74
74
 
75
75
  def construct_cipher(self, nonce: bytes) -> GcmMode:
76
76
  cipher = AES.new(
@@ -83,7 +83,6 @@ class AESCipher(Cipher):
83
83
 
84
84
  def decrypt(self, ciphertext: bytes) -> bytes:
85
85
  """Return plaintext for given ciphertext."""
86
-
87
86
  # Split out the nonce, tag, and encrypted data.
88
87
  nonce = ciphertext[:12]
89
88
  if len(nonce) != 12:
@@ -7,13 +7,9 @@ from eventsourcing.persistence import Compressor
7
7
 
8
8
  class ZlibCompressor(Compressor):
9
9
  def compress(self, data: bytes) -> bytes:
10
- """
11
- Compress bytes using zlib.
12
- """
10
+ """Compress bytes using zlib."""
13
11
  return zlib.compress(data)
14
12
 
15
13
  def decompress(self, data: bytes) -> bytes:
16
- """
17
- Decompress bytes using zlib.
18
- """
14
+ """Decompress bytes using zlib."""
19
15
  return zlib.decompress(data)
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from base64 import b64decode, b64encode
5
+ from typing import TYPE_CHECKING
6
+
7
+ from cryptography.exceptions import InvalidTag
8
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
9
+
10
+ from eventsourcing.persistence import Cipher
11
+
12
+ if TYPE_CHECKING:
13
+ from eventsourcing.utils import Environment
14
+
15
+
16
+ class AESCipher(Cipher):
17
+ """Cipher strategy that uses AES cipher (in GCM mode)
18
+ from the Python cryptography package.
19
+ """
20
+
21
+ CIPHER_KEY = "CIPHER_KEY"
22
+ KEY_SIZES = (16, 24, 32)
23
+
24
+ @staticmethod
25
+ def create_key(num_bytes: int) -> str:
26
+ """Creates AES cipher key, with length num_bytes.
27
+
28
+ :param num_bytes: An int value, either 16, 24, or 32.
29
+
30
+ """
31
+ AESCipher.check_key_size(num_bytes)
32
+ key = AESGCM.generate_key(num_bytes * 8)
33
+ return b64encode(key).decode("utf8")
34
+
35
+ @staticmethod
36
+ def check_key_size(num_bytes: int) -> None:
37
+ if num_bytes not in AESCipher.KEY_SIZES:
38
+ msg = f"Invalid key size: {num_bytes} not in {AESCipher.KEY_SIZES}"
39
+ raise ValueError(msg)
40
+
41
+ @staticmethod
42
+ def random_bytes(num_bytes: int) -> bytes:
43
+ return os.urandom(num_bytes)
44
+
45
+ def __init__(self, environment: Environment):
46
+ """Initialises AES cipher with ``cipher_key``.
47
+
48
+ :param str cipher_key: 16, 24, or 32 bytes encoded as base64
49
+ """
50
+ cipher_key = environment.get(self.CIPHER_KEY)
51
+ if not cipher_key:
52
+ msg = f"'{self.CIPHER_KEY}' not in env"
53
+ raise OSError(msg)
54
+ key = b64decode(cipher_key.encode("utf8"))
55
+ AESCipher.check_key_size(len(key))
56
+ self.key = key
57
+
58
+ def encrypt(self, plaintext: bytes) -> bytes:
59
+ """Return ciphertext for given plaintext."""
60
+ # Construct AES-GCM cipher, with 96-bit nonce.
61
+ aesgcm = AESGCM(self.key)
62
+ nonce = AESCipher.random_bytes(12)
63
+ res = aesgcm.encrypt(nonce, plaintext, None)
64
+ # Put tag at the front for compatibility with eventsourcing.crypto.AESCipher.
65
+ tag = res[-16:]
66
+ encrypted = res[:-16]
67
+ return nonce + tag + encrypted
68
+
69
+ def decrypt(self, ciphertext: bytes) -> bytes:
70
+ """Return plaintext for given ciphertext."""
71
+ # Split out the nonce, tag, and encrypted data.
72
+ nonce = ciphertext[:12]
73
+ if len(nonce) != 12:
74
+ msg = "Damaged cipher text: invalid nonce length"
75
+ raise ValueError(msg)
76
+
77
+ # Expect tag at the front.
78
+ tag = ciphertext[12:28]
79
+ if len(tag) != 16:
80
+ msg = "Damaged cipher text: invalid tag length"
81
+ raise ValueError(msg)
82
+ encrypted = ciphertext[28:]
83
+
84
+ aesgcm = AESGCM(self.key)
85
+ try:
86
+ plaintext = aesgcm.decrypt(nonce, encrypted + tag, None)
87
+ except InvalidTag as e:
88
+ msg = "Invalid cipher tag"
89
+ raise ValueError(msg) from e
90
+ # Decrypt and verify.
91
+ return plaintext
eventsourcing/dispatch.py CHANGED
@@ -1,14 +1,50 @@
1
1
  from __future__ import annotations
2
2
 
3
- from functools import singledispatchmethod as _singledispatchmethod
3
+ import functools
4
+ from typing import TYPE_CHECKING, Any, Callable, Generic, TypeVar, cast, overload
4
5
 
6
+ _T = TypeVar("_T")
7
+ _S = TypeVar("_S")
5
8
 
6
- class singledispatchmethod(_singledispatchmethod): # noqa: N801
7
- def __init__(self, func):
9
+ if TYPE_CHECKING:
10
+
11
+ class _singledispatchmethod(functools.singledispatchmethod[_T]): # noqa: N801
12
+ pass
13
+
14
+ else:
15
+
16
+ class _singledispatchmethod( # noqa: N801
17
+ functools.singledispatchmethod, Generic[_T]
18
+ ):
19
+ pass
20
+
21
+
22
+ class singledispatchmethod(_singledispatchmethod[_T]): # noqa: N801
23
+ def __init__(self, func: Callable[..., _T]) -> None:
8
24
  super().__init__(func)
9
- self.deferred_registrations = []
25
+ self.deferred_registrations: list[
26
+ tuple[type[Any] | Callable[..., _T], Callable[..., _T] | None]
27
+ ] = []
28
+
29
+ @overload
30
+ def register(
31
+ self, cls: type[Any], method: None = None
32
+ ) -> Callable[[Callable[..., _T]], Callable[..., _T]]: ... # pragma: no cover
33
+ @overload
34
+ def register(
35
+ self, cls: Callable[..., _T], method: None = None
36
+ ) -> Callable[..., _T]: ... # pragma: no cover
37
+
38
+ @overload
39
+ def register(
40
+ self, cls: type[Any], method: Callable[..., _T]
41
+ ) -> Callable[..., _T]: ... # pragma: no cover
10
42
 
11
- def register(self, cls, method=None):
43
+ def register(
44
+ self,
45
+ cls: type[Any] | Callable[..., _T],
46
+ method: Callable[..., _T] | None = None,
47
+ ) -> Callable[[Callable[..., _T]], Callable[..., _T]] | Callable[..., _T]:
12
48
  """generic_method.register(cls, func) -> func
13
49
 
14
50
  Registers a new implementation for the given *cls* on a *generic_method*.
@@ -22,17 +58,22 @@ class singledispatchmethod(_singledispatchmethod): # noqa: N801
22
58
 
23
59
  # for globals in typing.get_type_hints() in Python 3.8 and 3.9
24
60
  if not hasattr(cls, "__wrapped__"):
25
- cls.__wrapped__ = cls.__func__
61
+ cls.__dict__["__wrapped__"] = cls.__func__
62
+ # cls.__wrapped__ = cls.__func__
26
63
 
27
64
  try:
28
- return self.dispatcher.register(cls, func=method)
65
+ return self.dispatcher.register(cast("type[Any]", cls), func=method)
29
66
  except NameError:
30
- self.deferred_registrations.append([cls, method])
67
+ self.deferred_registrations.append(
68
+ (cls, method) # pyright: ignore [reportArgumentType]
69
+ )
31
70
  # TODO: Fix this....
32
- return method or cls
71
+ return method or cls # pyright: ignore [reportReturnType]
33
72
 
34
- def __get__(self, obj, cls=None):
73
+ def __get__(self, obj: _S, cls: type[_S] | None = None) -> Callable[..., _T]:
35
74
  for registered_cls, registered_method in self.deferred_registrations:
36
- self.dispatcher.register(registered_cls, func=registered_method)
75
+ self.dispatcher.register(
76
+ cast("type[Any]", registered_cls), func=registered_method
77
+ )
37
78
  self.deferred_registrations = []
38
79
  return super().__get__(obj, cls=cls)