pypomes-iam 0.5.0__tar.gz → 0.5.2__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.5.0 → pypomes_iam-0.5.2}/PKG-INFO +1 -1
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/pyproject.toml +1 -1
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/src/pypomes_iam/iam_common.py +58 -29
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/src/pypomes_iam/iam_pomes.py +89 -14
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/src/pypomes_iam/iam_services.py +34 -34
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/src/pypomes_iam/jusbr_pomes.py +4 -1
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/src/pypomes_iam/token_pomes.py +39 -0
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/.gitignore +0 -0
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/LICENSE +0 -0
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/README.md +0 -0
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/src/pypomes_iam/__init__.py +0 -0
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/src/pypomes_iam/keycloak_pomes.py +0 -0
- {pypomes_iam-0.5.0 → pypomes_iam-0.5.2}/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.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -55,6 +55,64 @@ _IAM_SERVERS: Final[dict[IamServer, dict[str, Any]]] = {}
|
|
|
55
55
|
_iam_lock: Final[RLock] = RLock()
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
def _iam_server_from_endpoint(endpoint: str,
|
|
59
|
+
errors: list[str] | None,
|
|
60
|
+
logger: Logger | None) -> IamServer | None:
|
|
61
|
+
"""
|
|
62
|
+
Retrieve the registered *IAM* server associated with the service's invocation *endpoint*.
|
|
63
|
+
|
|
64
|
+
:param endpoint: the service's invocation endpoint
|
|
65
|
+
:param errors: incidental error messages
|
|
66
|
+
:param logger: optional logger
|
|
67
|
+
:return: the corresponding *IAM* server, or *None* if one could not be obtained
|
|
68
|
+
"""
|
|
69
|
+
# declare the return variable
|
|
70
|
+
result: IamServer | None
|
|
71
|
+
|
|
72
|
+
if endpoint.startswith("jusbr"):
|
|
73
|
+
result = IamServer.IAM_JUSRBR
|
|
74
|
+
elif endpoint.startswith("keycloak"):
|
|
75
|
+
result = IamServer.IAM_KEYCLOAK
|
|
76
|
+
else:
|
|
77
|
+
result = None
|
|
78
|
+
msg: str = f"Unable to find a IAM server to service endpoint '{endpoint}'"
|
|
79
|
+
if logger:
|
|
80
|
+
logger.error(msg=msg)
|
|
81
|
+
if isinstance(errors, list):
|
|
82
|
+
errors.append(msg)
|
|
83
|
+
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _iam_server_from_issuer(issuer: str,
|
|
88
|
+
errors: list[str] | None,
|
|
89
|
+
logger: Logger | None) -> IamServer | None:
|
|
90
|
+
"""
|
|
91
|
+
Retrieve the registered *IAM* server associated with the token's *issuer*.
|
|
92
|
+
|
|
93
|
+
:param issuer: the token's issuer
|
|
94
|
+
:param errors: incidental error messages
|
|
95
|
+
:param logger: optional logger
|
|
96
|
+
:return: the corresponding *IAM* server, or *None* if one could not be obtained
|
|
97
|
+
"""
|
|
98
|
+
# initialize the return variable
|
|
99
|
+
result: IamServer | None = None
|
|
100
|
+
|
|
101
|
+
for iam_server, server_data in _IAM_SERVERS.items():
|
|
102
|
+
if server_data["base-url"] == issuer:
|
|
103
|
+
result = IamServer(iam_server)
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
if not result:
|
|
107
|
+
msg: str = f"Unable to find a IAM server associated with token issuer '{issuer}'"
|
|
108
|
+
if logger:
|
|
109
|
+
logger.error(msg=msg)
|
|
110
|
+
if isinstance(errors, list):
|
|
111
|
+
errors.append(msg)
|
|
112
|
+
|
|
113
|
+
return result
|
|
114
|
+
|
|
115
|
+
|
|
58
116
|
def _get_public_key(iam_server: IamServer,
|
|
59
117
|
errors: list[str] | None,
|
|
60
118
|
logger: Logger | None) -> str:
|
|
@@ -178,35 +236,6 @@ def _get_user_data(iam_server: IamServer,
|
|
|
178
236
|
return result
|
|
179
237
|
|
|
180
238
|
|
|
181
|
-
def _get_iam_server(endpoint: str,
|
|
182
|
-
errors: list[str] | None,
|
|
183
|
-
logger: Logger | None) -> IamServer | None:
|
|
184
|
-
"""
|
|
185
|
-
Retrieve the registered *IAM* server associated with the service's invocation *endpoint*.
|
|
186
|
-
|
|
187
|
-
:param endpoint: the service's invocation endpoint
|
|
188
|
-
:param errors: incidental error messages
|
|
189
|
-
:param logger: optional logger
|
|
190
|
-
:return: the corresponding *IAM* server, or *None* if one could not be obtained
|
|
191
|
-
"""
|
|
192
|
-
# declare the return variable
|
|
193
|
-
result: IamServer | None
|
|
194
|
-
|
|
195
|
-
if endpoint.startswith("jusbr"):
|
|
196
|
-
result = IamServer.IAM_JUSRBR
|
|
197
|
-
elif endpoint.startswith("keycloak"):
|
|
198
|
-
result = IamServer.IAM_KEYCLOAK
|
|
199
|
-
else:
|
|
200
|
-
result = None
|
|
201
|
-
msg: str = f"Unable to find a IAM server to service endpoint '{endpoint}'"
|
|
202
|
-
if logger:
|
|
203
|
-
logger.error(msg=msg)
|
|
204
|
-
if isinstance(errors, list):
|
|
205
|
-
errors.append(msg)
|
|
206
|
-
|
|
207
|
-
return result
|
|
208
|
-
|
|
209
|
-
|
|
210
239
|
def _get_iam_registry(iam_server: IamServer,
|
|
211
240
|
errors: list[str] | None,
|
|
212
241
|
logger: Logger | None) -> dict[str, Any]:
|
|
@@ -4,16 +4,34 @@ import secrets
|
|
|
4
4
|
import string
|
|
5
5
|
import sys
|
|
6
6
|
from datetime import datetime
|
|
7
|
+
from flask import Request, Response, request
|
|
7
8
|
from logging import Logger
|
|
8
9
|
from pypomes_core import TZ_LOCAL, exc_format
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
from .iam_common import (
|
|
12
13
|
IamServer, _iam_lock,
|
|
13
|
-
_get_iam_users, _get_iam_registry,
|
|
14
|
-
_get_login_timeout, _get_user_data,
|
|
14
|
+
_get_iam_users, _get_iam_registry, # _get_public_key,
|
|
15
|
+
_get_login_timeout, _get_user_data, _iam_server_from_issuer
|
|
15
16
|
)
|
|
16
|
-
from .token_pomes import token_validate
|
|
17
|
+
from .token_pomes import token_get_claims, token_validate
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def jwt_required(func: callable) -> callable:
|
|
21
|
+
"""
|
|
22
|
+
Create a decorator to authenticate service endpoints with JWT tokens.
|
|
23
|
+
|
|
24
|
+
:param func: the function being decorated
|
|
25
|
+
"""
|
|
26
|
+
# ruff: noqa: ANN003 - Missing type annotation for *{name}
|
|
27
|
+
def wrapper(*args, **kwargs) -> Response:
|
|
28
|
+
response: Response = __request_validate(request=request)
|
|
29
|
+
return response if response else func(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
# prevent a rogue error ("View function mapping is overwriting an existing endpoint function")
|
|
32
|
+
wrapper.__name__ = func.__name__
|
|
33
|
+
|
|
34
|
+
return wrapper
|
|
17
35
|
|
|
18
36
|
|
|
19
37
|
def user_login(iam_server: IamServer,
|
|
@@ -24,7 +42,7 @@ def user_login(iam_server: IamServer,
|
|
|
24
42
|
Build the URL for redirecting the request to *iam_server*'s authentication page.
|
|
25
43
|
|
|
26
44
|
These are the expected attributes in *args*:
|
|
27
|
-
- user-id: optional, identifies the reference user (
|
|
45
|
+
- user-id: optional, identifies the reference user (alias: 'login')
|
|
28
46
|
- redirect-uri: a parameter to be added to the query part of the returned URL
|
|
29
47
|
|
|
30
48
|
If provided, the user identification will be validated against the authorization data
|
|
@@ -41,7 +59,7 @@ def user_login(iam_server: IamServer,
|
|
|
41
59
|
result: str | None = None
|
|
42
60
|
|
|
43
61
|
# obtain the optional user's identification
|
|
44
|
-
user_id: str = args.get("user-id") or args.get("
|
|
62
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
45
63
|
|
|
46
64
|
# build the user data
|
|
47
65
|
# ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
|
|
@@ -84,7 +102,7 @@ def user_logout(iam_server: IamServer,
|
|
|
84
102
|
"""
|
|
85
103
|
Logout the user, by removing all data associating it from *iam_server*'s registry.
|
|
86
104
|
|
|
87
|
-
The user is identified by the attribute *user-id
|
|
105
|
+
The user is identified by the attribute *user-id* or "login", provided in *args*.
|
|
88
106
|
If successful, remove all data relating to the user from the *IAM* server's registry.
|
|
89
107
|
Otherwise, this operation fails silently, unless an error has ocurred.
|
|
90
108
|
|
|
@@ -94,7 +112,7 @@ def user_logout(iam_server: IamServer,
|
|
|
94
112
|
:param logger: optional logger
|
|
95
113
|
"""
|
|
96
114
|
# obtain the user's identification
|
|
97
|
-
user_id: str = args.get("user-id") or args.get("
|
|
115
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
98
116
|
|
|
99
117
|
if user_id:
|
|
100
118
|
with _iam_lock:
|
|
@@ -115,19 +133,19 @@ def user_token(iam_server: IamServer,
|
|
|
115
133
|
"""
|
|
116
134
|
Retrieve the authentication token for the user, from *iam_server*.
|
|
117
135
|
|
|
118
|
-
The user is identified by the attribute *user-id
|
|
136
|
+
The user is identified by the attribute *user-id* or *login*, provided in *args*.
|
|
119
137
|
|
|
120
138
|
:param iam_server: the reference registered *IAM* server
|
|
121
139
|
:param args: the arguments passed when requesting the service
|
|
122
140
|
:param errors: incidental error messages
|
|
123
141
|
:param logger: optional logger
|
|
124
|
-
:return: the token for
|
|
142
|
+
:return: the token for user indicated, or *None* if error
|
|
125
143
|
"""
|
|
126
144
|
# initialize the return variable
|
|
127
145
|
result: str | None = None
|
|
128
146
|
|
|
129
147
|
# obtain the user's identification
|
|
130
|
-
user_id: str = args.get("user-id") or args.get("
|
|
148
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
131
149
|
|
|
132
150
|
err_msg: str | None = None
|
|
133
151
|
if user_id:
|
|
@@ -272,7 +290,7 @@ def token_exchange(iam_server: IamServer,
|
|
|
272
290
|
Request *iam_server* to issue a token in exchange for the token obtained from another *IAM* server.
|
|
273
291
|
|
|
274
292
|
The expected parameters in *args* are:
|
|
275
|
-
- user-id: identification for the reference user (
|
|
293
|
+
- user-id: identification for the reference user (alias: 'login')
|
|
276
294
|
- token: the token to be exchanged
|
|
277
295
|
|
|
278
296
|
The typical data set returned contains the following attributes:
|
|
@@ -294,10 +312,10 @@ def token_exchange(iam_server: IamServer,
|
|
|
294
312
|
result: dict[str, Any] | None = None
|
|
295
313
|
|
|
296
314
|
# obtain the user's identification
|
|
297
|
-
user_id: str = args.get("user-id") or args.get("
|
|
315
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
298
316
|
|
|
299
|
-
# obtain the token to be
|
|
300
|
-
token: str = args.get("token")
|
|
317
|
+
# obtain the token to be exchanged
|
|
318
|
+
token: str = args.get("access-token")
|
|
301
319
|
|
|
302
320
|
if user_id and token:
|
|
303
321
|
# HAZARD: only 'IAM_KEYCLOAK' is currently supported
|
|
@@ -339,6 +357,63 @@ def token_exchange(iam_server: IamServer,
|
|
|
339
357
|
return result
|
|
340
358
|
|
|
341
359
|
|
|
360
|
+
def __request_validate(request: Request) -> Response:
|
|
361
|
+
"""
|
|
362
|
+
Verify whether the HTTP *request* has the proper authorization, as per the JWT standard.
|
|
363
|
+
|
|
364
|
+
This implementation assumes that HTTP requests are handled with the *Flask* framework.
|
|
365
|
+
|
|
366
|
+
:param request: the *request* to be verified
|
|
367
|
+
:return: *None* if the *request* is valid, otherwise a *Response* reporting the error
|
|
368
|
+
"""
|
|
369
|
+
# initialize the return variable
|
|
370
|
+
result: Response | None = None
|
|
371
|
+
|
|
372
|
+
# retrieve the authorization from the request header
|
|
373
|
+
auth_header: str = request.headers.get("Authorization")
|
|
374
|
+
|
|
375
|
+
# validate the authorization token
|
|
376
|
+
bad_token: bool = True
|
|
377
|
+
if auth_header and auth_header.startswith("Bearer "):
|
|
378
|
+
# extract and validate the JWT access token
|
|
379
|
+
token: str = auth_header.split(" ")[1]
|
|
380
|
+
claims: dict[str, Any] = token_get_claims(token=token)
|
|
381
|
+
if claims:
|
|
382
|
+
issuer: str = claims["payload"].get("iss")
|
|
383
|
+
recipient_attr: str | None = None
|
|
384
|
+
recipient_id: str = request.values.get("user-id") or request.values.get("login")
|
|
385
|
+
with _iam_lock:
|
|
386
|
+
iam_server: IamServer = _iam_server_from_issuer(issuer=issuer,
|
|
387
|
+
errors=None,
|
|
388
|
+
logger=None)
|
|
389
|
+
# public_key: str = _get_public_key(iam_server=iam_server,
|
|
390
|
+
# errors=errors,
|
|
391
|
+
# logger=logger)
|
|
392
|
+
public_key = None
|
|
393
|
+
|
|
394
|
+
# validate the token's recipient only if a user identification is provided
|
|
395
|
+
if recipient_id:
|
|
396
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
397
|
+
errors=None,
|
|
398
|
+
logger=None)
|
|
399
|
+
recipient_attr = registry["recipient-attr"]
|
|
400
|
+
|
|
401
|
+
# validate the token
|
|
402
|
+
if token_validate(token=token,
|
|
403
|
+
issuer=issuer,
|
|
404
|
+
recipient_id=recipient_id,
|
|
405
|
+
recipient_attr=recipient_attr,
|
|
406
|
+
public_key=public_key):
|
|
407
|
+
# token is valid
|
|
408
|
+
bad_token = False
|
|
409
|
+
|
|
410
|
+
# deny the authorization
|
|
411
|
+
if bad_token:
|
|
412
|
+
result = Response(response="Authorization failed",
|
|
413
|
+
status=401)
|
|
414
|
+
return result
|
|
415
|
+
|
|
416
|
+
|
|
342
417
|
def __post_for_token(iam_server: IamServer,
|
|
343
418
|
body_data: dict[str, Any],
|
|
344
419
|
errors: list[str] | None,
|
|
@@ -3,7 +3,7 @@ from flask import Request, Response, request, jsonify
|
|
|
3
3
|
from logging import Logger
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
|
-
from .iam_common import IamServer, _iam_lock,
|
|
6
|
+
from .iam_common import IamServer, _iam_lock, _iam_server_from_endpoint
|
|
7
7
|
from .iam_pomes import (
|
|
8
8
|
user_login, user_logout,
|
|
9
9
|
user_token, token_exchange, login_callback
|
|
@@ -33,7 +33,7 @@ def service_login() -> Response:
|
|
|
33
33
|
Entry point for the IAM server's login service.
|
|
34
34
|
|
|
35
35
|
These are the expected request parameters:
|
|
36
|
-
- user-id: optional, identifies the reference user (
|
|
36
|
+
- user-id: optional, identifies the reference user (alias: 'login')
|
|
37
37
|
- redirect-uri: a parameter to be added to the query part of the returned URL
|
|
38
38
|
|
|
39
39
|
If provided, the user identification will be validated against the authorization data
|
|
@@ -55,9 +55,9 @@ def service_login() -> Response:
|
|
|
55
55
|
errors: list[str] = []
|
|
56
56
|
with _iam_lock:
|
|
57
57
|
# retrieve the IAM server
|
|
58
|
-
iam_server: IamServer =
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
|
|
59
|
+
errors=errors,
|
|
60
|
+
logger=__IAM_LOGGER)
|
|
61
61
|
if iam_server:
|
|
62
62
|
# obtain the login URL
|
|
63
63
|
login_url: str = user_login(iam_server=iam_server,
|
|
@@ -67,8 +67,8 @@ def service_login() -> Response:
|
|
|
67
67
|
if login_url:
|
|
68
68
|
result = jsonify({"login-url": login_url})
|
|
69
69
|
if errors:
|
|
70
|
-
result = Response("; ".join(errors)
|
|
71
|
-
|
|
70
|
+
result = Response(response="; ".join(errors),
|
|
71
|
+
status=400)
|
|
72
72
|
|
|
73
73
|
# log the response
|
|
74
74
|
if __IAM_LOGGER:
|
|
@@ -85,7 +85,7 @@ def service_logout() -> Response:
|
|
|
85
85
|
"""
|
|
86
86
|
Entry point for the JusBR logout service.
|
|
87
87
|
|
|
88
|
-
The user is identified by the attribute *user-id
|
|
88
|
+
The user is identified by the attribute *user-id* or "login", provided as a request parameter.
|
|
89
89
|
If successful, remove all data relating to the user from the *IAM* server's registry.
|
|
90
90
|
Otherwise, this operation fails silently, unless an error has ocurred.
|
|
91
91
|
|
|
@@ -101,9 +101,9 @@ def service_logout() -> Response:
|
|
|
101
101
|
errors: list[str] = []
|
|
102
102
|
with _iam_lock:
|
|
103
103
|
# retrieve the IAM server
|
|
104
|
-
iam_server: IamServer =
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
|
|
105
|
+
errors=errors,
|
|
106
|
+
logger=__IAM_LOGGER)
|
|
107
107
|
if iam_server:
|
|
108
108
|
# logout the user
|
|
109
109
|
user_logout(iam_server=iam_server,
|
|
@@ -111,8 +111,8 @@ def service_logout() -> Response:
|
|
|
111
111
|
errors=errors,
|
|
112
112
|
logger=__IAM_LOGGER)
|
|
113
113
|
if errors:
|
|
114
|
-
result = Response("; ".join(errors)
|
|
115
|
-
|
|
114
|
+
result = Response(response="; ".join(errors),
|
|
115
|
+
status=400)
|
|
116
116
|
else:
|
|
117
117
|
result = Response(status=204)
|
|
118
118
|
|
|
@@ -142,7 +142,7 @@ def service_callback() -> Response:
|
|
|
142
142
|
On success, the returned *Response* will contain the following JSON:
|
|
143
143
|
{
|
|
144
144
|
"user-id": <reference-user-identification>,
|
|
145
|
-
"token": <token>
|
|
145
|
+
"access-token": <token>
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
:return: *Response* containing the reference user identification and the token, or *BAD REQUEST*
|
|
@@ -155,9 +155,9 @@ def service_callback() -> Response:
|
|
|
155
155
|
token_data: tuple[str, str] | None = None
|
|
156
156
|
with _iam_lock:
|
|
157
157
|
# retrieve the IAM server
|
|
158
|
-
iam_server: IamServer =
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
|
|
159
|
+
errors=errors,
|
|
160
|
+
logger=__IAM_LOGGER)
|
|
161
161
|
if iam_server:
|
|
162
162
|
# process the callback operation
|
|
163
163
|
token_data = login_callback(iam_server=iam_server,
|
|
@@ -170,7 +170,7 @@ def service_callback() -> Response:
|
|
|
170
170
|
result.status_code = 400
|
|
171
171
|
else:
|
|
172
172
|
result = jsonify({"user-id": token_data[0],
|
|
173
|
-
"token": token_data[1]})
|
|
173
|
+
"access-token": token_data[1]})
|
|
174
174
|
# log the response
|
|
175
175
|
if __IAM_LOGGER:
|
|
176
176
|
__IAM_LOGGER.debug(msg=f"Response {result}, {result.get_data(as_text=True)}")
|
|
@@ -186,12 +186,12 @@ def service_token() -> Response:
|
|
|
186
186
|
"""
|
|
187
187
|
Entry point for retrieving a token from the *IAM* server.
|
|
188
188
|
|
|
189
|
-
The user is identified by the attribute *user-id
|
|
189
|
+
The user is identified by the attribute *user-id* or "login", provided as a request parameter.
|
|
190
190
|
|
|
191
191
|
On success, the returned *Response* will contain the following JSON:
|
|
192
192
|
{
|
|
193
193
|
"user-id": <reference-user-identification>,
|
|
194
|
-
"token": <token>
|
|
194
|
+
"access-token": <token>
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
:return: *Response* containing the user reference identification and the token, or *BAD REQUEST*
|
|
@@ -202,16 +202,16 @@ def service_token() -> Response:
|
|
|
202
202
|
|
|
203
203
|
# obtain the user's identification
|
|
204
204
|
args: dict[str, Any] = request.args
|
|
205
|
-
user_id: str = args.get("user-id") or args.get("
|
|
205
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
206
206
|
|
|
207
207
|
errors: list[str] = []
|
|
208
208
|
token: str | None = None
|
|
209
209
|
if user_id:
|
|
210
210
|
with _iam_lock:
|
|
211
211
|
# retrieve the IAM server
|
|
212
|
-
iam_server: IamServer =
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
|
|
213
|
+
errors=errors,
|
|
214
|
+
logger=__IAM_LOGGER)
|
|
215
215
|
if iam_server:
|
|
216
216
|
# retrieve the token
|
|
217
217
|
errors: list[str] = []
|
|
@@ -227,11 +227,11 @@ def service_token() -> Response:
|
|
|
227
227
|
|
|
228
228
|
result: Response
|
|
229
229
|
if errors:
|
|
230
|
-
result = Response("; ".join(errors)
|
|
231
|
-
|
|
230
|
+
result = Response(response="; ".join(errors),
|
|
231
|
+
status=400)
|
|
232
232
|
else:
|
|
233
233
|
result = jsonify({"user-id": user_id,
|
|
234
|
-
"token": token})
|
|
234
|
+
"access-token": token})
|
|
235
235
|
# log the response
|
|
236
236
|
if __IAM_LOGGER:
|
|
237
237
|
__IAM_LOGGER.debug(msg=f"Response {result}, {result.get_data(as_text=True)}")
|
|
@@ -247,8 +247,8 @@ def service_exchange() -> Response:
|
|
|
247
247
|
|
|
248
248
|
This is currently limited to the *KEYCLOAK* server. The token itself is stored in *KEYCLOAK*'s registry.
|
|
249
249
|
The expected request parameters are:
|
|
250
|
-
- user-id: identification for the reference user (
|
|
251
|
-
- token: the token to be exchanged
|
|
250
|
+
- user-id: identification for the reference user (alias: 'login')
|
|
251
|
+
- access-token: the token to be exchanged
|
|
252
252
|
|
|
253
253
|
If the exchange is successful, the token data is stored in the *IAM* server's registry, and returned.
|
|
254
254
|
Otherwise, *errors* will contain the appropriate error message.
|
|
@@ -271,9 +271,9 @@ def service_exchange() -> Response:
|
|
|
271
271
|
errors: list[str] = []
|
|
272
272
|
with _iam_lock:
|
|
273
273
|
# retrieve the IAM server (currently, only 'IAM_KEYCLOAK' is supported)
|
|
274
|
-
iam_server: IamServer =
|
|
275
|
-
|
|
276
|
-
|
|
274
|
+
iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
|
|
275
|
+
errors=errors,
|
|
276
|
+
logger=__IAM_LOGGER)
|
|
277
277
|
# exchange the token
|
|
278
278
|
token_data: dict[str, Any] | None = None
|
|
279
279
|
if iam_server:
|
|
@@ -284,8 +284,8 @@ def service_exchange() -> Response:
|
|
|
284
284
|
logger=__IAM_LOGGER)
|
|
285
285
|
result: Response
|
|
286
286
|
if errors:
|
|
287
|
-
result = Response("; ".join(errors)
|
|
288
|
-
|
|
287
|
+
result = Response(response="; ".join(errors),
|
|
288
|
+
status=400)
|
|
289
289
|
else:
|
|
290
290
|
result = jsonify(token_data)
|
|
291
291
|
|
|
@@ -24,6 +24,7 @@ JUSBR_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT
|
|
|
24
24
|
|
|
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
|
+
JUSBR_REALM: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_REALM")
|
|
27
28
|
JUSBR_RECIPIENT_ATTR: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_RECIPIENT_ATTR",
|
|
28
29
|
def_value="preferred_username")
|
|
29
30
|
JUSBR_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_BASE")
|
|
@@ -31,6 +32,7 @@ JUSBR_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_
|
|
|
31
32
|
|
|
32
33
|
def jusbr_setup(flask_app: Flask,
|
|
33
34
|
base_url: str = JUSBR_URL_AUTH_BASE,
|
|
35
|
+
realm: str = JUSBR_REALM,
|
|
34
36
|
client_id: str = JUSBR_CLIENT_ID,
|
|
35
37
|
client_secret: str = JUSBR_CLIENT_SECRET,
|
|
36
38
|
client_timeout: int = JUSBR_CLIENT_TIMEOUT,
|
|
@@ -47,6 +49,7 @@ def jusbr_setup(flask_app: Flask,
|
|
|
47
49
|
|
|
48
50
|
:param flask_app: the Flask application
|
|
49
51
|
:param base_url: base URL to request JusBR services
|
|
52
|
+
:param realm: the JusBR realm
|
|
50
53
|
:param client_id: the client's identification with JusBR
|
|
51
54
|
:param client_secret: the client's password with JusBR
|
|
52
55
|
:param client_timeout: timeout for login authentication (in seconds,defaults to no timeout)
|
|
@@ -64,7 +67,7 @@ def jusbr_setup(flask_app: Flask,
|
|
|
64
67
|
cache["users"] = {}
|
|
65
68
|
with _iam_lock:
|
|
66
69
|
_IAM_SERVERS[IamServer.IAM_JUSRBR] = {
|
|
67
|
-
"base-url": base_url,
|
|
70
|
+
"base-url": f"{base_url}/realms/{realm}",
|
|
68
71
|
"client-id": client_id,
|
|
69
72
|
"client-secret": client_secret,
|
|
70
73
|
"client-timeout": client_timeout,
|
|
@@ -7,6 +7,45 @@ from pypomes_core import exc_format
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
def token_get_claims(token: str,
|
|
11
|
+
errors: list[str] = None,
|
|
12
|
+
logger: Logger = None) -> dict[str, dict[str, Any]] | None:
|
|
13
|
+
"""
|
|
14
|
+
Retrieve the claims set of a JWT *token*.
|
|
15
|
+
|
|
16
|
+
Any well-constructed JWT token may be provided in *token*.
|
|
17
|
+
Note that neither the token's signature nor its expiration is verified.
|
|
18
|
+
|
|
19
|
+
:param token: the refrence token
|
|
20
|
+
:param errors: incidental error messages
|
|
21
|
+
:param logger: optional logger
|
|
22
|
+
:return: the token's claimset, or *None* if error
|
|
23
|
+
"""
|
|
24
|
+
# initialize the return variable
|
|
25
|
+
result: dict[str, dict[str, Any]] | None = None
|
|
26
|
+
|
|
27
|
+
if logger:
|
|
28
|
+
logger.debug(msg="Retrieve claims for token")
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
header: dict[str, Any] = jwt.get_unverified_header(jwt=token)
|
|
32
|
+
payload: dict[str, Any] = jwt.decode(jwt=token,
|
|
33
|
+
options={"verify_signature": False})
|
|
34
|
+
result = {
|
|
35
|
+
"header": header,
|
|
36
|
+
"payload": payload
|
|
37
|
+
}
|
|
38
|
+
except Exception as e:
|
|
39
|
+
exc_err: str = exc_format(exc=e,
|
|
40
|
+
exc_info=sys.exc_info())
|
|
41
|
+
if logger:
|
|
42
|
+
logger.error(msg=f"Error retrieving the token's claims: {exc_err}")
|
|
43
|
+
if isinstance(errors, list):
|
|
44
|
+
errors.append(exc_err)
|
|
45
|
+
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
10
49
|
def token_validate(token: str,
|
|
11
50
|
issuer: str = None,
|
|
12
51
|
recipient_id: str = None,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|