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.
- {jamlib-3.3.0/src/jamlib.egg-info → jamlib-4.0.0a0}/PKG-INFO +1 -1
- {jamlib-3.3.0 → jamlib-4.0.0a0}/pyproject.toml +1 -1
- jamlib-4.0.0a0/src/jam/__base__.py +348 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__init__.py +10 -2
- jamlib-4.0.0a0/src/jam/authz.py +99 -0
- jamlib-4.0.0a0/src/jam/instance.py +243 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/__base__.py +1 -1
- jamlib-4.0.0a0/src/jam/jose/__init__.py +20 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jwe.py +8 -1
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jws.py +7 -1
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jwt.py +54 -10
- jamlib-4.0.0a0/src/jam/jose/lists/__init__.py +22 -0
- {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/__base__.py +2 -2
- jamlib-4.0.0a0/src/jam/lists/__init__.py +14 -0
- {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/json.py +2 -2
- {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/memory.py +2 -2
- {jamlib-3.3.0/src/jam/jose → jamlib-4.0.0a0/src/jam}/lists/redis.py +2 -2
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/__base__.py +8 -1
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/__init__.py +20 -16
- jamlib-4.0.0a0/src/jam/paseto/__base__.py +301 -0
- jamlib-4.0.0a0/src/jam/paseto/__init__.py +29 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v1.py +34 -42
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v2.py +31 -31
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v3.py +33 -42
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/v4.py +34 -38
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/sessions/__base__.py +42 -1
- jamlib-4.0.0a0/src/jam/sessions/__init__.py +25 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/sessions/json.py +13 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/sessions/redis.py +12 -0
- jamlib-4.0.0a0/src/jam/subject.py +57 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/tests/clients.py +70 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/config_maker.py +0 -25
- jamlib-4.0.0a0/src/jam/utils/config_meta.py +69 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0/src/jamlib.egg-info}/PKG-INFO +1 -1
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/SOURCES.txt +8 -14
- jamlib-3.3.0/src/jam/__base__.py +0 -644
- jamlib-3.3.0/src/jam/instance.py +0 -581
- jamlib-3.3.0/src/jam/jose/__init__.py +0 -113
- jamlib-3.3.0/src/jam/jose/lists/__init__.py +0 -11
- jamlib-3.3.0/src/jam/jwt/__algorithms__.py +0 -488
- jamlib-3.3.0/src/jam/jwt/__base__.py +0 -60
- jamlib-3.3.0/src/jam/jwt/__init__.py +0 -65
- jamlib-3.3.0/src/jam/jwt/__types__.py +0 -15
- jamlib-3.3.0/src/jam/jwt/lists/__base__.py +0 -30
- jamlib-3.3.0/src/jam/jwt/lists/__init__.py +0 -8
- jamlib-3.3.0/src/jam/jwt/lists/json.py +0 -120
- jamlib-3.3.0/src/jam/jwt/lists/redis.py +0 -113
- jamlib-3.3.0/src/jam/jwt/module.py +0 -238
- jamlib-3.3.0/src/jam/jwt/utils.py +0 -36
- jamlib-3.3.0/src/jam/paseto/__base__.py +0 -172
- jamlib-3.3.0/src/jam/paseto/__init__.py +0 -57
- jamlib-3.3.0/src/jam/sessions/__init__.py +0 -89
- {jamlib-3.3.0 → jamlib-4.0.0a0}/LICENSE.md +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/README.md +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/setup.cfg +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__base_encoder__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__defaults__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/__deprecated__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/__base__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/instance.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/jwt/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/__base__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/github.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/gitlab.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/google.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/builtin/yandex.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/oauth2/client.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/__base__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/json.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/aio/sessions/redis.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/cli.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/commands/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/commands/keys.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/cli/commands/password.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/encoders.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/base.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/jose.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/jwt.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/oauth2.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/paseto.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/plugins.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/saml.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/exceptions/sessions.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/fastapi/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/flask/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/flask/extensions.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/flask/objects.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/middleware.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/objects.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/litestar/plugins.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/starlette/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/starlette/backends.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/ext/starlette/objects.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/__algorithms__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/jwk.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/jose/utils.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/logger.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/github.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/gitlab.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/google.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/builtin/yandex.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/oauth2/client.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/__base__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/hotp.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/otp/totp.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/paseto/utils.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/plugins/__base__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/plugins/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/py.typed +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/__base__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/binding.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/encryption.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/metadata.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/saml.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/signature.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/types.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/saml/xml.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/tests/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/tests/fakers.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/__init__.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/aes.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/await_maybe.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/basic_auth.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/ed.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/otp_keys.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/rsa.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/salt_hash.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/symmetric.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/version_check.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/xchacha20poly1305.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jam/utils/xor.py +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/dependency_links.txt +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/entry_points.txt +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/requires.txt +0 -0
- {jamlib-3.3.0 → jamlib-4.0.0a0}/src/jamlib.egg-info/top_level.txt +0 -0
|
@@ -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__ = "
|
|
14
|
-
__all__ = [
|
|
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))
|