pypomes-iam 0.2.5__tar.gz → 0.2.7__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.
Potentially problematic release.
This version of pypomes-iam might be problematic. Click here for more details.
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/PKG-INFO +1 -1
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/pyproject.toml +1 -1
- pypomes_iam-0.2.5/src/pypomes_iam/common_pomes.py → pypomes_iam-0.2.7/src/pypomes_iam/iam_common.py +91 -61
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/src/pypomes_iam/iam_pomes.py +26 -24
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/src/pypomes_iam/jusbr_pomes.py +10 -63
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/src/pypomes_iam/keycloak_pomes.py +11 -64
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/src/pypomes_iam/token_pomes.py +4 -1
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/.gitignore +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/LICENSE +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/README.md +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/src/pypomes_iam/__init__.py +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.7}/src/pypomes_iam/provider_pomes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_iam
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: A collection of Python pomes, penyeach (IAM modules)
|
|
5
5
|
Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-IAM
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-IAM/issues
|
pypomes_iam-0.2.5/src/pypomes_iam/common_pomes.py → pypomes_iam-0.2.7/src/pypomes_iam/iam_common.py
RENAMED
|
@@ -5,41 +5,55 @@ import string
|
|
|
5
5
|
import sys
|
|
6
6
|
from cachetools import Cache
|
|
7
7
|
from datetime import datetime
|
|
8
|
+
from enum import StrEnum
|
|
8
9
|
from flask import Request
|
|
9
10
|
from logging import Logger
|
|
10
11
|
from pypomes_core import TZ_LOCAL, exc_format
|
|
11
12
|
from pypomes_crypto import crypto_jwk_convert
|
|
12
|
-
from typing import Any
|
|
13
|
+
from typing import Any, Final
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IamServer(StrEnum):
|
|
17
|
+
IAM_JUSRBR = "iam-jusbr",
|
|
18
|
+
IAM_KEYCLOAK = "iam-keycloak"
|
|
19
|
+
|
|
13
20
|
|
|
14
21
|
# registry structure:
|
|
15
|
-
# {
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
22
|
+
# { <IamServer>:
|
|
23
|
+
# {
|
|
24
|
+
# "client-id": <str>,
|
|
25
|
+
# "client-secret": <str>,
|
|
26
|
+
# "client-timeout": <int>,
|
|
27
|
+
# "public_key": <str>,
|
|
28
|
+
# "pk-lifetime": <int>,
|
|
29
|
+
# "pk-expiration": <int>,
|
|
30
|
+
# "base-url": <str>,
|
|
31
|
+
# "logger": <Logger>,
|
|
32
|
+
# "cache": <FIFOCache>,
|
|
33
|
+
# "redirect-uri": <str> <-- transient
|
|
34
|
+
# },
|
|
35
|
+
# ...
|
|
25
36
|
# }
|
|
26
|
-
# data in "
|
|
37
|
+
# data in "cache":
|
|
27
38
|
# {
|
|
28
39
|
# "users": {
|
|
29
40
|
# "<user-id>": {
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
41
|
+
# "access-token": <str>
|
|
42
|
+
# "refresh-token": <str>
|
|
43
|
+
# "access-expiration": <timestamp>,
|
|
44
|
+
# "refresh-expiration": <timestamp>,
|
|
45
|
+
# "login-expiration": <timestamp>, <-- transient
|
|
46
|
+
# "login-id": <str>, <-- transient
|
|
35
47
|
# }
|
|
36
|
-
# }
|
|
48
|
+
# },
|
|
49
|
+
# ...
|
|
37
50
|
# }
|
|
51
|
+
IAM_SERVERS: Final[dict[IamServer, dict[str, Any]]] = {}
|
|
38
52
|
|
|
39
53
|
|
|
40
54
|
def _service_login(registry: dict[str, Any],
|
|
41
55
|
args: dict[str, Any],
|
|
42
|
-
logger: Logger | None) -> str:
|
|
56
|
+
logger: Logger | None) -> dict[str, str]:
|
|
43
57
|
"""
|
|
44
58
|
Build the callback URL for redirecting the request to the IAM's authentication page.
|
|
45
59
|
|
|
@@ -48,7 +62,6 @@ def _service_login(registry: dict[str, Any],
|
|
|
48
62
|
:param logger: optional logger
|
|
49
63
|
:return: the callback URL, with the appropriate parameters
|
|
50
64
|
"""
|
|
51
|
-
|
|
52
65
|
# retrieve user data
|
|
53
66
|
oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
54
67
|
|
|
@@ -61,19 +74,17 @@ def _service_login(registry: dict[str, Any],
|
|
|
61
74
|
user_data["login-id"] = user_id
|
|
62
75
|
timeout: int = _get_login_timeout(registry=registry)
|
|
63
76
|
user_data["login-expiration"] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout if timeout else None
|
|
77
|
+
redirect_uri: str = args.get("redirect-uri")
|
|
78
|
+
registry["redirect-uri"] = redirect_uri
|
|
64
79
|
|
|
65
|
-
# build the
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
_service_logout(registry=registry,
|
|
74
|
-
args=args,
|
|
75
|
-
logger=logger)
|
|
76
|
-
return result
|
|
80
|
+
# build the login url
|
|
81
|
+
return {
|
|
82
|
+
"login-url": (f"{registry["base-url"]}/protocol/openid-connect/auth"
|
|
83
|
+
f"?response_type=code&scope=openid"
|
|
84
|
+
f"&client_id={registry["client-id"]}"
|
|
85
|
+
f"&redirect_uri={redirect_uri}"
|
|
86
|
+
f"&state={oauth_state}")
|
|
87
|
+
}
|
|
77
88
|
|
|
78
89
|
|
|
79
90
|
def _service_logout(registry: dict[str, Any],
|
|
@@ -89,7 +100,7 @@ def _service_logout(registry: dict[str, Any],
|
|
|
89
100
|
# remove the user data
|
|
90
101
|
user_id: str = args.get("user-id") or args.get("login")
|
|
91
102
|
if user_id:
|
|
92
|
-
cache: Cache = registry["
|
|
103
|
+
cache: Cache = registry["cache"]
|
|
93
104
|
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
94
105
|
if user_id in users:
|
|
95
106
|
users.pop(user_id)
|
|
@@ -102,7 +113,7 @@ def _service_callback(registry: dict[str, Any],
|
|
|
102
113
|
errors: list[str],
|
|
103
114
|
logger: Logger | None) -> tuple[str, str]:
|
|
104
115
|
"""
|
|
105
|
-
Entry point for the callback from JusBR on authentication operation.
|
|
116
|
+
Entry point for the callback from JusBR via the front-end application on authentication operation.
|
|
106
117
|
|
|
107
118
|
:param registry: the registry holding the authentication data
|
|
108
119
|
:param args: the arguments passed when requesting the service
|
|
@@ -115,7 +126,7 @@ def _service_callback(registry: dict[str, Any],
|
|
|
115
126
|
result: tuple[str, str] | None = None
|
|
116
127
|
|
|
117
128
|
# retrieve the users authentication data
|
|
118
|
-
cache: Cache = registry["
|
|
129
|
+
cache: Cache = registry["cache"]
|
|
119
130
|
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
120
131
|
|
|
121
132
|
# validate the OAuth2 state
|
|
@@ -138,7 +149,7 @@ def _service_callback(registry: dict[str, Any],
|
|
|
138
149
|
body_data: dict[str, Any] = {
|
|
139
150
|
"grant_type": "authorization_code",
|
|
140
151
|
"code": code,
|
|
141
|
-
"redirect_uri": registry.get("
|
|
152
|
+
"redirect_uri": registry.get("redirect-uri"),
|
|
142
153
|
}
|
|
143
154
|
token = _post_for_token(registry=registry,
|
|
144
155
|
user_data=user_data,
|
|
@@ -147,8 +158,8 @@ def _service_callback(registry: dict[str, Any],
|
|
|
147
158
|
logger=logger)
|
|
148
159
|
# retrieve the token's claims
|
|
149
160
|
if not errors:
|
|
150
|
-
public_key:
|
|
151
|
-
|
|
161
|
+
public_key: str = _get_public_key(registry=registry,
|
|
162
|
+
logger=logger)
|
|
152
163
|
token_claims: dict[str, dict[str, Any]] = token_validate(token=token,
|
|
153
164
|
issuer=registry["base-url"],
|
|
154
165
|
public_key=public_key,
|
|
@@ -156,7 +167,8 @@ def _service_callback(registry: dict[str, Any],
|
|
|
156
167
|
logger=logger)
|
|
157
168
|
if not errors:
|
|
158
169
|
token_user: str = token_claims["payload"].get("preferred_username")
|
|
159
|
-
|
|
170
|
+
login_id = user_data.pop("login-id", None)
|
|
171
|
+
if not login_id or (login_id == token_user):
|
|
160
172
|
users[token_user] = user_data
|
|
161
173
|
result = (token_user, token)
|
|
162
174
|
else:
|
|
@@ -164,6 +176,9 @@ def _service_callback(registry: dict[str, Any],
|
|
|
164
176
|
else:
|
|
165
177
|
errors.append("Unknown state received")
|
|
166
178
|
|
|
179
|
+
if errors and logger:
|
|
180
|
+
logger.error(msg="; ".join(errors))
|
|
181
|
+
|
|
167
182
|
return result
|
|
168
183
|
|
|
169
184
|
|
|
@@ -187,6 +202,7 @@ def _service_token(registry: dict[str, Any],
|
|
|
187
202
|
user_data: dict[str, Any] = _get_user_data(registry=registry,
|
|
188
203
|
user_id=user_id,
|
|
189
204
|
logger=logger)
|
|
205
|
+
err_msg: str | None = None
|
|
190
206
|
token: str = user_data["access-token"]
|
|
191
207
|
if token:
|
|
192
208
|
access_expiration: int = user_data.get("access-expiration")
|
|
@@ -197,41 +213,51 @@ def _service_token(registry: dict[str, Any],
|
|
|
197
213
|
# access token has expired
|
|
198
214
|
refresh_token: str = user_data["refresh-token"]
|
|
199
215
|
if refresh_token:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
refresh_expiration = user_data["refresh-expiration"]
|
|
217
|
+
if now < refresh_expiration:
|
|
218
|
+
body_data: dict[str, str] = {
|
|
219
|
+
"grant_type": "refresh_token",
|
|
220
|
+
"refresh_token": refresh_token
|
|
221
|
+
}
|
|
222
|
+
result = _post_for_token(registry=registry,
|
|
223
|
+
user_data=user_data,
|
|
224
|
+
body_data=body_data,
|
|
225
|
+
errors=errors,
|
|
226
|
+
logger=logger)
|
|
227
|
+
else:
|
|
228
|
+
# refresh token has expired
|
|
229
|
+
err_msg = "Access and refresh tokens expired"
|
|
230
|
+
else:
|
|
231
|
+
err_msg = "Access token expired, no refresh token available"
|
|
232
|
+
else:
|
|
233
|
+
err_msg = f"User '{user_id}' not authenticated"
|
|
234
|
+
|
|
235
|
+
if err_msg and (logger or isinstance(errors, list)):
|
|
211
236
|
err_msg: str = f"User '{user_id}' not authenticated"
|
|
212
237
|
if isinstance(errors, list):
|
|
213
238
|
errors.append(err_msg)
|
|
214
239
|
if logger:
|
|
215
240
|
logger.error(msg=err_msg)
|
|
241
|
+
logger.error(msg=err_msg)
|
|
216
242
|
|
|
217
243
|
return result
|
|
218
244
|
|
|
219
245
|
|
|
220
246
|
def _get_public_key(registry: dict[str, Any],
|
|
221
|
-
logger: Logger | None) ->
|
|
247
|
+
logger: Logger | None) -> str:
|
|
222
248
|
"""
|
|
223
249
|
Obtain the public key used by the *IAM* to sign the authentication tokens.
|
|
224
250
|
|
|
225
251
|
The public key is saved in *registry*.
|
|
226
252
|
|
|
227
253
|
:param registry: the registry holding the authentication data
|
|
228
|
-
:return: the public key, in *
|
|
254
|
+
:return: the public key, in *PEM* format
|
|
229
255
|
"""
|
|
230
256
|
# initialize the return variable
|
|
231
|
-
result:
|
|
257
|
+
result: str | None = None
|
|
232
258
|
|
|
233
259
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
234
|
-
if now > registry["
|
|
260
|
+
if now > registry["pk-expiration"]:
|
|
235
261
|
# obtain a new public key
|
|
236
262
|
url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
|
|
237
263
|
if logger:
|
|
@@ -243,10 +269,10 @@ def _get_public_key(registry: dict[str, Any],
|
|
|
243
269
|
logger.debug(msg=f"GET success, status {response.status_code}")
|
|
244
270
|
reply: dict[str, Any] = response.json()
|
|
245
271
|
result = crypto_jwk_convert(jwk=reply["keys"][0],
|
|
246
|
-
fmt="
|
|
272
|
+
fmt="PEM")
|
|
247
273
|
registry["public-key"] = result
|
|
248
|
-
|
|
249
|
-
registry["
|
|
274
|
+
lifetime: int = registry["pk-lifetime"] or 0
|
|
275
|
+
registry["pk-expiration"] = now + lifetime
|
|
250
276
|
elif logger:
|
|
251
277
|
msg: str = f"GET failure, status {response.status_code}, reason '{response.reason}'"
|
|
252
278
|
if hasattr(response, "content") and response.content:
|
|
@@ -281,14 +307,15 @@ def _get_user_data(registry: dict[str, Any],
|
|
|
281
307
|
:param registry: the registry holding the authentication data
|
|
282
308
|
:return: the data for *user_id* in the registry
|
|
283
309
|
"""
|
|
284
|
-
cache: Cache = registry["
|
|
310
|
+
cache: Cache = registry["cache"]
|
|
285
311
|
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
286
312
|
result: dict[str, Any] = users.get(user_id)
|
|
287
313
|
if not result:
|
|
288
314
|
result = {
|
|
289
315
|
"access-token": None,
|
|
290
316
|
"refresh-token": None,
|
|
291
|
-
"access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
317
|
+
"access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
|
|
318
|
+
"refresh-expiration": sys.maxsize
|
|
292
319
|
}
|
|
293
320
|
users[user_id] = result
|
|
294
321
|
if logger:
|
|
@@ -310,7 +337,7 @@ def _post_for_token(registry: dict[str, Any],
|
|
|
310
337
|
For token exchange, *body_data* will have the attributes
|
|
311
338
|
- "grant_type": "authorization_code"
|
|
312
339
|
- "code": <16-character-random-code>
|
|
313
|
-
- "redirect_uri": <
|
|
340
|
+
- "redirect_uri": <redirect-uri>
|
|
314
341
|
For token refresh, *body_data* will have the attributes
|
|
315
342
|
- "grant_type": "refresh_token"
|
|
316
343
|
- "refresh_token": <current-refresh-token>
|
|
@@ -347,7 +374,8 @@ def _post_for_token(registry: dict[str, Any],
|
|
|
347
374
|
# "token_type": "Bearer",
|
|
348
375
|
# "access_token": <str>,
|
|
349
376
|
# "expires_in": <number-of-seconds>,
|
|
350
|
-
# "refresh_token": <str
|
|
377
|
+
# "refresh_token": <str>,
|
|
378
|
+
# "refesh_expires_in": <number-of-seconds>
|
|
351
379
|
# }
|
|
352
380
|
response: requests.Response = requests.post(url=url,
|
|
353
381
|
data=body_data)
|
|
@@ -361,6 +389,8 @@ def _post_for_token(registry: dict[str, Any],
|
|
|
361
389
|
# on token refresh, keep current refresh token if a new one is not provided
|
|
362
390
|
user_data["refresh-token"] = reply.get("refresh_token") or body_data.get("refresh_token")
|
|
363
391
|
user_data["access-expiration"] = now + reply.get("expires_in")
|
|
392
|
+
refresh_expiration: int = user_data.get("refresh_expires_in")
|
|
393
|
+
user_data["refresh-expiration"] = (now + refresh_expiration) if refresh_expiration else sys.maxsize
|
|
364
394
|
else:
|
|
365
395
|
# request resulted in error
|
|
366
396
|
err_msg = f"POST failure, status {response.status_code}, reason '{response.reason}'"
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
from flask import Response,
|
|
1
|
+
from flask import Response, request, jsonify
|
|
2
2
|
from logging import Logger
|
|
3
3
|
from typing import Any
|
|
4
4
|
|
|
5
|
-
from .
|
|
5
|
+
from .iam_common import (
|
|
6
|
+
IAM_SERVERS, IamServer,
|
|
6
7
|
_service_login, _service_logout,
|
|
7
8
|
_service_callback, _service_token, _log_init
|
|
8
9
|
)
|
|
9
|
-
from .jusbr_pomes import _jusbr_get_logger, _jusbr_get_registry
|
|
10
|
-
from .keycloak_pomes import _keycloak_get_logger, _keycloak_get_registry
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
# @flask_app.route(rule=<login_endpoint>, # JUSBR_LOGIN_ENDPOINT: /iam/jusbr:login
|
|
@@ -23,18 +22,18 @@ def service_login() -> Response:
|
|
|
23
22
|
:return: the response from the redirect operation
|
|
24
23
|
"""
|
|
25
24
|
# retrieve logger and registry
|
|
26
|
-
|
|
25
|
+
registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
|
|
26
|
+
logger: Logger = registry["logger"]
|
|
27
27
|
|
|
28
28
|
# log the request
|
|
29
29
|
if logger:
|
|
30
30
|
logger.debug(msg=_log_init(request=request))
|
|
31
31
|
|
|
32
|
-
# obtain the
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
result: Response = redirect(location=auth_url)
|
|
32
|
+
# obtain the login URL
|
|
33
|
+
login_data: dict[str, str] = _service_login(registry=registry,
|
|
34
|
+
args=request.args,
|
|
35
|
+
logger=logger)
|
|
36
|
+
result = jsonify(login_data)
|
|
38
37
|
|
|
39
38
|
# log the response
|
|
40
39
|
if logger:
|
|
@@ -56,7 +55,8 @@ def service_logout() -> Response:
|
|
|
56
55
|
:return: response *OK*
|
|
57
56
|
"""
|
|
58
57
|
# retrieve logger and registry
|
|
59
|
-
|
|
58
|
+
registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
|
|
59
|
+
logger: Logger = registry["logger"]
|
|
60
60
|
|
|
61
61
|
# log the request
|
|
62
62
|
if logger:
|
|
@@ -84,10 +84,14 @@ def service_callback() -> Response:
|
|
|
84
84
|
"""
|
|
85
85
|
Entry point for the callback from JusBR on authentication operation.
|
|
86
86
|
|
|
87
|
+
This callback is typically invoked from a front-end application after a successful login at the
|
|
88
|
+
JusBR login page, forwarding the data received.
|
|
89
|
+
|
|
87
90
|
:return: the response containing the token, or *BAD REQUEST*
|
|
88
91
|
"""
|
|
89
92
|
# retrieve logger and registry
|
|
90
|
-
|
|
93
|
+
registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
|
|
94
|
+
logger: Logger = registry["logger"]
|
|
91
95
|
|
|
92
96
|
# log the request
|
|
93
97
|
if logger:
|
|
@@ -126,7 +130,8 @@ def service_token() -> Response:
|
|
|
126
130
|
:return: the response containing the token, or *UNAUTHORIZED*
|
|
127
131
|
"""
|
|
128
132
|
# retrieve logger and registry
|
|
129
|
-
|
|
133
|
+
registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
|
|
134
|
+
logger: Logger = registry["logger"]
|
|
130
135
|
|
|
131
136
|
# log the request
|
|
132
137
|
if logger:
|
|
@@ -152,23 +157,20 @@ def service_token() -> Response:
|
|
|
152
157
|
return result
|
|
153
158
|
|
|
154
159
|
|
|
155
|
-
def
|
|
160
|
+
def __get_iam_registry(endpoint: str) -> dict[str, Any]:
|
|
156
161
|
"""
|
|
157
|
-
Retrieve the
|
|
162
|
+
Retrieve the registry associated the the IAM identifies by *endpoint*.
|
|
158
163
|
|
|
159
164
|
:param endpoint: the service enpoint identifying the IAM.
|
|
160
165
|
:return: the tuple (*logger*, *registry*) associated with *endpoint*
|
|
161
166
|
"""
|
|
162
|
-
# initialize the return
|
|
163
|
-
|
|
164
|
-
result_registry: dict[str, Any] | None = None
|
|
167
|
+
# initialize the return variable
|
|
168
|
+
result: dict[str, Any] | None = None
|
|
165
169
|
|
|
166
170
|
if endpoint.startswith("jusbr-"):
|
|
167
|
-
|
|
168
|
-
result_registry = _jusbr_get_registry()
|
|
171
|
+
result = IAM_SERVERS[IamServer.IAM_JUSRBR]
|
|
169
172
|
elif endpoint.startswith("keycloak-"):
|
|
170
|
-
|
|
171
|
-
result_registry = _keycloak_get_registry()
|
|
173
|
+
result = IAM_SERVERS[IamServer.IAM_KEYCLOAK]
|
|
172
174
|
|
|
173
|
-
return
|
|
175
|
+
return result
|
|
174
176
|
|
|
@@ -7,7 +7,7 @@ from pypomes_core import (
|
|
|
7
7
|
)
|
|
8
8
|
from typing import Any, Final
|
|
9
9
|
|
|
10
|
-
from .
|
|
10
|
+
from .iam_common import IAM_SERVERS, IamServer, _service_token
|
|
11
11
|
|
|
12
12
|
JUSBR_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_ID")
|
|
13
13
|
JUSBR_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_SECRET")
|
|
@@ -25,36 +25,6 @@ JUSBR_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT
|
|
|
25
25
|
JUSBR_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_JUSBR_PUBLIC_KEY_LIFETIME",
|
|
26
26
|
def_value=86400) # 24 hours
|
|
27
27
|
JUSBR_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_BASE")
|
|
28
|
-
JUSBR_URL_AUTH_CALLBACK: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_CALLBACK")
|
|
29
|
-
|
|
30
|
-
# registry structure:
|
|
31
|
-
# {
|
|
32
|
-
# "client-id": <str>,
|
|
33
|
-
# "client-secret": <str>,
|
|
34
|
-
# "client-timeout": <int>,
|
|
35
|
-
# "public_key": <str>,
|
|
36
|
-
# "key-lifetime": <int>,
|
|
37
|
-
# "key-expiration": <int>,
|
|
38
|
-
# "base-url": <str>,
|
|
39
|
-
# "callback-url": <str>,
|
|
40
|
-
# "safe-cache": <FIFOCache>
|
|
41
|
-
# }
|
|
42
|
-
# data in "safe-cache":
|
|
43
|
-
# {
|
|
44
|
-
# "users": {
|
|
45
|
-
# "<user-id>": {
|
|
46
|
-
# "access-token": <str>
|
|
47
|
-
# "refresh-token": <str>
|
|
48
|
-
# "access-expiration": <timestamp>,
|
|
49
|
-
# "login-expiration": <timestamp>, <-- transient
|
|
50
|
-
# "login-id": <str>, <-- transient
|
|
51
|
-
# }
|
|
52
|
-
# }
|
|
53
|
-
# }
|
|
54
|
-
_jusbr_registry: dict[str, Any] | None = None
|
|
55
|
-
|
|
56
|
-
# dafault logger
|
|
57
|
-
_jusbr_logger: Logger | None = None
|
|
58
28
|
|
|
59
29
|
|
|
60
30
|
def jusbr_setup(flask_app: Flask,
|
|
@@ -67,7 +37,6 @@ def jusbr_setup(flask_app: Flask,
|
|
|
67
37
|
login_endpoint: str = JUSBR_ENDPOINT_LOGIN,
|
|
68
38
|
logout_endpoint: str = JUSBR_ENDPOINT_LOGOUT,
|
|
69
39
|
base_url: str = JUSBR_URL_AUTH_BASE,
|
|
70
|
-
callback_url: str = JUSBR_URL_AUTH_CALLBACK,
|
|
71
40
|
logger: Logger = None) -> None:
|
|
72
41
|
"""
|
|
73
42
|
Configure the JusBR IAM.
|
|
@@ -84,27 +53,23 @@ def jusbr_setup(flask_app: Flask,
|
|
|
84
53
|
:param login_endpoint: endpoint for redirecting user to JusBR login page
|
|
85
54
|
:param logout_endpoint: endpoint for terminating user access to JusBR
|
|
86
55
|
:param base_url: base URL to request the JusBR services
|
|
87
|
-
:param callback_url: URL for JusBR to callback on login
|
|
88
56
|
:param logger: optional logger
|
|
89
57
|
"""
|
|
90
58
|
from .iam_pomes import service_login, service_logout, service_callback, service_token
|
|
91
|
-
global _jusbr_logger, _jusbr_registry
|
|
92
|
-
|
|
93
|
-
# establish the logger
|
|
94
|
-
_jusbr_logger = logger
|
|
95
59
|
|
|
96
60
|
# configure the JusBR registry
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
61
|
+
cache: Cache = FIFOCache(maxsize=1048576)
|
|
62
|
+
cache["users"] = {}
|
|
63
|
+
IAM_SERVERS[IamServer.IAM_JUSRBR] = {
|
|
100
64
|
"client-id": client_id,
|
|
101
65
|
"client-secret": client_secret,
|
|
102
66
|
"client-timeout": client_timeout,
|
|
103
67
|
"base-url": base_url,
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"
|
|
107
|
-
"
|
|
68
|
+
"pk-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
|
|
69
|
+
"pk-lifetime": public_key_lifetime,
|
|
70
|
+
"cache": cache,
|
|
71
|
+
"logger": logger,
|
|
72
|
+
"redirect-uri": None
|
|
108
73
|
}
|
|
109
74
|
|
|
110
75
|
# establish the endpoints
|
|
@@ -141,27 +106,9 @@ def jusbr_get_token(user_id: str,
|
|
|
141
106
|
:param logger: optional logger
|
|
142
107
|
:return: the uthentication tokem
|
|
143
108
|
"""
|
|
144
|
-
global _jusbr_registry
|
|
145
|
-
|
|
146
109
|
# retrieve the token
|
|
147
110
|
args: dict[str, Any] = {"user-id": user_id}
|
|
148
|
-
return _service_token(registry=
|
|
111
|
+
return _service_token(registry=IAM_SERVERS[IamServer.IAM_JUSRBR],
|
|
149
112
|
args=args,
|
|
150
113
|
errors=errors,
|
|
151
114
|
logger=logger)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _jusbr_get_logger() -> Logger:
|
|
155
|
-
"""
|
|
156
|
-
Retrieve the logger for JusBR operations.
|
|
157
|
-
:return: the Keycloak logger
|
|
158
|
-
"""
|
|
159
|
-
return _jusbr_logger
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
def _jusbr_get_registry() -> dict[str, Any]:
|
|
163
|
-
"""
|
|
164
|
-
Retrieve the registry holding user authentication data related to JusBR operations.
|
|
165
|
-
:return: the Keycloak registry
|
|
166
|
-
"""
|
|
167
|
-
return _jusbr_registry
|
|
@@ -7,7 +7,7 @@ from pypomes_core import (
|
|
|
7
7
|
)
|
|
8
8
|
from typing import Any, Final
|
|
9
9
|
|
|
10
|
-
from .
|
|
10
|
+
from .iam_common import IAM_SERVERS, IamServer, _service_token
|
|
11
11
|
|
|
12
12
|
KEYCLOAK_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_ID")
|
|
13
13
|
KEYCLOAK_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_SECRET")
|
|
@@ -26,36 +26,6 @@ KEYCLOAK_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_KEYCLO
|
|
|
26
26
|
def_value=86400) # 24 hours
|
|
27
27
|
KEYCLOAK_REALM: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_REALM")
|
|
28
28
|
KEYCLOAK_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_URL_AUTH_BASE")
|
|
29
|
-
KEYCLOAK_URL_AUTH_CALLBACK: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_URL_AUTH_CALLBACK")
|
|
30
|
-
|
|
31
|
-
# registry structure:
|
|
32
|
-
# {
|
|
33
|
-
# "client-id": <str>,
|
|
34
|
-
# "client-secret": <str>,
|
|
35
|
-
# "client-timeout": <int>,
|
|
36
|
-
# "public_key": <str>,
|
|
37
|
-
# "key-lifetime": <int>,
|
|
38
|
-
# "key-expiration": <int>,
|
|
39
|
-
# "base-url": <str>,
|
|
40
|
-
# "callback-url": <str>,
|
|
41
|
-
# "safe-cache": <FIFOCache>
|
|
42
|
-
# }
|
|
43
|
-
# data in "safe-cache":
|
|
44
|
-
# {
|
|
45
|
-
# "users": {
|
|
46
|
-
# "<user-id>": {
|
|
47
|
-
# "access-token": <str>
|
|
48
|
-
# "refresh-token": <str>
|
|
49
|
-
# "access-expiration": <timestamp>,
|
|
50
|
-
# "login-expiration": <timestamp>, <-- transient
|
|
51
|
-
# "login-id": <str>, <-- transient
|
|
52
|
-
# }
|
|
53
|
-
# }
|
|
54
|
-
# }
|
|
55
|
-
_keycloak_registry: dict[str, Any] = {}
|
|
56
|
-
|
|
57
|
-
# dafault logger
|
|
58
|
-
_keycloak_logger: Logger | None = None
|
|
59
29
|
|
|
60
30
|
|
|
61
31
|
def keycloak_setup(flask_app: Flask,
|
|
@@ -69,7 +39,6 @@ def keycloak_setup(flask_app: Flask,
|
|
|
69
39
|
login_endpoint: str = KEYCLOAK_ENDPOINT_LOGIN,
|
|
70
40
|
logout_endpoint: str = KEYCLOAK_ENDPOINT_LOGOUT,
|
|
71
41
|
base_url: str = KEYCLOAK_URL_AUTH_BASE,
|
|
72
|
-
callback_url: str = KEYCLOAK_URL_AUTH_CALLBACK,
|
|
73
42
|
logger: Logger = None) -> None:
|
|
74
43
|
"""
|
|
75
44
|
Configure the Keycloak IAM.
|
|
@@ -87,27 +56,23 @@ def keycloak_setup(flask_app: Flask,
|
|
|
87
56
|
:param login_endpoint: endpoint for redirecting user to JusBR login page
|
|
88
57
|
:param logout_endpoint: endpoint for terminating user access to JusBR
|
|
89
58
|
:param base_url: base URL to request the JusBR services
|
|
90
|
-
:param callback_url: URL for Keycloak to callback on login
|
|
91
59
|
:param logger: optional logger
|
|
92
60
|
"""
|
|
93
61
|
from .iam_pomes import service_login, service_logout, service_callback, service_token
|
|
94
|
-
global _keycloak_logger, _keycloak_registry
|
|
95
|
-
|
|
96
|
-
# establish the logger
|
|
97
|
-
_keycloak_logger = logger
|
|
98
62
|
|
|
99
|
-
# configure the
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
63
|
+
# configure the Keycloak registry
|
|
64
|
+
cache: Cache = FIFOCache(maxsize=1048576)
|
|
65
|
+
cache["users"] = {}
|
|
66
|
+
IAM_SERVERS[IamServer.IAM_KEYCLOAK] = {
|
|
103
67
|
"client-id": client_id,
|
|
104
68
|
"client-secret": client_secret,
|
|
105
69
|
"client-timeout": client_timeout,
|
|
106
70
|
"base-url": f"{base_url}/realms/{realm}",
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
110
|
-
|
|
71
|
+
"pk-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
|
|
72
|
+
"pk-lifetime": public_key_lifetime,
|
|
73
|
+
"cache": cache,
|
|
74
|
+
logger: logger,
|
|
75
|
+
"redirect-uri": None
|
|
111
76
|
}
|
|
112
77
|
|
|
113
78
|
# establish the endpoints
|
|
@@ -144,27 +109,9 @@ def keycloak_get_token(user_id: str,
|
|
|
144
109
|
:param logger: optional logger
|
|
145
110
|
:return: the uthentication tokem
|
|
146
111
|
"""
|
|
147
|
-
global _keycloak_registry
|
|
148
|
-
|
|
149
112
|
# retrieve the token
|
|
150
113
|
args: dict[str, Any] = {"user-id": user_id}
|
|
151
|
-
return _service_token(registry=
|
|
114
|
+
return _service_token(registry=IAM_SERVERS[IamServer.IAM_KEYCLOAK],
|
|
152
115
|
args=args,
|
|
153
116
|
errors=errors,
|
|
154
117
|
logger=logger)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def _keycloak_get_logger() -> Logger:
|
|
158
|
-
"""
|
|
159
|
-
Retrieve the logger for Keycloak operations.
|
|
160
|
-
:return: the Keycloak logger
|
|
161
|
-
"""
|
|
162
|
-
return _keycloak_logger
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def _keycloak_get_registry() -> dict[str, Any]:
|
|
166
|
-
"""
|
|
167
|
-
Retrieve the registry holding user authentication data related to Keycloak operations.
|
|
168
|
-
:return: the Keycloak registry
|
|
169
|
-
"""
|
|
170
|
-
return _keycloak_registry
|
|
@@ -58,8 +58,11 @@ def token_validate(token: str,
|
|
|
58
58
|
# validate the token
|
|
59
59
|
if not errors:
|
|
60
60
|
token_alg: str = token_header.get("alg")
|
|
61
|
+
require: list[str] = ["exp", "iat"]
|
|
62
|
+
if issuer:
|
|
63
|
+
require.append("iss")
|
|
61
64
|
options: dict[str, Any] = {
|
|
62
|
-
"require":
|
|
65
|
+
"require": require,
|
|
63
66
|
"verify_aud": False,
|
|
64
67
|
"verify_exp": True,
|
|
65
68
|
"verify_iat": True,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|