pypomes-iam 0.5.7__tar.gz → 0.5.9__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.7 → pypomes_iam-0.5.9}/PKG-INFO +1 -1
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/pyproject.toml +1 -1
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/src/pypomes_iam/__init__.py +7 -12
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/src/pypomes_iam/iam_actions.py +357 -47
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/src/pypomes_iam/iam_common.py +115 -49
- pypomes_iam-0.5.9/src/pypomes_iam/iam_pomes.py +156 -0
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/src/pypomes_iam/iam_services.py +7 -7
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/src/pypomes_iam/provider_pomes.py +46 -26
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/src/pypomes_iam/token_pomes.py +0 -2
- pypomes_iam-0.5.7/src/pypomes_iam/jusbr_pomes.py +0 -125
- pypomes_iam-0.5.7/src/pypomes_iam/keycloak_pomes.py +0 -136
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/.gitignore +0 -0
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/LICENSE +0 -0
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/README.md +0 -0
- {pypomes_iam-0.5.7 → pypomes_iam-0.5.9}/src/__init__.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.9
|
|
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
|
|
@@ -3,17 +3,14 @@ from .iam_actions import (
|
|
|
3
3
|
action_login, action_logout, action_token
|
|
4
4
|
)
|
|
5
5
|
from .iam_common import (
|
|
6
|
-
IamServer
|
|
6
|
+
IamServer, IamParam
|
|
7
|
+
)
|
|
8
|
+
from .iam_pomes import (
|
|
9
|
+
iam_setup, iam_get_env_parameters, iam_get_token
|
|
7
10
|
)
|
|
8
11
|
from .iam_services import (
|
|
9
12
|
jwt_required, logger_register
|
|
10
13
|
)
|
|
11
|
-
from .jusbr_pomes import (
|
|
12
|
-
jusbr_setup, jusbr_get_token
|
|
13
|
-
)
|
|
14
|
-
from .keycloak_pomes import (
|
|
15
|
-
keycloak_setup, keycloak_get_token
|
|
16
|
-
)
|
|
17
14
|
from .provider_pomes import (
|
|
18
15
|
provider_register, provider_get_token
|
|
19
16
|
)
|
|
@@ -26,13 +23,11 @@ __all__ = [
|
|
|
26
23
|
"action_callback", "action_exchange",
|
|
27
24
|
"action_login", "action_logout", "action_token",
|
|
28
25
|
# iam_commons
|
|
29
|
-
"IamServer",
|
|
26
|
+
"IamServer", "IamParam",
|
|
27
|
+
# iam_pomes
|
|
28
|
+
"iam_setup", "iam_get_env_parameters", "iam_get_token",
|
|
30
29
|
# iam_services
|
|
31
30
|
"jwt_required", "logger_register",
|
|
32
|
-
# jusbr_pomes
|
|
33
|
-
"jusbr_setup", "jusbr_get_token",
|
|
34
|
-
# keycloak_pomes
|
|
35
|
-
"keycloak_setup", "keycloak_get_token",
|
|
36
31
|
# provider_pomes
|
|
37
32
|
"provider_register", "provider_get_token",
|
|
38
33
|
# token_pomes
|
|
@@ -9,11 +9,11 @@ from pypomes_core import TZ_LOCAL, exc_format
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from .iam_common import (
|
|
12
|
-
IamServer, _iam_lock,
|
|
12
|
+
IamServer, IamParam, UserParam, _iam_lock,
|
|
13
13
|
_get_iam_users, _get_iam_registry, # _get_public_key,
|
|
14
14
|
_get_login_timeout, _get_user_data
|
|
15
15
|
)
|
|
16
|
-
from .token_pomes import token_validate
|
|
16
|
+
from .token_pomes import token_get_claims, token_validate
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def action_login(iam_server: IamServer,
|
|
@@ -54,24 +54,25 @@ def action_login(iam_server: IamServer,
|
|
|
54
54
|
errors=errors,
|
|
55
55
|
logger=logger)
|
|
56
56
|
if user_data:
|
|
57
|
-
user_data[
|
|
57
|
+
user_data[UserParam.LOGIN_ID] = user_id
|
|
58
58
|
timeout: int = _get_login_timeout(iam_server=iam_server,
|
|
59
59
|
errors=errors,
|
|
60
60
|
logger=logger)
|
|
61
61
|
if not errors:
|
|
62
|
-
user_data[
|
|
62
|
+
user_data[UserParam.LOGIN_EXPIRATION] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout \
|
|
63
63
|
if timeout else None
|
|
64
|
-
redirect_uri: str = args.get(
|
|
65
|
-
user_data[
|
|
64
|
+
redirect_uri: str = args.get(UserParam.REDIRECT_URI)
|
|
65
|
+
user_data[UserParam.REDIRECT_URI] = redirect_uri
|
|
66
66
|
|
|
67
67
|
# build the login url
|
|
68
68
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
69
69
|
errors=errors,
|
|
70
70
|
logger=logger)
|
|
71
71
|
if registry:
|
|
72
|
-
|
|
72
|
+
base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
|
|
73
|
+
result = (f"{base_url}/protocol/openid-connect/auth"
|
|
73
74
|
f"?response_type=code&scope=openid"
|
|
74
|
-
f"&client_id={registry[
|
|
75
|
+
f"&client_id={registry[IamParam.CLIENT_ID]}"
|
|
75
76
|
f"&redirect_uri={redirect_uri}"
|
|
76
77
|
f"&state={oauth_state}")
|
|
77
78
|
return result
|
|
@@ -137,24 +138,28 @@ def action_token(iam_server: IamServer,
|
|
|
137
138
|
user_id=user_id,
|
|
138
139
|
errors=errors,
|
|
139
140
|
logger=logger)
|
|
140
|
-
token: str = user_data[
|
|
141
|
+
token: str = user_data[UserParam.ACCESS_TOKEN] if user_data else None
|
|
141
142
|
if token:
|
|
142
|
-
access_expiration: int = user_data.get(
|
|
143
|
+
access_expiration: int = user_data.get(UserParam.ACCESS_EXPIRATION)
|
|
143
144
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
144
145
|
if now < access_expiration:
|
|
145
146
|
result = token
|
|
146
147
|
else:
|
|
147
148
|
# access token has expired
|
|
148
|
-
refresh_token: str = user_data[
|
|
149
|
+
refresh_token: str = user_data[UserParam.REFRESH_TOKEN]
|
|
149
150
|
if refresh_token:
|
|
150
|
-
refresh_expiration = user_data[
|
|
151
|
+
refresh_expiration = user_data[UserParam.REFRESH_EXPIRATION]
|
|
151
152
|
if now < refresh_expiration:
|
|
153
|
+
header_data: dict[str, str] = {
|
|
154
|
+
"Content-Type": "application/json"
|
|
155
|
+
}
|
|
152
156
|
body_data: dict[str, str] = {
|
|
153
157
|
"grant_type": "refresh_token",
|
|
154
158
|
"refresh_token": refresh_token
|
|
155
159
|
}
|
|
156
160
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
157
161
|
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
162
|
+
header_data=header_data,
|
|
158
163
|
body_data=body_data,
|
|
159
164
|
errors=errors,
|
|
160
165
|
logger=logger)
|
|
@@ -169,7 +174,7 @@ def action_token(iam_server: IamServer,
|
|
|
169
174
|
result = token_info[1]
|
|
170
175
|
else:
|
|
171
176
|
# refresh token is no longer valid
|
|
172
|
-
user_data[
|
|
177
|
+
user_data[UserParam.REFRESH_TOKEN] = None
|
|
173
178
|
else:
|
|
174
179
|
# refresh token has expired
|
|
175
180
|
err_msg = "Access and refresh tokens expired"
|
|
@@ -236,6 +241,9 @@ def action_callback(iam_server: IamServer,
|
|
|
236
241
|
else:
|
|
237
242
|
users.pop(oauth_state)
|
|
238
243
|
code: str = args.get("code")
|
|
244
|
+
header_data: dict[str, str] = {
|
|
245
|
+
"Content-Type": "application/json"
|
|
246
|
+
}
|
|
239
247
|
body_data: dict[str, Any] = {
|
|
240
248
|
"grant_type": "authorization_code",
|
|
241
249
|
"code": code,
|
|
@@ -243,6 +251,7 @@ def action_callback(iam_server: IamServer,
|
|
|
243
251
|
}
|
|
244
252
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
245
253
|
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
254
|
+
header_data=header_data,
|
|
246
255
|
body_data=body_data,
|
|
247
256
|
errors=errors,
|
|
248
257
|
logger=logger)
|
|
@@ -297,9 +306,8 @@ def action_exchange(iam_server: IamServer,
|
|
|
297
306
|
user_id: str = args.get("user-id") or args.get("login")
|
|
298
307
|
|
|
299
308
|
# obtain the token to be exchanged
|
|
300
|
-
token: str = args.get("access-token")
|
|
301
|
-
|
|
302
|
-
if user_id and token:
|
|
309
|
+
token: str = args.get("access-token") if user_id else None
|
|
310
|
+
if token:
|
|
303
311
|
# HAZARD: only 'IAM_KEYCLOAK' is currently supported
|
|
304
312
|
with _iam_lock:
|
|
305
313
|
# retrieve the IAM server's registry
|
|
@@ -307,30 +315,166 @@ def action_exchange(iam_server: IamServer,
|
|
|
307
315
|
errors=errors,
|
|
308
316
|
logger=logger)
|
|
309
317
|
if registry:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
318
|
+
# make sure 'client_id' is linked to the token's 'token_sub' at the IAM server
|
|
319
|
+
__assert_link(iam_server=iam_server,
|
|
320
|
+
user_id=user_id,
|
|
321
|
+
token=token,
|
|
322
|
+
errors=errors,
|
|
323
|
+
logger=logger)
|
|
324
|
+
if not errors:
|
|
325
|
+
# exchange the token
|
|
326
|
+
header_data: dict[str, Any] = {
|
|
327
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
328
|
+
}
|
|
329
|
+
body_data: dict[str, str] = {
|
|
330
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
331
|
+
"subject_token": token,
|
|
332
|
+
"subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
333
|
+
"requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
334
|
+
"audience": registry[IamParam.CLIENT_ID],
|
|
335
|
+
"subject_issuer": "oidc"
|
|
336
|
+
}
|
|
337
|
+
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
338
|
+
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
339
|
+
header_data=header_data,
|
|
340
|
+
body_data=body_data,
|
|
341
|
+
errors=errors,
|
|
342
|
+
logger=logger)
|
|
343
|
+
# validate and store the token data
|
|
344
|
+
if token_data:
|
|
345
|
+
user_data: dict[str, Any] = {}
|
|
346
|
+
result = __validate_and_store(iam_server=iam_server,
|
|
347
|
+
user_data=user_data,
|
|
348
|
+
token_data=token_data,
|
|
349
|
+
now=now,
|
|
350
|
+
errors=errors,
|
|
351
|
+
logger=logger)
|
|
352
|
+
else:
|
|
353
|
+
msg: str = "User identification or token not provided"
|
|
354
|
+
if logger:
|
|
355
|
+
logger.error(msg=msg)
|
|
356
|
+
if isinstance(errors, list):
|
|
357
|
+
errors.append(msg)
|
|
358
|
+
|
|
359
|
+
return result
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def __assert_link(iam_server: IamServer,
|
|
363
|
+
user_id: str,
|
|
364
|
+
token: str,
|
|
365
|
+
errors: list[str] | None,
|
|
366
|
+
logger: Logger | None) -> None:
|
|
367
|
+
"""
|
|
368
|
+
Make sure *iam_server* has a link associating *user_id* to an internal user identification.
|
|
369
|
+
This is a requirement for exchanging a token issued by a federated *IAM* server for an equivalent
|
|
370
|
+
one from *iam_server.
|
|
371
|
+
|
|
372
|
+
:param iam_server: the reference *IAM* server
|
|
373
|
+
:param user_id: the reference user identification
|
|
374
|
+
:param token: the reference token
|
|
375
|
+
:param errors: incidental errors
|
|
376
|
+
:param logger: optional logger
|
|
377
|
+
"""
|
|
378
|
+
# obtain a token with administrative rights
|
|
379
|
+
admin_token: str = __get_administrative_token(iam_server=iam_server,
|
|
330
380
|
errors=errors,
|
|
331
381
|
logger=logger)
|
|
382
|
+
if admin_token:
|
|
383
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
384
|
+
errors=errors,
|
|
385
|
+
logger=logger)
|
|
386
|
+
# obtain the internal user identification for 'from_id'
|
|
387
|
+
url: str = f"{registry[IamParam.URL_BASE]}/admin/realms/{registry[IamParam.CLIENT_REALM]}/users"
|
|
388
|
+
header_data: dict[str, str] = {
|
|
389
|
+
"Authorization": f"Bearer {admin_token}",
|
|
390
|
+
"Content-Type": "application/json"
|
|
391
|
+
}
|
|
392
|
+
params: dict[str, str] = {
|
|
393
|
+
"username": user_id,
|
|
394
|
+
"exact": "true"
|
|
395
|
+
}
|
|
396
|
+
users: dict[str, Any] = __get_for_data(url=url,
|
|
397
|
+
header_data=header_data,
|
|
398
|
+
params=params,
|
|
399
|
+
errors=errors,
|
|
400
|
+
logger=logger)
|
|
401
|
+
if users:
|
|
402
|
+
# verify whether the 'oidc' protocol is referred to in an
|
|
403
|
+
# association between 'from_id' and the internal user identification
|
|
404
|
+
internal_id: str = users.get("id")
|
|
405
|
+
url = (f"{registry[IamParam.URL_BASE]}/admin/realms/"
|
|
406
|
+
f"{registry[IamParam.CLIENT_REALM]}/users/{internal_id}/federated-identity")
|
|
407
|
+
providers: list[dict[str, Any]] = __get_for_data(url=url,
|
|
408
|
+
header_data=header_data,
|
|
409
|
+
params=None,
|
|
410
|
+
errors=errors,
|
|
411
|
+
logger=logger)
|
|
412
|
+
no_link: bool = True
|
|
413
|
+
for provider in providers:
|
|
414
|
+
if provider.get("identityProvider") == "oidc":
|
|
415
|
+
no_link = False
|
|
416
|
+
break
|
|
417
|
+
if no_link:
|
|
418
|
+
# link the identities
|
|
419
|
+
claims: dict[str, dict[str, Any]] = token_get_claims(token=token,
|
|
420
|
+
errors=errors,
|
|
421
|
+
logger=logger)
|
|
422
|
+
if claims:
|
|
423
|
+
token_sub: str = claims["paylod"]["sub"]
|
|
424
|
+
url += "/oidc"
|
|
425
|
+
body_data: dict[str, Any] = {
|
|
426
|
+
"userId": token_sub,
|
|
427
|
+
"userName": user_id
|
|
428
|
+
}
|
|
429
|
+
__post_data(url=url,
|
|
430
|
+
header_data=header_data,
|
|
431
|
+
body_data=body_data,
|
|
432
|
+
errors=errors,
|
|
433
|
+
logger=logger)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def __get_administrative_token(iam_server: IamServer,
|
|
437
|
+
errors: list[str] | None,
|
|
438
|
+
logger: Logger | None) -> str:
|
|
439
|
+
"""
|
|
440
|
+
Obtain a token with administrative rights from *iam_server*'s reference realm.
|
|
441
|
+
|
|
442
|
+
The reference realm is the realm specified at *iam_server*'s setup time. This operation requires
|
|
443
|
+
the realm administrator's identification and secret password to have also been provided.
|
|
444
|
+
|
|
445
|
+
:param iam_server: the reference *IAM* server
|
|
446
|
+
:param errors: incidental errors
|
|
447
|
+
:param logger: optional logger
|
|
448
|
+
:return: a token with administrative rights for the reference realm
|
|
449
|
+
"""
|
|
450
|
+
# initialize the return variable
|
|
451
|
+
result: str | None = None
|
|
452
|
+
|
|
453
|
+
# obtain the IAM server's registry
|
|
454
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
455
|
+
errors=errors,
|
|
456
|
+
logger=logger)
|
|
457
|
+
if registry and registry[IamParam.ADMIN_ID] and registry[IamParam.ADMIN_SECRET]:
|
|
458
|
+
header_data: dict[str, str] = {
|
|
459
|
+
"Content-Type": "application/json"
|
|
460
|
+
}
|
|
461
|
+
body_data: dict[str, str] = {
|
|
462
|
+
"grant-type": "password",
|
|
463
|
+
"username": registry[IamParam.ADMIN_ID],
|
|
464
|
+
"password": registry[IamParam.ADMIN_SECRET],
|
|
465
|
+
"client_id": registry[IamParam.CLIENT_ID]
|
|
466
|
+
}
|
|
467
|
+
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
468
|
+
header_data=header_data,
|
|
469
|
+
body_data=body_data,
|
|
470
|
+
errors=errors,
|
|
471
|
+
logger=logger)
|
|
472
|
+
if token_data:
|
|
473
|
+
# obtain the token
|
|
474
|
+
result = token_data["access_token"]
|
|
332
475
|
else:
|
|
333
|
-
msg: str = "
|
|
476
|
+
msg: str = (f"To obtain token with administrative rights from '{iam_server}', "
|
|
477
|
+
f"the credentials for the realm administrator must be provided at setup time")
|
|
334
478
|
if logger:
|
|
335
479
|
logger.error(msg=msg)
|
|
336
480
|
if isinstance(errors, list):
|
|
@@ -339,12 +483,165 @@ def action_exchange(iam_server: IamServer,
|
|
|
339
483
|
return result
|
|
340
484
|
|
|
341
485
|
|
|
486
|
+
def __get_client_secret(iam_server: IamServer,
|
|
487
|
+
errors: list[str] | None,
|
|
488
|
+
logger: Logger | None) -> str:
|
|
489
|
+
"""
|
|
490
|
+
Retrieve the client's secret password.
|
|
491
|
+
|
|
492
|
+
If it has not been provided at *iam_server*'s setup time, an attempt is made to obtain it
|
|
493
|
+
from the *IAM* server itself. This would require the realm administrator's identification and
|
|
494
|
+
secret password to have been provided, instead.
|
|
495
|
+
|
|
496
|
+
:param iam_server: the reference *IAM* server
|
|
497
|
+
:param errors: incidental errors
|
|
498
|
+
:param logger: optional logger
|
|
499
|
+
:return: the client's secret password, or *None* if error
|
|
500
|
+
"""
|
|
501
|
+
# retrieve client's secret password stored in the IAM server's registry
|
|
502
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
503
|
+
errors=errors,
|
|
504
|
+
logger=logger)
|
|
505
|
+
result: str = registry[IamParam.CLIENT_SECRET] if registry else None
|
|
506
|
+
|
|
507
|
+
if not result and not errors:
|
|
508
|
+
# obtain a token with administrative rights
|
|
509
|
+
token: str = __get_administrative_token(iam_server=iam_server,
|
|
510
|
+
errors=errors,
|
|
511
|
+
logger=logger)
|
|
512
|
+
if token:
|
|
513
|
+
# obtain the client UUID
|
|
514
|
+
url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}/clients"
|
|
515
|
+
header_data: dict[str, str] = {
|
|
516
|
+
"Authorization": f"Bearer {token}",
|
|
517
|
+
"Content-Type": "application/json"
|
|
518
|
+
}
|
|
519
|
+
params: dict[str, str] = {
|
|
520
|
+
"clientId": registry[IamParam.CLIENT_ID]
|
|
521
|
+
}
|
|
522
|
+
clients: list[dict[str, Any]] = __get_for_data(url=url,
|
|
523
|
+
header_data=header_data,
|
|
524
|
+
params=params,
|
|
525
|
+
errors=errors,
|
|
526
|
+
logger=logger)
|
|
527
|
+
if clients:
|
|
528
|
+
# obtain the client's secret password
|
|
529
|
+
client_uuid: str = clients[0]["id"]
|
|
530
|
+
url += f"/{client_uuid}/client-secret"
|
|
531
|
+
reply: dict[str, Any] = __get_for_data(url=url,
|
|
532
|
+
header_data=header_data,
|
|
533
|
+
params=None,
|
|
534
|
+
errors=errors,
|
|
535
|
+
logger=logger)
|
|
536
|
+
if reply:
|
|
537
|
+
# store the client's secret password and return it
|
|
538
|
+
result = reply["value"]
|
|
539
|
+
registry[IamParam.CLIENT_ID] = result
|
|
540
|
+
return result
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def __get_for_data(url: str,
|
|
544
|
+
header_data: dict[str, str],
|
|
545
|
+
params: dict[str, Any] | None,
|
|
546
|
+
errors: list[str] | None,
|
|
547
|
+
logger: Logger | None) -> Any:
|
|
548
|
+
"""
|
|
549
|
+
Send a *GET* request to *url* and return the data obtained.
|
|
550
|
+
|
|
551
|
+
:param url: the target URL
|
|
552
|
+
:param header_data: the data to send in the header of the request
|
|
553
|
+
:param params: the query parameters to send in the request
|
|
554
|
+
:param errors: incidental errors
|
|
555
|
+
:param logger: optional logger
|
|
556
|
+
:return: the data requested, or *None* if error
|
|
557
|
+
"""
|
|
558
|
+
# initialize the return variable
|
|
559
|
+
result: Any = None
|
|
560
|
+
|
|
561
|
+
# log the GET
|
|
562
|
+
if logger:
|
|
563
|
+
logger.debug(msg=f"GET {url}, {json.dumps(obj=params,
|
|
564
|
+
ensure_ascii=False)}")
|
|
565
|
+
try:
|
|
566
|
+
response: requests.Response = requests.get(url=url,
|
|
567
|
+
headers=header_data,
|
|
568
|
+
params=params)
|
|
569
|
+
if response.status_code == 200:
|
|
570
|
+
# request succeeded
|
|
571
|
+
result = response.json() or {}
|
|
572
|
+
if logger:
|
|
573
|
+
logger.debug(msg=f"GET success, {json.dumps(obj=result,
|
|
574
|
+
ensure_ascii=False)}")
|
|
575
|
+
else:
|
|
576
|
+
# request resulted in error
|
|
577
|
+
msg: str = f"GET failure, status {response.status_code}, reason {response.reason}"
|
|
578
|
+
if hasattr(response, "content") and response.content:
|
|
579
|
+
msg += f", content '{response.content}'"
|
|
580
|
+
if logger:
|
|
581
|
+
logger.error(msg=msg)
|
|
582
|
+
if isinstance(errors, list):
|
|
583
|
+
errors.append(msg)
|
|
584
|
+
except Exception as e:
|
|
585
|
+
# the operation raised an exception
|
|
586
|
+
msg: str = exc_format(exc=e,
|
|
587
|
+
exc_info=sys.exc_info())
|
|
588
|
+
if logger:
|
|
589
|
+
logger.error(msg=msg)
|
|
590
|
+
if isinstance(errors, list):
|
|
591
|
+
errors.append(msg)
|
|
592
|
+
|
|
593
|
+
return result
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def __post_data(url: str,
|
|
597
|
+
header_data: dict[str, str],
|
|
598
|
+
body_data: dict[str, Any],
|
|
599
|
+
errors: list[str] | None,
|
|
600
|
+
logger: Logger | None) -> None:
|
|
601
|
+
"""
|
|
602
|
+
Submit a *POST* request to *url*.
|
|
603
|
+
|
|
604
|
+
:param header_data: the data to send in the header of the request
|
|
605
|
+
:param body_data: the data to send in the body of the request
|
|
606
|
+
:param errors: incidental errors
|
|
607
|
+
:param logger: optional logger
|
|
608
|
+
"""
|
|
609
|
+
# log the POST
|
|
610
|
+
if logger:
|
|
611
|
+
logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
|
|
612
|
+
ensure_ascii=False)}")
|
|
613
|
+
try:
|
|
614
|
+
response: requests.Response = requests.get(url=url,
|
|
615
|
+
headers=header_data,
|
|
616
|
+
data=body_data)
|
|
617
|
+
if response.status_code >= 400:
|
|
618
|
+
# request resulted in error
|
|
619
|
+
msg = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
620
|
+
if hasattr(response, "content") and response.content:
|
|
621
|
+
msg += f", content '{response.content}'"
|
|
622
|
+
if logger:
|
|
623
|
+
logger.error(msg=msg)
|
|
624
|
+
if isinstance(errors, list):
|
|
625
|
+
errors.append(msg)
|
|
626
|
+
elif logger:
|
|
627
|
+
logger.debug(msg=f"POST success")
|
|
628
|
+
except Exception as e:
|
|
629
|
+
# the operation raised an exception
|
|
630
|
+
msg = exc_format(exc=e,
|
|
631
|
+
exc_info=sys.exc_info())
|
|
632
|
+
if logger:
|
|
633
|
+
logger.error(msg=msg)
|
|
634
|
+
if isinstance(errors, list):
|
|
635
|
+
errors.append(msg)
|
|
636
|
+
|
|
637
|
+
|
|
342
638
|
def __post_for_token(iam_server: IamServer,
|
|
639
|
+
header_data: dict[str, str],
|
|
343
640
|
body_data: dict[str, Any],
|
|
344
641
|
errors: list[str] | None,
|
|
345
642
|
logger: Logger | None) -> dict[str, Any] | None:
|
|
346
643
|
"""
|
|
347
|
-
Send a POST request to
|
|
644
|
+
Send a *POST* request to *iam_server* and return the authentication token data obtained.
|
|
348
645
|
|
|
349
646
|
For token acquisition, *body_data* will have the attributes:
|
|
350
647
|
- "grant_type": "authorization_code"
|
|
@@ -363,9 +660,14 @@ def __post_for_token(iam_server: IamServer,
|
|
|
363
660
|
- "audience": <client-id>,
|
|
364
661
|
- "subject_issuer": "oidc"
|
|
365
662
|
|
|
663
|
+
For administrative token acquisition, *body_data* will have the attributes:
|
|
664
|
+
- "grant_type": "password"
|
|
665
|
+
- "username": <realm-administrator-identification>
|
|
666
|
+
- "password": <realm-administrator-secret>
|
|
667
|
+
|
|
366
668
|
These attributes are then added to *body_data*:
|
|
367
|
-
- "client_id": <client-id
|
|
368
|
-
- "client_secret": <client-secret
|
|
669
|
+
- "client_id": <client-id>
|
|
670
|
+
- "client_secret": <client-secret> <- except for acquiring administrative tokens
|
|
369
671
|
|
|
370
672
|
If the operation is successful, the token data is stored in the *IAM* server's registry, and returned.
|
|
371
673
|
Otherwise, *errors* will contain the appropriate error message.
|
|
@@ -380,6 +682,7 @@ def __post_for_token(iam_server: IamServer,
|
|
|
380
682
|
}
|
|
381
683
|
|
|
382
684
|
:param iam_server: the reference registered *IAM* server
|
|
685
|
+
:param header_data: the data to send in the header of the request
|
|
383
686
|
:param body_data: the data to send in the body of the request
|
|
384
687
|
:param errors: incidental errors
|
|
385
688
|
:param logger: optional logger
|
|
@@ -396,18 +699,23 @@ def __post_for_token(iam_server: IamServer,
|
|
|
396
699
|
logger=logger)
|
|
397
700
|
if registry:
|
|
398
701
|
# complete the data to send in body of request
|
|
399
|
-
body_data["client_id"] = registry[
|
|
400
|
-
client_secret: str = registry["client-secret"]
|
|
702
|
+
body_data["client_id"] = registry[IamParam.CLIENT_ID]
|
|
401
703
|
|
|
402
|
-
#
|
|
403
|
-
|
|
704
|
+
# build the URL
|
|
705
|
+
base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
|
|
706
|
+
url: str = f"{base_url}/protocol/openid-connect/token"
|
|
404
707
|
|
|
405
708
|
# log the POST ('client_secret' data must not be shown in log)
|
|
406
709
|
if logger:
|
|
407
710
|
logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
|
|
408
711
|
ensure_ascii=False)}")
|
|
409
|
-
|
|
712
|
+
client_secret: str = __get_client_secret(iam_server=iam_server,
|
|
713
|
+
errors=errors,
|
|
714
|
+
logger=logger)
|
|
715
|
+
if body_data["grant_type"] != "password" and client_secret:
|
|
410
716
|
body_data["client_secret"] = client_secret
|
|
717
|
+
|
|
718
|
+
# obtain the token
|
|
411
719
|
try:
|
|
412
720
|
# typical return on a token request:
|
|
413
721
|
# {
|
|
@@ -418,6 +726,7 @@ def __post_for_token(iam_server: IamServer,
|
|
|
418
726
|
# "refesh_expires_in": <number-of-seconds>
|
|
419
727
|
# }
|
|
420
728
|
response: requests.Response = requests.post(url=url,
|
|
729
|
+
headers=header_data,
|
|
421
730
|
data=body_data)
|
|
422
731
|
if response.status_code == 200:
|
|
423
732
|
# request succeeded
|
|
@@ -490,10 +799,11 @@ def __validate_and_store(iam_server: IamServer,
|
|
|
490
799
|
# public_key: str = _get_public_key(iam_server=iam_server,
|
|
491
800
|
# errors=errors,
|
|
492
801
|
# logger=logger)
|
|
493
|
-
recipient_attr = registry[
|
|
802
|
+
recipient_attr = registry[IamParam.RECIPIENT_ATTR]
|
|
494
803
|
login_id = user_data.pop("login-id", None)
|
|
804
|
+
base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
|
|
495
805
|
claims: dict[str, dict[str, Any]] = token_validate(token=token,
|
|
496
|
-
issuer=
|
|
806
|
+
issuer=base_url,
|
|
497
807
|
recipient_id=login_id,
|
|
498
808
|
recipient_attr=recipient_attr,
|
|
499
809
|
# public_key=public_key,
|