python3-commons 0.17.4__tar.gz → 0.17.6__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 (74) hide show
  1. {python3_commons-0.17.4/src/python3_commons.egg-info → python3_commons-0.17.6}/PKG-INFO +3 -1
  2. {python3_commons-0.17.4 → python3_commons-0.17.6}/pyproject.toml +3 -1
  3. python3_commons-0.17.6/src/python3_commons/auth.py +178 -0
  4. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/conf.py +6 -5
  5. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/object_storage.py +4 -4
  6. {python3_commons-0.17.4 → python3_commons-0.17.6/src/python3_commons.egg-info}/PKG-INFO +3 -1
  7. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/requires.txt +2 -0
  8. {python3_commons-0.17.4 → python3_commons-0.17.6}/uv.lock +43 -32
  9. python3_commons-0.17.4/src/python3_commons/auth.py +0 -79
  10. {python3_commons-0.17.4 → python3_commons-0.17.6}/.coveragerc +0 -0
  11. {python3_commons-0.17.4 → python3_commons-0.17.6}/.devcontainer/Dockerfile +0 -0
  12. {python3_commons-0.17.4 → python3_commons-0.17.6}/.devcontainer/devcontainer.json +0 -0
  13. {python3_commons-0.17.4 → python3_commons-0.17.6}/.devcontainer/docker-compose.yml +0 -0
  14. {python3_commons-0.17.4 → python3_commons-0.17.6}/.env_template +0 -0
  15. {python3_commons-0.17.4 → python3_commons-0.17.6}/.github/workflows/checks.yml +0 -0
  16. {python3_commons-0.17.4 → python3_commons-0.17.6}/.github/workflows/python-publish.yaml +0 -0
  17. {python3_commons-0.17.4 → python3_commons-0.17.6}/.github/workflows/release-on-tag-push.yml +0 -0
  18. {python3_commons-0.17.4 → python3_commons-0.17.6}/.gitignore +0 -0
  19. {python3_commons-0.17.4 → python3_commons-0.17.6}/.pre-commit-config.yaml +0 -0
  20. {python3_commons-0.17.4 → python3_commons-0.17.6}/.python-version +0 -0
  21. {python3_commons-0.17.4 → python3_commons-0.17.6}/AUTHORS.rst +0 -0
  22. {python3_commons-0.17.4 → python3_commons-0.17.6}/CHANGELOG.rst +0 -0
  23. {python3_commons-0.17.4 → python3_commons-0.17.6}/LICENSE +0 -0
  24. {python3_commons-0.17.4 → python3_commons-0.17.6}/README.md +0 -0
  25. {python3_commons-0.17.4 → python3_commons-0.17.6}/README.rst +0 -0
  26. {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/Makefile +0 -0
  27. {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/_static/.gitignore +0 -0
  28. {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/authors.rst +0 -0
  29. {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/changelog.rst +0 -0
  30. {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/conf.py +0 -0
  31. {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/index.rst +0 -0
  32. {python3_commons-0.17.4 → python3_commons-0.17.6}/docs/license.rst +0 -0
  33. {python3_commons-0.17.4 → python3_commons-0.17.6}/setup.cfg +0 -0
  34. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/__init__.py +0 -0
  35. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/api_client.py +0 -0
  36. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/async_functools.py +0 -0
  37. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/audit.py +0 -0
  38. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/cache.py +0 -0
  39. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/__init__.py +0 -0
  40. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/helpers.py +0 -0
  41. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/__init__.py +0 -0
  42. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/auth.py +0 -0
  43. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/common.py +0 -0
  44. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/rbac.py +0 -0
  45. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/db/models/users.py +0 -0
  46. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/exceptions.py +0 -0
  47. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/fs.py +0 -0
  48. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/generators.py +0 -0
  49. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/helpers.py +0 -0
  50. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/log/__init__.py +0 -0
  51. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/log/filters.py +0 -0
  52. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/log/formatters.py +0 -0
  53. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/permissions.py +0 -0
  54. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/__init__.py +0 -0
  55. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/common.py +0 -0
  56. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/json.py +0 -0
  57. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/msgpack.py +0 -0
  58. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons/serializers/msgspec.py +0 -0
  59. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/SOURCES.txt +0 -0
  60. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  61. {python3_commons-0.17.4 → python3_commons-0.17.6}/src/python3_commons.egg-info/top_level.txt +0 -0
  62. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/__init__.py +0 -0
  63. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/integration/__init__.py +0 -0
  64. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/integration/test_cache.py +0 -0
  65. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/integration/test_osc.py +0 -0
  66. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/__init__.py +0 -0
  67. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/conftest.py +0 -0
  68. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/log/__init__.py +0 -0
  69. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/log/test_formatters.py +0 -0
  70. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_async_functools.py +0 -0
  71. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_audit.py +0 -0
  72. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_helpers.py +0 -0
  73. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_msgpack.py +0 -0
  74. {python3_commons-0.17.4 → python3_commons-0.17.6}/tests/unit/test_msgspec.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.17.4
3
+ Version: 0.17.6
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -28,8 +28,10 @@ Requires-Dist: zeep~=4.3.2; extra == "audit"
28
28
  Requires-Dist: python3_commons[object-storage]; extra == "audit"
29
29
  Provides-Extra: authn
30
30
  Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
31
+ Requires-Dist: python3_commons[api-client]; extra == "authn"
31
32
  Provides-Extra: authz
32
33
  Requires-Dist: python3_commons[database]; extra == "authz"
34
+ Requires-Dist: python3_commons[api-client]; extra == "authz"
33
35
  Provides-Extra: cache
34
36
  Requires-Dist: valkey[libvalkey]~=6.1.1; extra == "cache"
35
37
  Provides-Extra: database
@@ -40,9 +40,11 @@ audit = [
40
40
  ]
41
41
  authn = [
42
42
  "aiohttp[speedups]>=3.13.5,<3.15.0",
43
+ "python3_commons[api-client]"
43
44
  ]
44
45
  authz = [
45
- "python3_commons[database]"
46
+ "python3_commons[database]",
47
+ "python3_commons[api-client]"
46
48
  ]
47
49
  cache = [
48
50
  "valkey[libvalkey]~=6.1.1"
@@ -0,0 +1,178 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+ from collections.abc import Sequence
5
+ from http import HTTPStatus
6
+ from typing import Self, TypeVar
7
+
8
+ try:
9
+ import aiohttp
10
+ except ImportError as e:
11
+ msg = 'Install python3-commons[authn] to use this feature'
12
+ raise RuntimeError(msg) from e
13
+
14
+ import msgspec
15
+
16
+ from python3_commons.conf import oidc_settings
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ class TokenData(msgspec.Struct):
22
+ exp: int
23
+ iat: int
24
+ iss: str
25
+ sub: str
26
+ aud: str | Sequence[str] | None = None
27
+ email: str | None = None
28
+ name: str | None = None
29
+ preferred_username: str | None = None
30
+ realm_access: dict[str, Sequence[str]] | None = None
31
+ resource_access: dict[str, dict[str, Sequence[str]]] | None = None
32
+
33
+ @property
34
+ def roles(self) -> list[str]:
35
+ roles_list = []
36
+
37
+ if self.realm_access:
38
+ roles_list.extend(self.realm_access.get('roles', []))
39
+
40
+ if self.resource_access:
41
+ for client in self.resource_access.values():
42
+ roles_list.extend(client.get('roles', []))
43
+
44
+ return list(set(roles_list))
45
+
46
+
47
+ T = TypeVar('T', bound=TokenData)
48
+ OIDC_CONFIG_URL = (
49
+ f'{oidc_settings.authority_internal_url or oidc_settings.authority_url}/.well-known/openid-configuration'
50
+ )
51
+
52
+
53
+ class OIDCTokenResponse(msgspec.Struct):
54
+ access_token: str
55
+ token_type: str
56
+ expires_in: int
57
+ refresh_token: str
58
+ scope: str
59
+ id_token: str
60
+
61
+
62
+ async def fetch_openid_config() -> dict:
63
+ """
64
+ Fetch the OpenID configuration (including JWKS URI) from OIDC authority.
65
+ """
66
+ async with aiohttp.ClientSession() as session, session.get(OIDC_CONFIG_URL) as response:
67
+ if response.status != HTTPStatus.OK:
68
+ _msg = 'Failed to fetch OpenID configuration'
69
+
70
+ raise RuntimeError(_msg)
71
+
72
+ return await response.json()
73
+
74
+
75
+ async def fetch_jwks(jwks_uri: str) -> dict:
76
+ """
77
+ Fetch the JSON Web Key Set (JWKS) for validating the token's signature.
78
+ """
79
+ if oidc_settings.authority_internal_url:
80
+ jwks_uri = jwks_uri.replace(str(oidc_settings.authority_url), str(oidc_settings.authority_internal_url))
81
+
82
+ async with aiohttp.ClientSession() as session, session.get(jwks_uri) as response:
83
+ if response.status != HTTPStatus.OK:
84
+ _msg = 'Failed to fetch JWKS'
85
+
86
+ raise RuntimeError(_msg)
87
+
88
+ return await response.json()
89
+
90
+
91
+ class OIDCError(Exception):
92
+ pass
93
+
94
+
95
+ class OIDCAuthError(OIDCError):
96
+ pass
97
+
98
+
99
+ class OIDCClient:
100
+ def __init__(
101
+ self,
102
+ token_url: str,
103
+ client_id: str,
104
+ client_secret: str | None = None,
105
+ *,
106
+ timeout: float = 10.0,
107
+ session: aiohttp.ClientSession | None = None,
108
+ ) -> None:
109
+ self._token_url = token_url
110
+ self._client_id = client_id
111
+ self._client_secret = client_secret
112
+ self._timeout = aiohttp.ClientTimeout(total=timeout)
113
+ self._session = session
114
+ self._owns_session = session is None
115
+
116
+ async def __aenter__(self) -> Self:
117
+ if self._session is None:
118
+ self._session = aiohttp.ClientSession(timeout=self._timeout)
119
+ return self
120
+
121
+ async def __aexit__(self, *_: object) -> None:
122
+ if self._owns_session and self._session:
123
+ await self._session.close()
124
+
125
+ async def fetch_token(
126
+ self,
127
+ *,
128
+ username: str,
129
+ password: str,
130
+ scope: str = 'openid profile email',
131
+ ) -> OIDCTokenResponse:
132
+ if self._session is None:
133
+ msg = 'ClientSession not initialized'
134
+
135
+ raise RuntimeError(msg)
136
+
137
+ data = {
138
+ 'grant_type': 'password',
139
+ 'username': username,
140
+ 'password': password,
141
+ 'client_id': self._client_id,
142
+ 'scope': scope,
143
+ }
144
+
145
+ if self._client_secret:
146
+ data['client_secret'] = self._client_secret
147
+
148
+ try:
149
+ async with self._session.post(
150
+ self._token_url,
151
+ data=data,
152
+ headers={'Content-Type': 'application/x-www-form-urlencoded'},
153
+ ) as resp:
154
+ payload = await resp.json(content_type=None)
155
+
156
+ except TimeoutError as e:
157
+ msg = 'OIDC request timed out'
158
+
159
+ raise OIDCError(msg) from e
160
+ except aiohttp.ClientError as e:
161
+ msg = 'OIDC transport error'
162
+
163
+ raise OIDCError(msg) from e
164
+
165
+ if not resp.ok:
166
+ error = payload.get('error')
167
+ description = payload.get('error_description')
168
+
169
+ if error in {'invalid_grant', 'invalid_client'}:
170
+ msg = f'{error}: {description}'
171
+
172
+ raise OIDCAuthError(msg)
173
+
174
+ msg = f'{error}: {description}'
175
+
176
+ raise OIDCError(msg)
177
+
178
+ return payload
@@ -21,6 +21,7 @@ class OIDCSettings(BaseSettings):
21
21
  authority_url: HttpUrl | None = None
22
22
  authority_internal_url: HttpUrl | None = None
23
23
  client_id: str | None = None
24
+ client_secret: SecretStr = SecretStr('')
24
25
  redirect_uri: str | None = None
25
26
  scope: StringSeq = (
26
27
  'openid',
@@ -58,11 +59,11 @@ class DBSettings(BaseSettings):
58
59
  @model_validator(mode='after')
59
60
  def build_dsn_if_missing(self) -> DBSettings:
60
61
  if self.dsn is None and all(
61
- (
62
- self.user,
63
- self.password,
64
- self.name,
65
- )
62
+ (
63
+ self.user,
64
+ self.password,
65
+ self.name,
66
+ )
66
67
  ):
67
68
  self.dsn = PostgresDsn.build(
68
69
  scheme=self.scheme,
@@ -140,7 +140,7 @@ async def list_objects(bucket_name: str, prefix: str, *, recursive: bool = True)
140
140
 
141
141
 
142
142
  async def get_object_streams(
143
- bucket_name: str, path: str, *, recursive: bool = True
143
+ bucket_name: str, path: str, *, recursive: bool = True
144
144
  ) -> AsyncGenerator[tuple[str, datetime, StreamingBody]]:
145
145
  async for obj in list_objects(bucket_name, path, recursive=recursive):
146
146
  object_name = obj['Key']
@@ -151,7 +151,7 @@ async def get_object_streams(
151
151
 
152
152
 
153
153
  async def get_objects(
154
- bucket_name: str, path: str, *, recursive: bool = True
154
+ bucket_name: str, path: str, *, recursive: bool = True
155
155
  ) -> AsyncGenerator[tuple[str, datetime, bytes]]:
156
156
  async for object_name, last_modified, stream in get_object_streams(bucket_name, path, recursive=recursive):
157
157
  data = await stream.read()
@@ -173,7 +173,7 @@ async def remove_object(bucket_name: str, object_name: str) -> None:
173
173
 
174
174
 
175
175
  async def remove_objects(
176
- bucket_name: str, prefix: str | None = None, object_names: Iterable[str] | None = None
176
+ bucket_name: str, prefix: str | None = None, object_names: Iterable[str] | None = None
177
177
  ) -> Sequence[Mapping] | None:
178
178
  storage = ObjectStorage(s3_settings)
179
179
 
@@ -196,7 +196,7 @@ async def remove_objects(
196
196
  chunk_size = 1000
197
197
 
198
198
  for i in range(0, len(objects_to_delete), chunk_size):
199
- chunk = objects_to_delete[i: i + chunk_size]
199
+ chunk = objects_to_delete[i : i + chunk_size]
200
200
 
201
201
  response = await s3_client.delete_objects(Bucket=bucket_name, Delete={'Objects': chunk})
202
202
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.17.4
3
+ Version: 0.17.6
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -28,8 +28,10 @@ Requires-Dist: zeep~=4.3.2; extra == "audit"
28
28
  Requires-Dist: python3_commons[object-storage]; extra == "audit"
29
29
  Provides-Extra: authn
30
30
  Requires-Dist: aiohttp[speedups]<3.15.0,>=3.13.5; extra == "authn"
31
+ Requires-Dist: python3_commons[api-client]; extra == "authn"
31
32
  Provides-Extra: authz
32
33
  Requires-Dist: python3_commons[database]; extra == "authz"
34
+ Requires-Dist: python3_commons[api-client]; extra == "authz"
33
35
  Provides-Extra: cache
34
36
  Requires-Dist: valkey[libvalkey]~=6.1.1; extra == "cache"
35
37
  Provides-Extra: database
@@ -18,9 +18,11 @@ python3_commons[object-storage]
18
18
 
19
19
  [authn]
20
20
  aiohttp[speedups]<3.15.0,>=3.13.5
21
+ python3_commons[api-client]
21
22
 
22
23
  [authz]
23
24
  python3_commons[database]
25
+ python3_commons[api-client]
24
26
 
25
27
  [cache]
26
28
  valkey[libvalkey]~=6.1.1
@@ -229,16 +229,16 @@ wheels = [
229
229
 
230
230
  [[package]]
231
231
  name = "build"
232
- version = "1.4.4"
232
+ version = "1.5.0"
233
233
  source = { registry = "https://pypi.org/simple" }
234
234
  dependencies = [
235
235
  { name = "colorama", marker = "os_name == 'nt'" },
236
236
  { name = "packaging" },
237
237
  { name = "pyproject-hooks" },
238
238
  ]
239
- sdist = { url = "https://files.pythonhosted.org/packages/02/ec/bf5ae0a7e5ab57abe8aabdd0759c971883895d1a20c49ae99f8146840c3c/build-1.4.4.tar.gz", hash = "sha256:f832ae053061f3fb524af812dc94b8b84bac6880cd587630e3b5d91a6a9c1703", size = 89220, upload-time = "2026-04-22T20:53:44.807Z" }
239
+ sdist = { url = "https://files.pythonhosted.org/packages/78/e0/df5e171f685f82f37b12e1f208064e24244911079d7b767447d1af7e0d70/build-1.5.0.tar.gz", hash = "sha256:302c22c3ba2a0fd5f3911918651341ebb3896176cbdec15bd421f80b1afc7647", size = 89796, upload-time = "2026-04-30T03:18:25.17Z" }
240
240
  wheels = [
241
- { url = "https://files.pythonhosted.org/packages/fa/88/6764e7a109dd84294850741501145da90d13cdeac9d4e614929464a37420/build-1.4.4-py3-none-any.whl", hash = "sha256:8c3f48a6090b39edec1a273d2d57949aaf13723b01e02f9d518396887519f64d", size = 25921, upload-time = "2026-04-22T20:53:43.251Z" },
241
+ { url = "https://files.pythonhosted.org/packages/0d/fe/6bea5c9162869c5beba5d9c8abbed835ec85bf1ec1fba05a3822325c45f3/build-1.5.0-py3-none-any.whl", hash = "sha256:13f3eecb844759ab66efec90ca17639bbf14dc06cb2fdf37a9010322d9c50a6f", size = 26018, upload-time = "2026-04-30T03:18:23.644Z" },
242
242
  ]
243
243
 
244
244
  [[package]]
@@ -442,29 +442,29 @@ wheels = [
442
442
 
443
443
  [[package]]
444
444
  name = "greenlet"
445
- version = "3.4.0"
446
- source = { registry = "https://pypi.org/simple" }
447
- sdist = { url = "https://files.pythonhosted.org/packages/86/94/a5935717b307d7c71fe877b52b884c6af707d2d2090db118a03fbd799369/greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff", size = 195913, upload-time = "2026-04-08T17:08:00.863Z" }
448
- wheels = [
449
- { url = "https://files.pythonhosted.org/packages/78/02/bde66806e8f169cf90b14d02c500c44cdbe02c8e224c9c67bafd1b8cadd1/greenlet-3.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:10a07aca6babdd18c16a3f4f8880acfffc2b88dfe431ad6aa5f5740759d7d75e", size = 286291, upload-time = "2026-04-08T17:09:34.307Z" },
450
- { url = "https://files.pythonhosted.org/packages/05/1f/39da1c336a87d47c58352fb8a78541ce63d63ae57c5b9dae1fe02801bbc2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:076e21040b3a917d3ce4ad68fb5c3c6b32f1405616c4a57aa83120979649bd3d", size = 656749, upload-time = "2026-04-08T16:24:41.721Z" },
451
- { url = "https://files.pythonhosted.org/packages/d3/6c/90ee29a4ee27af7aa2e2ec408799eeb69ee3fcc5abcecac6ddd07a5cd0f2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e82689eea4a237e530bb5cb41b180ef81fa2160e1f89422a67be7d90da67f615", size = 669084, upload-time = "2026-04-08T16:31:01.372Z" },
452
- { url = "https://files.pythonhosted.org/packages/d2/4a/74078d3936712cff6d3c91a930016f476ce4198d84e224fe6d81d3e02880/greenlet-3.4.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06c2d3b89e0c62ba50bd7adf491b14f39da9e7e701647cb7b9ff4c99bee04b19", size = 673405, upload-time = "2026-04-08T16:40:42.527Z" },
453
- { url = "https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf", size = 665621, upload-time = "2026-04-08T15:56:35.995Z" },
454
- { url = "https://files.pythonhosted.org/packages/79/3e/df8a83ab894751bc31e1106fdfaa80ca9753222f106b04de93faaa55feb7/greenlet-3.4.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:070b8bac2ff3b4d9e0ff36a0d19e42103331d9737e8504747cd1e659f76297bd", size = 471670, upload-time = "2026-04-08T16:43:08.512Z" },
455
- { url = "https://files.pythonhosted.org/packages/37/31/d1edd54f424761b5d47718822f506b435b6aab2f3f93b465441143ea5119/greenlet-3.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8bff29d586ea415688f4cec96a591fcc3bf762d046a796cdadc1fdb6e7f2d5bf", size = 1622259, upload-time = "2026-04-08T16:26:23.201Z" },
456
- { url = "https://files.pythonhosted.org/packages/b0/c6/6d3f9cdcb21c4e12a79cb332579f1c6aa1af78eb68059c5a957c7812d95e/greenlet-3.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a569c2fb840c53c13a2b8967c63621fafbd1a0e015b9c82f408c33d626a2fda", size = 1686916, upload-time = "2026-04-08T15:57:34.282Z" },
457
- { url = "https://files.pythonhosted.org/packages/63/45/c1ca4a1ad975de4727e52d3ffe641ae23e1d7a8ffaa8ff7a0477e1827b92/greenlet-3.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:207ba5b97ea8b0b60eb43ffcacf26969dd83726095161d676aac03ff913ee50d", size = 239821, upload-time = "2026-04-08T17:03:48.423Z" },
458
- { url = "https://files.pythonhosted.org/packages/71/c4/6f621023364d7e85a4769c014c8982f98053246d142420e0328980933ceb/greenlet-3.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:f8296d4e2b92af34ebde81085a01690f26a51eb9ac09a0fcadb331eb36dbc802", size = 236932, upload-time = "2026-04-08T17:04:33.551Z" },
459
- { url = "https://files.pythonhosted.org/packages/d4/8f/18d72b629783f5e8d045a76f5325c1e938e659a9e4da79c7dcd10169a48d/greenlet-3.4.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d70012e51df2dbbccfaf63a40aaf9b40c8bed37c3e3a38751c926301ce538ece", size = 294681, upload-time = "2026-04-08T15:52:35.778Z" },
460
- { url = "https://files.pythonhosted.org/packages/9e/ad/5fa86ec46769c4153820d58a04062285b3b9e10ba3d461ee257b68dcbf53/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a58bec0751f43068cd40cff31bb3ca02ad6000b3a51ca81367af4eb5abc480c8", size = 658899, upload-time = "2026-04-08T16:24:43.32Z" },
461
- { url = "https://files.pythonhosted.org/packages/43/f0/4e8174ca0e87ae748c409f055a1ba161038c43cc0a5a6f1433a26ac2e5bf/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05fa0803561028f4b2e3b490ee41216a842eaee11aed004cc343a996d9523aa2", size = 665284, upload-time = "2026-04-08T16:31:02.833Z" },
462
- { url = "https://files.pythonhosted.org/packages/ef/92/466b0d9afd44b8af623139a3599d651c7564fa4152f25f117e1ee5949ffb/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c4cd56a9eb7a6444edbc19062f7b6fbc8f287c663b946e3171d899693b1c19fa", size = 665872, upload-time = "2026-04-08T16:40:43.912Z" },
463
- { url = "https://files.pythonhosted.org/packages/19/da/991cf7cd33662e2df92a1274b7eb4d61769294d38a1bba8a45f31364845e/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e60d38719cb80b3ab5e85f9f1aed4960acfde09868af6762ccb27b260d68f4ed", size = 661861, upload-time = "2026-04-08T15:56:37.269Z" },
464
- { url = "https://files.pythonhosted.org/packages/0d/14/3395a7ef3e260de0325152ddfe19dffb3e49fe10873b94654352b53ad48e/greenlet-3.4.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:1f85f204c4d54134ae850d401fa435c89cd667d5ce9dc567571776b45941af72", size = 489237, upload-time = "2026-04-08T16:43:09.993Z" },
465
- { url = "https://files.pythonhosted.org/packages/36/c5/6c2c708e14db3d9caea4b459d8464f58c32047451142fe2cfd90e7458f41/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f50c804733b43eded05ae694691c9aa68bca7d0a867d67d4a3f514742a2d53f", size = 1622182, upload-time = "2026-04-08T16:26:24.777Z" },
466
- { url = "https://files.pythonhosted.org/packages/7a/4c/50c5fed19378e11a29fabab1f6be39ea95358f4a0a07e115a51ca93385d8/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d4f0635dc4aa638cda4b2f5a07ae9a2cff9280327b581a3fcb6f317b4fbc38a", size = 1685050, upload-time = "2026-04-08T15:57:36.453Z" },
467
- { url = "https://files.pythonhosted.org/packages/db/72/85ae954d734703ab48e622c59d4ce35d77ce840c265814af9c078cacc7aa/greenlet-3.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1a4a48f24681300c640f143ba7c404270e1ebbbcf34331d7104a4ff40f8ea705", size = 245554, upload-time = "2026-04-08T17:03:50.044Z" },
445
+ version = "3.5.0"
446
+ source = { registry = "https://pypi.org/simple" }
447
+ sdist = { url = "https://files.pythonhosted.org/packages/3c/3f/dbf99fb14bfeb88c28f16729215478c0e265cacd6dc22270c8f31bb6892f/greenlet-3.5.0.tar.gz", hash = "sha256:d419647372241bc68e957bf38d5c1f98852155e4146bd1e4121adea81f4f01e4", size = 196995, upload-time = "2026-04-27T13:37:15.544Z" }
448
+ wheels = [
449
+ { url = "https://files.pythonhosted.org/packages/94/5e/a70f31e3e8d961c4ce589c15b28e4225d63704e431a23932a3808cbcc867/greenlet-3.5.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:f35807464c4c58c55f0d31dfa83c541a5615d825c2fe3d2b95360cf7c4e3c0a8", size = 285564, upload-time = "2026-04-27T12:23:08.555Z" },
450
+ { url = "https://files.pythonhosted.org/packages/af/a6/046c0a28e21833e4086918218cfb3d8bed51c075a1b700f20b9d7861c0f4/greenlet-3.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55fa7ea52771be44af0de27d8b80c02cd18c2c3cddde6c847ecebdf72418b6a1", size = 651166, upload-time = "2026-04-27T12:52:43.644Z" },
451
+ { url = "https://files.pythonhosted.org/packages/47/f8/4af27f71c5ff32a7fbc516adb46370d9c4ae2bc7bd3dc7d066ac542b4b15/greenlet-3.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a97e4821aa710603f94de0da25f25096454d78ffdace5dc77f3a006bc01abba3", size = 663792, upload-time = "2026-04-27T12:59:44.93Z" },
452
+ { url = "https://files.pythonhosted.org/packages/fb/89/2dadb89793c37ee8b4c237857188293e9060dc085f19845c292e00f8e091/greenlet-3.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bf2d8a80bec89ab46221ae45c5373d5ba0bd36c19aa8508e85c6cd7e5106cd37", size = 668086, upload-time = "2026-04-27T13:02:42.314Z" },
453
+ { url = "https://files.pythonhosted.org/packages/a3/59/1bd6d7428d6ed9106efbb8c52310c60fd04f6672490f452aeaa3829aa436/greenlet-3.5.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f52a464e4ed91780bdfbbdd2b97197f3accaa629b98c200f4dffada759f3ae7", size = 660933, upload-time = "2026-04-27T12:25:33.276Z" },
454
+ { url = "https://files.pythonhosted.org/packages/82/35/75722be7e26a2af4cbd2dc35b0ed382dacf9394b7e75551f76ed1abe87f2/greenlet-3.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:1bae92a1dd94c5f9d9493c3a212dd874c202442047cf96446412c862feca83a2", size = 470799, upload-time = "2026-04-27T13:05:17.094Z" },
455
+ { url = "https://files.pythonhosted.org/packages/83/e4/b903e5a5fae1e8a28cdd32a0cfbfd560b668c25b692f67768822ddc5f40f/greenlet-3.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:762612baf1161ccb8437c0161c668a688223cba28e1bf038f4eb47b13e39ccdf", size = 1618401, upload-time = "2026-04-27T12:53:31.062Z" },
456
+ { url = "https://files.pythonhosted.org/packages/0e/e3/5ec408a329acb854fb607a122e1ee5fb3ff649f9a97952948a90803c0d8e/greenlet-3.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:57a43c6079a89713522bc4bcb9f75070ecf5d3dbad7792bfe42239362cbf2a16", size = 1682038, upload-time = "2026-04-27T12:25:31.838Z" },
457
+ { url = "https://files.pythonhosted.org/packages/91/20/6b165108058767ee643c55c5c4904d591a830ee2b3c7dbd359828fbc829f/greenlet-3.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:3bc59be3945ae9750b9e7d45067d01ae3fe90ea5f9ade99239dabdd6e28a5033", size = 239835, upload-time = "2026-04-27T12:24:54.136Z" },
458
+ { url = "https://files.pythonhosted.org/packages/4e/62/1c498375cee177b55d980c1db319f26470e5309e54698c8f8fc06c0fd539/greenlet-3.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:a96fcee45e03fe30a62669fd16ab5c9d3c172660d3085605cb1e2d1280d3c988", size = 236862, upload-time = "2026-04-27T12:23:24.957Z" },
459
+ { url = "https://files.pythonhosted.org/packages/78/a8/4522939255bb5409af4e87132f915446bf3622c2c292d14d3c38d128ae82/greenlet-3.5.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a10a732421ab4fec934783ce3e54763470d0181db6e3468f9103a275c3ed1853", size = 293614, upload-time = "2026-04-27T12:24:12.874Z" },
460
+ { url = "https://files.pythonhosted.org/packages/15/5e/8744c52e2c027b5a8772a01561934c8835f869733e101f62075c60430340/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fc391b1566f2907d17aaebe78f8855dc45675159a775fcf9e61f8ee0078e87f", size = 650723, upload-time = "2026-04-27T12:52:45.412Z" },
461
+ { url = "https://files.pythonhosted.org/packages/00/ef/7b4c39c03cf46ceca512c5d3f914afd85aa30b2cc9a93015b0dd73e4be6c/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:680bd0e7ad5e8daa8a4aa89f68fd6adc834b8a8036dc256533f7e08f4a4b01f7", size = 656529, upload-time = "2026-04-27T12:59:46.295Z" },
462
+ { url = "https://files.pythonhosted.org/packages/5f/5c/0602239503b124b70e39355cbdb39361ecfe65b87a5f2f63752c32f5286f/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:1aa4ce8debcd4ea7fb2e150f3036588c41493d1d52c43538924ae1819003f4ce", size = 657015, upload-time = "2026-04-27T13:02:43.973Z" },
463
+ { url = "https://files.pythonhosted.org/packages/0b/b5/c7768f352f5c010f92064d0063f987e7dc0cd290a6d92a34109015ce4aa1/greenlet-3.5.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddb36c7d6c9c0a65f18c7258634e0c416c6ab59caac8c987b96f80c2ebda0112", size = 654364, upload-time = "2026-04-27T12:25:35.64Z" },
464
+ { url = "https://files.pythonhosted.org/packages/38/51/8699f865f125dc952384cb432b0f7138aa4d8f2969a7d12d0df5b94d054d/greenlet-3.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:728a73687e39ae9ca34e4694cbf2f049d3fbc7174639468d0f67200a97d8f9e2", size = 488275, upload-time = "2026-04-27T13:05:18.28Z" },
465
+ { url = "https://files.pythonhosted.org/packages/ef/d0/079ebe12e4b1fc758857ce5be1a5e73f06870f2101e52611d1e71925ce54/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e5ddf316ced87539144621453c3aef229575825fe60c604e62bedc4003f372b2", size = 1614204, upload-time = "2026-04-27T12:53:32.618Z" },
466
+ { url = "https://files.pythonhosted.org/packages/6d/89/6c2fb63df3596552d20e58fb4d96669243388cf680cff222758812c7bfaa/greenlet-3.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4a448128607be0de65342dc9b31be7f948ef4cc0bc8832069350abefd310a8f2", size = 1675480, upload-time = "2026-04-27T12:25:34.168Z" },
467
+ { url = "https://files.pythonhosted.org/packages/15/32/77ee8a6c1564fc345a491a4e85b3bf360e4cf26eac98c4532d2fdb96e01f/greenlet-3.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d60097128cb0a1cab9ea541186ea13cd7b847b8449a7787c2e2350da0cb82d86", size = 245324, upload-time = "2026-04-27T12:24:40.295Z" },
468
468
  ]
469
469
 
470
470
  [[package]]
@@ -688,11 +688,11 @@ wheels = [
688
688
 
689
689
  [[package]]
690
690
  name = "pip"
691
- version = "26.0.1"
691
+ version = "26.1"
692
692
  source = { registry = "https://pypi.org/simple" }
693
- sdist = { url = "https://files.pythonhosted.org/packages/48/83/0d7d4e9efe3344b8e2fe25d93be44f64b65364d3c8d7bc6dc90198d5422e/pip-26.0.1.tar.gz", hash = "sha256:c4037d8a277c89b320abe636d59f91e6d0922d08a05b60e85e53b296613346d8", size = 1812747, upload-time = "2026-02-05T02:20:18.702Z" }
693
+ sdist = { url = "https://files.pythonhosted.org/packages/73/7e/d2b04004e1068ad4fdfa2f227b839b5d03e602e47cdbbf49de71137c9546/pip-26.1.tar.gz", hash = "sha256:81e13ebcca3ffa8cc85e4deff5c27e1ee26dea0aa7fc2f294a073ac208806ff3", size = 1840316, upload-time = "2026-04-26T21:00:05.406Z" }
694
694
  wheels = [
695
- { url = "https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl", hash = "sha256:bdb1b08f4274833d62c1aa29e20907365a2ceb950410df15fc9521bad440122b", size = 1787723, upload-time = "2026-02-05T02:20:16.416Z" },
695
+ { url = "https://files.pythonhosted.org/packages/70/7a/be4bd8bcbb24ea475856dd68159d78b03b2bb53dae369f69c9606b8888f5/pip-26.1-py3-none-any.whl", hash = "sha256:4e8486d821d814b77319acb7b9e8bf5a4ee7590a643e7cb21029f209be8573c1", size = 1812804, upload-time = "2026-04-26T21:00:03.194Z" },
696
696
  ]
697
697
 
698
698
  [[package]]
@@ -1022,11 +1022,20 @@ audit = [
1022
1022
  { name = "zeep" },
1023
1023
  ]
1024
1024
  authn = [
1025
+ { name = "aiobotocore" },
1025
1026
  { name = "aiohttp", extra = ["speedups"] },
1027
+ { name = "lxml" },
1028
+ { name = "object-storage-client" },
1029
+ { name = "zeep" },
1026
1030
  ]
1027
1031
  authz = [
1032
+ { name = "aiobotocore" },
1033
+ { name = "aiohttp", extra = ["speedups"] },
1028
1034
  { name = "asyncpg" },
1035
+ { name = "lxml" },
1036
+ { name = "object-storage-client" },
1029
1037
  { name = "sqlalchemy", extra = ["asyncio"] },
1038
+ { name = "zeep" },
1030
1039
  ]
1031
1040
  cache = [
1032
1041
  { name = "valkey", extra = ["libvalkey"] },
@@ -1069,6 +1078,8 @@ requires-dist = [
1069
1078
  { name = "msgspec", specifier = "==0.21.1" },
1070
1079
  { name = "object-storage-client", marker = "extra == 'object-storage'", specifier = "==0.0.23" },
1071
1080
  { name = "pydantic-settings", specifier = "~=2.14.0" },
1081
+ { name = "python3-commons", extras = ["api-client"], marker = "extra == 'authn'" },
1082
+ { name = "python3-commons", extras = ["api-client"], marker = "extra == 'authz'" },
1072
1083
  { name = "python3-commons", extras = ["api-client", "audit", "authn", "authz", "cache", "database", "object-storage"], marker = "extra == 'all'" },
1073
1084
  { name = "python3-commons", extras = ["database"], marker = "extra == 'authz'" },
1074
1085
  { name = "python3-commons", extras = ["object-storage"], marker = "extra == 'api-client'" },
@@ -1327,7 +1338,7 @@ wheels = [
1327
1338
 
1328
1339
  [[package]]
1329
1340
  name = "virtualenv"
1330
- version = "21.2.4"
1341
+ version = "21.3.0"
1331
1342
  source = { registry = "https://pypi.org/simple" }
1332
1343
  dependencies = [
1333
1344
  { name = "distlib" },
@@ -1335,9 +1346,9 @@ dependencies = [
1335
1346
  { name = "platformdirs" },
1336
1347
  { name = "python-discovery" },
1337
1348
  ]
1338
- sdist = { url = "https://files.pythonhosted.org/packages/0c/98/3a7e644e19cb26133488caff231be390579860bbbb3da35913c49a1d0a46/virtualenv-21.2.4.tar.gz", hash = "sha256:b294ef68192638004d72524ce7ef303e9d0cf5a44c95ce2e54a7500a6381cada", size = 5850742, upload-time = "2026-04-14T22:15:31.438Z" }
1349
+ sdist = { url = "https://files.pythonhosted.org/packages/3f/8b/6331f7a7fe70131c301106ec1e7cf23e2501bf7d4ca3636805801ca191bb/virtualenv-21.3.0.tar.gz", hash = "sha256:733750db978ec95c2d8eb4feadaa57091002bce404cb39ba69899cf7bd28944e", size = 7614069, upload-time = "2026-04-27T17:05:58.927Z" }
1339
1350
  wheels = [
1340
- { url = "https://files.pythonhosted.org/packages/27/8d/edd0bd910ff803c308ee9a6b7778621af0d10252219ad9f19ef4d4982a61/virtualenv-21.2.4-py3-none-any.whl", hash = "sha256:29d21e941795206138d0f22f4e45ff7050e5da6c6472299fb7103318763861ac", size = 5831232, upload-time = "2026-04-14T22:15:29.342Z" },
1351
+ { url = "https://files.pythonhosted.org/packages/4b/eb/03bfb1299d4c4510329e470f13f9a4ce793df7fcb5a2fd3510f911066f61/virtualenv-21.3.0-py3-none-any.whl", hash = "sha256:4d28ee41f6d9ec8f1f00cd472b9ffbcedda1b3d3b9a575b5c94a2d004fd51bd7", size = 7594690, upload-time = "2026-04-27T17:05:55.468Z" },
1341
1352
  ]
1342
1353
 
1343
1354
  [[package]]
@@ -1,79 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import logging
4
- from collections.abc import Sequence
5
- from http import HTTPStatus
6
- from typing import TypeVar
7
-
8
- try:
9
- import aiohttp
10
- except ImportError as e:
11
- msg = 'Install python3-commons[authn] to use this feature'
12
- raise RuntimeError(msg) from e
13
-
14
- import msgspec
15
-
16
- from python3_commons.conf import oidc_settings
17
-
18
- logger = logging.getLogger(__name__)
19
-
20
-
21
- class TokenData(msgspec.Struct):
22
- exp: int
23
- iat: int
24
- iss: str
25
- sub: str
26
- aud: str | Sequence[str] | None = None
27
- email: str | None = None
28
- name: str | None = None
29
- preferred_username: str | None = None
30
- realm_access: dict[str, Sequence[str]] | None = None
31
- resource_access: dict[str, dict[str, Sequence[str]]] | None = None
32
-
33
- @property
34
- def roles(self) -> list[str]:
35
- roles_list = []
36
-
37
- if self.realm_access:
38
- roles_list.extend(self.realm_access.get('roles', []))
39
-
40
- if self.resource_access:
41
- for client in self.resource_access.values():
42
- roles_list.extend(client.get('roles', []))
43
-
44
- return list(set(roles_list))
45
-
46
-
47
- T = TypeVar('T', bound=TokenData)
48
- OIDC_CONFIG_URL = (
49
- f'{oidc_settings.authority_internal_url or oidc_settings.authority_url}/.well-known/openid-configuration'
50
- )
51
-
52
-
53
- async def fetch_openid_config() -> dict:
54
- """
55
- Fetch the OpenID configuration (including JWKS URI) from OIDC authority.
56
- """
57
- async with aiohttp.ClientSession() as session, session.get(OIDC_CONFIG_URL) as response:
58
- if response.status != HTTPStatus.OK:
59
- _msg = 'Failed to fetch OpenID configuration'
60
-
61
- raise RuntimeError(_msg)
62
-
63
- return await response.json()
64
-
65
-
66
- async def fetch_jwks(jwks_uri: str) -> dict:
67
- """
68
- Fetch the JSON Web Key Set (JWKS) for validating the token's signature.
69
- """
70
- if oidc_settings.authority_internal_url:
71
- jwks_uri = jwks_uri.replace(str(oidc_settings.authority_url), str(oidc_settings.authority_internal_url))
72
-
73
- async with aiohttp.ClientSession() as session, session.get(jwks_uri) as response:
74
- if response.status != HTTPStatus.OK:
75
- _msg = 'Failed to fetch JWKS'
76
-
77
- raise RuntimeError(_msg)
78
-
79
- return await response.json()