jamlib 3.2.0rc2__tar.gz → 3.3.0__tar.gz

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.
Files changed (136) hide show
  1. {jamlib-3.2.0rc2/src/jamlib.egg-info → jamlib-3.3.0}/PKG-INFO +5 -1
  2. {jamlib-3.2.0rc2 → jamlib-3.3.0}/README.md +4 -0
  3. {jamlib-3.2.0rc2 → jamlib-3.3.0}/pyproject.toml +1 -1
  4. jamlib-3.3.0/src/jam/__defaults__.py +21 -0
  5. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__init__.py +1 -1
  6. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/__init__.py +20 -0
  7. jamlib-3.3.0/src/jam/exceptions/saml.py +66 -0
  8. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/middleware.py +7 -7
  9. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/objects.py +2 -5
  10. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/starlette/objects.py +5 -6
  11. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/__algorithms__.py +9 -0
  12. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jwe.py +2 -0
  13. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jwk.py +0 -9
  14. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jws.py +1 -1
  15. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jwt.py +52 -25
  16. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/redis.py +5 -3
  17. jamlib-3.3.0/src/jam/saml/__base__.py +577 -0
  18. jamlib-3.3.0/src/jam/saml/__init__.py +129 -0
  19. jamlib-3.3.0/src/jam/saml/binding.py +188 -0
  20. jamlib-3.3.0/src/jam/saml/encryption.py +244 -0
  21. jamlib-3.3.0/src/jam/saml/metadata.py +211 -0
  22. jamlib-3.3.0/src/jam/saml/saml.py +1943 -0
  23. jamlib-3.3.0/src/jam/saml/signature.py +287 -0
  24. jamlib-3.3.0/src/jam/saml/types.py +185 -0
  25. jamlib-3.3.0/src/jam/saml/xml.py +174 -0
  26. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/tests/clients.py +267 -53
  27. jamlib-3.3.0/src/jam/tests/fakers.py +187 -0
  28. {jamlib-3.2.0rc2 → jamlib-3.3.0/src/jamlib.egg-info}/PKG-INFO +5 -1
  29. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/SOURCES.txt +11 -0
  30. jamlib-3.2.0rc2/src/jam/tests/fakers.py +0 -102
  31. {jamlib-3.2.0rc2 → jamlib-3.3.0}/LICENSE.md +0 -0
  32. {jamlib-3.2.0rc2 → jamlib-3.3.0}/setup.cfg +0 -0
  33. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__base__.py +0 -0
  34. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__base_encoder__.py +0 -0
  35. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__deprecated__.py +0 -0
  36. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/__base__.py +0 -0
  37. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/__init__.py +0 -0
  38. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/instance.py +0 -0
  39. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/jwt/__init__.py +0 -0
  40. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/__base__.py +0 -0
  41. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/__init__.py +0 -0
  42. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/__init__.py +0 -0
  43. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/github.py +0 -0
  44. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/gitlab.py +0 -0
  45. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/google.py +0 -0
  46. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/yandex.py +0 -0
  47. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/client.py +0 -0
  48. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/__base__.py +0 -0
  49. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/__init__.py +0 -0
  50. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/json.py +0 -0
  51. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/redis.py +0 -0
  52. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/__init__.py +0 -0
  53. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/cli.py +0 -0
  54. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/commands/__init__.py +0 -0
  55. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/commands/keys.py +0 -0
  56. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/commands/password.py +0 -0
  57. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/encoders.py +0 -0
  58. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/base.py +0 -0
  59. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/jose.py +0 -0
  60. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/jwt.py +0 -0
  61. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/oauth2.py +0 -0
  62. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/paseto.py +0 -0
  63. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/plugins.py +0 -0
  64. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/sessions.py +0 -0
  65. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/__init__.py +0 -0
  66. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/fastapi/__init__.py +0 -0
  67. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/flask/__init__.py +0 -0
  68. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/flask/extensions.py +0 -0
  69. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/flask/objects.py +0 -0
  70. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/__init__.py +0 -0
  71. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/plugins.py +0 -0
  72. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/starlette/__init__.py +0 -0
  73. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/starlette/backends.py +0 -0
  74. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/instance.py +0 -0
  75. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/__base__.py +0 -0
  76. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/__init__.py +0 -0
  77. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/__base__.py +0 -0
  78. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/__init__.py +0 -0
  79. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/json.py +0 -0
  80. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/memory.py +0 -0
  81. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/utils.py +0 -0
  82. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__algorithms__.py +0 -0
  83. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__base__.py +0 -0
  84. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__init__.py +0 -0
  85. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__types__.py +0 -0
  86. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/__base__.py +0 -0
  87. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/__init__.py +0 -0
  88. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/json.py +0 -0
  89. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/redis.py +0 -0
  90. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/module.py +0 -0
  91. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/utils.py +0 -0
  92. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/logger.py +0 -0
  93. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/__base__.py +0 -0
  94. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/__init__.py +0 -0
  95. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/__init__.py +0 -0
  96. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/github.py +0 -0
  97. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/gitlab.py +0 -0
  98. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/google.py +0 -0
  99. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/yandex.py +0 -0
  100. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/client.py +0 -0
  101. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/__base__.py +0 -0
  102. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/__init__.py +0 -0
  103. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/hotp.py +0 -0
  104. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/totp.py +0 -0
  105. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/__base__.py +0 -0
  106. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/__init__.py +0 -0
  107. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/utils.py +0 -0
  108. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v1.py +0 -0
  109. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v2.py +0 -0
  110. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v3.py +0 -0
  111. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v4.py +0 -0
  112. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/plugins/__base__.py +0 -0
  113. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/plugins/__init__.py +0 -0
  114. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/py.typed +0 -0
  115. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/__base__.py +0 -0
  116. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/__init__.py +0 -0
  117. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/json.py +0 -0
  118. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/redis.py +0 -0
  119. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/tests/__init__.py +0 -0
  120. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/__init__.py +0 -0
  121. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/aes.py +0 -0
  122. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/await_maybe.py +0 -0
  123. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/basic_auth.py +0 -0
  124. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/config_maker.py +0 -0
  125. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/ed.py +0 -0
  126. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/otp_keys.py +0 -0
  127. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/rsa.py +0 -0
  128. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/salt_hash.py +0 -0
  129. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/symmetric.py +0 -0
  130. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/version_check.py +0 -0
  131. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/xchacha20poly1305.py +0 -0
  132. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/xor.py +0 -0
  133. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/dependency_links.txt +0 -0
  134. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/entry_points.txt +0 -0
  135. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/requires.txt +0 -0
  136. {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jamlib
3
- Version: 3.2.0rc2
3
+ Version: 3.3.0
4
4
  Summary: Simple and universal library for authorization.
5
5
  Author-email: Makridenko Adrian <adrianmakridenko@duck.com>, Ksenia Travnikova <kseniatravnikova@duck.com>
6
6
  License: Apache-2.0
@@ -62,6 +62,8 @@ Dynamic: license-file
62
62
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/lyaguxafrog/jam)
63
63
  [![GitHub License](https://img.shields.io/github/license/lyaguxafrog/jam)](https://github.com/lyaguxafrog/jam/blob/master/LICENSE.md)
64
64
 
65
+ **Jam (Jam Auth Module)** - A universal auth* combine that provides popular auth mechanisms strictly according to the specification.
66
+
65
67
  * Documentation: [jam.makridenko.ru](https://jam.makridenko.ru)
66
68
  * Changelog: [CHANGELOG.md](https://github.com/lyaguxafrog/jam/blob/master/CHANGELOG.md)
67
69
 
@@ -90,6 +92,7 @@ Jam is a library that provides the most popular AUTH* mechanisms right out of th
90
92
  * [Server side sessions](https://jam.makridenko.ru/usage/sessions/)
91
93
  * [OTP](https://jam.makridenko.ru/usage/otp/)
92
94
  * [OAuth2](https://jam.makridenko.ru/usage/oauth2/)
95
+ * [SAML](https://jam.makridenko.ru/usage/saml/)
93
96
 
94
97
  ### Framework integrations
95
98
 
@@ -115,5 +118,6 @@ Here is a comparison with other libraries:
115
118
  | Server side sessions | ✅ | ✅ | ❌ | ❌ | ❌ |
116
119
  | OTP | ✅ | ❌ | ❌ | ❌ | ✅ |
117
120
  | OAuth2 | ✅ | ✅ | ❌ | ✅ | ❌ |
121
+ | SAML 2.0 | ✅ | ❌ | ❌ | ❌ | ❌ |
118
122
  | Flexible config | ✅ | ❌ | ❌ | ❌ | ❌ |
119
123
  | Modularity | ✅ | ❌ | ❌ | ❌ | ❌ |
@@ -9,6 +9,8 @@
9
9
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/lyaguxafrog/jam)
10
10
  [![GitHub License](https://img.shields.io/github/license/lyaguxafrog/jam)](https://github.com/lyaguxafrog/jam/blob/master/LICENSE.md)
11
11
 
12
+ **Jam (Jam Auth Module)** - A universal auth* combine that provides popular auth mechanisms strictly according to the specification.
13
+
12
14
  * Documentation: [jam.makridenko.ru](https://jam.makridenko.ru)
13
15
  * Changelog: [CHANGELOG.md](https://github.com/lyaguxafrog/jam/blob/master/CHANGELOG.md)
14
16
 
@@ -37,6 +39,7 @@ Jam is a library that provides the most popular AUTH* mechanisms right out of th
37
39
  * [Server side sessions](https://jam.makridenko.ru/usage/sessions/)
38
40
  * [OTP](https://jam.makridenko.ru/usage/otp/)
39
41
  * [OAuth2](https://jam.makridenko.ru/usage/oauth2/)
42
+ * [SAML](https://jam.makridenko.ru/usage/saml/)
40
43
 
41
44
  ### Framework integrations
42
45
 
@@ -62,5 +65,6 @@ Here is a comparison with other libraries:
62
65
  | Server side sessions | ✅ | ✅ | ❌ | ❌ | ❌ |
63
66
  | OTP | ✅ | ❌ | ❌ | ❌ | ✅ |
64
67
  | OAuth2 | ✅ | ✅ | ❌ | ✅ | ❌ |
68
+ | SAML 2.0 | ✅ | ❌ | ❌ | ❌ | ❌ |
65
69
  | Flexible config | ✅ | ❌ | ❌ | ❌ | ❌ |
66
70
  | Modularity | ✅ | ❌ | ❌ | ❌ | ❌ |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "jamlib"
3
- version = "3.2.0rc2"
3
+ version = "3.3.0"
4
4
  description = "Simple and universal library for authorization."
5
5
  authors = [
6
6
  {name = "Makridenko Adrian",email = "adrianmakridenko@duck.com"},
@@ -0,0 +1,21 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from dataclasses import dataclass
4
+ import os
5
+
6
+
7
+ @dataclass(slots=True, frozen=True)
8
+ class DEFAULTS:
9
+ """Jam defaults vars."""
10
+
11
+ DEBUG: bool = os.getenv("JAM_DEBUG", "False") == "True"
12
+ """Debug flag. Use only while developing jam module."""
13
+
14
+ TEMP_DIR: str = os.getenv("JAM_TMP_DIR", "/tmp/jam/")
15
+ """tmp directory for storing temporary files and blobs."""
16
+
17
+ ENABLE_PLUGINS: bool = os.getenv("JAM_ENABLE_PLUGINS", "1") == "0"
18
+ """Enabling experimental plugin features."""
19
+
20
+
21
+ defaults = DEFAULTS()
@@ -10,5 +10,5 @@ from jam.__base__ import BaseJam
10
10
  from jam.instance import Jam
11
11
 
12
12
 
13
- __version__ = "3.2.0rc2"
13
+ __version__ = "3.3.0"
14
14
  __all__ = ["Jam", "BaseJam"]
@@ -45,6 +45,17 @@ from .sessions import (
45
45
  JamSessionEmptyAESKey,
46
46
  JamSessionNotFound,
47
47
  )
48
+ from .saml import (
49
+ JamSAMLError,
50
+ JamSAMLExpired,
51
+ JamSAMLNotYetValid,
52
+ JamSAMLInvalidAudience,
53
+ JamSAMLInvalidIssuer,
54
+ JamSAMLEmptyPrivateKey,
55
+ JamSAMLEmptyPublicKey,
56
+ JamSAMLUnsupportedAlgorithm,
57
+ JamSAMLValidationError,
58
+ )
48
59
 
49
60
 
50
61
  __all__ = [
@@ -81,4 +92,13 @@ __all__ = [
81
92
  "JamStarlettePluginError",
82
93
  "JamSessionNotFound",
83
94
  "JamSessionEmptyAESKey",
95
+ "JamSAMLError",
96
+ "JamSAMLExpired",
97
+ "JamSAMLNotYetValid",
98
+ "JamSAMLInvalidAudience",
99
+ "JamSAMLInvalidIssuer",
100
+ "JamSAMLEmptyPrivateKey",
101
+ "JamSAMLEmptyPublicKey",
102
+ "JamSAMLUnsupportedAlgorithm",
103
+ "JamSAMLValidationError",
84
104
  ]
@@ -0,0 +1,66 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .base import JamConfigurationError, JamError, JamValidationError
4
+
5
+
6
+ class JamSAMLError(JamError):
7
+ default_message = "SAML error occurred."
8
+ default_code = "saml.error"
9
+
10
+
11
+ class JamSAMLExpired(JamError):
12
+ default_message = "SAML assertion has expired."
13
+ default_code = "saml.assertion_expired"
14
+
15
+
16
+ class JamSAMLNotYetValid(JamError):
17
+ default_message = "SAML assertion is not yet valid."
18
+ default_code = "saml.assertion_not_yet_valid"
19
+
20
+
21
+ class JamSAMLInvalidAudience(JamError):
22
+ default_message = "SAML assertion audience validation failed."
23
+ default_code = "saml.invalid_audience"
24
+
25
+
26
+ class JamSAMLInvalidIssuer(JamError):
27
+ default_message = "SAML assertion issuer validation failed."
28
+ default_code = "saml.invalid_issuer"
29
+
30
+
31
+ class JamSAMLEmptyPrivateKey(JamConfigurationError):
32
+ default_message = "For SAML signing, you must specify `private_key`."
33
+ default_code = "saml.config.empty_private_key"
34
+
35
+
36
+ class JamSAMLEmptyPublicKey(JamConfigurationError):
37
+ default_message = "For SAML verification, you must specify `public_key`."
38
+ default_code = "saml.config.empty_public_key"
39
+
40
+
41
+ class JamSAMLUnsupportedAlgorithm(JamConfigurationError):
42
+ default_message = "Unsupported SAML algorithm."
43
+ default_code = "saml.config.unsupported_algorithm"
44
+
45
+
46
+ class JamSAMLValidationError(JamValidationError):
47
+ default_message = "SAML assertion validation failed."
48
+ default_code = "saml.validation.assertion_error"
49
+
50
+
51
+ class JamSAMLInvalidRecipient(JamError):
52
+ default_message = (
53
+ "SAML assertion SubjectConfirmationData Recipient "
54
+ "does not match the expected ACS URL."
55
+ )
56
+ default_code = "saml.invalid_recipient"
57
+
58
+
59
+ class JamSAMLReplayDetected(JamError):
60
+ default_message = "SAML message ID has already been consumed (replay attack)."
61
+ default_code = "saml.replay_detected"
62
+
63
+
64
+ class JamSAMLSOAPError(JamError):
65
+ default_message = "SAML SOAP/artifact resolution failed."
66
+ default_code = "saml.soap_error"
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- from collections.abc import Callable
3
+ from typing import Any
4
4
 
5
5
  from litestar.connection import ASGIConnection
6
6
  from litestar.middleware import (
@@ -18,7 +18,7 @@ from jam.paseto import BasePASETO
18
18
  class BaseMiddleware(AbstractAuthenticationMiddleware):
19
19
  """Base Jam middleware for litestar."""
20
20
 
21
- AUTH_MODULE: Callable
21
+ AUTH_MODULE: Any
22
22
  HEADER_NAME: str | None
23
23
  COOKIE_NAME: str | None
24
24
  BEARER: bool = False
@@ -95,7 +95,7 @@ class SessionMiddleware(BaseMiddleware):
95
95
  if not data:
96
96
  return AuthenticationResult(None, auth=token_model)
97
97
  user = self.USER.from_payload(data)
98
- return AuthenticationResult(user, token)
98
+ return AuthenticationResult(user=user, auth=token_model)
99
99
  except Exception as e:
100
100
  raise JamLitestarPluginError(message=str(e))
101
101
 
@@ -113,10 +113,10 @@ class PASETOMiddleware(BaseMiddleware):
113
113
  if not token:
114
114
  return AuthenticationResult(None, auth=token_model)
115
115
  try:
116
- data = self.AUTH_MODULE.decode(token)
117
- if not data:
116
+ result = self.AUTH_MODULE.decode(token)
117
+ if not result:
118
118
  return AuthenticationResult(None, auth=token_model)
119
- user = self.USER.from_payload(data)
120
- return AuthenticationResult(user, token)
119
+ user = self.USER.from_payload(result[0])
120
+ return AuthenticationResult(user=user, auth=token_model)
121
121
  except Exception as e:
122
122
  raise JamLitestarPluginError(message=str(e))
@@ -4,10 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from abc import ABC, abstractmethod
6
6
  from dataclasses import dataclass
7
- from typing import Any, TypeVar
8
-
9
-
10
- User = TypeVar("User", bound="BaseUser")
7
+ from typing import Any
11
8
 
12
9
 
13
10
  @dataclass(slots=True)
@@ -23,7 +20,7 @@ class BaseUser(ABC):
23
20
 
24
21
  @classmethod
25
22
  @abstractmethod
26
- def from_payload(cls, payload: dict[str, Any]) -> User:
23
+ def from_payload(cls, payload: dict[str, Any]) -> BaseUser:
27
24
  """Build model from payload.
28
25
 
29
26
  Args:
@@ -1,21 +1,20 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
+ from __future__ import annotations
4
+
3
5
  from abc import ABC, abstractmethod
4
6
  from dataclasses import dataclass
5
- from typing import Any, TypeVar
7
+ from typing import Any
6
8
 
7
9
  from starlette.authentication import BaseUser as StarletteBaseUser
8
10
 
9
11
 
10
- User = TypeVar("User", bound="BaseUser")
11
-
12
-
13
12
  class BaseUser(ABC, StarletteBaseUser):
14
13
  """BaseUser with builder from payload."""
15
14
 
16
15
  @classmethod
17
16
  @abstractmethod
18
- def from_payload(cls, payload: dict[str, Any]) -> User:
17
+ def from_payload(cls, payload: dict[str, Any]) -> BaseUser:
19
18
  """Create instance by payload.
20
19
 
21
20
  Args:
@@ -49,7 +48,7 @@ class SimpleUser(BaseUser):
49
48
  payload: dict[str, Any]
50
49
 
51
50
  @classmethod
52
- def from_payload(cls, payload: dict[str, Any]): # noqa
51
+ def from_payload(cls, payload: dict[str, Any]) -> SimpleUser: # noqa
53
52
  return cls(payload=payload)
54
53
 
55
54
  def is_authenticated(self) -> bool: # type: ignore # noqa
@@ -112,6 +112,8 @@ class BaseAlgorithm(ABC):
112
112
  except OSError:
113
113
  is_file = False
114
114
  return path.read_bytes() if is_file else data.encode()
115
+ if isinstance(data, rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey):
116
+ data = data.public_key()
115
117
  return data.public_bytes(
116
118
  encoding=serialization.Encoding.PEM,
117
119
  format=serialization.PublicFormat.SubjectPublicKeyInfo,
@@ -602,6 +604,11 @@ class BaseKeyAlgorithm(ABC):
602
604
  if hasattr(self._key, "private_key"):
603
605
  return self._key.private_key()
604
606
 
607
+ if isinstance(
608
+ self._key, rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey
609
+ ):
610
+ return self._key
611
+
605
612
  key_bytes = self._load_key_data(self._key)
606
613
  return serialization.load_pem_private_key(
607
614
  key_bytes, password=self._password
@@ -620,6 +627,8 @@ class BaseKeyAlgorithm(ABC):
620
627
  except OSError:
621
628
  is_file = False
622
629
  return path.read_bytes() if is_file else data.encode()
630
+ if isinstance(data, rsa.RSAPrivateKey | ec.EllipticCurvePrivateKey):
631
+ data = data.public_key()
623
632
  return data.public_bytes(
624
633
  encoding=serialization.Encoding.PEM,
625
634
  format=serialization.PublicFormat.SubjectPublicKeyInfo,
@@ -66,6 +66,8 @@ class JWE(BaseJWE):
66
66
 
67
67
  self._alg = alg.upper()
68
68
  self._enc = enc.upper()
69
+ if isinstance(key, JWKClass):
70
+ key = key._to_keylike()
69
71
  self._key = key
70
72
  self._password = password
71
73
  self._serializer = serializer
@@ -56,9 +56,6 @@ class JWKRSA(JWKCommon, total=False):
56
56
  ... }
57
57
  """
58
58
 
59
- kty: Literal["RSA"]
60
- """Key Type. Fixed to "RSA"."""
61
-
62
59
  n: str
63
60
  """RSA modulus n. The modulus value for the RSA public key."""
64
61
 
@@ -98,9 +95,6 @@ class JWKEC(JWKCommon, total=False):
98
95
  ... }
99
96
  """
100
97
 
101
- kty: Literal["EC"]
102
- """Key Type. Fixed to "EC"."""
103
-
104
98
  crv: Literal["P-256", "P-384", "P-521"]
105
99
  """Elliptic curve name. The curve on which the key is based."""
106
100
 
@@ -126,9 +120,6 @@ class JWKOct(JWKCommon, total=False):
126
120
  ... }
127
121
  """
128
122
 
129
- kty: Literal["oct"]
130
- """Key Type. Fixed to "oct"."""
131
-
132
123
  k: str
133
124
  """Key value. The base64url-encoded symmetric key value."""
134
125
 
@@ -84,7 +84,7 @@ class JWS(BaseJWS):
84
84
  def serialize_compact(
85
85
  self,
86
86
  protected: dict[str, Any],
87
- payload: bytes | str,
87
+ payload: bytes | str | dict[str, Any],
88
88
  ) -> str:
89
89
  """Create JWS Compact Serialization.
90
90
 
@@ -25,7 +25,7 @@ from jam.jose.__algorithms__ import (
25
25
  KeyLike,
26
26
  create_algorithm,
27
27
  )
28
- from jam.jose.__base__ import BaseJWS, BaseJWT
28
+ from jam.jose.__base__ import BaseJWT
29
29
  from jam.jose.jwe import JWE
30
30
  from jam.jose.jws import JWS
31
31
  from jam.jose.lists import BaseJWTList
@@ -81,6 +81,11 @@ class JWT(BaseJWT):
81
81
  self._logger = logger
82
82
  self._serializer = serializer
83
83
 
84
+ self.jws: JWS | None = None
85
+ self.jwe: JWE | None = None
86
+ self._alg: str | None = None
87
+ self._enc: str | None = None
88
+
84
89
  if jws is not None:
85
90
  if alg is not None:
86
91
  raise JamConfigurationError(
@@ -158,47 +163,60 @@ class JWT(BaseJWT):
158
163
  return key.encode() if key else key
159
164
  return key
160
165
 
161
- def _build_jws(self) -> BaseJWS:
162
- if not self._alg or not self._key:
166
+ def _build_jws(self) -> JWS:
167
+ alg = self._alg
168
+ key = self._key
169
+ if not alg or not key:
163
170
  raise JamConfigurationError(message="JWS requires 'alg' and 'key'")
164
171
 
165
- return self.JWS(
166
- alg=self._alg,
167
- key=self._key,
172
+ return JWS(
173
+ alg=alg,
174
+ key=key,
168
175
  password=self._password,
169
176
  logger=self._logger,
170
177
  )
171
178
 
172
179
  def _build_jwe(self) -> JWE:
173
- if not self._enc:
180
+ enc = self._enc
181
+ if not enc:
174
182
  raise JamConfigurationError(
175
183
  message="JWE requires 'enc' to be provided"
176
184
  )
177
185
 
178
- key_type = self._detect_key_type(self._key)
186
+ key = self._key
187
+ key_type = self._detect_key_type(key)
179
188
 
180
189
  if key_type == "rsa":
181
190
  jwe_alg = "RSA-OAEP"
182
- enc_key = self._key
191
+ enc_key = key
183
192
  elif key_type == "ec":
184
193
  jwe_alg = "ECDH-ES"
185
- enc_key = self._key
194
+ enc_key = key
186
195
  elif key_type == "symmetric":
187
- key_len = (
188
- len(self._key)
189
- if isinstance(self._key, bytes)
190
- else len(self._key.encode())
191
- )
196
+ if isinstance(key, bytes):
197
+ key_bytes = key
198
+ elif isinstance(key, str):
199
+ key_bytes = key.encode("utf-8")
200
+ else:
201
+ raise JamInvalidKeyTypeError(
202
+ message="Symmetric JWE requires a bytes or str key"
203
+ )
204
+ key_len = len(key_bytes)
192
205
  jwe_alg = "A256KW" if key_len >= 32 else "A128KW"
193
- enc_key = self._derive_encryption_key(self._key)
206
+ enc_key = self._derive_encryption_key(key_bytes)
194
207
  else:
195
208
  raise JamInvalidKeyTypeError(
196
209
  message=f"Unsupported key type for JWE: {key_type}"
197
210
  )
198
211
 
212
+ if enc_key is None:
213
+ raise JamInvalidKeyTypeError(
214
+ message=f"Unsupported key type for JWE: {key_type}"
215
+ )
216
+
199
217
  return self.JWE(
200
218
  alg=jwe_alg,
201
- enc=self._enc,
219
+ enc=enc,
202
220
  key=enc_key,
203
221
  password=self._password,
204
222
  serializer=self._serializer,
@@ -354,13 +372,15 @@ class JWT(BaseJWT):
354
372
  @property
355
373
  def _algo(self) -> BaseAlgorithm:
356
374
  """Get or create algorithm instance."""
375
+ alg = self._alg
376
+ key = self._key
357
377
  if self._algorithm is None:
358
- if not self._alg or not self._key:
378
+ if not alg or not key:
359
379
  raise JamConfigurationError(
360
380
  message="JWS requires 'alg' and 'key'"
361
381
  )
362
382
  self._algorithm = create_algorithm(
363
- self._alg, self._key, self._password, self._logger
383
+ alg, key, self._password, self._logger
364
384
  )
365
385
  return self._algorithm
366
386
 
@@ -569,10 +589,15 @@ class JWT(BaseJWT):
569
589
 
570
590
  decoded = self.jws.verify(plaintext, True)
571
591
  payload = decoded.get("payload")
572
- if isinstance(payload, bytes):
573
- payload_str = payload.decode("utf-8")
574
- else:
575
- payload_str = payload
592
+ payload_str = (
593
+ payload.decode("utf-8")
594
+ if isinstance(payload, bytes)
595
+ else payload
596
+ )
597
+ if not isinstance(payload_str, str):
598
+ raise JamJWSVerificationError(
599
+ message="Invalid JWS payload in nested token.",
600
+ )
576
601
 
577
602
  if "." in payload_str:
578
603
  inner_parts = payload_str.split(".")
@@ -594,9 +619,11 @@ class JWT(BaseJWT):
594
619
  inner_decoded = self.jws.verify(payload_str, True)
595
620
 
596
621
  inner_payload = inner_decoded.get("payload")
597
- if isinstance(inner_payload, bytes):
622
+ if isinstance(inner_payload, bytes | str):
598
623
  return self._serializer.loads(inner_payload)
599
- return self._serializer.loads(inner_payload)
624
+ raise JamJWSVerificationError(
625
+ message="Invalid inner JWS payload.",
626
+ )
600
627
 
601
628
  return self._serializer.loads(payload_str)
602
629
 
@@ -134,9 +134,11 @@ class RedisList(BaseJWTList):
134
134
  """
135
135
  if not tokens:
136
136
  return {}
137
- keys = [self._make_key(t) for t in tokens]
138
- exists = self._redis.exists(*keys)
139
- return {token: bool(result) for token, result in zip(tokens, exists)}
137
+ pipe = self._redis.pipeline()
138
+ for token in tokens:
139
+ pipe.exists(self._make_key(token))
140
+ results = pipe.execute()
141
+ return {token: bool(result) for token, result in zip(tokens, results)}
140
142
 
141
143
  def delete(self, token: str) -> None:
142
144
  """Remove a token from the list.