jamlib 3.3.0__tar.gz → 4.0.0a0__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 (146) hide show
  1. {jamlib-3.3.0/src/jamlib.egg-info → jamlib-4.0.0a0}/PKG-INFO +1 -1
  2. {jamlib-3.3.0 → jamlib-4.0.0a0}/pyproject.toml +1 -1
  3. jamlib-4.0.0a0/src/jam/__base__.py +348 -0
  4. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__init__.py +10 -2
  5. jamlib-4.0.0a0/src/jam/authz.py +99 -0
  6. jamlib-4.0.0a0/src/jam/instance.py +243 -0
  7. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/__base__.py +1 -1
  8. jamlib-4.0.0a0/src/jam/jose/__init__.py +20 -0
  9. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jwe.py +8 -1
  10. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jws.py +7 -1
  11. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jwt.py +54 -10
  12. jamlib-4.0.0a0/src/jam/jose/lists/__init__.py +22 -0
  13. {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/__base__.py +2 -2
  14. jamlib-4.0.0a0/src/jam/lists/__init__.py +14 -0
  15. {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/json.py +2 -2
  16. {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/memory.py +2 -2
  17. {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/redis.py +2 -2
  18. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/__base__.py +8 -1
  19. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/__init__.py +20 -16
  20. jamlib-4.0.0a0/src/jam/paseto/__base__.py +301 -0
  21. jamlib-4.0.0a0/src/jam/paseto/__init__.py +29 -0
  22. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v1.py +34 -42
  23. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v2.py +31 -31
  24. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v3.py +33 -42
  25. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v4.py +34 -38
  26. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/sessions/__base__.py +42 -1
  27. jamlib-4.0.0a0/src/jam/sessions/__init__.py +25 -0
  28. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/sessions/json.py +13 -0
  29. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/sessions/redis.py +12 -0
  30. jamlib-4.0.0a0/src/jam/subject.py +57 -0
  31. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/tests/clients.py +70 -0
  32. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/config_maker.py +0 -25
  33. jamlib-4.0.0a0/src/jam/utils/config_meta.py +69 -0
  34. {jamlib-3.3.0 → jamlib-4.0.0a0/src/jamlib.egg-info}/PKG-INFO +1 -1
  35. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/SOURCES.txt +8 -14
  36. jamlib-3.3.0/src/jam/__base__.py +0 -644
  37. jamlib-3.3.0/src/jam/instance.py +0 -581
  38. jamlib-3.3.0/src/jam/jose/__init__.py +0 -113
  39. jamlib-3.3.0/src/jam/jose/lists/__init__.py +0 -11
  40. jamlib-3.3.0/src/jam/jwt/__algorithms__.py +0 -488
  41. jamlib-3.3.0/src/jam/jwt/__base__.py +0 -60
  42. jamlib-3.3.0/src/jam/jwt/__init__.py +0 -65
  43. jamlib-3.3.0/src/jam/jwt/__types__.py +0 -15
  44. jamlib-3.3.0/src/jam/jwt/lists/__base__.py +0 -30
  45. jamlib-3.3.0/src/jam/jwt/lists/__init__.py +0 -8
  46. jamlib-3.3.0/src/jam/jwt/lists/json.py +0 -120
  47. jamlib-3.3.0/src/jam/jwt/lists/redis.py +0 -113
  48. jamlib-3.3.0/src/jam/jwt/module.py +0 -238
  49. jamlib-3.3.0/src/jam/jwt/utils.py +0 -36
  50. jamlib-3.3.0/src/jam/paseto/__base__.py +0 -172
  51. jamlib-3.3.0/src/jam/paseto/__init__.py +0 -57
  52. jamlib-3.3.0/src/jam/sessions/__init__.py +0 -89
  53. {jamlib-3.3.0 → jamlib-4.0.0a0}/LICENSE.md +0 -0
  54. {jamlib-3.3.0 → jamlib-4.0.0a0}/README.md +0 -0
  55. {jamlib-3.3.0 → jamlib-4.0.0a0}/setup.cfg +0 -0
  56. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__base_encoder__.py +0 -0
  57. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__defaults__.py +0 -0
  58. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__deprecated__.py +0 -0
  59. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/__base__.py +0 -0
  60. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/__init__.py +0 -0
  61. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/instance.py +0 -0
  62. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/jwt/__init__.py +0 -0
  63. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/__base__.py +0 -0
  64. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/__init__.py +0 -0
  65. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/__init__.py +0 -0
  66. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/github.py +0 -0
  67. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/gitlab.py +0 -0
  68. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/google.py +0 -0
  69. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/yandex.py +0 -0
  70. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/client.py +0 -0
  71. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/__base__.py +0 -0
  72. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/__init__.py +0 -0
  73. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/json.py +0 -0
  74. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/redis.py +0 -0
  75. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/__init__.py +0 -0
  76. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/cli.py +0 -0
  77. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/commands/__init__.py +0 -0
  78. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/commands/keys.py +0 -0
  79. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/commands/password.py +0 -0
  80. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/encoders.py +0 -0
  81. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/__init__.py +0 -0
  82. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/base.py +0 -0
  83. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/jose.py +0 -0
  84. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/jwt.py +0 -0
  85. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/oauth2.py +0 -0
  86. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/paseto.py +0 -0
  87. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/plugins.py +0 -0
  88. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/saml.py +0 -0
  89. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/sessions.py +0 -0
  90. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/__init__.py +0 -0
  91. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/fastapi/__init__.py +0 -0
  92. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/flask/__init__.py +0 -0
  93. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/flask/extensions.py +0 -0
  94. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/flask/objects.py +0 -0
  95. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/__init__.py +0 -0
  96. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/middleware.py +0 -0
  97. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/objects.py +0 -0
  98. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/plugins.py +0 -0
  99. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/starlette/__init__.py +0 -0
  100. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/starlette/backends.py +0 -0
  101. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/starlette/objects.py +0 -0
  102. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/__algorithms__.py +0 -0
  103. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jwk.py +0 -0
  104. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/utils.py +0 -0
  105. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/logger.py +0 -0
  106. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/__init__.py +0 -0
  107. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/github.py +0 -0
  108. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/gitlab.py +0 -0
  109. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/google.py +0 -0
  110. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/yandex.py +0 -0
  111. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/client.py +0 -0
  112. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/__base__.py +0 -0
  113. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/__init__.py +0 -0
  114. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/hotp.py +0 -0
  115. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/totp.py +0 -0
  116. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/utils.py +0 -0
  117. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/plugins/__base__.py +0 -0
  118. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/plugins/__init__.py +0 -0
  119. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/py.typed +0 -0
  120. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/__base__.py +0 -0
  121. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/__init__.py +0 -0
  122. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/binding.py +0 -0
  123. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/encryption.py +0 -0
  124. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/metadata.py +0 -0
  125. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/saml.py +0 -0
  126. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/signature.py +0 -0
  127. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/types.py +0 -0
  128. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/xml.py +0 -0
  129. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/tests/__init__.py +0 -0
  130. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/tests/fakers.py +0 -0
  131. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/__init__.py +0 -0
  132. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/aes.py +0 -0
  133. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/await_maybe.py +0 -0
  134. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/basic_auth.py +0 -0
  135. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/ed.py +0 -0
  136. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/otp_keys.py +0 -0
  137. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/rsa.py +0 -0
  138. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/salt_hash.py +0 -0
  139. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/symmetric.py +0 -0
  140. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/version_check.py +0 -0
  141. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/xchacha20poly1305.py +0 -0
  142. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/xor.py +0 -0
  143. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/dependency_links.txt +0 -0
  144. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/entry_points.txt +0 -0
  145. {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/requires.txt +0 -0
  146. {jamlib-3.3.0 → jamlib-4.0.0a0}/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.3.0
3
+ Version: 4.0.0a0
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "jamlib"
3
- version = "3.3.0"
3
+ version = "4.0.0a0"
4
4
  description = "Simple and universal library for authorization."
5
5
  authors = [
6
6
  {name = "Makridenko Adrian",email = "adrianmakridenko@duck.com"},
@@ -0,0 +1,348 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any, Literal
7
+
8
+ from jam.__base_encoder__ import BaseEncoder
9
+ from jam.authz import BasePolicy, Policy
10
+ from jam.encoders import JsonEncoder
11
+ from jam.exceptions import JamConfigurationError
12
+ from jam.logger import BaseLogger, JamLogger
13
+ from jam.plugins.__base__ import BasePlugin
14
+ from jam.subject import BaseSubject
15
+ from jam.utils.config_maker import __config_maker__, __module_loader__
16
+
17
+
18
+ class BaseJam(ABC):
19
+ """Base jam instance."""
20
+
21
+ subject: type[BaseSubject] = BaseSubject
22
+ config: dict[str, Any] = {}
23
+
24
+ jwt: Any = None
25
+ jws: Any = None
26
+ jwe: Any = None
27
+ jose: dict[str, Any] | None = None
28
+ session: Any = None
29
+ oauth2: dict[str, Any] | None = None
30
+ otp: Any = None
31
+ paseto: Any = None
32
+ _policy: BasePolicy | None = None
33
+
34
+ def __init__(
35
+ self,
36
+ config: str | dict[str, Any] | None = None,
37
+ pointer: str = "jam",
38
+ *,
39
+ logger: type[BaseLogger] = JamLogger,
40
+ log_level: Literal[
41
+ "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
42
+ ] = "INFO",
43
+ serializer: BaseEncoder | type[BaseEncoder] = JsonEncoder,
44
+ subject: type[BaseSubject] | None = None,
45
+ plugins: list[type[BasePlugin]] = [],
46
+ ) -> None:
47
+ """Initialize instance.
48
+
49
+ Args:
50
+ config (Union[str, dict[str, Any], None]): Configuration dict or
51
+ file path. Defaults to the class attribute.
52
+ pointer (str): Config pointer. Defaults to "jam".
53
+ logger (BaseLogger): Logger.
54
+ log_level (Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]): Log level.
55
+ serializer (Union[BaseEncoder, type[BaseEncoder]]): Serializer.
56
+ subject (type[BaseSubject] | None): Subject class override.
57
+ plugins (list[type[BasePlugin]]): List of plugins.
58
+ """
59
+ if config is None:
60
+ config = self.config or {}
61
+ config = __config_maker__(config, pointer)
62
+ main_config = self.__build_main_config(
63
+ config, logger, log_level, serializer
64
+ )
65
+
66
+ logger = main_config["logger"]
67
+ log_level = main_config["log_level"]
68
+ serializer = main_config["serializer"]
69
+
70
+ self.config = config
71
+ self._logger = logger(log_level)
72
+ self._serializer = serializer
73
+ self._plugins = []
74
+
75
+ if subject is not None:
76
+ self.subject = subject
77
+
78
+ self.jwt = None
79
+ self.jws = None
80
+ self.jwe = None
81
+ self.jose: dict[str, Any] | None = None
82
+ self.session = None
83
+ self.oauth2: dict[str, Any] | None = None
84
+ self.otp = None
85
+ self.paseto = None
86
+ self._policy: BasePolicy | None = None
87
+
88
+ self._logger.debug(
89
+ f"Initializing BaseJam with log_level={log_level}, serializer={serializer}"
90
+ )
91
+ self.__build_instance(config)
92
+ self._logger.debug(
93
+ "BaseJam initialization complete. Modules loaded:\n"
94
+ f" jwt={self.jwt is not None}, jws={self.jws is not None}, jwe={self.jwe is not None}, session={self.session is not None}, oauth2={self.oauth2 is not None}, paseto={self.paseto is not None}, otp={self.otp is not None}"
95
+ )
96
+
97
+ def __build_main_config(
98
+ self,
99
+ config: dict[str, Any],
100
+ default_logger: type[BaseLogger],
101
+ default_log_level: Literal[
102
+ "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"
103
+ ],
104
+ default_serializer: BaseEncoder | type[BaseEncoder],
105
+ ) -> dict[str, Any]:
106
+ """Build main params from config like logger, loglevel, etc.
107
+
108
+ Args:
109
+ config (dict[str, Any]): Configuration dictionary
110
+ default_logger (type[BaseLogger]): Default logger class
111
+ default_log_level (Literal): Default log level
112
+ default_serializer (BaseEncoder | type[BaseEncoder]): Default serializer
113
+
114
+ Returns:
115
+ dict[str, Any]: Dictionary with logger, log_level, and serializer
116
+ """
117
+ logger = default_logger
118
+ log_level = default_log_level
119
+ serializer = default_serializer
120
+
121
+ # Read logger from config
122
+ if "logger" in config:
123
+ logger_cfg = config["logger"]
124
+ if isinstance(logger_cfg, str):
125
+ logger = __module_loader__(logger_cfg)
126
+ elif isinstance(logger_cfg, type) and issubclass(
127
+ logger_cfg, BaseLogger
128
+ ):
129
+ logger = logger_cfg
130
+
131
+ if "log_level" in config:
132
+ log_level_cfg = config["log_level"]
133
+ if isinstance(log_level_cfg, str) and log_level_cfg.upper() in (
134
+ "DEBUG",
135
+ "INFO",
136
+ "WARNING",
137
+ "ERROR",
138
+ "CRITICAL",
139
+ ):
140
+ log_level = log_level_cfg.upper()
141
+
142
+ if "serializer" in config:
143
+ serializer_cfg = config["serializer"]
144
+ if isinstance(serializer_cfg, str):
145
+ serializer = __module_loader__(serializer_cfg)
146
+ elif isinstance(serializer_cfg, type) and issubclass(
147
+ serializer_cfg, BaseEncoder
148
+ ):
149
+ serializer = serializer_cfg
150
+ elif isinstance(serializer_cfg, BaseEncoder):
151
+ serializer = serializer_cfg
152
+
153
+ return {
154
+ "logger": logger,
155
+ "log_level": log_level,
156
+ "serializer": serializer,
157
+ }
158
+
159
+ def __build_instance(self, config: dict[str, Any]) -> None:
160
+ """Build module instances from configuration.
161
+
162
+ Args:
163
+ config (dict[str, Any]): Configuration
164
+ """
165
+ from jam.jose import JWE, JWS, JWT
166
+ from jam.oauth2 import build_clients
167
+ from jam.paseto import REGISTRY as PASETO_REGISTRY
168
+ from jam.sessions import REGISTRY as SESSION_REGISTRY
169
+
170
+ jose_cfg = config.get("jose") or {}
171
+ if not isinstance(jose_cfg, dict):
172
+ jose_cfg = {}
173
+ self.jose = {}
174
+
175
+ jwt_cfg = jose_cfg.get("jwt")
176
+ if jwt_cfg is not None:
177
+ self.jwt = JWT(config=jwt_cfg)
178
+ self.jose["jwt"] = self.jwt
179
+
180
+ jws_cfg = jose_cfg.get("jws")
181
+ if jws_cfg is not None:
182
+ self.jws = JWS(config=jws_cfg)
183
+ self.jose["jws"] = self.jws
184
+
185
+ jwe_cfg = jose_cfg.get("jwe")
186
+ if jwe_cfg is not None:
187
+ self.jwe = JWE(config=jwe_cfg)
188
+ self.jose["jwe"] = self.jwe
189
+
190
+ if not self.jose:
191
+ self.jose = None
192
+
193
+ session_cfg = config.get("session")
194
+ if isinstance(session_cfg, dict):
195
+ cfg = session_cfg.copy()
196
+ session_type = cfg.pop("type", None)
197
+ if session_type not in SESSION_REGISTRY:
198
+ raise JamConfigurationError(
199
+ message=(
200
+ f"Unknown session type: {session_type}. "
201
+ f"Available: {list(SESSION_REGISTRY)}"
202
+ ),
203
+ error_code="configuration.session.unknown_type",
204
+ )
205
+ module_cls = SESSION_REGISTRY[session_type]
206
+ self.session = module_cls(config=cfg, session_type=session_type)
207
+
208
+ oauth2_cfg = config.get("oauth2")
209
+ if isinstance(oauth2_cfg, dict) and oauth2_cfg:
210
+ self.oauth2 = build_clients(oauth2_cfg, serializer=self._serializer)
211
+
212
+ paseto_cfg = config.get("paseto")
213
+ if isinstance(paseto_cfg, dict):
214
+ cfg = paseto_cfg.copy()
215
+ version = cfg.pop("version", None)
216
+ if version not in PASETO_REGISTRY:
217
+ raise JamConfigurationError(
218
+ message=(
219
+ f"Unknown PASETO version: {version}. "
220
+ f"Available: {list(PASETO_REGISTRY)}"
221
+ ),
222
+ error_code="configuration.paseto.unknown_version",
223
+ )
224
+ module_cls = PASETO_REGISTRY[version]
225
+ self.paseto = module_cls(config=cfg)
226
+
227
+ otp_cfg = config.get("otp")
228
+ if isinstance(otp_cfg, dict):
229
+ from jam.otp import create_instance as create_otp
230
+
231
+ otp_type = otp_cfg.get("type")
232
+ if otp_type not in ("hotp", "totp"):
233
+ raise JamConfigurationError(
234
+ message=(
235
+ f"Unknown OTP type: {otp_type}. Available: hotp, totp"
236
+ ),
237
+ error_code="configuration.otp.unknown_type",
238
+ )
239
+ self.otp = create_otp(**otp_cfg)
240
+
241
+ authz_cfg = config.get("authz")
242
+ if isinstance(authz_cfg, dict):
243
+ module = authz_cfg.get("module")
244
+ if module is not None:
245
+ policy_cls = __module_loader__(module)
246
+ self._policy = policy_cls(authz_cfg.get("rules") or {})
247
+ else:
248
+ self._policy = Policy(rules=authz_cfg.get("rules") or {})
249
+
250
+ def _subject_from_payload(self, payload: dict[str, Any]) -> Any:
251
+ """Build a subject from a token/session payload.
252
+
253
+ Args:
254
+ payload (dict[str, Any]): Decoded payload.
255
+
256
+ Returns:
257
+ Any: Subject instance or dict if no subject class is configured.
258
+ """
259
+ import dataclasses
260
+
261
+ if not dataclasses.is_dataclass(self.subject):
262
+ return payload
263
+ field_names = {f.name for f in dataclasses.fields(self.subject)}
264
+ data = {k: v for k, v in payload.items() if k in field_names}
265
+ return self.subject.from_dict(data)
266
+
267
+ @abstractmethod
268
+ def authorize(self, subject: BaseSubject, permission: str) -> bool:
269
+ """Check whether a subject is allowed to perform a permission.
270
+
271
+ Args:
272
+ subject (BaseSubject): Subject instance.
273
+ permission (str): Permission name.
274
+
275
+ Returns:
276
+ bool: True if allowed, False otherwise.
277
+ """
278
+ raise NotImplementedError
279
+
280
+ @abstractmethod
281
+ def issue(
282
+ self,
283
+ subject: BaseSubject,
284
+ via: str | None = None,
285
+ exp: int | None = None,
286
+ iss: str | None = None,
287
+ aud: str | None = None,
288
+ nbf: int | None = None,
289
+ jti: str | None = None,
290
+ **claims: Any,
291
+ ) -> str:
292
+ """Issue a token or session for a subject.
293
+
294
+ Args:
295
+ subject (BaseSubject): Subject instance.
296
+ via (str | None): Token type: "jwt", "paseto", "session" or None
297
+ for auto-detect.
298
+ exp (int | None): Expiration in seconds.
299
+ iss (str | None): Issuer.
300
+ aud (str | None): Audience.
301
+ nbf (int | None): Not-before in seconds.
302
+ jti (str | None): Token ID.
303
+ **claims: Extra payload claims.
304
+
305
+ Returns:
306
+ str: Issued token or session ID.
307
+ """
308
+ raise NotImplementedError
309
+
310
+ @abstractmethod
311
+ def authenticate(
312
+ self, token: str, via: str | None = None
313
+ ) -> BaseSubject | dict[str, Any]:
314
+ """Authenticate a token or session and return a subject.
315
+
316
+ Args:
317
+ token (str): Token or session ID.
318
+ via (str | None): Token type: "jwt", "paseto", "session" or None
319
+ for auto-detect.
320
+
321
+ Returns:
322
+ BaseSubject | dict[str, Any]: Subject instance or raw payload dict.
323
+ """
324
+ raise NotImplementedError
325
+
326
+ def emit(self, event: str, **kwargs: Any) -> dict[str, Any]:
327
+ """Emit event.
328
+
329
+ Args:
330
+ event (str): Event name,
331
+ **kwargs: Event data
332
+
333
+ Returns:
334
+ dict[str, Any]: Updated event data.
335
+ """
336
+ for plugin in self._plugins:
337
+ handler = getattr(plugin, f"on_{event}", None)
338
+
339
+ if handler:
340
+ try:
341
+ result = handler(**kwargs)
342
+ if isinstance(result, dict):
343
+ kwargs.update(result)
344
+
345
+ except Exception as e:
346
+ self._logger.error(f"Plugin:{plugin.name} | error: {e}")
347
+
348
+ return kwargs
@@ -7,8 +7,16 @@ Documentation: https://jam.makridenko.ru
7
7
  """
8
8
 
9
9
  from jam.__base__ import BaseJam
10
+ from jam.authz import BasePolicy, Policy
10
11
  from jam.instance import Jam
12
+ from jam.subject import BaseSubject
11
13
 
12
14
 
13
- __version__ = "3.3.0"
14
- __all__ = ["Jam", "BaseJam"]
15
+ __version__ = "4.0.0a0"
16
+ __all__ = [
17
+ "Jam",
18
+ "BaseJam",
19
+ "BaseSubject",
20
+ "BasePolicy",
21
+ "Policy",
22
+ ]
@@ -0,0 +1,99 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ """Declarative authorization policies."""
4
+
5
+ from abc import ABC, abstractmethod
6
+ from collections.abc import Callable
7
+ from typing import Any
8
+
9
+ from jam.subject import BaseSubject
10
+
11
+
12
+ Predicate = str | Callable[[BaseSubject], bool]
13
+
14
+
15
+ class BasePolicy(ABC):
16
+ """Base policy contract.
17
+
18
+ Implementations decide whether ``subject`` may perform ``permission``.
19
+ """
20
+
21
+ @abstractmethod
22
+ def check(self, subject: BaseSubject, permission: str) -> bool:
23
+ """Check whether a subject is allowed to perform a permission.
24
+
25
+ Args:
26
+ subject (BaseSubject): Subject instance.
27
+ permission (str): Permission name, e.g. ``"post:edit"``.
28
+
29
+ Returns:
30
+ bool: True if allowed, False otherwise.
31
+ """
32
+ raise NotImplementedError
33
+
34
+
35
+ class Policy(BasePolicy):
36
+ """Declarative policy built from rules.
37
+
38
+ Rules map a permission to a list of predicates. A subject is granted a
39
+ permission if any predicate matches. Deny by default.
40
+
41
+ Predicate forms:
42
+ * ``"*"`` — allow every subject.
43
+ * ``"field=value"`` — allow if ``subject.field == value``.
44
+ * ``"field"`` — allow if ``subject.field`` is truthy.
45
+ * A callable receiving the subject and returning bool.
46
+
47
+ Example::
48
+
49
+ Policy(rules={"post:read": ["*"], "post:edit": ["author_id=42"]})
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ rules: dict[str, list[Predicate]] | None = None,
55
+ **kwargs: Any,
56
+ ) -> None:
57
+ """Initialize the policy.
58
+
59
+ Args:
60
+ rules (dict[str, list[Predicate]] | None): Rules mapping permissions
61
+ to predicate lists. Defaults to None.
62
+ **kwargs: Ignored (config compatibility).
63
+ """
64
+ self._rules: dict[str, list[Predicate]] = rules or {}
65
+
66
+ def check(self, subject: BaseSubject, permission: str) -> bool:
67
+ """Check a subject against the configured rules.
68
+
69
+ Args:
70
+ subject (BaseSubject): Subject instance.
71
+ permission (str): Permission name.
72
+
73
+ Returns:
74
+ bool: True if allowed, False otherwise.
75
+ """
76
+ predicates = self._rules.get(permission)
77
+ if not predicates:
78
+ return False
79
+ return any(self._match(subject, predicate) for predicate in predicates)
80
+
81
+ @staticmethod
82
+ def _match(subject: BaseSubject, predicate: Predicate) -> bool:
83
+ """Match a single predicate against a subject.
84
+
85
+ Args:
86
+ subject (BaseSubject): Subject instance.
87
+ predicate (Predicate): Predicate to evaluate.
88
+
89
+ Returns:
90
+ bool: Match result.
91
+ """
92
+ if callable(predicate):
93
+ return bool(predicate(subject))
94
+ if predicate == "*":
95
+ return True
96
+ if "=" in predicate:
97
+ field, value = predicate.split("=", 1)
98
+ return getattr(subject, field, None) == value
99
+ return bool(getattr(subject, predicate, None))