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.
- {jamlib-3.2.0rc2/src/jamlib.egg-info → jamlib-3.3.0}/PKG-INFO +5 -1
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/README.md +4 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/pyproject.toml +1 -1
- jamlib-3.3.0/src/jam/__defaults__.py +21 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__init__.py +1 -1
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/__init__.py +20 -0
- jamlib-3.3.0/src/jam/exceptions/saml.py +66 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/middleware.py +7 -7
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/objects.py +2 -5
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/starlette/objects.py +5 -6
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/__algorithms__.py +9 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jwe.py +2 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jwk.py +0 -9
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jws.py +1 -1
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/jwt.py +52 -25
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/redis.py +5 -3
- jamlib-3.3.0/src/jam/saml/__base__.py +577 -0
- jamlib-3.3.0/src/jam/saml/__init__.py +129 -0
- jamlib-3.3.0/src/jam/saml/binding.py +188 -0
- jamlib-3.3.0/src/jam/saml/encryption.py +244 -0
- jamlib-3.3.0/src/jam/saml/metadata.py +211 -0
- jamlib-3.3.0/src/jam/saml/saml.py +1943 -0
- jamlib-3.3.0/src/jam/saml/signature.py +287 -0
- jamlib-3.3.0/src/jam/saml/types.py +185 -0
- jamlib-3.3.0/src/jam/saml/xml.py +174 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/tests/clients.py +267 -53
- jamlib-3.3.0/src/jam/tests/fakers.py +187 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0/src/jamlib.egg-info}/PKG-INFO +5 -1
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/SOURCES.txt +11 -0
- jamlib-3.2.0rc2/src/jam/tests/fakers.py +0 -102
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/LICENSE.md +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/setup.cfg +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__base_encoder__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/__deprecated__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/instance.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/jwt/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/github.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/gitlab.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/google.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/builtin/yandex.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/oauth2/client.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/json.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/aio/sessions/redis.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/cli.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/commands/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/commands/keys.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/cli/commands/password.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/encoders.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/base.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/jose.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/jwt.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/oauth2.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/paseto.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/plugins.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/exceptions/sessions.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/fastapi/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/flask/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/flask/extensions.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/flask/objects.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/litestar/plugins.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/starlette/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/ext/starlette/backends.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/instance.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/json.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/lists/memory.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jose/utils.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__algorithms__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/__types__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/json.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/lists/redis.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/module.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/jwt/utils.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/logger.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/github.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/gitlab.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/google.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/builtin/yandex.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/oauth2/client.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/hotp.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/otp/totp.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/utils.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v1.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v2.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v3.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/paseto/v4.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/plugins/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/plugins/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/py.typed +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/__base__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/json.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/sessions/redis.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/tests/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/__init__.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/aes.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/await_maybe.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/basic_auth.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/config_maker.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/ed.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/otp_keys.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/rsa.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/salt_hash.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/symmetric.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/version_check.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/xchacha20poly1305.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jam/utils/xor.py +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/dependency_links.txt +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/entry_points.txt +0 -0
- {jamlib-3.2.0rc2 → jamlib-3.3.0}/src/jamlib.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
[](https://deepwiki.com/lyaguxafrog/jam)
|
|
63
63
|
[](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
|
[](https://deepwiki.com/lyaguxafrog/jam)
|
|
10
10
|
[](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 | ✅ | ❌ | ❌ | ❌ | ❌ |
|
|
@@ -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()
|
|
@@ -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
|
|
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:
|
|
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,
|
|
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
|
-
|
|
117
|
-
if not
|
|
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(
|
|
120
|
-
return AuthenticationResult(user,
|
|
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
|
|
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]) ->
|
|
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
|
|
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]) ->
|
|
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,
|
|
@@ -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
|
|
|
@@ -25,7 +25,7 @@ from jam.jose.__algorithms__ import (
|
|
|
25
25
|
KeyLike,
|
|
26
26
|
create_algorithm,
|
|
27
27
|
)
|
|
28
|
-
from jam.jose.__base__ import
|
|
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) ->
|
|
162
|
-
|
|
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
|
|
166
|
-
alg=
|
|
167
|
-
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
|
-
|
|
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
|
-
|
|
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 =
|
|
191
|
+
enc_key = key
|
|
183
192
|
elif key_type == "ec":
|
|
184
193
|
jwe_alg = "ECDH-ES"
|
|
185
|
-
enc_key =
|
|
194
|
+
enc_key = key
|
|
186
195
|
elif key_type == "symmetric":
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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(
|
|
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=
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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.
|