python3-commons 0.22.6__py3-none-any.whl → 0.22.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- python3_commons/auth.py +50 -30
- python3_commons/db/__init__.py +1 -1
- {python3_commons-0.22.6.dist-info → python3_commons-0.22.8.dist-info}/METADATA +1 -1
- {python3_commons-0.22.6.dist-info → python3_commons-0.22.8.dist-info}/RECORD +10 -10
- {python3_commons-0.22.6.dist-info → python3_commons-0.22.8.dist-info}/WHEEL +1 -1
- {python3_commons-0.22.6.dist-info → python3_commons-0.22.8.dist-info}/scm_file_list.json +45 -44
- python3_commons-0.22.8.dist-info/scm_version.json +8 -0
- python3_commons-0.22.6.dist-info/scm_version.json +0 -8
- {python3_commons-0.22.6.dist-info → python3_commons-0.22.8.dist-info}/licenses/AUTHORS.rst +0 -0
- {python3_commons-0.22.6.dist-info → python3_commons-0.22.8.dist-info}/licenses/LICENSE +0 -0
- {python3_commons-0.22.6.dist-info → python3_commons-0.22.8.dist-info}/top_level.txt +0 -0
python3_commons/auth.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
2
|
-
import
|
|
3
|
-
from
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from time import monotonic
|
|
4
5
|
from typing import Any, Self, TypeVar
|
|
5
6
|
|
|
6
7
|
from pydantic import HttpUrl
|
|
@@ -18,9 +19,8 @@ except ImportError as e:
|
|
|
18
19
|
import msgspec
|
|
19
20
|
|
|
20
21
|
logger = logging.getLogger(__name__)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
_OIDC_SESSION_LOCK = threading.Lock()
|
|
22
|
+
|
|
23
|
+
DEFAULT_JWKS_CACHE_TTL = 300.0
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class TokenData(msgspec.Struct):
|
|
@@ -81,6 +81,7 @@ class OIDCClient:
|
|
|
81
81
|
timeout: float = 10.0,
|
|
82
82
|
verify_cert: bool = True,
|
|
83
83
|
connection_limit: int = 100,
|
|
84
|
+
jwks_cache_ttl: float = DEFAULT_JWKS_CACHE_TTL,
|
|
84
85
|
authority_internal_host: HttpUrl | None = None,
|
|
85
86
|
audit_name: str | None = None,
|
|
86
87
|
) -> None:
|
|
@@ -97,17 +98,22 @@ class OIDCClient:
|
|
|
97
98
|
self._timeout = timeout
|
|
98
99
|
self._verify_cert = verify_cert
|
|
99
100
|
|
|
100
|
-
self.
|
|
101
|
-
self.
|
|
101
|
+
self._session_lock = asyncio.Lock()
|
|
102
|
+
self._config: dict[str, Any] | None = None
|
|
103
|
+
self._config_lock = asyncio.Lock()
|
|
104
|
+
self._jwks: dict[str, Any] | None = None
|
|
105
|
+
self._jwks_lock = asyncio.Lock()
|
|
106
|
+
self._jwks_cache_ttl = jwks_cache_ttl
|
|
107
|
+
self._jwks_fetched_at: float | None = None
|
|
102
108
|
self._audit_name: str | None = audit_name
|
|
103
109
|
|
|
104
|
-
def _get_session(self) -> aiohttp.ClientSession:
|
|
105
|
-
if self._session:
|
|
106
|
-
return
|
|
110
|
+
async def _get_session(self) -> aiohttp.ClientSession:
|
|
111
|
+
if (session := self._session) and not session.closed:
|
|
112
|
+
return session
|
|
107
113
|
|
|
108
|
-
with
|
|
109
|
-
if self._session:
|
|
110
|
-
return
|
|
114
|
+
async with self._session_lock:
|
|
115
|
+
if (session := self._session) and not session.closed:
|
|
116
|
+
return session
|
|
111
117
|
|
|
112
118
|
connector = aiohttp.TCPConnector(verify_ssl=self._verify_cert, limit=self._connection_limit)
|
|
113
119
|
timeout = aiohttp.ClientTimeout(total=self._timeout)
|
|
@@ -117,7 +123,7 @@ class OIDCClient:
|
|
|
117
123
|
return session
|
|
118
124
|
|
|
119
125
|
async def __aenter__(self) -> Self:
|
|
120
|
-
self._get_session()
|
|
126
|
+
await self._get_session()
|
|
121
127
|
|
|
122
128
|
return self
|
|
123
129
|
|
|
@@ -130,27 +136,27 @@ class OIDCClient:
|
|
|
130
136
|
Fetch the OpenID configuration (including JWKS URI) from OIDC authority.
|
|
131
137
|
"""
|
|
132
138
|
async with api_client.request(
|
|
133
|
-
self._get_session(),
|
|
139
|
+
await self._get_session(),
|
|
134
140
|
str(self._authority_url),
|
|
135
141
|
'/.well-known/openid-configuration',
|
|
136
142
|
audit_name=self._audit_name,
|
|
137
143
|
) as response:
|
|
138
144
|
return await response.json()
|
|
139
145
|
|
|
140
|
-
async def get_config(self) ->
|
|
141
|
-
if self._config:
|
|
142
|
-
return
|
|
146
|
+
async def get_config(self) -> dict[str, Any]:
|
|
147
|
+
if config := self._config:
|
|
148
|
+
return config
|
|
143
149
|
|
|
144
|
-
with
|
|
145
|
-
if self._config:
|
|
146
|
-
return
|
|
150
|
+
async with self._config_lock:
|
|
151
|
+
if config := self._config:
|
|
152
|
+
return config
|
|
147
153
|
|
|
148
154
|
config = await self._fetch_config()
|
|
149
155
|
self._config = config
|
|
150
156
|
|
|
151
157
|
return config
|
|
152
158
|
|
|
153
|
-
async def _fetch_jwks(self, jwks_uri: str) -> dict:
|
|
159
|
+
async def _fetch_jwks(self, jwks_uri: str) -> dict[str, Any]:
|
|
154
160
|
"""
|
|
155
161
|
Fetch the JSON Web Key Set (JWKS) for validating the token's signature.
|
|
156
162
|
"""
|
|
@@ -160,21 +166,35 @@ class OIDCClient:
|
|
|
160
166
|
jwks_uri = str(replace_origin(HttpUrl(jwks_uri), authority_internal_host))
|
|
161
167
|
logger.debug('Modified jwks_uri: %s', jwks_uri)
|
|
162
168
|
|
|
163
|
-
async with api_client.request(self._get_session(), jwks_uri, '', audit_name=self._audit_name) as response:
|
|
169
|
+
async with api_client.request(await self._get_session(), jwks_uri, '', audit_name=self._audit_name) as response:
|
|
164
170
|
return await response.json()
|
|
165
171
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
172
|
+
def _is_fresh_jwks(self) -> bool:
|
|
173
|
+
fetched_at = self._jwks_fetched_at
|
|
174
|
+
|
|
175
|
+
if self._jwks is None or fetched_at is None:
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
return (monotonic() - fetched_at) < self._jwks_cache_ttl
|
|
179
|
+
|
|
180
|
+
async def get_jwks(self, *, force_refresh: bool = False) -> dict[str, Any]:
|
|
181
|
+
if not force_refresh and (jwks := self._jwks) and self._is_fresh_jwks():
|
|
182
|
+
return jwks
|
|
183
|
+
|
|
184
|
+
fetched_at = self._jwks_fetched_at
|
|
185
|
+
|
|
186
|
+
async with self._jwks_lock:
|
|
187
|
+
if (jwks := self._jwks) and self._jwks_fetched_at != fetched_at:
|
|
188
|
+
return jwks
|
|
169
189
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
return self._jwks
|
|
190
|
+
if not force_refresh and jwks and self._is_fresh_jwks():
|
|
191
|
+
return jwks
|
|
173
192
|
|
|
174
193
|
oidc_config = await self.get_config()
|
|
175
194
|
|
|
176
195
|
jwks = await self._fetch_jwks(oidc_config['jwks_uri'])
|
|
177
196
|
self._jwks = jwks
|
|
197
|
+
self._jwks_fetched_at = monotonic()
|
|
178
198
|
|
|
179
199
|
return jwks
|
|
180
200
|
|
|
@@ -200,7 +220,7 @@ class OIDCClient:
|
|
|
200
220
|
|
|
201
221
|
try:
|
|
202
222
|
async with api_client.request(
|
|
203
|
-
self._get_session(),
|
|
223
|
+
await self._get_session(),
|
|
204
224
|
openid_config['token_endpoint'],
|
|
205
225
|
'',
|
|
206
226
|
method='post',
|
python3_commons/db/__init__.py
CHANGED
|
@@ -185,7 +185,7 @@ class AsyncSessionManager:
|
|
|
185
185
|
try:
|
|
186
186
|
yield session
|
|
187
187
|
except Exception:
|
|
188
|
-
logger.exception('Error
|
|
188
|
+
logger.exception('Error occurred while db session %r was open; rolling back', name)
|
|
189
189
|
await session.rollback()
|
|
190
190
|
|
|
191
191
|
raise
|
|
@@ -2,7 +2,7 @@ python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,1
|
|
|
2
2
|
python3_commons/api_client.py,sha256=SThSzLhZJxVsKP8n1xDMCczA7SBFbxGFgrhKy9k-9_g,5532
|
|
3
3
|
python3_commons/async_functools.py,sha256=A2HvwFzZHxOWTp4IQM5UiBY2yg1S_0U1CWra5BWK0gk,9101
|
|
4
4
|
python3_commons/audit.py,sha256=uGoCwenDJ0Gdwbr_VNOZm5scT8luxW1weprJbbMoHo0,2608
|
|
5
|
-
python3_commons/auth.py,sha256=
|
|
5
|
+
python3_commons/auth.py,sha256=s928lYwpyX03BDTo1R6LpOgHiUSrjVrd1OVgTjvIiQU,8279
|
|
6
6
|
python3_commons/cache.py,sha256=lowiXJqFgFy1Yg86wi9IhuoNqIUGP6nc5eNibmf0dfY,8018
|
|
7
7
|
python3_commons/conf.py,sha256=1HYtNkNBQc5IIABzhH3kz7GnLeF7rj5aNcOoW8xbEa4,3101
|
|
8
8
|
python3_commons/exceptions.py,sha256=EGjHZVBnsM6CeBfPMqhL0IPMKjDJ_2-Z-aSPXwq91LE,36
|
|
@@ -12,7 +12,7 @@ python3_commons/helpers.py,sha256=WTPu_jIOGYtxt1GYnH6sBMNDHry99AKv6ZaggGW2l1A,49
|
|
|
12
12
|
python3_commons/object_storage.py,sha256=2l8v1mDB5WWN6jhxH2t5xmHBXaVPRJl0z44wExmlO80,7175
|
|
13
13
|
python3_commons/permissions.py,sha256=gaMKSWg0MgPQTdP1voll4ItXcblXku9BlD0Lq3Xv64U,1724
|
|
14
14
|
python3_commons/soap_client.py,sha256=w2lOyhhtkhwiNnHjvir8DfjGBO51XVg5rlDHyuudU2A,7169
|
|
15
|
-
python3_commons/db/__init__.py,sha256=
|
|
15
|
+
python3_commons/db/__init__.py,sha256=GP_9GXZUUgNvehFUCsXTIWThIMFW60Hwve7sve2oPgM,10188
|
|
16
16
|
python3_commons/db/helpers.py,sha256=xRpWs4aVkBge6HCLjb6OLSnWc1nlV6rKGyaVhZ_x12w,2001
|
|
17
17
|
python3_commons/db/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
18
|
python3_commons/db/models/auth.py,sha256=t6z_0c7l_1eB-CCdnPSV9OzJ_ymgkwD1cLpecNhQgoA,773
|
|
@@ -27,11 +27,11 @@ python3_commons/serializers/common.py,sha256=VkA7C6wODvHk0QBXVX_x2JieDstihx3U__U
|
|
|
27
27
|
python3_commons/serializers/json.py,sha256=UPkC3ps13x2C_NxwVV-K7Ewp4VjkVHSSUkJVw5k7Wiw,712
|
|
28
28
|
python3_commons/serializers/msgpack.py,sha256=zESFBX34GsZ8rDu6Zk5V6CLT6P0mPilU0r04Ka6TblI,1474
|
|
29
29
|
python3_commons/serializers/msgspec.py,sha256=upy5CBmK66-8hYnK5bAM_sZvZY5CAqZmzCw9GIF346I,2988
|
|
30
|
-
python3_commons-0.22.
|
|
31
|
-
python3_commons-0.22.
|
|
32
|
-
python3_commons-0.22.
|
|
33
|
-
python3_commons-0.22.
|
|
34
|
-
python3_commons-0.22.
|
|
35
|
-
python3_commons-0.22.
|
|
36
|
-
python3_commons-0.22.
|
|
37
|
-
python3_commons-0.22.
|
|
30
|
+
python3_commons-0.22.8.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
|
|
31
|
+
python3_commons-0.22.8.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
|
|
32
|
+
python3_commons-0.22.8.dist-info/METADATA,sha256=B3tGLZdnB93pCTxE36f7DhGJTsj2FrXEp3HyTE-PuFY,9472
|
|
33
|
+
python3_commons-0.22.8.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
34
|
+
python3_commons-0.22.8.dist-info/scm_file_list.json,sha256=_VHernCbTfExHtROxsobMxaHoIS1TJJ6S-8WTB3Aqvs,2549
|
|
35
|
+
python3_commons-0.22.8.dist-info/scm_version.json,sha256=ftAm38mZuMBNfuYAg6OlyS5O00-fUbhn1VRa7r9eL3Y,161
|
|
36
|
+
python3_commons-0.22.8.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
|
|
37
|
+
python3_commons-0.22.8.dist-info/RECORD,,
|
|
@@ -1,75 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"files": [
|
|
3
|
+
".gitignore",
|
|
4
|
+
".python-version",
|
|
5
|
+
"README.md",
|
|
6
|
+
"AUTHORS.rst",
|
|
7
|
+
"CHANGELOG.rst",
|
|
8
|
+
"pyproject.toml",
|
|
9
|
+
".env_template",
|
|
3
10
|
".coveragerc",
|
|
4
11
|
".pre-commit-config.yaml",
|
|
5
|
-
"README.md",
|
|
6
12
|
"uv.lock",
|
|
7
|
-
".python-version",
|
|
8
13
|
"LICENSE",
|
|
9
|
-
"pyproject.toml",
|
|
10
|
-
"CHANGELOG.rst",
|
|
11
|
-
".env_template",
|
|
12
14
|
"README.rst",
|
|
13
|
-
".
|
|
14
|
-
"AUTHORS.rst",
|
|
15
|
-
".devcontainer/Dockerfile",
|
|
16
|
-
".devcontainer/docker-compose.yml",
|
|
17
|
-
".devcontainer/devcontainer.json",
|
|
18
|
-
"docs/Makefile",
|
|
19
|
-
"docs/license.rst",
|
|
20
|
-
"docs/authors.rst",
|
|
21
|
-
"docs/index.rst",
|
|
22
|
-
"docs/changelog.rst",
|
|
23
|
-
"docs/conf.py",
|
|
24
|
-
"docs/_static/.gitignore",
|
|
25
|
-
"src/python3_commons/helpers.py",
|
|
15
|
+
"src/python3_commons/conf.py",
|
|
26
16
|
"src/python3_commons/soap_client.py",
|
|
27
|
-
"src/python3_commons/
|
|
28
|
-
"src/python3_commons/__init__.py",
|
|
29
|
-
"src/python3_commons/exceptions.py",
|
|
30
|
-
"src/python3_commons/object_storage.py",
|
|
17
|
+
"src/python3_commons/async_functools.py",
|
|
31
18
|
"src/python3_commons/permissions.py",
|
|
32
|
-
"src/python3_commons/auth.py",
|
|
33
|
-
"src/python3_commons/audit.py",
|
|
34
|
-
"src/python3_commons/cache.py",
|
|
35
19
|
"src/python3_commons/fs.py",
|
|
36
|
-
"src/python3_commons/
|
|
37
|
-
"src/python3_commons/
|
|
20
|
+
"src/python3_commons/api_client.py",
|
|
21
|
+
"src/python3_commons/audit.py",
|
|
22
|
+
"src/python3_commons/object_storage.py",
|
|
23
|
+
"src/python3_commons/exceptions.py",
|
|
24
|
+
"src/python3_commons/helpers.py",
|
|
38
25
|
"src/python3_commons/generators.py",
|
|
26
|
+
"src/python3_commons/cache.py",
|
|
27
|
+
"src/python3_commons/auth.py",
|
|
28
|
+
"src/python3_commons/__init__.py",
|
|
39
29
|
"src/python3_commons/db/helpers.py",
|
|
40
30
|
"src/python3_commons/db/__init__.py",
|
|
31
|
+
"src/python3_commons/db/models/common.py",
|
|
41
32
|
"src/python3_commons/db/models/rbac.py",
|
|
42
|
-
"src/python3_commons/db/models/__init__.py",
|
|
43
|
-
"src/python3_commons/db/models/users.py",
|
|
44
33
|
"src/python3_commons/db/models/auth.py",
|
|
45
|
-
"src/python3_commons/db/models/
|
|
46
|
-
"src/python3_commons/
|
|
34
|
+
"src/python3_commons/db/models/users.py",
|
|
35
|
+
"src/python3_commons/db/models/__init__.py",
|
|
47
36
|
"src/python3_commons/log/filters.py",
|
|
48
37
|
"src/python3_commons/log/formatters.py",
|
|
49
|
-
"src/python3_commons/
|
|
50
|
-
"src/python3_commons/serializers/msgpack.py",
|
|
51
|
-
"src/python3_commons/serializers/json.py",
|
|
38
|
+
"src/python3_commons/log/__init__.py",
|
|
52
39
|
"src/python3_commons/serializers/msgspec.py",
|
|
40
|
+
"src/python3_commons/serializers/json.py",
|
|
53
41
|
"src/python3_commons/serializers/common.py",
|
|
42
|
+
"src/python3_commons/serializers/msgpack.py",
|
|
43
|
+
"src/python3_commons/serializers/__init__.py",
|
|
44
|
+
".devcontainer/docker-compose.yml",
|
|
45
|
+
".devcontainer/Dockerfile",
|
|
46
|
+
".devcontainer/devcontainer.json",
|
|
47
|
+
".github/workflows/checks.yml",
|
|
48
|
+
".github/workflows/release-on-tag-push.yml",
|
|
49
|
+
".github/workflows/python-publish.yaml",
|
|
54
50
|
"tests/__init__.py",
|
|
55
|
-
"tests/integration/__init__.py",
|
|
56
|
-
"tests/integration/test_auth.py",
|
|
57
51
|
"tests/integration/conftest.py",
|
|
58
52
|
"tests/integration/test_osc.py",
|
|
59
53
|
"tests/integration/test_cache.py",
|
|
60
|
-
"tests/
|
|
61
|
-
"tests/
|
|
54
|
+
"tests/integration/test_auth.py",
|
|
55
|
+
"tests/integration/__init__.py",
|
|
62
56
|
"tests/unit/test_async_functools.py",
|
|
63
|
-
"tests/unit/test_msgspec.py",
|
|
64
57
|
"tests/unit/conftest.py",
|
|
58
|
+
"tests/unit/test_msgspec.py",
|
|
65
59
|
"tests/unit/test_audit.py",
|
|
60
|
+
"tests/unit/test_auth.py",
|
|
66
61
|
"tests/unit/test_msgpack.py",
|
|
67
|
-
"tests/unit/
|
|
62
|
+
"tests/unit/test_helpers.py",
|
|
63
|
+
"tests/unit/__init__.py",
|
|
68
64
|
"tests/unit/db/test_async_session_manager.py",
|
|
69
|
-
"tests/unit/
|
|
65
|
+
"tests/unit/db/__init__.py",
|
|
70
66
|
"tests/unit/log/test_formatters.py",
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
"
|
|
67
|
+
"tests/unit/log/__init__.py",
|
|
68
|
+
"docs/conf.py",
|
|
69
|
+
"docs/Makefile",
|
|
70
|
+
"docs/index.rst",
|
|
71
|
+
"docs/authors.rst",
|
|
72
|
+
"docs/changelog.rst",
|
|
73
|
+
"docs/license.rst",
|
|
74
|
+
"docs/_static/.gitignore"
|
|
74
75
|
]
|
|
75
76
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|