pypomes-iam 0.5.8__py3-none-any.whl → 0.6.9__py3-none-any.whl
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/__init__.py +2 -2
- pypomes_iam/iam_actions.py +126 -77
- pypomes_iam/iam_common.py +40 -96
- pypomes_iam/iam_pomes.py +41 -15
- pypomes_iam/iam_services.py +2 -2
- pypomes_iam/provider_pomes.py +125 -64
- {pypomes_iam-0.5.8.dist-info → pypomes_iam-0.6.9.dist-info}/METADATA +1 -2
- pypomes_iam-0.6.9.dist-info/RECORD +11 -0
- pypomes_iam-0.5.8.dist-info/RECORD +0 -11
- {pypomes_iam-0.5.8.dist-info → pypomes_iam-0.6.9.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.5.8.dist-info → pypomes_iam-0.6.9.dist-info}/licenses/LICENSE +0 -0
pypomes_iam/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ from .iam_common import (
|
|
|
6
6
|
IamServer, IamParam
|
|
7
7
|
)
|
|
8
8
|
from .iam_pomes import (
|
|
9
|
-
iam_setup, iam_get_token
|
|
9
|
+
iam_setup, iam_get_env_parameters, iam_get_token
|
|
10
10
|
)
|
|
11
11
|
from .iam_services import (
|
|
12
12
|
jwt_required, logger_register
|
|
@@ -25,7 +25,7 @@ __all__ = [
|
|
|
25
25
|
# iam_commons
|
|
26
26
|
"IamServer", "IamParam",
|
|
27
27
|
# iam_pomes
|
|
28
|
-
"iam_setup", "iam_get_token",
|
|
28
|
+
"iam_setup", "iam_get_env_parameters", "iam_get_token",
|
|
29
29
|
# iam_services
|
|
30
30
|
"jwt_required", "logger_register",
|
|
31
31
|
# provider_pomes
|
pypomes_iam/iam_actions.py
CHANGED
|
@@ -10,8 +10,8 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
from .iam_common import (
|
|
12
12
|
IamServer, IamParam, UserParam, _iam_lock,
|
|
13
|
-
_get_iam_users, _get_iam_registry,
|
|
14
|
-
_get_login_timeout, _get_user_data
|
|
13
|
+
_get_iam_users, _get_iam_registry, _get_public_key,
|
|
14
|
+
_get_login_timeout, _get_user_data, _iam_server_from_issuer
|
|
15
15
|
)
|
|
16
16
|
from .token_pomes import token_get_claims, token_validate
|
|
17
17
|
|
|
@@ -59,7 +59,7 @@ def action_login(iam_server: IamServer,
|
|
|
59
59
|
errors=errors,
|
|
60
60
|
logger=logger)
|
|
61
61
|
if not errors:
|
|
62
|
-
user_data[UserParam.LOGIN_EXPIRATION] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout \
|
|
62
|
+
user_data[UserParam.LOGIN_EXPIRATION] = (int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout) \
|
|
63
63
|
if timeout else None
|
|
64
64
|
redirect_uri: str = args.get(UserParam.REDIRECT_URI)
|
|
65
65
|
user_data[UserParam.REDIRECT_URI] = redirect_uri
|
|
@@ -138,6 +138,7 @@ def action_token(iam_server: IamServer,
|
|
|
138
138
|
user_id=user_id,
|
|
139
139
|
errors=errors,
|
|
140
140
|
logger=logger)
|
|
141
|
+
# retrieve the stored access token
|
|
141
142
|
token: str = user_data[UserParam.ACCESS_TOKEN] if user_data else None
|
|
142
143
|
if token:
|
|
143
144
|
access_expiration: int = user_data.get(UserParam.ACCESS_EXPIRATION)
|
|
@@ -148,7 +149,7 @@ def action_token(iam_server: IamServer,
|
|
|
148
149
|
# access token has expired
|
|
149
150
|
refresh_token: str = user_data[UserParam.REFRESH_TOKEN]
|
|
150
151
|
if refresh_token:
|
|
151
|
-
refresh_expiration = user_data[UserParam.REFRESH_EXPIRATION]
|
|
152
|
+
refresh_expiration: int = user_data[UserParam.REFRESH_EXPIRATION]
|
|
152
153
|
if now < refresh_expiration:
|
|
153
154
|
header_data: dict[str, str] = {
|
|
154
155
|
"Content-Type": "application/json"
|
|
@@ -242,7 +243,7 @@ def action_callback(iam_server: IamServer,
|
|
|
242
243
|
users.pop(oauth_state)
|
|
243
244
|
code: str = args.get("code")
|
|
244
245
|
header_data: dict[str, str] = {
|
|
245
|
-
"Content-Type": "application/
|
|
246
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
246
247
|
}
|
|
247
248
|
body_data: dict[str, Any] = {
|
|
248
249
|
"grant_type": "authorization_code",
|
|
@@ -367,7 +368,7 @@ def __assert_link(iam_server: IamServer,
|
|
|
367
368
|
"""
|
|
368
369
|
Make sure *iam_server* has a link associating *user_id* to an internal user identification.
|
|
369
370
|
This is a requirement for exchanging a token issued by a federated *IAM* server for an equivalent
|
|
370
|
-
one from *iam_server
|
|
371
|
+
one from *iam_server*.
|
|
371
372
|
|
|
372
373
|
:param iam_server: the reference *IAM* server
|
|
373
374
|
:param user_id: the reference user identification
|
|
@@ -375,6 +376,9 @@ def __assert_link(iam_server: IamServer,
|
|
|
375
376
|
:param errors: incidental errors
|
|
376
377
|
:param logger: optional logger
|
|
377
378
|
"""
|
|
379
|
+
if logger:
|
|
380
|
+
logger.debug(msg="Verifying associations for user "
|
|
381
|
+
f"'{user_id}' in IAM server '{iam_server}'")
|
|
378
382
|
# obtain a token with administrative rights
|
|
379
383
|
admin_token: str = __get_administrative_token(iam_server=iam_server,
|
|
380
384
|
errors=errors,
|
|
@@ -383,7 +387,10 @@ def __assert_link(iam_server: IamServer,
|
|
|
383
387
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
384
388
|
errors=errors,
|
|
385
389
|
logger=logger)
|
|
386
|
-
# obtain the internal user identification for '
|
|
390
|
+
# obtain the internal user identification for 'user_id'
|
|
391
|
+
if logger:
|
|
392
|
+
logger.debug(msg="Obtaining internal identification "
|
|
393
|
+
f"for user {user_id} in IAM server {iam_server}")
|
|
387
394
|
url: str = f"{registry[IamParam.URL_BASE]}/admin/realms/{registry[IamParam.CLIENT_REALM]}/users"
|
|
388
395
|
header_data: dict[str, str] = {
|
|
389
396
|
"Authorization": f"Bearer {admin_token}",
|
|
@@ -393,15 +400,18 @@ def __assert_link(iam_server: IamServer,
|
|
|
393
400
|
"username": user_id,
|
|
394
401
|
"exact": "true"
|
|
395
402
|
}
|
|
396
|
-
users: dict[str, Any] = __get_for_data(url=url,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
403
|
+
users: list[dict[str, Any]] = __get_for_data(url=url,
|
|
404
|
+
header_data=header_data,
|
|
405
|
+
params=params,
|
|
406
|
+
errors=errors,
|
|
407
|
+
logger=logger)
|
|
401
408
|
if users:
|
|
402
409
|
# verify whether the 'oidc' protocol is referred to in an
|
|
403
|
-
# association between '
|
|
404
|
-
internal_id: str = users.get("id")
|
|
410
|
+
# association between 'user_id' and the internal user identification
|
|
411
|
+
internal_id: str = users[0].get("id")
|
|
412
|
+
if logger:
|
|
413
|
+
logger.debug(msg="Obtaining the providers federated with "
|
|
414
|
+
f"IAM server '{iam_server}' for internal identification '{internal_id}'")
|
|
405
415
|
url = (f"{registry[IamParam.URL_BASE]}/admin/realms/"
|
|
406
416
|
f"{registry[IamParam.CLIENT_REALM]}/users/{internal_id}/federated-identity")
|
|
407
417
|
providers: list[dict[str, Any]] = __get_for_data(url=url,
|
|
@@ -410,27 +420,38 @@ def __assert_link(iam_server: IamServer,
|
|
|
410
420
|
errors=errors,
|
|
411
421
|
logger=logger)
|
|
412
422
|
no_link: bool = True
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
423
|
+
claims: dict[str, dict[str, Any]] = token_get_claims(token=token,
|
|
424
|
+
errors=errors,
|
|
425
|
+
logger=logger)
|
|
426
|
+
issuer: str = claims["payload"]["iss"] if claims else None
|
|
427
|
+
provider_name: str = _iam_server_from_issuer(issuer=issuer,
|
|
428
|
+
errors=errors,
|
|
429
|
+
logger=logger) if issuer else None
|
|
430
|
+
if provider_name:
|
|
431
|
+
for provider in providers:
|
|
432
|
+
if provider.get("identityProvider") == provider_name:
|
|
433
|
+
no_link = False
|
|
434
|
+
break
|
|
435
|
+
if no_link:
|
|
436
|
+
# link the identities
|
|
437
|
+
claims: dict[str, dict[str, Any]] = token_get_claims(token=token,
|
|
438
|
+
errors=errors,
|
|
439
|
+
logger=logger)
|
|
440
|
+
if claims:
|
|
441
|
+
token_sub: str = claims["payload"]["sub"]
|
|
442
|
+
if logger:
|
|
443
|
+
logger.debug(msg="Creating an association between identifications "
|
|
444
|
+
f"'{user_id}' and '{token_sub}' in IAM server {iam_server}")
|
|
445
|
+
url += f"/{provider_name}"
|
|
446
|
+
body_data: dict[str, Any] = {
|
|
447
|
+
"userId": token_sub,
|
|
448
|
+
"userName": user_id
|
|
449
|
+
}
|
|
450
|
+
__post_data(url=url,
|
|
451
|
+
header_data=header_data,
|
|
452
|
+
body_data=body_data,
|
|
453
|
+
errors=errors,
|
|
454
|
+
logger=logger)
|
|
434
455
|
|
|
435
456
|
|
|
436
457
|
def __get_administrative_token(iam_server: IamServer,
|
|
@@ -450,31 +471,47 @@ def __get_administrative_token(iam_server: IamServer,
|
|
|
450
471
|
# initialize the return variable
|
|
451
472
|
result: str | None = None
|
|
452
473
|
|
|
474
|
+
if logger:
|
|
475
|
+
logger.debug(msg="Requesting a token with "
|
|
476
|
+
f"administrative rights to IAM Server '{iam_server}'")
|
|
477
|
+
|
|
453
478
|
# obtain the IAM server's registry
|
|
454
479
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
455
480
|
errors=errors,
|
|
456
481
|
logger=logger)
|
|
457
|
-
if registry
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
482
|
+
if registry:
|
|
483
|
+
if registry[IamParam.ADMIN_ID] and registry[IamParam.ADMIN_SECRET]:
|
|
484
|
+
header_data: dict[str, str] = {
|
|
485
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
486
|
+
}
|
|
487
|
+
body_data: dict[str, str] = {
|
|
488
|
+
"grant_type": "password",
|
|
489
|
+
"username": registry[IamParam.ADMIN_ID],
|
|
490
|
+
"password": registry[IamParam.ADMIN_SECRET],
|
|
491
|
+
"client_id": "admin-cli"
|
|
492
|
+
}
|
|
493
|
+
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
494
|
+
header_data=header_data,
|
|
495
|
+
body_data=body_data,
|
|
496
|
+
errors=errors,
|
|
497
|
+
logger=logger)
|
|
498
|
+
if token_data:
|
|
499
|
+
# obtain the token
|
|
500
|
+
result = token_data["access_token"]
|
|
501
|
+
if logger:
|
|
502
|
+
logger.debug(msg="Administrative token obtained")
|
|
503
|
+
|
|
504
|
+
elif logger or isinstance(errors, list):
|
|
505
|
+
msg: str = ("Credentials for administrator of realm "
|
|
506
|
+
f"'{registry[IamParam.CLIENT_REALM]}' "
|
|
507
|
+
f"at IAM server '{iam_server}' not provided")
|
|
508
|
+
if logger:
|
|
509
|
+
logger.error(msg=msg)
|
|
510
|
+
if isinstance(errors, list):
|
|
511
|
+
errors.append(msg)
|
|
512
|
+
|
|
513
|
+
elif logger or isinstance(errors, list):
|
|
514
|
+
msg: str = f"Unknown IAM server {iam_server}"
|
|
478
515
|
if logger:
|
|
479
516
|
logger.error(msg=msg)
|
|
480
517
|
if isinstance(errors, list):
|
|
@@ -510,14 +547,19 @@ def __get_client_secret(iam_server: IamServer,
|
|
|
510
547
|
errors=errors,
|
|
511
548
|
logger=logger)
|
|
512
549
|
if token:
|
|
550
|
+
realm: str = registry[IamParam.CLIENT_REALM]
|
|
551
|
+
client_id: str = registry[IamParam.CLIENT_ID]
|
|
552
|
+
if logger:
|
|
553
|
+
logger.debug(msg=f"Obtaining the UUID for client '{client_id}', "
|
|
554
|
+
f"in realm '{realm}' at IAM server '{iam_server}'")
|
|
513
555
|
# obtain the client UUID
|
|
514
|
-
url: str = f"{registry[IamParam.URL_BASE]}/realms/{
|
|
556
|
+
url: str = f"{registry[IamParam.URL_BASE]}/realms/{realm}/clients"
|
|
515
557
|
header_data: dict[str, str] = {
|
|
516
558
|
"Authorization": f"Bearer {token}",
|
|
517
559
|
"Content-Type": "application/json"
|
|
518
560
|
}
|
|
519
561
|
params: dict[str, str] = {
|
|
520
|
-
"clientId":
|
|
562
|
+
"clientId": client_id
|
|
521
563
|
}
|
|
522
564
|
clients: list[dict[str, Any]] = __get_for_data(url=url,
|
|
523
565
|
header_data=header_data,
|
|
@@ -527,6 +569,9 @@ def __get_client_secret(iam_server: IamServer,
|
|
|
527
569
|
if clients:
|
|
528
570
|
# obtain the client's secret password
|
|
529
571
|
client_uuid: str = clients[0]["id"]
|
|
572
|
+
if logger:
|
|
573
|
+
logger.debug(msg=f"Obtaining the secret for client UUID '{client_uuid}', "
|
|
574
|
+
f"in realm '{realm}' at IAM server '{iam_server}'")
|
|
530
575
|
url += f"/{client_uuid}/client-secret"
|
|
531
576
|
reply: dict[str, Any] = __get_for_data(url=url,
|
|
532
577
|
header_data=header_data,
|
|
@@ -573,7 +618,7 @@ def __get_for_data(url: str,
|
|
|
573
618
|
logger.debug(msg=f"GET success, {json.dumps(obj=result,
|
|
574
619
|
ensure_ascii=False)}")
|
|
575
620
|
else:
|
|
576
|
-
# request
|
|
621
|
+
# request failed, report the problem
|
|
577
622
|
msg: str = f"GET failure, status {response.status_code}, reason {response.reason}"
|
|
578
623
|
if hasattr(response, "content") and response.content:
|
|
579
624
|
msg += f", content '{response.content}'"
|
|
@@ -615,7 +660,7 @@ def __post_data(url: str,
|
|
|
615
660
|
headers=header_data,
|
|
616
661
|
data=body_data)
|
|
617
662
|
if response.status_code >= 400:
|
|
618
|
-
# request
|
|
663
|
+
# request failed, report the problem
|
|
619
664
|
msg = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
620
665
|
if hasattr(response, "content") and response.content:
|
|
621
666
|
msg += f", content '{response.content}'"
|
|
@@ -665,9 +710,9 @@ def __post_for_token(iam_server: IamServer,
|
|
|
665
710
|
- "username": <realm-administrator-identification>
|
|
666
711
|
- "password": <realm-administrator-secret>
|
|
667
712
|
|
|
668
|
-
These attributes are then added to *body_data
|
|
713
|
+
These attributes are then added to *body_data*, except for acquiring administrative tokens:
|
|
669
714
|
- "client_id": <client-id>
|
|
670
|
-
- "client_secret": <client-secret>
|
|
715
|
+
- "client_secret": <client-secret>
|
|
671
716
|
|
|
672
717
|
If the operation is successful, the token data is stored in the *IAM* server's registry, and returned.
|
|
673
718
|
Otherwise, *errors* will contain the appropriate error message.
|
|
@@ -699,21 +744,25 @@ def __post_for_token(iam_server: IamServer,
|
|
|
699
744
|
logger=logger)
|
|
700
745
|
if registry:
|
|
701
746
|
# complete the data to send in body of request
|
|
702
|
-
body_data["
|
|
747
|
+
if body_data["grant_type"] != "password":
|
|
748
|
+
body_data["client_id"] = registry[IamParam.CLIENT_ID]
|
|
703
749
|
|
|
704
750
|
# build the URL
|
|
705
751
|
base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
|
|
706
752
|
url: str = f"{base_url}/protocol/openid-connect/token"
|
|
707
|
-
|
|
708
|
-
|
|
753
|
+
# 'client_secret' data must not be shown in log
|
|
754
|
+
msg: str = f"POST {url}, {json.dumps(obj=body_data,
|
|
755
|
+
ensure_ascii=False)}"
|
|
756
|
+
if body_data["grant_type"] != "password":
|
|
757
|
+
# 'client_secret' not required for requesting tokens from staging environments
|
|
758
|
+
client_secret: str = __get_client_secret(iam_server=iam_server,
|
|
759
|
+
errors=None,
|
|
760
|
+
logger=logger)
|
|
761
|
+
if client_secret:
|
|
762
|
+
body_data["client_secret"] = client_secret
|
|
763
|
+
# log the POST
|
|
709
764
|
if logger:
|
|
710
|
-
logger.debug(msg=
|
|
711
|
-
ensure_ascii=False)}")
|
|
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:
|
|
716
|
-
body_data["client_secret"] = client_secret
|
|
765
|
+
logger.debug(msg=msg)
|
|
717
766
|
|
|
718
767
|
# obtain the token
|
|
719
768
|
try:
|
|
@@ -735,7 +784,7 @@ def __post_for_token(iam_server: IamServer,
|
|
|
735
784
|
logger.debug(msg=f"POST success, {json.dumps(obj=result,
|
|
736
785
|
ensure_ascii=False)}")
|
|
737
786
|
else:
|
|
738
|
-
# request
|
|
787
|
+
# request failed, report the problem
|
|
739
788
|
err_msg = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
740
789
|
if hasattr(response, "content") and response.content:
|
|
741
790
|
err_msg += f", content '{response.content}'"
|
|
@@ -796,9 +845,9 @@ def __validate_and_store(iam_server: IamServer,
|
|
|
796
845
|
user_data["access-expiration"] = now + token_data.get("expires_in")
|
|
797
846
|
refresh_exp: int = user_data.get("refresh_expires_in")
|
|
798
847
|
user_data["refresh-expiration"] = (now + refresh_exp) if refresh_exp else sys.maxsize
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
848
|
+
public_key: str = _get_public_key(iam_server=iam_server,
|
|
849
|
+
errors=errors,
|
|
850
|
+
logger=logger)
|
|
802
851
|
recipient_attr = registry[IamParam.RECIPIENT_ATTR]
|
|
803
852
|
login_id = user_data.pop("login-id", None)
|
|
804
853
|
base_url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
|
|
@@ -806,7 +855,7 @@ def __validate_and_store(iam_server: IamServer,
|
|
|
806
855
|
issuer=base_url,
|
|
807
856
|
recipient_id=login_id,
|
|
808
857
|
recipient_attr=recipient_attr,
|
|
809
|
-
|
|
858
|
+
public_key=public_key,
|
|
810
859
|
errors=errors,
|
|
811
860
|
logger=logger)
|
|
812
861
|
if claims:
|
pypomes_iam/iam_common.py
CHANGED
|
@@ -3,10 +3,7 @@ import sys
|
|
|
3
3
|
from datetime import datetime
|
|
4
4
|
from enum import StrEnum, auto
|
|
5
5
|
from logging import Logger
|
|
6
|
-
from pypomes_core import
|
|
7
|
-
APP_PREFIX, TZ_LOCAL,
|
|
8
|
-
env_get_int, env_get_str, env_get_enum, env_get_enums, exc_format
|
|
9
|
-
)
|
|
6
|
+
from pypomes_core import TZ_LOCAL, exc_format
|
|
10
7
|
from pypomes_crypto import crypto_jwk_convert
|
|
11
8
|
from threading import RLock
|
|
12
9
|
from typing import Any, Final
|
|
@@ -16,7 +13,7 @@ class IamServer(StrEnum):
|
|
|
16
13
|
"""
|
|
17
14
|
Supported IAM servers.
|
|
18
15
|
"""
|
|
19
|
-
|
|
16
|
+
JUSBR = auto()
|
|
20
17
|
KEYCLOAK = auto()
|
|
21
18
|
|
|
22
19
|
|
|
@@ -57,85 +54,38 @@ class UserParam(StrEnum):
|
|
|
57
54
|
REDIRECT_URI = "redirect-uri"
|
|
58
55
|
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
as they are reserved for internal use
|
|
86
|
-
|
|
87
|
-
3. for multiple *IAM* servers, specify a comma-separated list of servers in
|
|
88
|
-
*<APP_PREFIX>_IAM_SERVERS*, and for each server, specify the data set above,
|
|
89
|
-
respectively replacing *_IAM_* with *_JUSBR_* or *_KEYCLOAK_*, for the servers listed above
|
|
90
|
-
|
|
91
|
-
:return: the configuration data for the selected *IAM* servers
|
|
92
|
-
"""
|
|
93
|
-
# initialize the return valiable
|
|
94
|
-
result: dict[IamServer, dict[IamParam, Any]] = {}
|
|
95
|
-
|
|
96
|
-
servers: list[IamServer] = []
|
|
97
|
-
single_server: IamServer = env_get_enum(key=f"{APP_PREFIX}_IAM_SERVER",
|
|
98
|
-
enum_class=IamServer)
|
|
99
|
-
if single_server:
|
|
100
|
-
default_setup: bool = True
|
|
101
|
-
servers.append(single_server)
|
|
102
|
-
else:
|
|
103
|
-
default_setup: bool = False
|
|
104
|
-
multi_servers: list[IamServer] = env_get_enums(key=f"{APP_PREFIX}_IAM_SERVERS",
|
|
105
|
-
enum_class=IamServer)
|
|
106
|
-
if multi_servers:
|
|
107
|
-
servers.extend(multi_servers)
|
|
108
|
-
|
|
109
|
-
for server in servers:
|
|
110
|
-
if default_setup:
|
|
111
|
-
prefix: str = "IAM"
|
|
112
|
-
default_setup = False
|
|
113
|
-
else:
|
|
114
|
-
prefix: str = server
|
|
115
|
-
result[server] = {
|
|
116
|
-
IamParam.ADMIN_ID: env_get_str(key=f"{APP_PREFIX}_{prefix}_ADMIN_ID"),
|
|
117
|
-
IamParam.ADMIN_SECRET: env_get_str(key=f"{APP_PREFIX}_{prefix}_ADMIN_PWD"),
|
|
118
|
-
IamParam.CLIENT_ID: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_ID"),
|
|
119
|
-
IamParam.CLIENT_REALM: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_REALM"),
|
|
120
|
-
IamParam.CLIENT_SECRET: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_SECRET"),
|
|
121
|
-
IamParam.LOGIN_TIMEOUT: env_get_int(key=f"{APP_PREFIX}_{prefix}_CLIENT_TIMEOUT"),
|
|
122
|
-
IamParam.ENDPOINT_CALLBACK: env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_CALLBACK"),
|
|
123
|
-
IamParam.ENDPOINT_LOGIN: env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_LOGIN"),
|
|
124
|
-
IamParam.ENDPOINT_LOGOUT: env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_LOGOUT"),
|
|
125
|
-
IamParam.ENDPOINT_TOKEN: env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_TOKEN"),
|
|
126
|
-
IamParam.ENDPOINT_EXCHANGE: env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_EXCHANGE"),
|
|
127
|
-
IamParam.PK_LIFETIME: env_get_str(key=f"{APP_PREFIX}_{prefix}_PK_LIFETIME"),
|
|
128
|
-
IamParam.RECIPIENT_ATTR: env_get_str(key=f"{APP_PREFIX}_{prefix}_RECIPIENT_ATTR"),
|
|
129
|
-
IamParam.URL_BASE: env_get_str(key=f"{APP_PREFIX}_{prefix}_URL_BASE")
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return result
|
|
133
|
-
|
|
57
|
+
# The configuration parameters for the IAM servers are specified dynamically dynamically with *iam_setup()*
|
|
58
|
+
# Specifying configuration parameters with environment variables can be done in two ways:
|
|
59
|
+
#
|
|
60
|
+
# 1. for a single *IAM* server, specify the data set
|
|
61
|
+
# - *<APP_PREFIX>_IAM_ADMIN_ID* (optional, needed if administrative duties are performed)
|
|
62
|
+
# - *<APP_PREFIX>_IAM_ADMIN_PWD* (optional, needed if administrative duties are performed)
|
|
63
|
+
# - *<APP_PREFIX>_IAM_CLIENT_ID* (required)
|
|
64
|
+
# - *<APP_PREFIX>_IAM_CLIENT_REALM* (required)
|
|
65
|
+
# - *<APP_PREFIX>_IAM_CLIENT_SECRET* (required)
|
|
66
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_CALLBACK* (required)
|
|
67
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_EXCHANGE* (required)
|
|
68
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_LOGIN* (required)
|
|
69
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_LOGOUT* (required)
|
|
70
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_PROVIDER* (optional, needed if requesting tokens to providers)
|
|
71
|
+
# - *<APP_PREFIX>_IAM_ENDPOINT_TOKEN* (required)
|
|
72
|
+
# - *<APP_PREFIX>_IAM_LOGIN_TIMEOUT* (optional, defaults to no timeout)
|
|
73
|
+
# - *<APP_PREFIX>_IAM_PK_LIFETIME* (optional, defaults to non-terminating lifetime)
|
|
74
|
+
# - *<APP_PREFIX>_IAM_RECIPIENT_ATTR* (required)
|
|
75
|
+
# - *<APP_PREFIX>_IAM_URL_BASE* (required)
|
|
76
|
+
#
|
|
77
|
+
# 2. for multiple *IAM* servers, specify the data set above for each server,
|
|
78
|
+
# respectively replacing *IAM* with a name in *IamServer* (currently, *JUSBR* and *KEYCLOAK* are supported).
|
|
79
|
+
#
|
|
80
|
+
# 3. the parameters *PUBLIC_KEY*, *PK_EXPIRATION*, and *USERS* cannot be assigned values,
|
|
81
|
+
# as they are reserved for internal use
|
|
134
82
|
|
|
135
83
|
# registry structure:
|
|
136
84
|
# { <IamServer>:
|
|
137
85
|
# {
|
|
138
86
|
# "base-url": <str>,
|
|
87
|
+
# "admin-id": <str>,
|
|
88
|
+
# "admin-secret": <str>,
|
|
139
89
|
# "client-id": <str>,
|
|
140
90
|
# "client-secret": <str>,
|
|
141
91
|
# "client-realm": <str,
|
|
@@ -162,7 +112,7 @@ def __get_iam_data() -> dict[IamServer, dict[IamParam, Any]]:
|
|
|
162
112
|
# },
|
|
163
113
|
# ...
|
|
164
114
|
# }
|
|
165
|
-
_IAM_SERVERS: Final[dict[IamServer, dict[IamParam, Any]]] =
|
|
115
|
+
_IAM_SERVERS: Final[dict[IamServer, dict[IamParam, Any]]] = {}
|
|
166
116
|
|
|
167
117
|
|
|
168
118
|
# the lock protecting the data in '_IAM_SERVERS'
|
|
@@ -186,7 +136,7 @@ def _iam_server_from_endpoint(endpoint: str,
|
|
|
186
136
|
|
|
187
137
|
for iam_server in _IAM_SERVERS:
|
|
188
138
|
if endpoint.startswith(iam_server):
|
|
189
|
-
result =
|
|
139
|
+
result = iam_server
|
|
190
140
|
break
|
|
191
141
|
|
|
192
142
|
if not result:
|
|
@@ -408,21 +358,15 @@ def _get_iam_registry(iam_server: IamServer,
|
|
|
408
358
|
:param logger: optional logger
|
|
409
359
|
:return: the registry associated with *iam_server*, or *None* if the server is unknown
|
|
410
360
|
"""
|
|
411
|
-
#
|
|
412
|
-
result: dict[str, Any]
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
result = None
|
|
421
|
-
msg = f"Unknown IAM server '{iam_server}'"
|
|
422
|
-
if logger:
|
|
423
|
-
logger.error(msg=msg)
|
|
424
|
-
if isinstance(errors, list):
|
|
425
|
-
errors.append(msg)
|
|
361
|
+
# assign the return variable
|
|
362
|
+
result: dict[str, Any] = _IAM_SERVERS.get(iam_server)
|
|
363
|
+
|
|
364
|
+
if not result:
|
|
365
|
+
msg = f"Unknown IAM server '{iam_server}'"
|
|
366
|
+
if logger:
|
|
367
|
+
logger.error(msg=msg)
|
|
368
|
+
if isinstance(errors, list):
|
|
369
|
+
errors.append(msg)
|
|
426
370
|
|
|
427
371
|
return result
|
|
428
372
|
|
pypomes_iam/iam_pomes.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from flask import Flask
|
|
2
2
|
from logging import Logger
|
|
3
|
+
from pypomes_core import APP_PREFIX, env_get_int, env_get_str
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from .iam_common import (
|
|
@@ -16,17 +17,17 @@ def iam_setup(flask_app: Flask,
|
|
|
16
17
|
base_url: str,
|
|
17
18
|
client_id: str,
|
|
18
19
|
client_realm: str,
|
|
20
|
+
client_secret: str | None,
|
|
19
21
|
recipient_attribute: str,
|
|
20
|
-
client_secret: str = None,
|
|
21
|
-
login_timeout: int = None,
|
|
22
22
|
admin_id: str = None,
|
|
23
23
|
admin_secret: str = None,
|
|
24
|
+
login_timeout: int = None,
|
|
24
25
|
public_key_lifetime: int = None,
|
|
25
26
|
callback_endpoint: str = None,
|
|
27
|
+
exchange_endpoint: str = None,
|
|
26
28
|
login_endpoint: str = None,
|
|
27
29
|
logout_endpoint: str = None,
|
|
28
|
-
token_endpoint: str = None
|
|
29
|
-
exchange_endpoint: str = None) -> None:
|
|
30
|
+
token_endpoint: str = None) -> None:
|
|
30
31
|
"""
|
|
31
32
|
Establish the provided parameters for configuring the *IAM* server *iam_server*.
|
|
32
33
|
|
|
@@ -39,21 +40,21 @@ def iam_setup(flask_app: Flask,
|
|
|
39
40
|
the first time it is needed.
|
|
40
41
|
|
|
41
42
|
:param flask_app: the Flask application
|
|
42
|
-
:param iam_server: identifies the supported *IAM* server (*jusbr* or *keycloak*)
|
|
43
|
+
:param iam_server: identifies the supported *IAM* server (currently, *jusbr* or *keycloak*)
|
|
43
44
|
:param base_url: base URL to request services
|
|
44
45
|
:param client_id: the client's identification with the *IAM* server
|
|
45
46
|
:param client_realm: the client realm
|
|
46
|
-
:param recipient_attribute: attribute in the token's payload holding the token's subject
|
|
47
47
|
:param client_secret: the client's password with the *IAM* server
|
|
48
|
-
:param
|
|
48
|
+
:param recipient_attribute: attribute in the token's payload holding the token's subject
|
|
49
49
|
:param admin_id: identifies the realm administrator
|
|
50
50
|
:param admin_secret: password for the realm administrator
|
|
51
|
+
:param login_timeout: timeout for login authentication (in seconds,defaults to no timeout)
|
|
51
52
|
:param public_key_lifetime: how long to use *IAM* server's public key, before refreshing it (in seconds)
|
|
52
53
|
:param callback_endpoint: endpoint for the callback from the front end
|
|
54
|
+
:param exchange_endpoint: endpoint for requesting token exchange
|
|
53
55
|
:param login_endpoint: endpoint for redirecting user to the *IAM* server's login page
|
|
54
56
|
:param logout_endpoint: endpoint for terminating user access
|
|
55
57
|
:param token_endpoint: endpoint for retrieving authentication token
|
|
56
|
-
:param exchange_endpoint: endpoint for requesting token exchange
|
|
57
58
|
"""
|
|
58
59
|
|
|
59
60
|
# configure the Keycloak registry
|
|
@@ -63,18 +64,15 @@ def iam_setup(flask_app: Flask,
|
|
|
63
64
|
IamParam.CLIENT_ID: client_id,
|
|
64
65
|
IamParam.CLIENT_REALM: client_realm,
|
|
65
66
|
IamParam.CLIENT_SECRET: client_secret,
|
|
66
|
-
IamParam.LOGIN_TIMEOUT: login_timeout,
|
|
67
67
|
IamParam.RECIPIENT_ATTR: recipient_attribute,
|
|
68
|
+
IamParam.ADMIN_ID: admin_id,
|
|
69
|
+
IamParam.ADMIN_SECRET: admin_secret,
|
|
70
|
+
IamParam.LOGIN_TIMEOUT: login_timeout,
|
|
71
|
+
IamParam.PK_LIFETIME: public_key_lifetime,
|
|
68
72
|
IamParam.PK_EXPIRATION: 0,
|
|
69
73
|
IamParam.PUBLIC_KEY: None,
|
|
70
74
|
IamParam.USERS: {}
|
|
71
75
|
}
|
|
72
|
-
if admin_id and admin_secret:
|
|
73
|
-
IamParam.ADMIN_ID = admin_id
|
|
74
|
-
IamParam.ADMIN_SECRET = admin_secret
|
|
75
|
-
|
|
76
|
-
if public_key_lifetime:
|
|
77
|
-
IamParam.PK_LIFETIME = public_key_lifetime
|
|
78
76
|
|
|
79
77
|
# establish the endpoints
|
|
80
78
|
if callback_endpoint:
|
|
@@ -104,6 +102,34 @@ def iam_setup(flask_app: Flask,
|
|
|
104
102
|
methods=["POST"])
|
|
105
103
|
|
|
106
104
|
|
|
105
|
+
def iam_get_env_parameters(iam_prefix: str = None) -> dict[str, Any]:
|
|
106
|
+
"""
|
|
107
|
+
Retrieve the set parameters for a *IAM* server from the environment.
|
|
108
|
+
|
|
109
|
+
the parameters are returned ready to be used as a '**kwargs' parameter set in a call to *iam_setup()*,
|
|
110
|
+
and sorted in the order appropriate to use them instead with a '*args' parameter set.
|
|
111
|
+
|
|
112
|
+
:param iam_prefix: the prefix classifying the parameters
|
|
113
|
+
:return: the sorted parameters classified by *prefix*
|
|
114
|
+
"""
|
|
115
|
+
return {
|
|
116
|
+
"base_url": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_URL_AUTH_BASE"),
|
|
117
|
+
"client_id": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_CLIENT_ID"),
|
|
118
|
+
"client_realm": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_CLIENT_REALM"),
|
|
119
|
+
"client_secret": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_CLIENT_SECRET"),
|
|
120
|
+
"recipient_attribute": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_RECIPIENT_ATTR"),
|
|
121
|
+
"admin_id": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ADMIN_ID"),
|
|
122
|
+
"admin_secret": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ADMIN_SECRET"),
|
|
123
|
+
"login_timeout": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_LOGIN_TIMEOUT"),
|
|
124
|
+
"public_key_lifetime": env_get_int(key=f"{APP_PREFIX}_{iam_prefix}_PUBLIC_KEY_LIFETIME"),
|
|
125
|
+
"callback_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_CALLBACK"),
|
|
126
|
+
"exchange_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_EXCHANGE"),
|
|
127
|
+
"login_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_LOGIN"),
|
|
128
|
+
"logout_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_LOGOUT"),
|
|
129
|
+
"token_endpoint": env_get_str(key=f"{APP_PREFIX}_{iam_prefix}_ENDPOINT_TOKEN")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
107
133
|
def iam_get_token(iam_server: IamServer,
|
|
108
134
|
user_id: str,
|
|
109
135
|
errors: list[str] = None,
|
pypomes_iam/iam_services.py
CHANGED
|
@@ -168,7 +168,7 @@ def service_login() -> Response:
|
|
|
168
168
|
# methods=["GET"])
|
|
169
169
|
def service_logout() -> Response:
|
|
170
170
|
"""
|
|
171
|
-
Entry point for the
|
|
171
|
+
Entry point for the IAM server's logout service.
|
|
172
172
|
|
|
173
173
|
The user is identified by the attribute *user-id* or "login", provided as a request parameter.
|
|
174
174
|
If successful, remove all data relating to the user from the *IAM* server's registry.
|
|
@@ -214,7 +214,7 @@ def service_logout() -> Response:
|
|
|
214
214
|
# methods=["POST"])
|
|
215
215
|
def service_callback() -> Response:
|
|
216
216
|
"""
|
|
217
|
-
Entry point for the callback from
|
|
217
|
+
Entry point for the callback from the IAM server on authentication operation.
|
|
218
218
|
|
|
219
219
|
This callback is invoked from a front-end application after a successful login at the
|
|
220
220
|
*IAM* server's login page, forwarding the data received. In a typical OAuth2 flow faction,
|
pypomes_iam/provider_pomes.py
CHANGED
|
@@ -106,76 +106,137 @@ def provider_get_token(provider_id: str,
|
|
|
106
106
|
# initialize the return variable
|
|
107
107
|
result: str | None = None
|
|
108
108
|
|
|
109
|
-
err_msg: str | None = None
|
|
110
109
|
with _provider_lock:
|
|
111
110
|
provider: dict[str, Any] = _provider_registry.get(provider_id)
|
|
112
111
|
if provider:
|
|
113
|
-
now:
|
|
114
|
-
if now
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
body_data[custom_auth[0]] = user
|
|
122
|
-
body_data[custom_auth[1]] = pwd
|
|
123
|
-
else:
|
|
124
|
-
enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
|
|
125
|
-
headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
|
|
112
|
+
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
113
|
+
if now < provider.get(ProviderParam.ACCESS_EXPIRATION):
|
|
114
|
+
# retrieve the stored access token
|
|
115
|
+
result = provider.get(ProviderParam.ACCESS_TOKEN)
|
|
116
|
+
else:
|
|
117
|
+
# access token has expired
|
|
118
|
+
header_data: dict[str, str] | None = None
|
|
119
|
+
body_data: dict[str, str] | None = None
|
|
126
120
|
url: str = provider.get(ProviderParam.URL)
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
121
|
+
refresh_token: str = provider.get(ProviderParam.REFRESH_TOKEN)
|
|
122
|
+
if refresh_token:
|
|
123
|
+
# refresh token exists
|
|
124
|
+
refresh_expiration: int = provider.get(ProviderParam.REFRESH_EXPIRATION)
|
|
125
|
+
if now < refresh_expiration:
|
|
126
|
+
# refresh token has not expired
|
|
127
|
+
header_data: dict[str, str] = {
|
|
128
|
+
"Content-Type": "application/json"
|
|
129
|
+
}
|
|
130
|
+
body_data: dict[str, str] = {
|
|
131
|
+
"grant_type": "refresh_token",
|
|
132
|
+
"refresh_token": refresh_token
|
|
133
|
+
}
|
|
134
|
+
if not body_data:
|
|
135
|
+
# refresh token does not exist or has expired
|
|
136
|
+
user: str = provider.get(ProviderParam.USER)
|
|
137
|
+
pwd: str = provider.get(ProviderParam.PWD)
|
|
138
|
+
headers_data: dict[str, str] = provider.get(ProviderParam.HEADER_DATA) or {}
|
|
139
|
+
body_data: dict[str, str] = provider.get(ProviderParam.BODY_DATA) or {}
|
|
140
|
+
custom_auth: tuple[str, str] = provider.get(ProviderParam.CUSTOM_AUTH)
|
|
141
|
+
if custom_auth:
|
|
142
|
+
body_data[custom_auth[0]] = user
|
|
143
|
+
body_data[custom_auth[1]] = pwd
|
|
148
144
|
else:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
errors
|
|
174
|
-
|
|
175
|
-
logger.error(msg=err_msg)
|
|
176
|
-
else:
|
|
177
|
-
result = provider.get(ProviderParam.ACCESS_TOKEN)
|
|
145
|
+
enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
|
|
146
|
+
headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
|
|
147
|
+
|
|
148
|
+
# obtain the token
|
|
149
|
+
token_data: dict[str, Any] = __post_for_token(url=url,
|
|
150
|
+
header_data=header_data,
|
|
151
|
+
body_data=body_data,
|
|
152
|
+
errors=errors,
|
|
153
|
+
logger=logger)
|
|
154
|
+
if token_data:
|
|
155
|
+
result = token_data.get("access_token")
|
|
156
|
+
provider[ProviderParam.ACCESS_TOKEN] = result
|
|
157
|
+
provider[ProviderParam.ACCESS_EXPIRATION] = now + token_data.get("expires_in")
|
|
158
|
+
refresh_token = token_data.get("refresh_token")
|
|
159
|
+
if refresh_token:
|
|
160
|
+
provider[ProviderParam.REFRESH_TOKEN] = refresh_token
|
|
161
|
+
refresh_exp: int = token_data.get("refresh_expires_in")
|
|
162
|
+
provider[ProviderParam.REFRESH_EXPIRATION] = (now + refresh_exp) \
|
|
163
|
+
if refresh_exp else sys.maxsize
|
|
164
|
+
|
|
165
|
+
elif logger or isinstance(errors, list):
|
|
166
|
+
msg: str = f"Unknown provider '{provider_id}'"
|
|
167
|
+
if logger:
|
|
168
|
+
logger.error(msg=msg)
|
|
169
|
+
if isinstance(errors, list):
|
|
170
|
+
errors.append(msg)
|
|
178
171
|
|
|
179
172
|
return result
|
|
180
173
|
|
|
181
174
|
|
|
175
|
+
def __post_for_token(url: str,
|
|
176
|
+
header_data: dict[str, str],
|
|
177
|
+
body_data: dict[str, Any],
|
|
178
|
+
errors: list[str] | None,
|
|
179
|
+
logger: Logger | None) -> dict[str, Any] | None:
|
|
180
|
+
"""
|
|
181
|
+
Send a *POST* request to *url* and return the token data obtained.
|
|
182
|
+
|
|
183
|
+
Token acquisition and token refresh are the two types of requests contemplated herein.
|
|
184
|
+
For the former, *header_data* and *body_data* will have contents customized to the specific provider,
|
|
185
|
+
whereas the latter's *body_data* will contain these two attributes:
|
|
186
|
+
- "grant_type": "refresh_token"
|
|
187
|
+
- "refresh_token": <current-refresh-token>
|
|
188
|
+
|
|
189
|
+
The typical data set returned contains the following attributes:
|
|
190
|
+
{
|
|
191
|
+
"token_type": "Bearer",
|
|
192
|
+
"access_token": <str>,
|
|
193
|
+
"expires_in": <number-of-seconds>,
|
|
194
|
+
"refresh_token": <str>,
|
|
195
|
+
"refesh_expires_in": <number-of-seconds>
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
:param url: the target URL
|
|
199
|
+
:param header_data: the data to send in the header of the request
|
|
200
|
+
:param body_data: the data to send in the body of the request
|
|
201
|
+
:param errors: incidental errors
|
|
202
|
+
:param logger: optional logger
|
|
203
|
+
:return: the token data, or *None* if error
|
|
204
|
+
"""
|
|
205
|
+
# initialize the return variable
|
|
206
|
+
result: dict[str, Any] | None = None
|
|
207
|
+
|
|
208
|
+
# log the POST
|
|
209
|
+
if logger:
|
|
210
|
+
logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
|
|
211
|
+
ensure_ascii=False)}")
|
|
212
|
+
try:
|
|
213
|
+
response: requests.Response = requests.post(url=url,
|
|
214
|
+
data=body_data,
|
|
215
|
+
headers=header_data,
|
|
216
|
+
timeout=None)
|
|
217
|
+
if response.status_code == 200:
|
|
218
|
+
# request succeeded
|
|
219
|
+
result = response.json()
|
|
220
|
+
if logger:
|
|
221
|
+
logger.debug(msg=f"POST success, status {response.status_code}")
|
|
222
|
+
else:
|
|
223
|
+
# request failed, report the problem
|
|
224
|
+
msg: str = (f"POST failure, "
|
|
225
|
+
f"status {response.status_code}, reason {response.reason}")
|
|
226
|
+
if hasattr(response, "content") and response.content:
|
|
227
|
+
msg += f", content '{response.content}'"
|
|
228
|
+
if logger:
|
|
229
|
+
logger.error(msg=msg)
|
|
230
|
+
if isinstance(errors, list):
|
|
231
|
+
errors.append(msg)
|
|
232
|
+
except Exception as e:
|
|
233
|
+
# the operation raised an exception
|
|
234
|
+
err_msg = exc_format(exc=e,
|
|
235
|
+
exc_info=sys.exc_info())
|
|
236
|
+
msg: str = f"POST error, {err_msg}"
|
|
237
|
+
if logger:
|
|
238
|
+
logger.debug(msg=msg)
|
|
239
|
+
if isinstance(errors, list):
|
|
240
|
+
errors.append(msg)
|
|
241
|
+
|
|
242
|
+
return result
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_iam
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.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
|
|
@@ -10,7 +10,6 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Programming Language :: Python :: 3
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
|
-
Requires-Dist: cachetools>=6.2.1
|
|
14
13
|
Requires-Dist: flask>=3.1.2
|
|
15
14
|
Requires-Dist: pyjwt>=2.10.1
|
|
16
15
|
Requires-Dist: pypomes-core>=2.8.1
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pypomes_iam/__init__.py,sha256=_6tSFfjuU-5p6TAMqNLHSL6IQmaJMSYuEW-TG3ybhTI,1044
|
|
2
|
+
pypomes_iam/iam_actions.py,sha256=0PC7Z7xsepxynmgjMvKZLjLdvC3s5kGk_dqCFrG4Qs8,42083
|
|
3
|
+
pypomes_iam/iam_common.py,sha256=ki_-m6fqJqUbGjgTD41r9zaE-FOXgA_c_tLisIYYTfU,15457
|
|
4
|
+
pypomes_iam/iam_pomes.py,sha256=_kLnrZG25XhJsIv3wqDl_2sIJ2ho_2TIMKrPCyPmA7Q,7362
|
|
5
|
+
pypomes_iam/iam_services.py,sha256=uUD333SaTbo8MGRyIp5GGil7HAupK73ym4_bKtGkPFg,15878
|
|
6
|
+
pypomes_iam/provider_pomes.py,sha256=3mMj5LQs53YEINUEOfFBAxOwOP3aOR_szlE4daEBLK0,10523
|
|
7
|
+
pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
|
|
8
|
+
pypomes_iam-0.6.9.dist-info/METADATA,sha256=tPsAu2TWT9hb3vu-KHnYT77TYZytoCQfs78uxWLlLH8,661
|
|
9
|
+
pypomes_iam-0.6.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
+
pypomes_iam-0.6.9.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
|
|
11
|
+
pypomes_iam-0.6.9.dist-info/RECORD,,
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
pypomes_iam/__init__.py,sha256=f-2W_zrCmXExubJPExQrhAwGpiQCmybEC_wguRYFHsw,994
|
|
2
|
-
pypomes_iam/iam_actions.py,sha256=Bmd8rBg3948Afsg10B6B1ZrFY4wYtbxi55rX4Rlqiyk,39167
|
|
3
|
-
pypomes_iam/iam_common.py,sha256=I-HtwpvrhByTbOoSQrMktjpbYgeIPlYM1YC6wkFUhI4,18251
|
|
4
|
-
pypomes_iam/iam_pomes.py,sha256=BetEVGv41wkcP9E1wRvYiQgmJElDXH4Iz8qgf7iH6X0,5617
|
|
5
|
-
pypomes_iam/iam_services.py,sha256=IkCjrKDX1Ix7BiHh-BL3VKz5xogcNC8prXkHyJzQoZ8,15862
|
|
6
|
-
pypomes_iam/provider_pomes.py,sha256=N0nL9_hgHmAjG9JKFoXC33zk8b1ckPG1veu1jTp-2JE,8045
|
|
7
|
-
pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
|
|
8
|
-
pypomes_iam-0.5.8.dist-info/METADATA,sha256=Q60cQU69Gbay_IjFewESe9P4O4Z6mQ5tz_tYvw7yIMM,694
|
|
9
|
-
pypomes_iam-0.5.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
-
pypomes_iam-0.5.8.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
|
|
11
|
-
pypomes_iam-0.5.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|