pypomes-iam 0.8.1__tar.gz → 0.9.1__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.
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/PKG-INFO +1 -1
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/pyproject.toml +1 -1
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/pypomes_iam/__init__.py +15 -15
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/pypomes_iam/iam_actions.py +144 -40
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/pypomes_iam/iam_common.py +37 -41
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/pypomes_iam/iam_pomes.py +32 -22
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/pypomes_iam/iam_services.py +129 -53
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/pypomes_iam/provider_pomes.py +72 -63
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/.gitignore +0 -0
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/LICENSE +0 -0
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/README.md +0 -0
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/__init__.py +0 -0
- {pypomes_iam-0.8.1 → pypomes_iam-0.9.1}/src/pypomes_iam/token_pomes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_iam
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.1
|
|
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
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
from .iam_actions import (
|
|
2
2
|
iam_callback, iam_exchange,
|
|
3
|
-
iam_login, iam_logout, iam_get_token
|
|
3
|
+
iam_login, iam_logout, iam_get_token, iam_userinfo
|
|
4
4
|
)
|
|
5
5
|
from .iam_common import (
|
|
6
|
-
IamServer,
|
|
6
|
+
IamServer, ServerParam
|
|
7
7
|
)
|
|
8
8
|
from .iam_pomes import (
|
|
9
9
|
iam_setup_server, iam_setup_endpoints
|
|
10
10
|
)
|
|
11
11
|
from .iam_services import (
|
|
12
12
|
jwt_required, iam_setup_logger,
|
|
13
|
-
service_setup_server,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
service_callback_exchange
|
|
13
|
+
service_setup_server, service_login, service_logout,
|
|
14
|
+
service_get_token, service_userinfo, service_callback,
|
|
15
|
+
service_exchange, service_callback_exchange
|
|
17
16
|
)
|
|
18
17
|
from .provider_pomes import (
|
|
18
|
+
IamProvider, ProviderParam,
|
|
19
19
|
service_get_token, provider_get_token,
|
|
20
|
-
provider_setup_endpoint, provider_setup_logger
|
|
20
|
+
iam_setup_provider, provider_setup_endpoint, provider_setup_logger
|
|
21
21
|
)
|
|
22
22
|
from .token_pomes import (
|
|
23
23
|
token_get_claims, token_get_values, token_validate
|
|
@@ -26,20 +26,20 @@ from .token_pomes import (
|
|
|
26
26
|
__all__ = [
|
|
27
27
|
# iam_actions
|
|
28
28
|
"iam_callback", "iam_exchange",
|
|
29
|
-
"iam_login", "iam_logout", "iam_get_token",
|
|
29
|
+
"iam_login", "iam_logout", "iam_get_token", "iam_userinfo",
|
|
30
30
|
# iam_commons
|
|
31
|
-
"IamServer", "
|
|
31
|
+
"IamServer", "ServerParam",
|
|
32
32
|
# iam_pomes
|
|
33
33
|
"iam_setup_server", "iam_setup_endpoints",
|
|
34
34
|
# iam_services
|
|
35
35
|
"jwt_required", "iam_setup_logger",
|
|
36
|
-
"service_setup_server", "
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"service_callback_exchange",
|
|
36
|
+
"service_setup_server", "service_login", "service_logout",
|
|
37
|
+
"service_get_token", "service_userinfo", "service_callback",
|
|
38
|
+
"service_exchange", "service_callback_exchange",
|
|
40
39
|
# provider_pomes
|
|
41
|
-
"
|
|
42
|
-
"
|
|
40
|
+
"IamProvider", "ProviderParam",
|
|
41
|
+
"service_get_token", "provider_get_token",
|
|
42
|
+
"iam_setup_provider", "provider_setup_endpoint", "provider_setup_logger",
|
|
43
43
|
# token_pomes
|
|
44
44
|
"token_get_claims", "token_get_values", "token_validate"
|
|
45
45
|
]
|
|
@@ -9,7 +9,7 @@ from pypomes_core import TZ_LOCAL, exc_format
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from .iam_common import (
|
|
12
|
-
IamServer,
|
|
12
|
+
IamServer, ServerParam, UserParam, _iam_lock,
|
|
13
13
|
_get_iam_users, _get_iam_registry, _get_public_key,
|
|
14
14
|
_get_login_timeout, _get_user_data, _iam_server_from_issuer
|
|
15
15
|
)
|
|
@@ -56,7 +56,7 @@ def iam_login(iam_server: IamServer,
|
|
|
56
56
|
# ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
|
|
57
57
|
oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
58
58
|
if target_idp:
|
|
59
|
-
oauth_state += f"
|
|
59
|
+
oauth_state += f"_{target_idp}"
|
|
60
60
|
|
|
61
61
|
with _iam_lock:
|
|
62
62
|
# retrieve the user data from the IAM server's registry
|
|
@@ -80,10 +80,10 @@ def iam_login(iam_server: IamServer,
|
|
|
80
80
|
errors=errors,
|
|
81
81
|
logger=logger)
|
|
82
82
|
if registry:
|
|
83
|
-
base_url: str = f"{registry[
|
|
83
|
+
base_url: str = f"{registry[ServerParam.URL_BASE]}/realms/{registry[ServerParam.CLIENT_REALM]}"
|
|
84
84
|
result = (f"{base_url}/protocol/openid-connect/auth"
|
|
85
85
|
f"?response_type=code&scope=openid"
|
|
86
|
-
f"&client_id={registry[
|
|
86
|
+
f"&client_id={registry[ServerParam.CLIENT_ID]}"
|
|
87
87
|
f"&redirect_uri={redirect_uri}"
|
|
88
88
|
f"&state={oauth_state}")
|
|
89
89
|
if target_idp:
|
|
@@ -100,9 +100,9 @@ def iam_logout(iam_server: IamServer,
|
|
|
100
100
|
"""
|
|
101
101
|
Logout the user, by removing all data associating it from *iam_server*'s registry.
|
|
102
102
|
|
|
103
|
-
The user is identified by the attribute *user-id* or
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
The user is identified by the attribute *user-id* or *login*, provided in *args*.
|
|
104
|
+
A logout request is sent to *iam_server* and, if successful, remove all data relating to the user
|
|
105
|
+
from the *IAM* server's registry.
|
|
106
106
|
|
|
107
107
|
:param iam_server: the reference registered *IAM* server
|
|
108
108
|
:param args: the arguments passed when requesting the service
|
|
@@ -114,14 +114,65 @@ def iam_logout(iam_server: IamServer,
|
|
|
114
114
|
|
|
115
115
|
if user_id:
|
|
116
116
|
with _iam_lock:
|
|
117
|
-
# retrieve the data for all users
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
# retrieve the IAM server's registry and the data for all users therein
|
|
118
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server,
|
|
119
|
+
errors=errors,
|
|
120
|
+
logger=logger)
|
|
121
|
+
users: dict[str, dict[str, Any]] = registry[ServerParam.USERS] if registry else {}
|
|
122
|
+
user_data: dict[str, Any] = users.get(user_id)
|
|
123
|
+
if user_data:
|
|
124
|
+
# request the IAM server to logout 'client_id'
|
|
125
|
+
client_secret: str = __get_client_secret(iam_server=iam_server,
|
|
126
|
+
errors=errors,
|
|
127
|
+
logger=logger)
|
|
128
|
+
if client_secret:
|
|
129
|
+
url: str = (f"{registry[ServerParam.URL_BASE]}/realms/{registry[ServerParam.CLIENT_REALM]}"
|
|
130
|
+
"/protocol/openid-connect/logout")
|
|
131
|
+
header_data: dict[str, str] = {
|
|
132
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
133
|
+
}
|
|
134
|
+
body_data: dict[str, Any] = {
|
|
135
|
+
"client_id": registry[ServerParam.CLIENT_ID],
|
|
136
|
+
"client_secret": client_secret,
|
|
137
|
+
"refresh_token": user_data[UserParam.REFRESH_TOKEN]
|
|
138
|
+
}
|
|
139
|
+
# log the POST
|
|
140
|
+
if logger:
|
|
141
|
+
logger.debug(msg=f"POST {url}")
|
|
142
|
+
try:
|
|
143
|
+
response: requests.Response = requests.post(url=url,
|
|
144
|
+
headers=header_data,
|
|
145
|
+
data=body_data)
|
|
146
|
+
if response.status_code in [200, 204]:
|
|
147
|
+
# request succeeded
|
|
148
|
+
if logger:
|
|
149
|
+
logger.debug(msg=f"POST success")
|
|
150
|
+
else:
|
|
151
|
+
# request failed, report the problem
|
|
152
|
+
msg: str = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
153
|
+
if logger:
|
|
154
|
+
logger.error(msg=msg)
|
|
155
|
+
if isinstance(errors, list):
|
|
156
|
+
errors.append(msg)
|
|
157
|
+
except Exception as e:
|
|
158
|
+
# the operation raised an exception
|
|
159
|
+
msg: str = exc_format(exc=e,
|
|
160
|
+
exc_info=sys.exc_info())
|
|
161
|
+
if logger:
|
|
162
|
+
logger.error(msg=msg)
|
|
163
|
+
if isinstance(errors, list):
|
|
164
|
+
errors.append(msg)
|
|
165
|
+
|
|
166
|
+
if not errors and user_id in users:
|
|
167
|
+
users.pop(user_id)
|
|
168
|
+
if logger:
|
|
169
|
+
logger.debug(msg=f"User '{user_id}' removed from {iam_server}'s registry")
|
|
170
|
+
else:
|
|
171
|
+
msg: str = "User identification not provided"
|
|
172
|
+
if logger:
|
|
173
|
+
logger.error(msg=msg)
|
|
174
|
+
if isinstance(errors, list):
|
|
175
|
+
errors.append(msg)
|
|
125
176
|
|
|
126
177
|
|
|
127
178
|
def iam_get_token(iam_server: IamServer,
|
|
@@ -176,7 +227,7 @@ def iam_get_token(iam_server: IamServer,
|
|
|
176
227
|
refresh_expiration: int = user_data[UserParam.REFRESH_EXPIRATION]
|
|
177
228
|
if now < refresh_expiration:
|
|
178
229
|
header_data: dict[str, str] = {
|
|
179
|
-
"Content-Type": "application/
|
|
230
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
180
231
|
}
|
|
181
232
|
body_data: dict[str, str] = {
|
|
182
233
|
"grant_type": "refresh_token",
|
|
@@ -271,9 +322,9 @@ def iam_callback(iam_server: IamServer,
|
|
|
271
322
|
if int(datetime.now(tz=TZ_LOCAL).timestamp()) > expiration:
|
|
272
323
|
errors.append("Operation timeout")
|
|
273
324
|
else:
|
|
274
|
-
pos: int = oauth_state.rfind("
|
|
275
|
-
target_idp: str = oauth_state[pos+
|
|
276
|
-
target_iam = IamServer(target_idp) if target_idp in IamServer else None
|
|
325
|
+
pos: int = oauth_state.rfind("_")
|
|
326
|
+
target_idp: str = oauth_state[pos+1:] if pos > 0 else None
|
|
327
|
+
target_iam: IamServer = IamServer(target_idp) if target_idp in IamServer else None
|
|
277
328
|
target_data: dict[str, Any] = user_data.copy() if target_iam else None
|
|
278
329
|
users.pop(oauth_state)
|
|
279
330
|
code: str = args.get("code")
|
|
@@ -306,8 +357,8 @@ def iam_callback(iam_server: IamServer,
|
|
|
306
357
|
registry: dict[str, Any] = _get_iam_registry(iam_server,
|
|
307
358
|
errors=errors,
|
|
308
359
|
logger=logger)
|
|
309
|
-
url: str = f"{registry[
|
|
310
|
-
|
|
360
|
+
url: str = (f"{registry[ServerParam.URL_BASE]}/realms/"
|
|
361
|
+
f"{registry[ServerParam.CLIENT_REALM]}/broker/{target_idp}/token")
|
|
311
362
|
header_data: dict[str, str] = {
|
|
312
363
|
"Authorization": f"Bearer {result[1]}",
|
|
313
364
|
"Content-Type": "application/json"
|
|
@@ -375,7 +426,6 @@ def iam_exchange(iam_server: IamServer,
|
|
|
375
426
|
errors=errors,
|
|
376
427
|
logger=logger)
|
|
377
428
|
if not errors:
|
|
378
|
-
# HAZARD: only 'IAM_KEYCLOAK' is currently supported
|
|
379
429
|
with _iam_lock:
|
|
380
430
|
# retrieve the IAM server's registry
|
|
381
431
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
@@ -401,7 +451,7 @@ def iam_exchange(iam_server: IamServer,
|
|
|
401
451
|
"subject_token": token,
|
|
402
452
|
"subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
403
453
|
"requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
404
|
-
"audience": registry[
|
|
454
|
+
"audience": registry[ServerParam.CLIENT_ID],
|
|
405
455
|
"subject_issuer": token_issuer
|
|
406
456
|
}
|
|
407
457
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
@@ -429,6 +479,60 @@ def iam_exchange(iam_server: IamServer,
|
|
|
429
479
|
return result
|
|
430
480
|
|
|
431
481
|
|
|
482
|
+
def iam_userinfo(iam_server: IamServer,
|
|
483
|
+
args: dict[str, Any],
|
|
484
|
+
errors: list[str] = None,
|
|
485
|
+
logger: Logger = None) -> dict[str, Any] | None:
|
|
486
|
+
"""
|
|
487
|
+
Obtain user data from *iam_server*.
|
|
488
|
+
|
|
489
|
+
The user is identified by the attribute *user-id* or *login*, provided in *args*.
|
|
490
|
+
|
|
491
|
+
:param iam_server: the reference registered *IAM* server
|
|
492
|
+
:param args: the arguments passed when requesting the service
|
|
493
|
+
:param errors: incidental error messages
|
|
494
|
+
:param logger: optional logger
|
|
495
|
+
:return: the user information requested, or *None* if error
|
|
496
|
+
"""
|
|
497
|
+
# initialize the return variable
|
|
498
|
+
result: dict[str, Any] | None = None
|
|
499
|
+
|
|
500
|
+
# obtain the user's identification
|
|
501
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
502
|
+
|
|
503
|
+
err_msg: str | None = None
|
|
504
|
+
if user_id:
|
|
505
|
+
with _iam_lock:
|
|
506
|
+
# retrieve the IAM server's registry and the user data therein
|
|
507
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server,
|
|
508
|
+
errors=errors,
|
|
509
|
+
logger=logger)
|
|
510
|
+
user_data: dict[str, Any] = registry[ServerParam.USERS].get(user_id)
|
|
511
|
+
if user_data:
|
|
512
|
+
url: str = (f"{registry[ServerParam.URL_BASE]}/realms/{registry[ServerParam.CLIENT_REALM]}"
|
|
513
|
+
"/protocol/openid-connect/userinfo")
|
|
514
|
+
header_data: dict[str, str] = {
|
|
515
|
+
"Authorization": f"Bearer {args.get('access-token')}"
|
|
516
|
+
}
|
|
517
|
+
result = __get_for_data(url=url,
|
|
518
|
+
header_data=header_data,
|
|
519
|
+
params=None,
|
|
520
|
+
errors=errors,
|
|
521
|
+
logger=logger)
|
|
522
|
+
else:
|
|
523
|
+
err_msg = f"Unknown user '{user_id}'"
|
|
524
|
+
else:
|
|
525
|
+
err_msg: str = "User identification not provided"
|
|
526
|
+
|
|
527
|
+
if err_msg:
|
|
528
|
+
if logger:
|
|
529
|
+
logger.error(msg=err_msg)
|
|
530
|
+
if isinstance(errors, list):
|
|
531
|
+
errors.append(err_msg)
|
|
532
|
+
|
|
533
|
+
return result
|
|
534
|
+
|
|
535
|
+
|
|
432
536
|
def __assert_link(iam_server: IamServer,
|
|
433
537
|
user_id: str,
|
|
434
538
|
token: str,
|
|
@@ -461,7 +565,7 @@ def __assert_link(iam_server: IamServer,
|
|
|
461
565
|
if logger:
|
|
462
566
|
logger.debug(msg="Obtaining internal identification "
|
|
463
567
|
f"for user '{user_id}' in IAM server '{iam_server}'")
|
|
464
|
-
url: str = f"{registry[
|
|
568
|
+
url: str = f"{registry[ServerParam.URL_BASE]}/admin/realms/{registry[ServerParam.CLIENT_REALM]}/users"
|
|
465
569
|
header_data: dict[str, str] = {
|
|
466
570
|
"Authorization": f"Bearer {admin_token}",
|
|
467
571
|
"Content-Type": "application/json"
|
|
@@ -482,8 +586,8 @@ def __assert_link(iam_server: IamServer,
|
|
|
482
586
|
if logger:
|
|
483
587
|
logger.debug(msg="Obtaining the providers federated in IAM server "
|
|
484
588
|
f"'{iam_server}', for internal identification '{internal_id}'")
|
|
485
|
-
url = (f"{registry[
|
|
486
|
-
f"{registry[
|
|
589
|
+
url = (f"{registry[ServerParam.URL_BASE]}/admin/realms/"
|
|
590
|
+
f"{registry[ServerParam.CLIENT_REALM]}/users/{internal_id}/federated-identity")
|
|
487
591
|
providers: list[dict[str, Any]] = __get_for_data(url=url,
|
|
488
592
|
header_data=header_data,
|
|
489
593
|
params=None,
|
|
@@ -546,14 +650,14 @@ def __get_administrative_token(iam_server: IamServer,
|
|
|
546
650
|
errors=errors,
|
|
547
651
|
logger=logger)
|
|
548
652
|
if registry:
|
|
549
|
-
if registry[
|
|
653
|
+
if registry[ServerParam.ADMIN_ID] and registry[ServerParam.ADMIN_SECRET]:
|
|
550
654
|
header_data: dict[str, str] = {
|
|
551
655
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
552
656
|
}
|
|
553
657
|
body_data: dict[str, str] = {
|
|
554
658
|
"grant_type": "password",
|
|
555
|
-
"username": registry[
|
|
556
|
-
"password": registry[
|
|
659
|
+
"username": registry[ServerParam.ADMIN_ID],
|
|
660
|
+
"password": registry[ServerParam.ADMIN_SECRET],
|
|
557
661
|
"client_id": "admin-cli"
|
|
558
662
|
}
|
|
559
663
|
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
@@ -569,7 +673,7 @@ def __get_administrative_token(iam_server: IamServer,
|
|
|
569
673
|
|
|
570
674
|
elif logger or isinstance(errors, list):
|
|
571
675
|
msg: str = ("Credentials for administrator of realm "
|
|
572
|
-
f"'{registry[
|
|
676
|
+
f"'{registry[ServerParam.CLIENT_REALM]}' "
|
|
573
677
|
f"at IAM server '{iam_server}' not provided")
|
|
574
678
|
if logger:
|
|
575
679
|
logger.error(msg=msg)
|
|
@@ -605,7 +709,7 @@ def __get_client_secret(iam_server: IamServer,
|
|
|
605
709
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
606
710
|
errors=errors,
|
|
607
711
|
logger=logger)
|
|
608
|
-
result: str = registry[
|
|
712
|
+
result: str = registry[ServerParam.CLIENT_SECRET] if registry else None
|
|
609
713
|
|
|
610
714
|
if not result and not errors:
|
|
611
715
|
# obtain a token with administrative rights
|
|
@@ -613,13 +717,13 @@ def __get_client_secret(iam_server: IamServer,
|
|
|
613
717
|
errors=errors,
|
|
614
718
|
logger=logger)
|
|
615
719
|
if token:
|
|
616
|
-
realm: str = registry[
|
|
617
|
-
client_id: str = registry[
|
|
720
|
+
realm: str = registry[ServerParam.CLIENT_REALM]
|
|
721
|
+
client_id: str = registry[ServerParam.CLIENT_ID]
|
|
618
722
|
if logger:
|
|
619
723
|
logger.debug(msg=f"Obtaining the UUID for client '{client_id}', "
|
|
620
724
|
f"in realm '{realm}' at IAM server '{iam_server}'")
|
|
621
725
|
# obtain the client UUID
|
|
622
|
-
url: str = f"{registry[
|
|
726
|
+
url: str = f"{registry[ServerParam.URL_BASE]}/realms/{realm}/clients"
|
|
623
727
|
header_data: dict[str, str] = {
|
|
624
728
|
"Authorization": f"Bearer {token}",
|
|
625
729
|
"Content-Type": "application/json"
|
|
@@ -647,7 +751,7 @@ def __get_client_secret(iam_server: IamServer,
|
|
|
647
751
|
if reply:
|
|
648
752
|
# store the client's secret password and return it
|
|
649
753
|
result = reply["value"]
|
|
650
|
-
registry[
|
|
754
|
+
registry[ServerParam.CLIENT_ID] = result
|
|
651
755
|
return result
|
|
652
756
|
|
|
653
757
|
|
|
@@ -811,11 +915,11 @@ def __post_for_token(iam_server: IamServer,
|
|
|
811
915
|
if registry:
|
|
812
916
|
# complete the data to send in body of request
|
|
813
917
|
if body_data["grant_type"] != "password":
|
|
814
|
-
body_data["client_id"] = registry[
|
|
918
|
+
body_data["client_id"] = registry[ServerParam.CLIENT_ID]
|
|
815
919
|
|
|
816
920
|
# build the URL
|
|
817
|
-
|
|
818
|
-
|
|
921
|
+
url: str = (f"{registry[ServerParam.URL_BASE]}/realms/"
|
|
922
|
+
f"{registry[ServerParam.CLIENT_REALM]}/protocol/openid-connect/token")
|
|
819
923
|
# 'client_secret' data must not be shown in log
|
|
820
924
|
msg: str = f"POST {url}, {json.dumps(obj=body_data,
|
|
821
925
|
ensure_ascii=False)}"
|
|
@@ -916,9 +1020,9 @@ def __validate_and_store(iam_server: IamServer,
|
|
|
916
1020
|
public_key: str = _get_public_key(iam_server=iam_server,
|
|
917
1021
|
errors=errors,
|
|
918
1022
|
logger=logger)
|
|
919
|
-
recipient_attr = registry[
|
|
1023
|
+
recipient_attr = registry[ServerParam.RECIPIENT_ATTR]
|
|
920
1024
|
login_id = user_data.pop("login-id", None)
|
|
921
|
-
base_url: str = f"{registry[
|
|
1025
|
+
base_url: str = f"{registry[ServerParam.URL_BASE]}/realms/{registry[ServerParam.CLIENT_REALM]}"
|
|
922
1026
|
claims: dict[str, dict[str, Any]] = token_validate(token=token,
|
|
923
1027
|
issuer=base_url,
|
|
924
1028
|
recipient_id=login_id,
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import sys
|
|
3
3
|
from datetime import datetime
|
|
4
|
-
from enum import StrEnum
|
|
4
|
+
from enum import StrEnum
|
|
5
5
|
from logging import Logger
|
|
6
6
|
from pypomes_core import (
|
|
7
7
|
APP_PREFIX, TZ_LOCAL, exc_format,
|
|
8
|
-
|
|
8
|
+
env_get_int, env_get_str, env_get_strs
|
|
9
9
|
)
|
|
10
10
|
from pypomes_crypto import crypto_jwk_convert
|
|
11
11
|
from threading import RLock
|
|
12
12
|
from typing import Any, Final
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"""
|
|
19
|
-
JUSBR = auto()
|
|
20
|
-
KEYCLOAK = auto()
|
|
14
|
+
_members: dict[str, str] = {key.upper(): key.lower() for key in
|
|
15
|
+
env_get_strs(key=f"{APP_PREFIX}_AUTH_SERVERS")}
|
|
16
|
+
IamServer: type[StrEnum] = StrEnum("IamServer", _members)
|
|
17
|
+
del _members
|
|
21
18
|
|
|
22
19
|
|
|
23
|
-
class
|
|
20
|
+
class ServerParam(StrEnum):
|
|
24
21
|
"""
|
|
25
22
|
Parameters for configuring *IAM* servers.
|
|
26
23
|
"""
|
|
@@ -60,7 +57,7 @@ class UserParam(StrEnum):
|
|
|
60
57
|
REDIRECT_URI = "redirect-uri"
|
|
61
58
|
|
|
62
59
|
|
|
63
|
-
def __get_iam_data() -> dict[IamServer, dict[
|
|
60
|
+
def __get_iam_data() -> dict[IamServer, dict[ServerParam, Any]]:
|
|
64
61
|
"""
|
|
65
62
|
Obtain the configuration data for select *IAM* servers.
|
|
66
63
|
|
|
@@ -68,9 +65,9 @@ def __get_iam_data() -> dict[IamServer, dict[IamParam, Any]]:
|
|
|
68
65
|
or dynamically with calls to *iam_setup_server()*. Specifying configuration parameters with environment
|
|
69
66
|
variables can be done by following these steps:
|
|
70
67
|
|
|
71
|
-
1. Specify *<APP_PREFIX>
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
1. Specify *<APP_PREFIX>_AUTH_SERVERS* with a list of names among the values found in *IamServer* class
|
|
69
|
+
and the data set below for each server, where *<IAM>* stands for the server's name as presented in
|
|
70
|
+
*IamServer* class:
|
|
74
71
|
- *<APP_PREFIX>_<IAM>_ADMIN_ID* (optional, required if administrative duties are performed)
|
|
75
72
|
- *<APP_PREFIX>_<IAM>_ADMIN_PWD* (optional, required if administrative duties are performed)
|
|
76
73
|
- *<APP_PREFIX>_<IAM>_CLIENT_ID* (required)
|
|
@@ -91,30 +88,29 @@ def __get_iam_data() -> dict[IamServer, dict[IamParam, Any]]:
|
|
|
91
88
|
- *<APP_PREFIX>_<IAM>_ENDPOINT_LOGIN*
|
|
92
89
|
- *<APP_PREFIX>_<IAM>_ENDPOINT_LOGOUT*
|
|
93
90
|
- *<APP_PREFIX>_<IAM>_ENDPOINT_TOKEN*
|
|
91
|
+
- *<APP_PREFIX>_<IAM>_ENDPOINT_USERINFO*
|
|
94
92
|
|
|
95
93
|
:return: the configuration data for the select *IAM* servers.
|
|
96
94
|
"""
|
|
97
95
|
# initialize the return variable
|
|
98
|
-
result: dict[IamServer, dict[
|
|
96
|
+
result: dict[IamServer, dict[ServerParam, Any]] = {}
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
enum_class=IamServer) or []
|
|
102
|
-
for server in servers:
|
|
98
|
+
for server in IamServer:
|
|
103
99
|
prefix = server.name
|
|
104
100
|
result[server] = {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
101
|
+
ServerParam.ADMIN_ID: env_get_str(key=f"{APP_PREFIX}_{prefix}_ADMIN_ID"),
|
|
102
|
+
ServerParam.ADMIN_SECRET: env_get_str(key=f"{APP_PREFIX}_{prefix}_ADMIN_SECRET"),
|
|
103
|
+
ServerParam.CLIENT_ID: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_ID"),
|
|
104
|
+
ServerParam.CLIENT_REALM: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_REALM"),
|
|
105
|
+
ServerParam.CLIENT_SECRET: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_SECRET"),
|
|
106
|
+
ServerParam.LOGIN_TIMEOUT: env_get_str(key=f"{APP_PREFIX}_{prefix}_LOGIN_TIMEOUT"),
|
|
107
|
+
ServerParam.PK_LIFETIME: env_get_int(key=f"{APP_PREFIX}_{prefix}_PK_LIFETIME"),
|
|
108
|
+
ServerParam.RECIPIENT_ATTR: env_get_str(key=f"{APP_PREFIX}_{prefix}_RECIPIENT_ATTR"),
|
|
109
|
+
ServerParam.URL_BASE: env_get_str(key=f"{APP_PREFIX}_{prefix}_URL_BASE"),
|
|
114
110
|
# dynamically set
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
ServerParam.PK_EXPIRATION: 0,
|
|
112
|
+
ServerParam.PUBLIC_KEY: None,
|
|
113
|
+
ServerParam.USERS: {}
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
return result
|
|
@@ -153,7 +149,7 @@ def __get_iam_data() -> dict[IamServer, dict[IamParam, Any]]:
|
|
|
153
149
|
# },
|
|
154
150
|
# ...
|
|
155
151
|
# }
|
|
156
|
-
_IAM_SERVERS: Final[dict[IamServer, dict[
|
|
152
|
+
_IAM_SERVERS: Final[dict[IamServer, dict[ServerParam, Any]]] = __get_iam_data()
|
|
157
153
|
|
|
158
154
|
|
|
159
155
|
# the lock protecting the data in '_<IAM>_SERVERS'
|
|
@@ -173,7 +169,7 @@ def _iam_server_from_endpoint(endpoint: str,
|
|
|
173
169
|
:return: the corresponding *IAM* server, or *None* if one could not be obtained
|
|
174
170
|
"""
|
|
175
171
|
# initialize the return variable
|
|
176
|
-
result: IamServer | None = None
|
|
172
|
+
result: type(IamServer) | None = None
|
|
177
173
|
|
|
178
174
|
for iam_server in _IAM_SERVERS:
|
|
179
175
|
if endpoint.startswith(iam_server):
|
|
@@ -202,10 +198,10 @@ def _iam_server_from_issuer(issuer: str,
|
|
|
202
198
|
:return: the corresponding *IAM* server, or *None* if one could not be obtained
|
|
203
199
|
"""
|
|
204
200
|
# initialize the return variable
|
|
205
|
-
result: IamServer | None = None
|
|
201
|
+
result: type(IamServer) | None = None
|
|
206
202
|
|
|
207
203
|
for iam_server, registry in _IAM_SERVERS.items():
|
|
208
|
-
base_url: str = f"{registry[
|
|
204
|
+
base_url: str = f"{registry[ServerParam.URL_BASE]}/realms/{registry[ServerParam.CLIENT_REALM]}"
|
|
209
205
|
if base_url == issuer:
|
|
210
206
|
result = IamServer(iam_server)
|
|
211
207
|
break
|
|
@@ -268,9 +264,9 @@ def _get_public_key(iam_server: IamServer,
|
|
|
268
264
|
logger=logger)
|
|
269
265
|
if registry:
|
|
270
266
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
271
|
-
if now > registry[
|
|
267
|
+
if now > registry[ServerParam.PK_EXPIRATION]:
|
|
272
268
|
# obtain the JWKS (JSON Web Key Set) from the token issuer
|
|
273
|
-
base_url: str = f"{registry[
|
|
269
|
+
base_url: str = f"{registry[ServerParam.URL_BASE]}/realms/{registry[ServerParam.CLIENT_REALM]}"
|
|
274
270
|
url: str = f"{base_url}/protocol/openid-connect/certs"
|
|
275
271
|
if logger:
|
|
276
272
|
logger.debug(msg=f"Obtaining signature public key used by IAM server '{iam_server}'")
|
|
@@ -292,9 +288,9 @@ def _get_public_key(iam_server: IamServer,
|
|
|
292
288
|
# convert from 'JWK' to 'PEM' and save it for further use
|
|
293
289
|
result = crypto_jwk_convert(jwk=jwk,
|
|
294
290
|
fmt="PEM")
|
|
295
|
-
registry[
|
|
296
|
-
lifetime: int = registry[
|
|
297
|
-
registry[
|
|
291
|
+
registry[ServerParam.PUBLIC_KEY] = result
|
|
292
|
+
lifetime: int = registry[ServerParam.PK_LIFETIME] or 0
|
|
293
|
+
registry[ServerParam.PK_EXPIRATION] = now + lifetime if lifetime else sys.maxsize
|
|
298
294
|
if logger:
|
|
299
295
|
logger.debug("Public key obtained and saved")
|
|
300
296
|
else:
|
|
@@ -319,7 +315,7 @@ def _get_public_key(iam_server: IamServer,
|
|
|
319
315
|
if isinstance(errors, list):
|
|
320
316
|
errors.append(msg)
|
|
321
317
|
else:
|
|
322
|
-
result = registry[
|
|
318
|
+
result = registry[ServerParam.PUBLIC_KEY]
|
|
323
319
|
|
|
324
320
|
return result
|
|
325
321
|
|
|
@@ -426,4 +422,4 @@ def _get_iam_users(iam_server: IamServer,
|
|
|
426
422
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
427
423
|
errors=errors,
|
|
428
424
|
logger=logger)
|
|
429
|
-
return registry[
|
|
425
|
+
return registry[ServerParam.USERS] if registry else None
|