pypomes-iam 0.8.1__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.8.1 → pypomes_iam-0.8.5}/PKG-INFO +1 -1
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/pyproject.toml +1 -1
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/src/pypomes_iam/__init__.py +8 -10
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_actions.py +121 -16
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_common.py +4 -3
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_pomes.py +13 -3
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/src/pypomes_iam/iam_services.py +115 -41
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/src/pypomes_iam/provider_pomes.py +34 -30
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/.gitignore +0 -0
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/LICENSE +0 -0
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/README.md +0 -0
- {pypomes_iam-0.8.1 → pypomes_iam-0.8.5}/src/__init__.py +0 -0
- {pypomes_iam-0.8.1 → 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.8.
|
|
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
|
|
@@ -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
|
|
@@ -10,7 +10,8 @@ from .iam_common import (
|
|
|
10
10
|
)
|
|
11
11
|
from .iam_services import (
|
|
12
12
|
service_login, service_logout,
|
|
13
|
-
service_callback, service_callback_exchange,
|
|
13
|
+
service_callback, service_callback_exchange,
|
|
14
|
+
service_exchange, service_get_token, service_userinfo
|
|
14
15
|
)
|
|
15
16
|
|
|
16
17
|
|
|
@@ -102,7 +103,8 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
102
103
|
exchange_endpoint: str = None,
|
|
103
104
|
login_endpoint: str = None,
|
|
104
105
|
logout_endpoint: str = None,
|
|
105
|
-
token_endpoint: str = None
|
|
106
|
+
token_endpoint: str = None,
|
|
107
|
+
userinfo_endpoint: str = None) -> None:
|
|
106
108
|
"""
|
|
107
109
|
Setup the endpoints for accessing the services provided by *iam_server*.
|
|
108
110
|
|
|
@@ -117,6 +119,7 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
117
119
|
:param login_endpoint: endpoint for redirecting user to the *IAM* server's login page
|
|
118
120
|
:param logout_endpoint: endpoint for terminating user access
|
|
119
121
|
:param token_endpoint: endpoint for retrieving authentication token
|
|
122
|
+
:param userinfo_endpoint: endpoint for retrieving user data
|
|
120
123
|
"""
|
|
121
124
|
# obtain the defaulted parameters
|
|
122
125
|
defaulted_params: list[str] = func_defaulted_params.get()
|
|
@@ -135,6 +138,8 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
135
138
|
logout_endpoint = env_get_str(key=f"{APP_PREFIX}_{prefix}_ENDPOINT_LOGOUT")
|
|
136
139
|
if "token_endpoint" in defaulted_params:
|
|
137
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")
|
|
138
143
|
|
|
139
144
|
# establish the endpoints
|
|
140
145
|
if callback_endpoint:
|
|
@@ -161,9 +166,14 @@ def iam_setup_endpoints(flask_app: Flask,
|
|
|
161
166
|
flask_app.add_url_rule(rule=logout_endpoint,
|
|
162
167
|
endpoint=f"{iam_server}-logout",
|
|
163
168
|
view_func=service_logout,
|
|
164
|
-
methods=["
|
|
169
|
+
methods=["POST"])
|
|
165
170
|
if token_endpoint:
|
|
166
171
|
flask_app.add_url_rule(rule=token_endpoint,
|
|
167
172
|
endpoint=f"{iam_server}-token",
|
|
168
173
|
view_func=service_get_token,
|
|
169
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,8 +9,8 @@ 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
15
|
from .token_pomes import token_get_claims, token_validate
|
|
16
16
|
|
|
@@ -52,14 +52,11 @@ def __request_validate(request: Request) -> Response:
|
|
|
52
52
|
# initialize the return variable
|
|
53
53
|
result: Response | None = None
|
|
54
54
|
|
|
55
|
-
# retrieve the authorization from the request header
|
|
56
|
-
auth_header: str = request.headers.get("Authorization")
|
|
57
|
-
|
|
58
55
|
# validate the authorization token
|
|
59
56
|
bad_token: bool = True
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
57
|
+
token: str = __get_bearer_token(request=request)
|
|
58
|
+
if token:
|
|
59
|
+
# extract token claims
|
|
63
60
|
claims: dict[str, Any] = token_get_claims(token=token)
|
|
64
61
|
if claims:
|
|
65
62
|
issuer: str = claims["payload"].get("iss")
|
|
@@ -103,6 +100,26 @@ def __request_validate(request: Request) -> Response:
|
|
|
103
100
|
return result
|
|
104
101
|
|
|
105
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
|
+
|
|
106
123
|
def iam_setup_logger(logger: Logger) -> None:
|
|
107
124
|
"""
|
|
108
125
|
Register the logger for HTTP services.
|
|
@@ -145,13 +162,13 @@ def service_setup_server() -> Response:
|
|
|
145
162
|
|
|
146
163
|
:return: *Response OK*
|
|
147
164
|
"""
|
|
165
|
+
# retrieve the request arguments
|
|
166
|
+
args: dict[str, Any] = (dict(request.json) if request.is_json else dict(request.form)) or {}
|
|
167
|
+
|
|
148
168
|
# log the request
|
|
149
169
|
if __IAM_LOGGER:
|
|
150
|
-
__IAM_LOGGER.debug(msg=f"{
|
|
151
|
-
|
|
152
|
-
# retrieve the arguments
|
|
153
|
-
args: dict[str, Any] = request.json if request.is_json else request.form
|
|
154
|
-
|
|
170
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
171
|
+
ensure_ascii=False)}")
|
|
155
172
|
# setup the server
|
|
156
173
|
from .iam_pomes import iam_setup_server
|
|
157
174
|
iam_setup_server(**args)
|
|
@@ -191,10 +208,13 @@ def service_login() -> Response:
|
|
|
191
208
|
# declare the return variable
|
|
192
209
|
result: Response | None = None
|
|
193
210
|
|
|
211
|
+
# retrieve the request arguments
|
|
212
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
213
|
+
|
|
194
214
|
# log the request
|
|
195
215
|
if __IAM_LOGGER:
|
|
196
|
-
__IAM_LOGGER.debug(msg=
|
|
197
|
-
|
|
216
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
217
|
+
ensure_ascii=False)}")
|
|
198
218
|
errors: list[str] = []
|
|
199
219
|
with _iam_lock:
|
|
200
220
|
# retrieve the IAM server
|
|
@@ -204,7 +224,7 @@ def service_login() -> Response:
|
|
|
204
224
|
if iam_server:
|
|
205
225
|
# obtain the login URL
|
|
206
226
|
login_url: str = iam_login(iam_server=iam_server,
|
|
207
|
-
args=
|
|
227
|
+
args=args,
|
|
208
228
|
errors=errors,
|
|
209
229
|
logger=__IAM_LOGGER)
|
|
210
230
|
if login_url:
|
|
@@ -221,7 +241,8 @@ def service_login() -> Response:
|
|
|
221
241
|
|
|
222
242
|
|
|
223
243
|
# @flask_app.route(rule=<logout_endpoint>, # IAM_ENDPOINT_LOGOUT
|
|
224
|
-
# methods=["
|
|
244
|
+
# methods=["POST"])
|
|
245
|
+
@jwt_required
|
|
225
246
|
def service_logout() -> Response:
|
|
226
247
|
"""
|
|
227
248
|
Entry point for the *IAM* server's logout service.
|
|
@@ -239,10 +260,13 @@ def service_logout() -> Response:
|
|
|
239
260
|
# declare the return variable
|
|
240
261
|
result: Response | None
|
|
241
262
|
|
|
263
|
+
# retrieve the request arguments
|
|
264
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
265
|
+
|
|
242
266
|
# log the request
|
|
243
267
|
if __IAM_LOGGER:
|
|
244
|
-
__IAM_LOGGER.debug(msg=
|
|
245
|
-
|
|
268
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
269
|
+
ensure_ascii=False)}")
|
|
246
270
|
errors: list[str] = []
|
|
247
271
|
with _iam_lock:
|
|
248
272
|
# retrieve the IAM server
|
|
@@ -252,7 +276,7 @@ def service_logout() -> Response:
|
|
|
252
276
|
if iam_server:
|
|
253
277
|
# logout the user
|
|
254
278
|
iam_logout(iam_server=iam_server,
|
|
255
|
-
args=
|
|
279
|
+
args=args,
|
|
256
280
|
errors=errors,
|
|
257
281
|
logger=__IAM_LOGGER)
|
|
258
282
|
if errors:
|
|
@@ -294,10 +318,13 @@ def service_callback() -> Response:
|
|
|
294
318
|
|
|
295
319
|
:return: *Response* containing the reference user identification and the token, or *BAD REQUEST*
|
|
296
320
|
"""
|
|
321
|
+
# retrieve the request arguments
|
|
322
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
323
|
+
|
|
297
324
|
# log the request
|
|
298
325
|
if __IAM_LOGGER:
|
|
299
|
-
__IAM_LOGGER.debug(msg=
|
|
300
|
-
|
|
326
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
327
|
+
ensure_ascii=False)}")
|
|
301
328
|
errors: list[str] = []
|
|
302
329
|
token_data: tuple[str, str] | None = None
|
|
303
330
|
with _iam_lock:
|
|
@@ -308,7 +335,7 @@ def service_callback() -> Response:
|
|
|
308
335
|
if iam_server:
|
|
309
336
|
# process the callback operation
|
|
310
337
|
token_data = iam_callback(iam_server=iam_server,
|
|
311
|
-
args=
|
|
338
|
+
args=args,
|
|
312
339
|
errors=errors,
|
|
313
340
|
logger=__IAM_LOGGER)
|
|
314
341
|
result: Response
|
|
@@ -350,10 +377,13 @@ def service_exchange() -> Response:
|
|
|
350
377
|
|
|
351
378
|
:return: *Response* containing the reference user identification and the token, or *BAD REQUEST*
|
|
352
379
|
"""
|
|
380
|
+
# retrieve the request arguments
|
|
381
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
382
|
+
|
|
353
383
|
# log the request
|
|
354
384
|
if __IAM_LOGGER:
|
|
355
|
-
__IAM_LOGGER.debug(msg=
|
|
356
|
-
|
|
385
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
386
|
+
ensure_ascii=False)}")
|
|
357
387
|
errors: list[str] = []
|
|
358
388
|
with _iam_lock:
|
|
359
389
|
# retrieve the IAM server
|
|
@@ -365,7 +395,7 @@ def service_exchange() -> Response:
|
|
|
365
395
|
if iam_server:
|
|
366
396
|
errors: list[str] = []
|
|
367
397
|
token_info = iam_exchange(iam_server=iam_server,
|
|
368
|
-
args=
|
|
398
|
+
args=args,
|
|
369
399
|
errors=errors,
|
|
370
400
|
logger=__IAM_LOGGER)
|
|
371
401
|
result: Response
|
|
@@ -415,10 +445,13 @@ def service_callback_exchange() -> Response:
|
|
|
415
445
|
# declare the return variable
|
|
416
446
|
result: Response | None = None
|
|
417
447
|
|
|
448
|
+
# retrieve the request arguments
|
|
449
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
450
|
+
|
|
418
451
|
# log the request
|
|
419
452
|
if __IAM_LOGGER:
|
|
420
|
-
__IAM_LOGGER.debug(msg=
|
|
421
|
-
|
|
453
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
454
|
+
ensure_ascii=False)}")
|
|
422
455
|
errors: list[str] = []
|
|
423
456
|
with _iam_lock:
|
|
424
457
|
# retrieve the IAM server
|
|
@@ -427,7 +460,7 @@ def service_callback_exchange() -> Response:
|
|
|
427
460
|
logger=__IAM_LOGGER)
|
|
428
461
|
# obtain the login URL
|
|
429
462
|
token_info: tuple[str, str] = iam_callback(iam_server=iam_server,
|
|
430
|
-
args=
|
|
463
|
+
args=args,
|
|
431
464
|
errors=errors,
|
|
432
465
|
logger=__IAM_LOGGER)
|
|
433
466
|
if token_info:
|
|
@@ -477,13 +510,13 @@ def service_get_token() -> Response:
|
|
|
477
510
|
|
|
478
511
|
:return: *Response* containing the user reference identification and the token, or *BAD REQUEST*
|
|
479
512
|
"""
|
|
513
|
+
# retrieve the request arguments
|
|
514
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
515
|
+
|
|
480
516
|
# log the request
|
|
481
517
|
if __IAM_LOGGER:
|
|
482
|
-
__IAM_LOGGER.debug(msg=
|
|
483
|
-
|
|
484
|
-
# obtain the request arguments
|
|
485
|
-
args: dict[str, Any] = request.args
|
|
486
|
-
|
|
518
|
+
__IAM_LOGGER.debug(msg=f"Request {request.method}:{request.path}; {json.dumps(obj=args,
|
|
519
|
+
ensure_ascii=False)}")
|
|
487
520
|
errors: list[str] = []
|
|
488
521
|
token_info: dict[str, str] | None = None
|
|
489
522
|
with _iam_lock:
|
|
@@ -511,14 +544,55 @@ def service_get_token() -> Response:
|
|
|
511
544
|
return result
|
|
512
545
|
|
|
513
546
|
|
|
514
|
-
|
|
547
|
+
# @flask_app.route(rule=<token_endpoint>, # IAM_ENDPOINT_USERINFO
|
|
548
|
+
# methods=["GET"])
|
|
549
|
+
@jwt_required
|
|
550
|
+
def service_userinfo() -> Response:
|
|
515
551
|
"""
|
|
516
|
-
|
|
552
|
+
Entry point for retrieving user data from the *IAM* server.
|
|
517
553
|
|
|
518
|
-
|
|
519
|
-
|
|
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*
|
|
520
563
|
"""
|
|
564
|
+
# retrieve the request arguments
|
|
565
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
521
566
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
|
@@ -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,7 +53,7 @@ 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>
|
|
56
|
+
2. The special environment variable *<APP_PREFIX>_PROVIDER_ENDPOINT_TOKEN* identifies the endpoint
|
|
57
57
|
from which to obtain JWT tokens. It is not part of the *JWT* providers' setup, but is meant to be
|
|
58
58
|
used by function *provider_setup_endpoint()*, wherein the value in that variable would represent
|
|
59
59
|
the default value for its parameter.
|
|
@@ -63,16 +63,16 @@ def __get_provider_data() -> dict[str, dict[ProviderParam, Any]]:
|
|
|
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>,
|
|
@@ -111,7 +112,7 @@ def provider_setup_server(provider_id: str,
|
|
|
111
112
|
custom_auth: tuple[str, str] = None,
|
|
112
113
|
header_data: dict[str, str] = None,
|
|
113
114
|
body_data: dict[str, str] = None,
|
|
114
|
-
|
|
115
|
+
url_token: str = None) -> None:
|
|
115
116
|
"""
|
|
116
117
|
Setup the *JWT* provider *provider_id*.
|
|
117
118
|
|
|
@@ -122,9 +123,10 @@ def provider_setup_server(provider_id: str,
|
|
|
122
123
|
as key-value pairs in the body of the request. Otherwise, the external provider *provider_id* uses the standard
|
|
123
124
|
HTTP Basic Authorization scheme, wherein the credentials are B64-encoded and sent in the request headers.
|
|
124
125
|
|
|
125
|
-
Optional constant key-value pairs (such as ['Content-Type', 'application/x-www-form-urlencoded']),
|
|
126
|
-
added to the request headers, may be specified in *headers_data*. Likewise, optional constant
|
|
127
|
-
(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*.
|
|
128
130
|
|
|
129
131
|
:param provider_id: the provider's identification
|
|
130
132
|
:param user_id: the basic authorization user
|
|
@@ -132,7 +134,7 @@ def provider_setup_server(provider_id: str,
|
|
|
132
134
|
:param custom_auth: optional key names for sending the credentials as key-value pairs in the body of the request
|
|
133
135
|
:param header_data: optional key-value pairs to be added to the request headers
|
|
134
136
|
:param body_data: optional key-value pairs to be added to the request body
|
|
135
|
-
:param
|
|
137
|
+
:param url_token: the url to request *JWT* tokens with
|
|
136
138
|
"""
|
|
137
139
|
global _provider_registry
|
|
138
140
|
|
|
@@ -151,17 +153,17 @@ def provider_setup_server(provider_id: str,
|
|
|
151
153
|
header_data = env_get_obj(key=f"{APP_PREFIX}_{prefix}_HEADER_DATA")
|
|
152
154
|
if "body_data" in defaulted_params:
|
|
153
155
|
body_data = env_get_obj(key=f"{APP_PREFIX}_{prefix}_BODY_DATA")
|
|
154
|
-
if "
|
|
155
|
-
|
|
156
|
+
if "url_token" in defaulted_params:
|
|
157
|
+
url_token = env_get_str(key=f"{APP_PREFIX}_{prefix}_URL_TOKEN")
|
|
156
158
|
|
|
157
159
|
with _provider_lock:
|
|
158
160
|
_provider_registry[provider_id] = {
|
|
159
|
-
ProviderParam.
|
|
160
|
-
ProviderParam.USER_ID: user_id,
|
|
161
|
-
ProviderParam.USER_SECRET: user_secret,
|
|
161
|
+
ProviderParam.BODY_DATA: body_data,
|
|
162
162
|
ProviderParam.CUSTOM_AUTH: custom_auth,
|
|
163
163
|
ProviderParam.HEADER_DATA: header_data,
|
|
164
|
-
ProviderParam.
|
|
164
|
+
ProviderParam.USER_ID: user_id,
|
|
165
|
+
ProviderParam.USER_SECRET: user_secret,
|
|
166
|
+
ProviderParam.URL_TOKEN: url_token,
|
|
165
167
|
# dynamically set
|
|
166
168
|
ProviderParam.ACCESS_TOKEN: None,
|
|
167
169
|
ProviderParam.ACCESS_EXPIRATION: 0,
|
|
@@ -187,12 +189,12 @@ def provider_setup_endpoint(flask_app: Flask,
|
|
|
187
189
|
|
|
188
190
|
# read from the environment variable
|
|
189
191
|
if "provider_endpoint" in defaulted_params:
|
|
190
|
-
provider_endpoint = env_get_str(key=f"{APP_PREFIX}
|
|
192
|
+
provider_endpoint = env_get_str(key=f"{APP_PREFIX}_PROVIDER_ENDPOINT_TOKEN")
|
|
191
193
|
|
|
192
194
|
# establish the endpoints
|
|
193
195
|
if provider_endpoint:
|
|
194
196
|
flask_app.add_url_rule(rule=provider_endpoint,
|
|
195
|
-
endpoint=f"
|
|
197
|
+
endpoint=f"provider-get-token",
|
|
196
198
|
view_func=service_get_token,
|
|
197
199
|
methods=["GET"])
|
|
198
200
|
|
|
@@ -222,14 +224,16 @@ def service_get_token() -> Response:
|
|
|
222
224
|
|
|
223
225
|
:return: *Response* containing the JWT token, or *BAD REQUEST*
|
|
224
226
|
"""
|
|
227
|
+
# retrieve the request arguments
|
|
228
|
+
args: dict[str, Any] = dict(request.args) or {}
|
|
229
|
+
|
|
225
230
|
# log the request
|
|
226
231
|
if __JWT_LOGGER:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
__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)}")
|
|
230
234
|
|
|
231
235
|
# obtain the provider JWT
|
|
232
|
-
provider_id: str =
|
|
236
|
+
provider_id: str = args.get("jwt-provider")
|
|
233
237
|
|
|
234
238
|
# retrieve the token
|
|
235
239
|
token: str | None = None
|
|
@@ -284,7 +288,7 @@ def provider_get_token(provider_id: str,
|
|
|
284
288
|
# access token has expired
|
|
285
289
|
header_data: dict[str, str] | None = None
|
|
286
290
|
body_data: dict[str, str] | None = None
|
|
287
|
-
url: str = provider.get(ProviderParam.
|
|
291
|
+
url: str = provider.get(ProviderParam.URL_TOKEN)
|
|
288
292
|
refresh_token: str = provider.get(ProviderParam.REFRESH_TOKEN)
|
|
289
293
|
if refresh_token:
|
|
290
294
|
# refresh token exists
|
|
@@ -298,7 +302,7 @@ def provider_get_token(provider_id: str,
|
|
|
298
302
|
"grant_type": "refresh_token",
|
|
299
303
|
"refresh_token": refresh_token
|
|
300
304
|
}
|
|
301
|
-
if not
|
|
305
|
+
if not header_data:
|
|
302
306
|
# refresh token does not exist or has expired
|
|
303
307
|
user: str = provider.get(ProviderParam.USER_ID)
|
|
304
308
|
pwd: str = provider.get(ProviderParam.USER_SECRET)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|