pypomes-iam 0.2.5__tar.gz → 0.2.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.
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.6}/PKG-INFO +1 -1
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/pyproject.toml +1 -1
- pypomes_iam-0.2.5/src/pypomes_iam/common_pomes.py → pypomes_iam-0.2.6/src/pypomes_iam/iam_common.py +86 -60
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/src/pypomes_iam/iam_pomes.py +26 -24
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/src/pypomes_iam/jusbr_pomes.py +10 -63
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/src/pypomes_iam/keycloak_pomes.py +11 -64
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/src/pypomes_iam/token_pomes.py +4 -1
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/.gitignore +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/LICENSE +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/README.md +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/src/pypomes_iam/__init__.py +0 -0
- {pypomes_iam-0.2.5 → pypomes_iam-0.2.6}/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.6
|
|
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.6/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,
|
|
@@ -187,6 +198,7 @@ def _service_token(registry: dict[str, Any],
|
|
|
187
198
|
user_data: dict[str, Any] = _get_user_data(registry=registry,
|
|
188
199
|
user_id=user_id,
|
|
189
200
|
logger=logger)
|
|
201
|
+
err_msg: str | None = None
|
|
190
202
|
token: str = user_data["access-token"]
|
|
191
203
|
if token:
|
|
192
204
|
access_expiration: int = user_data.get("access-expiration")
|
|
@@ -197,41 +209,51 @@ def _service_token(registry: dict[str, Any],
|
|
|
197
209
|
# access token has expired
|
|
198
210
|
refresh_token: str = user_data["refresh-token"]
|
|
199
211
|
if refresh_token:
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
212
|
+
refresh_expiration = user_data["refresh-expiration"]
|
|
213
|
+
if now < refresh_expiration:
|
|
214
|
+
body_data: dict[str, str] = {
|
|
215
|
+
"grant_type": "refresh_token",
|
|
216
|
+
"refresh_token": refresh_token
|
|
217
|
+
}
|
|
218
|
+
result = _post_for_token(registry=registry,
|
|
219
|
+
user_data=user_data,
|
|
220
|
+
body_data=body_data,
|
|
221
|
+
errors=errors,
|
|
222
|
+
logger=logger)
|
|
223
|
+
else:
|
|
224
|
+
# refresh token has expired
|
|
225
|
+
err_msg = "Access and refresh tokens expired"
|
|
226
|
+
else:
|
|
227
|
+
err_msg = "Access token expired, no refresh token available"
|
|
228
|
+
else:
|
|
229
|
+
err_msg = f"User '{user_id}' not authenticated"
|
|
230
|
+
|
|
231
|
+
if err_msg and (logger or isinstance(errors, list)):
|
|
211
232
|
err_msg: str = f"User '{user_id}' not authenticated"
|
|
212
233
|
if isinstance(errors, list):
|
|
213
234
|
errors.append(err_msg)
|
|
214
235
|
if logger:
|
|
215
236
|
logger.error(msg=err_msg)
|
|
237
|
+
logger.error(msg=err_msg)
|
|
216
238
|
|
|
217
239
|
return result
|
|
218
240
|
|
|
219
241
|
|
|
220
242
|
def _get_public_key(registry: dict[str, Any],
|
|
221
|
-
logger: Logger | None) ->
|
|
243
|
+
logger: Logger | None) -> str:
|
|
222
244
|
"""
|
|
223
245
|
Obtain the public key used by the *IAM* to sign the authentication tokens.
|
|
224
246
|
|
|
225
247
|
The public key is saved in *registry*.
|
|
226
248
|
|
|
227
249
|
:param registry: the registry holding the authentication data
|
|
228
|
-
:return: the public key, in *
|
|
250
|
+
:return: the public key, in *PEM* format
|
|
229
251
|
"""
|
|
230
252
|
# initialize the return variable
|
|
231
|
-
result:
|
|
253
|
+
result: str | None = None
|
|
232
254
|
|
|
233
255
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
234
|
-
if now > registry["
|
|
256
|
+
if now > registry["pk-expiration"]:
|
|
235
257
|
# obtain a new public key
|
|
236
258
|
url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
|
|
237
259
|
if logger:
|
|
@@ -243,10 +265,10 @@ def _get_public_key(registry: dict[str, Any],
|
|
|
243
265
|
logger.debug(msg=f"GET success, status {response.status_code}")
|
|
244
266
|
reply: dict[str, Any] = response.json()
|
|
245
267
|
result = crypto_jwk_convert(jwk=reply["keys"][0],
|
|
246
|
-
fmt="
|
|
268
|
+
fmt="PEM")
|
|
247
269
|
registry["public-key"] = result
|
|
248
|
-
|
|
249
|
-
registry["
|
|
270
|
+
lifetime: int = registry["pk-lifetime"] or 0
|
|
271
|
+
registry["pk-expiration"] = now + lifetime
|
|
250
272
|
elif logger:
|
|
251
273
|
msg: str = f"GET failure, status {response.status_code}, reason '{response.reason}'"
|
|
252
274
|
if hasattr(response, "content") and response.content:
|
|
@@ -281,14 +303,15 @@ def _get_user_data(registry: dict[str, Any],
|
|
|
281
303
|
:param registry: the registry holding the authentication data
|
|
282
304
|
:return: the data for *user_id* in the registry
|
|
283
305
|
"""
|
|
284
|
-
cache: Cache = registry["
|
|
306
|
+
cache: Cache = registry["cache"]
|
|
285
307
|
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
286
308
|
result: dict[str, Any] = users.get(user_id)
|
|
287
309
|
if not result:
|
|
288
310
|
result = {
|
|
289
311
|
"access-token": None,
|
|
290
312
|
"refresh-token": None,
|
|
291
|
-
"access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
313
|
+
"access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
|
|
314
|
+
"refresh-expiration": sys.maxsize
|
|
292
315
|
}
|
|
293
316
|
users[user_id] = result
|
|
294
317
|
if logger:
|
|
@@ -310,7 +333,7 @@ def _post_for_token(registry: dict[str, Any],
|
|
|
310
333
|
For token exchange, *body_data* will have the attributes
|
|
311
334
|
- "grant_type": "authorization_code"
|
|
312
335
|
- "code": <16-character-random-code>
|
|
313
|
-
- "redirect_uri": <
|
|
336
|
+
- "redirect_uri": <redirect-uri>
|
|
314
337
|
For token refresh, *body_data* will have the attributes
|
|
315
338
|
- "grant_type": "refresh_token"
|
|
316
339
|
- "refresh_token": <current-refresh-token>
|
|
@@ -347,7 +370,8 @@ def _post_for_token(registry: dict[str, Any],
|
|
|
347
370
|
# "token_type": "Bearer",
|
|
348
371
|
# "access_token": <str>,
|
|
349
372
|
# "expires_in": <number-of-seconds>,
|
|
350
|
-
# "refresh_token": <str
|
|
373
|
+
# "refresh_token": <str>,
|
|
374
|
+
# "refesh_expires_in": <number-of-seconds>
|
|
351
375
|
# }
|
|
352
376
|
response: requests.Response = requests.post(url=url,
|
|
353
377
|
data=body_data)
|
|
@@ -361,6 +385,8 @@ def _post_for_token(registry: dict[str, Any],
|
|
|
361
385
|
# on token refresh, keep current refresh token if a new one is not provided
|
|
362
386
|
user_data["refresh-token"] = reply.get("refresh_token") or body_data.get("refresh_token")
|
|
363
387
|
user_data["access-expiration"] = now + reply.get("expires_in")
|
|
388
|
+
refresh_expiration: int = user_data.get("refresh_expires_in")
|
|
389
|
+
user_data["refresh-expiration"] = (now + refresh_expiration) if refresh_expiration else sys.maxsize
|
|
364
390
|
else:
|
|
365
391
|
# request resulted in error
|
|
366
392
|
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
|