pypomes-iam 0.7.0__tar.gz → 0.7.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/PKG-INFO +1 -1
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/pyproject.toml +1 -1
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/src/pypomes_iam/iam_actions.py +56 -13
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/src/pypomes_iam/iam_services.py +8 -9
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/.gitignore +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/LICENSE +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/README.md +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/src/__init__.py +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/src/pypomes_iam/__init__.py +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/src/pypomes_iam/iam_common.py +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/src/pypomes_iam/iam_pomes.py +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/src/pypomes_iam/provider_pomes.py +0 -0
- {pypomes_iam-0.7.0 → pypomes_iam-0.7.6}/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.7.
|
|
3
|
+
Version: 0.7.6
|
|
4
4
|
Summary: A collection of Python pomes, penyeach (IAM modules)
|
|
5
5
|
Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-IAM
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-IAM/issues
|
|
@@ -26,6 +26,7 @@ def action_login(iam_server: IamServer,
|
|
|
26
26
|
These are the expected attributes in *args*:
|
|
27
27
|
- user-id: optional, identifies the reference user (alias: 'login')
|
|
28
28
|
- redirect-uri: a parameter to be added to the query part of the returned URL
|
|
29
|
+
-target-idp: optionally, identify a target identity provider for the login operation
|
|
29
30
|
|
|
30
31
|
If provided, the user identification will be validated against the authorization data
|
|
31
32
|
returned by *iam_server* upon login. On success, the appropriate URL for invoking
|
|
@@ -43,9 +44,14 @@ def action_login(iam_server: IamServer,
|
|
|
43
44
|
# obtain the optional user's identification
|
|
44
45
|
user_id: str = args.get("user-id") or args.get("login")
|
|
45
46
|
|
|
47
|
+
# obtain the optional target identity provider
|
|
48
|
+
target_idp: str = args.get("target-idp")
|
|
49
|
+
|
|
46
50
|
# build the user data
|
|
47
51
|
# ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
|
|
48
52
|
oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
53
|
+
if target_idp:
|
|
54
|
+
oauth_state += f"idp={target_idp}"
|
|
49
55
|
|
|
50
56
|
with _iam_lock:
|
|
51
57
|
# retrieve the user data from the IAM server's registry
|
|
@@ -75,6 +81,10 @@ def action_login(iam_server: IamServer,
|
|
|
75
81
|
f"&client_id={registry[IamParam.CLIENT_ID]}"
|
|
76
82
|
f"&redirect_uri={redirect_uri}"
|
|
77
83
|
f"&state={oauth_state}")
|
|
84
|
+
if target_idp:
|
|
85
|
+
# HAZARD: the name 'kc_idp_hint' is Keycloak-specific
|
|
86
|
+
result += f"&kc_idp_hint={target_idp}"
|
|
87
|
+
|
|
78
88
|
return result
|
|
79
89
|
|
|
80
90
|
|
|
@@ -240,6 +250,10 @@ def action_callback(iam_server: IamServer,
|
|
|
240
250
|
if int(datetime.now(tz=TZ_LOCAL).timestamp()) > expiration:
|
|
241
251
|
errors.append("Operation timeout")
|
|
242
252
|
else:
|
|
253
|
+
pos: int = oauth_state.rfind("idp=")
|
|
254
|
+
target_idp: str = oauth_state[pos+4:] if pos > 0 else None
|
|
255
|
+
target_iam = IamServer(target_idp) if target_idp in IamServer else None
|
|
256
|
+
target_data: dict[str, Any] = user_data.copy() if target_iam else None
|
|
243
257
|
users.pop(oauth_state)
|
|
244
258
|
code: str = args.get("code")
|
|
245
259
|
header_data: dict[str, str] = {
|
|
@@ -264,6 +278,33 @@ def action_callback(iam_server: IamServer,
|
|
|
264
278
|
now=now,
|
|
265
279
|
errors=errors,
|
|
266
280
|
logger=logger)
|
|
281
|
+
if target_iam:
|
|
282
|
+
if logger:
|
|
283
|
+
logger.debug(msg=f"Requesting to IAM server '{iam_server}' "
|
|
284
|
+
f"the token issued by '{target_iam}' ")
|
|
285
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server,
|
|
286
|
+
errors=errors,
|
|
287
|
+
logger=logger)
|
|
288
|
+
url: str = f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
|
|
289
|
+
url += f"/broker/{target_idp}/token"
|
|
290
|
+
header_data: dict[str, str] = {
|
|
291
|
+
"Authorization": f"Bearer {result[1]}",
|
|
292
|
+
"Content-Type": "application/json"
|
|
293
|
+
}
|
|
294
|
+
token_data = __get_for_data(url=url,
|
|
295
|
+
header_data=header_data,
|
|
296
|
+
params=None,
|
|
297
|
+
errors=errors,
|
|
298
|
+
logger=logger)
|
|
299
|
+
if not errors:
|
|
300
|
+
token_info: tuple[str, str] = __validate_and_store(iam_server=target_iam,
|
|
301
|
+
user_data=target_data,
|
|
302
|
+
token_data=token_data,
|
|
303
|
+
now=now,
|
|
304
|
+
errors=errors,
|
|
305
|
+
logger=logger)
|
|
306
|
+
if token_info and logger:
|
|
307
|
+
logger.debug(msg=f"Token obtained: {json.dumps(obj=token_info)}")
|
|
267
308
|
else:
|
|
268
309
|
msg: str = f"State '{oauth_state}' not found in {iam_server}'s registry"
|
|
269
310
|
if logger:
|
|
@@ -277,7 +318,7 @@ def action_callback(iam_server: IamServer,
|
|
|
277
318
|
def action_exchange(iam_server: IamServer,
|
|
278
319
|
args: dict[str, Any],
|
|
279
320
|
errors: list[str] = None,
|
|
280
|
-
logger: Logger = None) ->
|
|
321
|
+
logger: Logger = None) -> tuple[str, str]:
|
|
281
322
|
"""
|
|
282
323
|
Request *iam_server* to issue a token in exchange for the token obtained from another *IAM* server.
|
|
283
324
|
|
|
@@ -298,10 +339,10 @@ def action_exchange(iam_server: IamServer,
|
|
|
298
339
|
:param args: the arguments passed when requesting the service
|
|
299
340
|
:param errors: incidental errors
|
|
300
341
|
:param logger: optional logger
|
|
301
|
-
:return: the
|
|
342
|
+
:return: a tuple containing the reference user identification and the token obtained, or *None* if error
|
|
302
343
|
"""
|
|
303
344
|
# initialize the return variable
|
|
304
|
-
result:
|
|
345
|
+
result: tuple[str, str] | None = None
|
|
305
346
|
|
|
306
347
|
# obtain the user's identification
|
|
307
348
|
user_id: str = args.get("user-id") or args.get("login")
|
|
@@ -451,13 +492,13 @@ def __assert_link(iam_server: IamServer,
|
|
|
451
492
|
logger.debug(msg="Creating an association between identifications "
|
|
452
493
|
f"'{user_id}' and '{token_sub}' in IAM server {iam_server}")
|
|
453
494
|
url += f"/{provider_name}"
|
|
454
|
-
|
|
495
|
+
json_data: dict[str, Any] = {
|
|
455
496
|
"userId": token_sub,
|
|
456
497
|
"userName": user_id
|
|
457
498
|
}
|
|
458
|
-
|
|
499
|
+
__post_json(url=url,
|
|
459
500
|
header_data=header_data,
|
|
460
|
-
|
|
501
|
+
json_data=json_data,
|
|
461
502
|
errors=errors,
|
|
462
503
|
logger=logger)
|
|
463
504
|
|
|
@@ -646,27 +687,27 @@ def __get_for_data(url: str,
|
|
|
646
687
|
return result
|
|
647
688
|
|
|
648
689
|
|
|
649
|
-
def
|
|
690
|
+
def __post_json(url: str,
|
|
650
691
|
header_data: dict[str, str],
|
|
651
|
-
|
|
692
|
+
json_data: dict[str, Any],
|
|
652
693
|
errors: list[str] | None,
|
|
653
694
|
logger: Logger | None) -> None:
|
|
654
695
|
"""
|
|
655
696
|
Submit a *POST* request to *url*.
|
|
656
697
|
|
|
657
698
|
:param header_data: the data to send in the header of the request
|
|
658
|
-
:param
|
|
699
|
+
:param json_data: the JSON data to send in the request
|
|
659
700
|
:param errors: incidental errors
|
|
660
701
|
:param logger: optional logger
|
|
661
702
|
"""
|
|
662
703
|
# log the POST
|
|
663
704
|
if logger:
|
|
664
|
-
logger.debug(msg=f"POST {url}, {json.dumps(obj=
|
|
705
|
+
logger.debug(msg=f"POST {url}, {json.dumps(obj=json_data,
|
|
665
706
|
ensure_ascii=False)}")
|
|
666
707
|
try:
|
|
667
|
-
response: requests.Response = requests.
|
|
668
|
-
|
|
669
|
-
|
|
708
|
+
response: requests.Response = requests.post(url=url,
|
|
709
|
+
headers=header_data,
|
|
710
|
+
json=json_data)
|
|
670
711
|
if response.status_code >= 400:
|
|
671
712
|
# request failed, report the problem
|
|
672
713
|
msg = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
@@ -839,6 +880,8 @@ def __validate_and_store(iam_server: IamServer,
|
|
|
839
880
|
# initialize the return variable
|
|
840
881
|
result: tuple[str, str] | None = None
|
|
841
882
|
|
|
883
|
+
if logger:
|
|
884
|
+
logger.debug(msg=f"Validating and storing the token")
|
|
842
885
|
with _iam_lock:
|
|
843
886
|
# retrieve the IAM server's registry
|
|
844
887
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
@@ -120,6 +120,7 @@ def service_login() -> Response:
|
|
|
120
120
|
These are the expected request parameters:
|
|
121
121
|
- user-id: optional, identifies the reference user (alias: 'login')
|
|
122
122
|
- redirect-uri: a parameter to be added to the query part of the returned URL
|
|
123
|
+
-target-idp: optionally, identify a target identity provider for the login operation
|
|
123
124
|
|
|
124
125
|
If provided, the user identification will be validated against the authorization data
|
|
125
126
|
returned by *iam_server* upon login. On success, the following JSON, containing the appropriate
|
|
@@ -338,13 +339,10 @@ def service_exchange() -> Response:
|
|
|
338
339
|
If the exchange is successful, the token data is stored in the *IAM* server's registry, and returned.
|
|
339
340
|
Otherwise, *errors* will contain the appropriate error message.
|
|
340
341
|
|
|
341
|
-
On success, the
|
|
342
|
+
On success, the returned *Response* will contain the following JSON:
|
|
342
343
|
{
|
|
343
|
-
"
|
|
344
|
-
"
|
|
345
|
-
"expires_in": <number-of-seconds>,
|
|
346
|
-
"refresh_token": <str>,
|
|
347
|
-
"refesh_expires_in": <number-of-seconds>
|
|
344
|
+
"user-id": <reference-user-identification>,
|
|
345
|
+
"access-token": <token>
|
|
348
346
|
}
|
|
349
347
|
|
|
350
348
|
:return: *Response* containing the token data, or *BAD REQUEST*
|
|
@@ -360,10 +358,10 @@ def service_exchange() -> Response:
|
|
|
360
358
|
errors=errors,
|
|
361
359
|
logger=__IAM_LOGGER)
|
|
362
360
|
# exchange the token
|
|
363
|
-
|
|
361
|
+
token_info: tuple[str, str] | None = None
|
|
364
362
|
if iam_server:
|
|
365
363
|
errors: list[str] = []
|
|
366
|
-
|
|
364
|
+
token_info = action_exchange(iam_server=iam_server,
|
|
367
365
|
args=request.args,
|
|
368
366
|
errors=errors,
|
|
369
367
|
logger=__IAM_LOGGER)
|
|
@@ -372,7 +370,8 @@ def service_exchange() -> Response:
|
|
|
372
370
|
result = Response(response="; ".join(errors),
|
|
373
371
|
status=400)
|
|
374
372
|
else:
|
|
375
|
-
result = jsonify(
|
|
373
|
+
result = jsonify({"user-id": token_info[0],
|
|
374
|
+
"access-token": token_info[1]})
|
|
376
375
|
|
|
377
376
|
# log the response
|
|
378
377
|
if __IAM_LOGGER:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|