pypomes-iam 0.7.9__tar.gz → 0.8.5__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.9 → pypomes_iam-0.8.5}/PKG-INFO +2 -2
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/pyproject.toml +2 -2
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/src/pypomes_iam/__init__.py +8 -10
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_actions.py +121 -16
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_common.py +4 -3
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_pomes.py +19 -6
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_services.py +120 -43
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/src/pypomes_iam/provider_pomes.py +43 -37
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/.gitignore +0 -0
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/LICENSE +0 -0
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/README.md +0 -0
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/src/__init__.py +0 -0
- {pypomes_iam-0.7.9 → pypomes_iam-0.8.5}/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.8.5
|
|
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
|
|
@@ -12,6 +12,6 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: >=3.12
|
|
13
13
|
Requires-Dist: flask>=3.1.2
|
|
14
14
|
Requires-Dist: pyjwt>=2.10.1
|
|
15
|
-
Requires-Dist: pypomes-core>=2.8.
|
|
15
|
+
Requires-Dist: pypomes-core>=2.8.6
|
|
16
16
|
Requires-Dist: pypomes-crypto>=0.4.8
|
|
17
17
|
Requires-Dist: requests>=2.32.5
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pypomes_iam"
|
|
9
|
-
version = "0.
|
|
9
|
+
version = "0.8.5"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name="GT Nunes", email="wisecoder01@gmail.com" }
|
|
12
12
|
]
|
|
@@ -21,7 +21,7 @@ classifiers = [
|
|
|
21
21
|
dependencies = [
|
|
22
22
|
"Flask>=3.1.2",
|
|
23
23
|
"PyJWT>=2.10.1",
|
|
24
|
-
"pypomes-core>=2.8.
|
|
24
|
+
"pypomes-core>=2.8.6",
|
|
25
25
|
"pypomes-crypto>=0.4.8",
|
|
26
26
|
"requests>=2.32.5"
|
|
27
27
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
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
6
|
IamServer, IamParam
|
|
@@ -10,10 +10,9 @@ from .iam_pomes import (
|
|
|
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 (
|
|
19
18
|
service_get_token, provider_get_token,
|
|
@@ -26,17 +25,16 @@ from .token_pomes import (
|
|
|
26
25
|
__all__ = [
|
|
27
26
|
# iam_actions
|
|
28
27
|
"iam_callback", "iam_exchange",
|
|
29
|
-
"iam_login", "iam_logout", "iam_get_token",
|
|
28
|
+
"iam_login", "iam_logout", "iam_get_token", "iam_userinfo",
|
|
30
29
|
# iam_commons
|
|
31
30
|
"IamServer", "IamParam",
|
|
32
31
|
# iam_pomes
|
|
33
32
|
"iam_setup_server", "iam_setup_endpoints",
|
|
34
33
|
# iam_services
|
|
35
34
|
"jwt_required", "iam_setup_logger",
|
|
36
|
-
"service_setup_server", "
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"service_callback_exchange",
|
|
35
|
+
"service_setup_server", "service_login", "service_logout",
|
|
36
|
+
"service_get_token", "service_userinfo", "service_callback",
|
|
37
|
+
"service_exchange", "service_callback_exchange",
|
|
40
38
|
# provider_pomes
|
|
41
39
|
"provider_setup_server", "provider_get_token",
|
|
42
40
|
"provider_setup_endpoint", "provider_setup_logger", "provider_setup_server",
|
|
@@ -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[IamParam.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[IamParam.URL_BASE]}/realms/{registry[IamParam.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[IamParam.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",
|
|
@@ -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[IamParam.URL_BASE]}/realms/
|
|
310
|
-
|
|
360
|
+
url: str = (f"{registry[IamParam.URL_BASE]}/realms/"
|
|
361
|
+
f"{registry[IamParam.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"
|
|
@@ -429,6 +480,60 @@ def iam_exchange(iam_server: IamServer,
|
|
|
429
480
|
return result
|
|
430
481
|
|
|
431
482
|
|
|
483
|
+
def iam_userinfo(iam_server: IamServer,
|
|
484
|
+
args: dict[str, Any],
|
|
485
|
+
errors: list[str] = None,
|
|
486
|
+
logger: Logger = None) -> dict[str, Any] | None:
|
|
487
|
+
"""
|
|
488
|
+
Obtain user data from *iam_server*.
|
|
489
|
+
|
|
490
|
+
The user is identified by the attribute *user-id* or *login*, provided in *args*.
|
|
491
|
+
|
|
492
|
+
:param iam_server: the reference registered *IAM* server
|
|
493
|
+
:param args: the arguments passed when requesting the service
|
|
494
|
+
:param errors: incidental error messages
|
|
495
|
+
:param logger: optional logger
|
|
496
|
+
:return: the user information requested, or *None* if error
|
|
497
|
+
"""
|
|
498
|
+
# initialize the return variable
|
|
499
|
+
result: dict[str, Any] | None = None
|
|
500
|
+
|
|
501
|
+
# obtain the user's identification
|
|
502
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
503
|
+
|
|
504
|
+
err_msg: str | None = None
|
|
505
|
+
if user_id:
|
|
506
|
+
with _iam_lock:
|
|
507
|
+
# retrieve the IAM server's registry and the user data therein
|
|
508
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server,
|
|
509
|
+
errors=errors,
|
|
510
|
+
logger=logger)
|
|
511
|
+
user_data: dict[str, Any] = registry[IamParam.USERS].get(user_id)
|
|
512
|
+
if user_data:
|
|
513
|
+
url: str = (f"{registry[IamParam.URL_BASE]}/realms/{registry[IamParam.CLIENT_REALM]}"
|
|
514
|
+
"/protocol/openid-connect/userinfo")
|
|
515
|
+
header_data: dict[str, str] = {
|
|
516
|
+
"Authorization": f"Bearer {args.get('access-token')}"
|
|
517
|
+
}
|
|
518
|
+
result = __get_for_data(url=url,
|
|
519
|
+
header_data=header_data,
|
|
520
|
+
params=None,
|
|
521
|
+
errors=errors,
|
|
522
|
+
logger=logger)
|
|
523
|
+
else:
|
|
524
|
+
err_msg = f"Unknown user '{user_id}'"
|
|
525
|
+
else:
|
|
526
|
+
err_msg: str = "User identification not provided"
|
|
527
|
+
|
|
528
|
+
if err_msg:
|
|
529
|
+
if logger:
|
|
530
|
+
logger.error(msg=err_msg)
|
|
531
|
+
if isinstance(errors, list):
|
|
532
|
+
errors.append(err_msg)
|
|
533
|
+
|
|
534
|
+
return result
|
|
535
|
+
|
|
536
|
+
|
|
432
537
|
def __assert_link(iam_server: IamServer,
|
|
433
538
|
user_id: str,
|
|
434
539
|
token: str,
|
|
@@ -814,8 +919,8 @@ def __post_for_token(iam_server: IamServer,
|
|
|
814
919
|
body_data["client_id"] = registry[IamParam.CLIENT_ID]
|
|
815
920
|
|
|
816
921
|
# build the URL
|
|
817
|
-
|
|
818
|
-
|
|
922
|
+
url: str = (f"{registry[IamParam.URL_BASE]}/realms/"
|
|
923
|
+
f"{registry[IamParam.CLIENT_REALM]}/protocol/openid-connect/token")
|
|
819
924
|
# 'client_secret' data must not be shown in log
|
|
820
925
|
msg: str = f"POST {url}, {json.dumps(obj=body_data,
|
|
821
926
|
ensure_ascii=False)}"
|
|
@@ -68,7 +68,7 @@ def __get_iam_data() -> dict[IamServer, dict[IamParam, Any]]:
|
|
|
68
68
|
or dynamically with calls to *iam_setup_server()*. Specifying configuration parameters with environment
|
|
69
69
|
variables can be done by following these steps:
|
|
70
70
|
|
|
71
|
-
1. Specify *<APP_PREFIX>
|
|
71
|
+
1. Specify *<APP_PREFIX>_AUTH_SERVERS* with a list of names among the values found in *IamServer* class
|
|
72
72
|
(currently, *jusbr* and *keycloak* are supported), and the data set below for each server, where
|
|
73
73
|
*<IAM>* stands for the server's name as presented in *IamServer* class:
|
|
74
74
|
- *<APP_PREFIX>_<IAM>_ADMIN_ID* (optional, required if administrative duties are performed)
|
|
@@ -91,13 +91,14 @@ def __get_iam_data() -> dict[IamServer, dict[IamParam, Any]]:
|
|
|
91
91
|
- *<APP_PREFIX>_<IAM>_ENDPOINT_LOGIN*
|
|
92
92
|
- *<APP_PREFIX>_<IAM>_ENDPOINT_LOGOUT*
|
|
93
93
|
- *<APP_PREFIX>_<IAM>_ENDPOINT_TOKEN*
|
|
94
|
+
- *<APP_PREFIX>_<IAM>_ENDPOINT_USERINFO*
|
|
94
95
|
|
|
95
96
|
:return: the configuration data for the select *IAM* servers.
|
|
96
97
|
"""
|
|
97
98
|
# initialize the return variable
|
|
98
99
|
result: dict[IamServer, dict[IamParam, Any]] = {}
|
|
99
100
|
|
|
100
|
-
servers: list[IamServer] = env_get_enums(key=f"{APP_PREFIX}
|
|
101
|
+
servers: list[IamServer] = env_get_enums(key=f"{APP_PREFIX}_AUTH_SERVERS",
|
|
101
102
|
enum_class=IamServer) or []
|
|
102
103
|
for server in servers:
|
|
103
104
|
prefix = server.name
|
|
@@ -108,7 +109,7 @@ def __get_iam_data() -> dict[IamServer, dict[IamParam, Any]]:
|
|
|
108
109
|
IamParam.CLIENT_REALM: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_REALM"),
|
|
109
110
|
IamParam.CLIENT_SECRET: env_get_str(key=f"{APP_PREFIX}_{prefix}_CLIENT_SECRET"),
|
|
110
111
|
IamParam.LOGIN_TIMEOUT: env_get_str(key=f"{APP_PREFIX}_{prefix}_LOGIN_TIMEOUT"),
|
|
111
|
-
IamParam.PK_LIFETIME: env_get_int(key=f"{APP_PREFIX}_{prefix}
|
|
112
|
+
IamParam.PK_LIFETIME: env_get_int(key=f"{APP_PREFIX}_{prefix}_PK_LIFETIME"),
|
|
112
113
|
IamParam.RECIPIENT_ATTR: env_get_str(key=f"{APP_PREFIX}_{prefix}_RECIPIENT_ATTR"),
|
|
113
114
|
IamParam.URL_BASE: env_get_str(key=f"{APP_PREFIX}_{prefix}_URL_AUTH_BASE"),
|
|
114
115
|
# dynamically set
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
from flask import Flask
|
|
2
2
|
from pypomes_core import (
|
|
3
3
|
APP_PREFIX,
|
|
4
|
-
env_get_int, env_get_str,
|
|
4
|
+
env_get_int, env_get_str,
|
|
5
|
+
func_capture_params, func_defaulted_params
|
|
5
6
|
)
|
|
6
7
|
|
|
7
8
|
from .iam_common import (
|
|
@@ -9,10 +10,12 @@ from .iam_common import (
|
|
|
9
10
|
)
|
|
10
11
|
from .iam_services import (
|
|
11
12
|
service_login, service_logout,
|
|
12
|
-
service_callback, service_callback_exchange,
|
|
13
|
+
service_callback, service_callback_exchange,
|
|
14
|
+
service_exchange, service_get_token, service_userinfo
|
|
13
15
|
)
|
|
14
16
|
|
|
15
17
|
|
|
18
|
+
@func_capture_params
|
|
16
19
|
def iam_setup_server(iam_server: IamServer,
|
|
17
20
|
admin_id: str = None,
|
|
18
21
|
admin_secret: str = None,
|
|
@@ -50,7 +53,7 @@ def iam_setup_server(iam_server: IamServer,
|
|
|
50
53
|
:param url_base: base URL to request services
|
|
51
54
|
"""
|
|
52
55
|
# obtain the defaulted parameters
|
|
53
|
-
defaulted_params: list[str] =
|
|
56
|
+
defaulted_params: list[str] = func_defaulted_params.get()
|
|
54
57
|
|
|
55
58
|
# read from the environment variables
|
|
56
59
|
prefix: str = iam_server.name
|
|
@@ -92,6 +95,7 @@ def iam_setup_server(iam_server: IamServer,
|
|
|
92
95
|
}
|
|
93
96
|
|
|
94
97
|
|
|
98
|
+
@func_capture_params
|
|
95
99
|
def iam_setup_endpoints(flask_app: Flask,
|
|
96
100
|
iam_server: IamServer,
|
|
97
101
|
callback_endpoint: str = None,
|
|
@@ -99,7 +103,8 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
99
103
|
exchange_endpoint: str = None,
|
|
100
104
|
login_endpoint: str = None,
|
|
101
105
|
logout_endpoint: str = None,
|
|
102
|
-
token_endpoint: str = None
|
|
106
|
+
token_endpoint: str = None,
|
|
107
|
+
userinfo_endpoint: str = None) -> None:
|
|
103
108
|
"""
|
|
104
109
|
Setup the endpoints for accessing the services provided by *iam_server*.
|
|
105
110
|
|
|
@@ -114,9 +119,10 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
114
119
|
:param login_endpoint: endpoint for redirecting user to the *IAM* server's login page
|
|
115
120
|
:param logout_endpoint: endpoint for terminating user access
|
|
116
121
|
:param token_endpoint: endpoint for retrieving authentication token
|
|
122
|
+
:param userinfo_endpoint: endpoint for retrieving user data
|
|
117
123
|
"""
|
|
118
124
|
# obtain the defaulted parameters
|
|
119
|
-
defaulted_params: list[str] =
|
|
125
|
+
defaulted_params: list[str] = func_defaulted_params.get()
|
|
120
126
|
|
|
121
127
|
# read from the environment variables
|
|
122
128
|
prefix: str = iam_server.name
|
|
@@ -132,6 +138,8 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
132
138
|
logout_endpoint = env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_LOGOUT")
|
|
133
139
|
if "token_endpoint" in defaulted_params:
|
|
134
140
|
token_endpoint = env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_TOKEN")
|
|
141
|
+
if "userinfo_endpoint" in defaulted_params:
|
|
142
|
+
userinfo_endpoint = env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_USERINFO")
|
|
135
143
|
|
|
136
144
|
# establish the endpoints
|
|
137
145
|
if callback_endpoint:
|
|
@@ -158,9 +166,14 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
158
166
|
flask_app.add_url_rule(rule=logout_endpoint,
|
|
159
167
|
endpoint=f"{iam_server}-logout",
|
|
160
168
|
view_func=service_logout,
|
|
161
|
-
methods=["
|
|
169
|
+
methods=["POST"])
|
|
162
170
|
if token_endpoint:
|
|
163
171
|
flask_app.add_url_rule(rule=token_endpoint,
|
|
164
172
|
endpoint=f"{iam_server}-token",
|
|
165
173
|
view_func=service_get_token,
|
|
166
174
|
methods=["GET"])
|
|
175
|
+
if userinfo_endpoint:
|
|
176
|
+
flask_app.add_url_rule(rule=userinfo_endpoint,
|
|
177
|
+
endpoint=f"{iam_server}-userinfo",
|
|
178
|
+
view_func=service_userinfo,
|
|
179
|
+
methods=["GET"])
|
|
@@ -9,10 +9,9 @@ from .iam_common import (
|
|
|
9
9
|
_iam_server_from_endpoint, _iam_server_from_issuer
|
|
10
10
|
)
|
|
11
11
|
from .iam_actions import (
|
|
12
|
-
iam_login, iam_logout,
|
|
13
|
-
|
|
12
|
+
iam_login, iam_logout, iam_callback,
|
|
13
|
+
iam_exchange, iam_get_token, iam_userinfo
|
|
14
14
|
)
|
|
15
|
-
from .iam_pomes import iam_setup_server
|
|
16
15
|
from .token_pomes import token_get_claims, token_validate
|
|
17
16
|
|
|
18
17
|
# the logger for IAM service operations
|
|
@@ -24,7 +23,10 @@ def jwt_required(func: callable) -> callable:
|
|
|
24
23
|
"""
|
|
25
24
|
Create a decorator to authenticate service endpoints with JWT tokens.
|
|
26
25
|
|
|
26
|
+
The decorated function must be a registered endpoint to a *Flask* application.
|
|
27
|
+
|
|
27
28
|
:param func: the function being decorated
|
|
29
|
+
:return: the return from the call to *func*, or a *Response NOT AUTHORIZED* if the authentication failed
|
|
28
30
|
"""
|
|
29
31
|
# ruff: noqa: ANN003 - Missing type annotation for *{name}
|
|
30
32
|
def wrapper(*args, **kwargs) -> Response:
|
|
@@ -45,19 +47,16 @@ def __request_validate(request: Request) -> Response:
|
|
|
45
47
|
Because this code has a high usage frequency, only authentication failures are logged.
|
|
46
48
|
|
|
47
49
|
:param request: the *request* to be verified
|
|
48
|
-
:return: *None* if the *request* is valid, otherwise a *Response
|
|
50
|
+
:return: *None* if the *request* is valid, otherwise a *Response NOT AUTHORIZED*
|
|
49
51
|
"""
|
|
50
52
|
# initialize the return variable
|
|
51
53
|
result: Response | None = None
|
|
52
54
|
|
|
53
|
-
# retrieve the authorization from the request header
|
|
54
|
-
auth_header: str = request.headers.get("Authorization")
|
|
55
|
-
|
|
56
55
|
# validate the authorization token
|
|
57
56
|
bad_token: bool = True
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
token: str = __get_bearer_token(request=request)
|
|
58
|
+
if token:
|
|
59
|
+
# extract token claims
|
|
61
60
|
claims: dict[str, Any] = token_get_claims(token=token)
|
|
62
61
|
if claims:
|
|
63
62
|
issuer: str = claims["payload"].get("iss")
|
|
@@ -101,6 +100,26 @@ def __request_validate(request: Request) -> Response:
|
|
|
101
100
|
return result
|
|
102
101
|
|
|
103
102
|
|
|
103
|
+
def __get_bearer_token(request: Request) -> str:
|
|
104
|
+
"""
|
|
105
|
+
Retrieve the bearer token sent in the header of *request*.
|
|
106
|
+
|
|
107
|
+
This implementation assumes that HTTP requests are handled with the *Flask* framework.
|
|
108
|
+
|
|
109
|
+
:param request: the *request* to retrieve the token from
|
|
110
|
+
:return: the bearer token, or *None* if not found
|
|
111
|
+
"""
|
|
112
|
+
# initialize the return variable
|
|
113
|
+
result: str | None = None
|
|
114
|
+
|
|
115
|
+
# retrieve the authorization from the request header
|
|
116
|
+
auth_header: str = request.headers.get("Authorization")
|
|
117
|
+
if auth_header and auth_header.startswith("Bearer "):
|
|
118
|
+
result: str = auth_header.split(" ")[1]
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
|
|
104
123
|
def iam_setup_logger(logger: Logger) -> None:
|
|
105
124
|
"""
|
|
106
125
|
Register the logger for HTTP services.
|
|
@@ -143,14 +162,15 @@ def service_setup_server() -> Response:
|
|
|
143
162
|
|
|
144
163
|
:return: *Response OK*
|
|
145
164
|
"""
|
|
165
|
+
# retrieve the request arguments
|
|
166
|
+
args: dict[str, Any] = (dict(request.json) if request.is_json else dict(request.form)) or {}
|
|
167
|
+
|
|
146
168
|
# log the request
|
|
147
169
|
if __IAM_LOGGER:
|
|
148
|
-
__IAM_LOGGER.debug(msg=f"{
|
|
149
|
-
|
|
150
|
-
# retrieve the arguments
|
|
151
|
-
args: dict[str, Any] = request.json if request.is_json else request.form
|
|
152
|
-
|
|
170
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
171
|
+
ensure_ascii=False)}")
|
|
153
172
|
# setup the server
|
|
173
|
+
from .iam_pomes import iam_setup_server
|
|
154
174
|
iam_setup_server(**args)
|
|
155
175
|
result = Response(status=200)
|
|
156
176
|
|
|
@@ -188,10 +208,13 @@ def service_login() -> Response:
|
|
|
188
208
|
# declare the return variable
|
|
189
209
|
result: Response | None = None
|
|
190
210
|
|
|
211
|
+
# retrieve the request arguments
|
|
212
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
213
|
+
|
|
191
214
|
# log the request
|
|
192
215
|
if __IAM_LOGGER:
|
|
193
|
-
__IAM_LOGGER.debug(msg=
|
|
194
|
-
|
|
216
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
217
|
+
ensure_ascii=False)}")
|
|
195
218
|
errors: list[str] = []
|
|
196
219
|
with _iam_lock:
|
|
197
220
|
# retrieve the IAM server
|
|
@@ -201,7 +224,7 @@ def service_login() -> Response:
|
|
|
201
224
|
if iam_server:
|
|
202
225
|
# obtain the login URL
|
|
203
226
|
login_url: str = iam_login(iam_server=iam_server,
|
|
204
|
-
args=
|
|
227
|
+
args=args,
|
|
205
228
|
errors=errors,
|
|
206
229
|
logger=__IAM_LOGGER)
|
|
207
230
|
if login_url:
|
|
@@ -218,7 +241,8 @@ def service_login() -> Response:
|
|
|
218
241
|
|
|
219
242
|
|
|
220
243
|
# @flask_app.route(rule=<logout_endpoint>, # IAM_ENDPOINT_LOGOUT
|
|
221
|
-
# methods=["
|
|
244
|
+
# methods=["POST"])
|
|
245
|
+
@jwt_required
|
|
222
246
|
def service_logout() -> Response:
|
|
223
247
|
"""
|
|
224
248
|
Entry point for the *IAM* server's logout service.
|
|
@@ -236,10 +260,13 @@ def service_logout() -> Response:
|
|
|
236
260
|
# declare the return variable
|
|
237
261
|
result: Response | None
|
|
238
262
|
|
|
263
|
+
# retrieve the request arguments
|
|
264
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
265
|
+
|
|
239
266
|
# log the request
|
|
240
267
|
if __IAM_LOGGER:
|
|
241
|
-
__IAM_LOGGER.debug(msg=
|
|
242
|
-
|
|
268
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
269
|
+
ensure_ascii=False)}")
|
|
243
270
|
errors: list[str] = []
|
|
244
271
|
with _iam_lock:
|
|
245
272
|
# retrieve the IAM server
|
|
@@ -249,7 +276,7 @@ def service_logout() -> Response:
|
|
|
249
276
|
if iam_server:
|
|
250
277
|
# logout the user
|
|
251
278
|
iam_logout(iam_server=iam_server,
|
|
252
|
-
args=
|
|
279
|
+
args=args,
|
|
253
280
|
errors=errors,
|
|
254
281
|
logger=__IAM_LOGGER)
|
|
255
282
|
if errors:
|
|
@@ -291,10 +318,13 @@ def service_callback() -> Response:
|
|
|
291
318
|
|
|
292
319
|
:return: *Response* containing the reference user identification and the token, or *BAD REQUEST*
|
|
293
320
|
"""
|
|
321
|
+
# retrieve the request arguments
|
|
322
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
323
|
+
|
|
294
324
|
# log the request
|
|
295
325
|
if __IAM_LOGGER:
|
|
296
|
-
__IAM_LOGGER.debug(msg=
|
|
297
|
-
|
|
326
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
327
|
+
ensure_ascii=False)}")
|
|
298
328
|
errors: list[str] = []
|
|
299
329
|
token_data: tuple[str, str] | None = None
|
|
300
330
|
with _iam_lock:
|
|
@@ -305,7 +335,7 @@ def service_callback() -> Response:
|
|
|
305
335
|
if iam_server:
|
|
306
336
|
# process the callback operation
|
|
307
337
|
token_data = iam_callback(iam_server=iam_server,
|
|
308
|
-
args=
|
|
338
|
+
args=args,
|
|
309
339
|
errors=errors,
|
|
310
340
|
logger=__IAM_LOGGER)
|
|
311
341
|
result: Response
|
|
@@ -347,10 +377,13 @@ def service_exchange() -> Response:
|
|
|
347
377
|
|
|
348
378
|
:return: *Response* containing the reference user identification and the token, or *BAD REQUEST*
|
|
349
379
|
"""
|
|
380
|
+
# retrieve the request arguments
|
|
381
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
382
|
+
|
|
350
383
|
# log the request
|
|
351
384
|
if __IAM_LOGGER:
|
|
352
|
-
__IAM_LOGGER.debug(msg=
|
|
353
|
-
|
|
385
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
386
|
+
ensure_ascii=False)}")
|
|
354
387
|
errors: list[str] = []
|
|
355
388
|
with _iam_lock:
|
|
356
389
|
# retrieve the IAM server
|
|
@@ -362,7 +395,7 @@ def service_exchange() -> Response:
|
|
|
362
395
|
if iam_server:
|
|
363
396
|
errors: list[str] = []
|
|
364
397
|
token_info = iam_exchange(iam_server=iam_server,
|
|
365
|
-
args=
|
|
398
|
+
args=args,
|
|
366
399
|
errors=errors,
|
|
367
400
|
logger=__IAM_LOGGER)
|
|
368
401
|
result: Response
|
|
@@ -412,10 +445,13 @@ def service_callback_exchange() -> Response:
|
|
|
412
445
|
# declare the return variable
|
|
413
446
|
result: Response | None = None
|
|
414
447
|
|
|
448
|
+
# retrieve the request arguments
|
|
449
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
450
|
+
|
|
415
451
|
# log the request
|
|
416
452
|
if __IAM_LOGGER:
|
|
417
|
-
__IAM_LOGGER.debug(msg=
|
|
418
|
-
|
|
453
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
454
|
+
ensure_ascii=False)}")
|
|
419
455
|
errors: list[str] = []
|
|
420
456
|
with _iam_lock:
|
|
421
457
|
# retrieve the IAM server
|
|
@@ -424,7 +460,7 @@ def service_callback_exchange() -> Response:
|
|
|
424
460
|
logger=__IAM_LOGGER)
|
|
425
461
|
# obtain the login URL
|
|
426
462
|
token_info: tuple[str, str] = iam_callback(iam_server=iam_server,
|
|
427
|
-
args=
|
|
463
|
+
args=args,
|
|
428
464
|
errors=errors,
|
|
429
465
|
logger=__IAM_LOGGER)
|
|
430
466
|
if token_info:
|
|
@@ -474,13 +510,13 @@ def service_get_token() -> Response:
|
|
|
474
510
|
|
|
475
511
|
:return: *Response* containing the user reference identification and the token, or *BAD REQUEST*
|
|
476
512
|
"""
|
|
513
|
+
# retrieve the request arguments
|
|
514
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
515
|
+
|
|
477
516
|
# log the request
|
|
478
517
|
if __IAM_LOGGER:
|
|
479
|
-
__IAM_LOGGER.debug(msg=
|
|
480
|
-
|
|
481
|
-
# obtain the request arguments
|
|
482
|
-
args: dict[str, Any] = request.args
|
|
483
|
-
|
|
518
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
519
|
+
ensure_ascii=False)}")
|
|
484
520
|
errors: list[str] = []
|
|
485
521
|
token_info: dict[str, str] | None = None
|
|
486
522
|
with _iam_lock:
|
|
@@ -508,14 +544,55 @@ def service_get_token() -> Response:
|
|
|
508
544
|
return result
|
|
509
545
|
|
|
510
546
|
|
|
511
|
-
|
|
547
|
+
# @flask_app.route(rule=<token_endpoint>, # IAM_ENDPOINT_USERINFO
|
|
548
|
+
# methods=["GET"])
|
|
549
|
+
@jwt_required
|
|
550
|
+
def service_userinfo() -> Response:
|
|
512
551
|
"""
|
|
513
|
-
|
|
552
|
+
Entry point for retrieving user data from the *IAM* server.
|
|
514
553
|
|
|
515
|
-
|
|
516
|
-
|
|
554
|
+
When registering this endpoint, the name used in *Flask*'s *endpoint* parameter must be prefixed with
|
|
555
|
+
the name of the *IAM* server in charge of handling this service. This prefixing is done automatically
|
|
556
|
+
if the endpoint is established with a call to *iam_setup_endpoints()*.
|
|
557
|
+
|
|
558
|
+
The user is identified by the attribute *user-id* or "login", provided as a request parameter.
|
|
559
|
+
|
|
560
|
+
On success, the returned *Response* will contain a JSON with information kept by *iam_server* about *user_id*.
|
|
561
|
+
|
|
562
|
+
:return: *Response* containing user data, or *BAD REQUEST*
|
|
517
563
|
"""
|
|
564
|
+
# retrieve the request arguments
|
|
565
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
518
566
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
567
|
+
# log the request
|
|
568
|
+
if __IAM_LOGGER:
|
|
569
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
570
|
+
ensure_ascii=False)}")
|
|
571
|
+
# retrieve the bearer token
|
|
572
|
+
args["access-token"] = __get_bearer_token(request=request)
|
|
573
|
+
|
|
574
|
+
errors: list[str] = []
|
|
575
|
+
user_info: dict[str, str] | None = None
|
|
576
|
+
with _iam_lock:
|
|
577
|
+
# retrieve the IAM server
|
|
578
|
+
iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
|
|
579
|
+
errors=errors,
|
|
580
|
+
logger=__IAM_LOGGER)
|
|
581
|
+
if iam_server:
|
|
582
|
+
# retrieve the token
|
|
583
|
+
errors: list[str] = []
|
|
584
|
+
user_info = iam_userinfo(iam_server=iam_server,
|
|
585
|
+
args=args,
|
|
586
|
+
errors=errors,
|
|
587
|
+
logger=__IAM_LOGGER)
|
|
588
|
+
result: Response
|
|
589
|
+
if errors:
|
|
590
|
+
result = Response(response="; ".join(errors),
|
|
591
|
+
status=400)
|
|
592
|
+
else:
|
|
593
|
+
result = jsonify(user_info)
|
|
594
|
+
if __IAM_LOGGER:
|
|
595
|
+
# log the response
|
|
596
|
+
__IAM_LOGGER.debug(msg=f"Response {result}; {json.dumps(obj=user_info,
|
|
597
|
+
ensure_ascii=False)}")
|
|
598
|
+
return result
|
|
@@ -8,8 +8,8 @@ from flask import Flask, Response, request, jsonify
|
|
|
8
8
|
from logging import Logger
|
|
9
9
|
from pypomes_core import (
|
|
10
10
|
APP_PREFIX, TZ_LOCAL,
|
|
11
|
-
env_get_str, env_get_strs, env_get_obj,
|
|
12
|
-
|
|
11
|
+
env_get_str, env_get_strs, env_get_obj, exc_format,
|
|
12
|
+
func_capture_params, func_defaulted_params
|
|
13
13
|
)
|
|
14
14
|
from threading import Lock
|
|
15
15
|
from typing import Any, Final
|
|
@@ -28,7 +28,7 @@ class ProviderParam(StrEnum):
|
|
|
28
28
|
ACCESS_EXPIRATION = "access-expiration"
|
|
29
29
|
REFRESH_TOKEN = "refresh-token"
|
|
30
30
|
REFRESH_EXPIRATION = "refresh-expiration"
|
|
31
|
-
|
|
31
|
+
URL_TOKEN = "url-token"
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
# the logger for IAM service operations
|
|
@@ -44,7 +44,7 @@ def __get_provider_data() -> dict[str, dict[ProviderParam, Any]]:
|
|
|
44
44
|
or dynamically with *provider_setup_server()*. Specifying configuration parameters with
|
|
45
45
|
environment variables can be done by following these steps:
|
|
46
46
|
|
|
47
|
-
1. Specify *<APP_PREFIX>
|
|
47
|
+
1. Specify *<APP_PREFIX>_AUTH_PROVIDERS* with a list of names (typically, in lower-case), and the data set
|
|
48
48
|
below for each providers, where *<JWT>* stands for the provider's name in upper-case:
|
|
49
49
|
- *<APP_PREFIX>_<JWT>_BODY_DATA* (optional)
|
|
50
50
|
- *<APP_PREFIX>_<JWT>_CUSTOM_AUTH* (optional)
|
|
@@ -53,26 +53,26 @@ def __get_provider_data() -> dict[str, dict[ProviderParam, Any]]:
|
|
|
53
53
|
- *<APP_PREFIX>_<JWT>_USER_SECRET* (required)
|
|
54
54
|
- *<APP_PREFIX>_<JWT>_URL_TOKEN* (required)
|
|
55
55
|
|
|
56
|
-
2. The special environment variable *<APP_PREFIX>
|
|
57
|
-
to obtain JWT tokens. It is not part of the *JWT* providers' setup, but is meant to be
|
|
58
|
-
by function *provider_setup_endpoint()*, wherein the value in that variable would represent
|
|
59
|
-
default value for its parameter.
|
|
56
|
+
2. The special environment variable *<APP_PREFIX>_PROVIDER_ENDPOINT_TOKEN* identifies the endpoint
|
|
57
|
+
from which to obtain JWT tokens. It is not part of the *JWT* providers' setup, but is meant to be
|
|
58
|
+
used by function *provider_setup_endpoint()*, wherein the value in that variable would represent
|
|
59
|
+
the default value for its parameter.
|
|
60
60
|
|
|
61
61
|
:return: the configuration data for the select *JWT* providers.
|
|
62
62
|
"""
|
|
63
63
|
# initialize the return variable
|
|
64
64
|
result: dict[str, dict[ProviderParam, Any]] = {}
|
|
65
65
|
|
|
66
|
-
servers: list[str] = env_get_strs(key=f"{APP_PREFIX}
|
|
66
|
+
servers: list[str] = env_get_strs(key=f"{APP_PREFIX}_AUTH_PROVIDERS") or []
|
|
67
67
|
for server in servers:
|
|
68
68
|
prefix = server.upper()
|
|
69
69
|
result[server] = {
|
|
70
|
+
ProviderParam.USER_ID: env_get_str(key=f"{APP_PREFIX}_{prefix}_USER_ID"),
|
|
71
|
+
ProviderParam.USER_SECRET: env_get_str(key=f"{APP_PREFIX}_{prefix}_USER_SECRET"),
|
|
70
72
|
ProviderParam.BODY_DATA: env_get_obj(key=f"{APP_PREFIX}_{prefix}_BODY_DATA"),
|
|
71
73
|
ProviderParam.CUSTOM_AUTH: env_get_strs(key=f"{APP_PREFIX}_{prefix}_CUSTOM_AUTH"),
|
|
72
74
|
ProviderParam.HEADER_DATA: env_get_obj(key=f"{APP_PREFIX}_{prefix}_HEADER_DATA"),
|
|
73
|
-
ProviderParam.
|
|
74
|
-
ProviderParam.USER_SECRET: env_get_str(key=f"{APP_PREFIX}_{prefix}_USER_SECRET"),
|
|
75
|
-
ProviderParam.URL_AUTH: env_get_str(key=f"{APP_PREFIX}_{prefix}_URL_AUTH"),
|
|
75
|
+
ProviderParam.URL_TOKEN: env_get_str(key=f"{APP_PREFIX}_{prefix}_URL_TOKEN"),
|
|
76
76
|
ProviderParam.ACCESS_TOKEN: None,
|
|
77
77
|
ProviderParam.ACCESS_EXPIRATION: 0,
|
|
78
78
|
ProviderParam.REFRESH_TOKEN: None,
|
|
@@ -85,12 +85,13 @@ def __get_provider_data() -> dict[str, dict[ProviderParam, Any]]:
|
|
|
85
85
|
# structure:
|
|
86
86
|
# {
|
|
87
87
|
# <provider-id>: {
|
|
88
|
-
# "
|
|
89
|
-
# "user": <str>,
|
|
90
|
-
# "pwd": <str>,
|
|
88
|
+
# "body-data": <dict[str, str],
|
|
91
89
|
# "custom-auth": <tuple[str, str]>,
|
|
92
90
|
# "headers-data": <dict[str, str]>,
|
|
93
|
-
# "
|
|
91
|
+
# "user-id": <str>,
|
|
92
|
+
# "user-secret": <str>,
|
|
93
|
+
# "url-token": <strl>,
|
|
94
|
+
# # dinamically set
|
|
94
95
|
# "access-token": <str>,
|
|
95
96
|
# "access-expiration": <timestamp>,
|
|
96
97
|
# "refresh-token": <str>,
|
|
@@ -104,13 +105,14 @@ _provider_registry: Final[dict[str, dict[str, Any]]] = __get_provider_data()
|
|
|
104
105
|
_provider_lock: Final[Lock] = Lock()
|
|
105
106
|
|
|
106
107
|
|
|
108
|
+
@func_capture_params
|
|
107
109
|
def provider_setup_server(provider_id: str,
|
|
108
110
|
user_id: str = None,
|
|
109
111
|
user_secret: str = None,
|
|
110
112
|
custom_auth: tuple[str, str] = None,
|
|
111
113
|
header_data: dict[str, str] = None,
|
|
112
114
|
body_data: dict[str, str] = None,
|
|
113
|
-
|
|
115
|
+
url_token: str = None) -> None:
|
|
114
116
|
"""
|
|
115
117
|
Setup the *JWT* provider *provider_id*.
|
|
116
118
|
|
|
@@ -121,9 +123,10 @@ def provider_setup_server(provider_id: str,
|
|
|
121
123
|
as key-value pairs in the body of the request. Otherwise, the external provider *provider_id* uses the standard
|
|
122
124
|
HTTP Basic Authorization scheme, wherein the credentials are B64-encoded and sent in the request headers.
|
|
123
125
|
|
|
124
|
-
Optional constant key-value pairs (such as ['Content-Type', 'application/x-www-form-urlencoded']),
|
|
125
|
-
added to the request headers, may be specified in *headers_data*. Likewise, optional constant
|
|
126
|
-
(such as ['grant_type', 'client_credentials']), to be added to the request body,
|
|
126
|
+
Optional constant key-value pairs (such as *['Content-Type', 'application/x-www-form-urlencoded']*),
|
|
127
|
+
to be added to the request headers, may be specified in *headers_data*. Likewise, optional constant
|
|
128
|
+
key-value pairs (such as *['grant_type', 'client_credentials']*), to be added to the request body,
|
|
129
|
+
may be specified in *body_data*.
|
|
127
130
|
|
|
128
131
|
:param provider_id: the provider's identification
|
|
129
132
|
:param user_id: the basic authorization user
|
|
@@ -131,12 +134,12 @@ def provider_setup_server(provider_id: str,
|
|
|
131
134
|
:param custom_auth: optional key names for sending the credentials as key-value pairs in the body of the request
|
|
132
135
|
:param header_data: optional key-value pairs to be added to the request headers
|
|
133
136
|
:param body_data: optional key-value pairs to be added to the request body
|
|
134
|
-
:param
|
|
137
|
+
:param url_token: the url to request *JWT* tokens with
|
|
135
138
|
"""
|
|
136
139
|
global _provider_registry
|
|
137
140
|
|
|
138
141
|
# obtain the defaulted parameters
|
|
139
|
-
defaulted_params: list[str] =
|
|
142
|
+
defaulted_params: list[str] = func_defaulted_params.get()
|
|
140
143
|
|
|
141
144
|
# read from the environment variables
|
|
142
145
|
prefix: str = provider_id.upper()
|
|
@@ -150,17 +153,17 @@ def provider_setup_server(provider_id: str,
|
|
|
150
153
|
header_data = env_get_obj(key=f"{APP_PREFIX}_{prefix}_HEADER_DATA")
|
|
151
154
|
if "body_data" in defaulted_params:
|
|
152
155
|
body_data = env_get_obj(key=f"{APP_PREFIX}_{prefix}_BODY_DATA")
|
|
153
|
-
if "
|
|
154
|
-
|
|
156
|
+
if "url_token" in defaulted_params:
|
|
157
|
+
url_token = env_get_str(key=f"{APP_PREFIX}_{prefix}_URL_TOKEN")
|
|
155
158
|
|
|
156
159
|
with _provider_lock:
|
|
157
160
|
_provider_registry[provider_id] = {
|
|
158
|
-
ProviderParam.
|
|
159
|
-
ProviderParam.USER_ID: user_id,
|
|
160
|
-
ProviderParam.USER_SECRET: user_secret,
|
|
161
|
+
ProviderParam.BODY_DATA: body_data,
|
|
161
162
|
ProviderParam.CUSTOM_AUTH: custom_auth,
|
|
162
163
|
ProviderParam.HEADER_DATA: header_data,
|
|
163
|
-
ProviderParam.
|
|
164
|
+
ProviderParam.USER_ID: user_id,
|
|
165
|
+
ProviderParam.USER_SECRET: user_secret,
|
|
166
|
+
ProviderParam.URL_TOKEN: url_token,
|
|
164
167
|
# dynamically set
|
|
165
168
|
ProviderParam.ACCESS_TOKEN: None,
|
|
166
169
|
ProviderParam.ACCESS_EXPIRATION: 0,
|
|
@@ -169,6 +172,7 @@ def provider_setup_server(provider_id: str,
|
|
|
169
172
|
}
|
|
170
173
|
|
|
171
174
|
|
|
175
|
+
@func_capture_params
|
|
172
176
|
def provider_setup_endpoint(flask_app: Flask,
|
|
173
177
|
provider_endpoint: str = None) -> None:
|
|
174
178
|
"""
|
|
@@ -181,16 +185,16 @@ def provider_setup_endpoint(flask_app: Flask,
|
|
|
181
185
|
:param provider_endpoint: endpoint for requenting tokens to provider
|
|
182
186
|
"""
|
|
183
187
|
# obtain the defaulted parameters
|
|
184
|
-
defaulted_params: list[str] =
|
|
188
|
+
defaulted_params: list[str] = func_defaulted_params.get()
|
|
185
189
|
|
|
186
190
|
# read from the environment variable
|
|
187
191
|
if "provider_endpoint" in defaulted_params:
|
|
188
|
-
provider_endpoint = env_get_str(key=f"{APP_PREFIX}
|
|
192
|
+
provider_endpoint = env_get_str(key=f"{APP_PREFIX}_PROVIDER_ENDPOINT_TOKEN")
|
|
189
193
|
|
|
190
194
|
# establish the endpoints
|
|
191
195
|
if provider_endpoint:
|
|
192
196
|
flask_app.add_url_rule(rule=provider_endpoint,
|
|
193
|
-
endpoint=f"
|
|
197
|
+
endpoint=f"provider-get-token",
|
|
194
198
|
view_func=service_get_token,
|
|
195
199
|
methods=["GET"])
|
|
196
200
|
|
|
@@ -220,14 +224,16 @@ def service_get_token() -> Response:
|
|
|
220
224
|
|
|
221
225
|
:return: *Response* containing the JWT token, or *BAD REQUEST*
|
|
222
226
|
"""
|
|
227
|
+
# retrieve the request arguments
|
|
228
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
229
|
+
|
|
223
230
|
# log the request
|
|
224
231
|
if __JWT_LOGGER:
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
__JWT_LOGGER.debug(msg=f"Request {request.method}:{request.path}, params {params}")
|
|
232
|
+
__JWT_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
233
|
+
ensure_ascii=False)}")
|
|
228
234
|
|
|
229
235
|
# obtain the provider JWT
|
|
230
|
-
provider_id: str =
|
|
236
|
+
provider_id: str = args.get("jwt-provider")
|
|
231
237
|
|
|
232
238
|
# retrieve the token
|
|
233
239
|
token: str | None = None
|
|
@@ -282,7 +288,7 @@ def provider_get_token(provider_id: str,
|
|
|
282
288
|
# access token has expired
|
|
283
289
|
header_data: dict[str, str] | None = None
|
|
284
290
|
body_data: dict[str, str] | None = None
|
|
285
|
-
url: str = provider.get(ProviderParam.
|
|
291
|
+
url: str = provider.get(ProviderParam.URL_TOKEN)
|
|
286
292
|
refresh_token: str = provider.get(ProviderParam.REFRESH_TOKEN)
|
|
287
293
|
if refresh_token:
|
|
288
294
|
# refresh token exists
|
|
@@ -296,7 +302,7 @@ def provider_get_token(provider_id: str,
|
|
|
296
302
|
"grant_type": "refresh_token",
|
|
297
303
|
"refresh_token": refresh_token
|
|
298
304
|
}
|
|
299
|
-
if not
|
|
305
|
+
if not header_data:
|
|
300
306
|
# refresh token does not exist or has expired
|
|
301
307
|
user: str = provider.get(ProviderParam.USER_ID)
|
|
302
308
|
pwd: str = provider.get(ProviderParam.USER_SECRET)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|