pypomes-iam 0.5.1__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 +16 -18
- pypomes_iam/iam_actions.py +870 -0
- pypomes_iam/iam_common.py +217 -83
- pypomes_iam/iam_pomes.py +131 -486
- pypomes_iam/iam_services.py +138 -53
- pypomes_iam/provider_pomes.py +160 -79
- pypomes_iam/token_pomes.py +39 -2
- {pypomes_iam-0.5.1.dist-info → pypomes_iam-0.6.9.dist-info}/METADATA +1 -2
- pypomes_iam-0.6.9.dist-info/RECORD +11 -0
- pypomes_iam/jusbr_pomes.py +0 -122
- pypomes_iam/keycloak_pomes.py +0 -136
- pypomes_iam-0.5.1.dist-info/RECORD +0 -12
- {pypomes_iam-0.5.1.dist-info → pypomes_iam-0.6.9.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.5.1.dist-info → pypomes_iam-0.6.9.dist-info}/licenses/LICENSE +0 -0
pypomes_iam/iam_pomes.py
CHANGED
|
@@ -1,511 +1,156 @@
|
|
|
1
|
-
import
|
|
2
|
-
import requests
|
|
3
|
-
import secrets
|
|
4
|
-
import string
|
|
5
|
-
import sys
|
|
6
|
-
from datetime import datetime
|
|
1
|
+
from flask import Flask
|
|
7
2
|
from logging import Logger
|
|
8
|
-
from pypomes_core import
|
|
3
|
+
from pypomes_core import APP_PREFIX, env_get_int, env_get_str
|
|
9
4
|
from typing import Any
|
|
10
5
|
|
|
11
6
|
from .iam_common import (
|
|
12
|
-
IamServer, _iam_lock
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
_IAM_SERVERS, IamServer, IamParam, _iam_lock
|
|
8
|
+
)
|
|
9
|
+
from .iam_actions import action_token
|
|
10
|
+
from .iam_services import (
|
|
11
|
+
service_login, service_logout, service_callback, service_exchange, service_token
|
|
15
12
|
)
|
|
16
|
-
from .token_pomes import token_validate
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def user_login(iam_server: IamServer,
|
|
20
|
-
args: dict[str, Any],
|
|
21
|
-
errors: list[str] = None,
|
|
22
|
-
logger: Logger = None) -> str:
|
|
23
|
-
"""
|
|
24
|
-
Build the URL for redirecting the request to *iam_server*'s authentication page.
|
|
25
|
-
|
|
26
|
-
These are the expected attributes in *args*:
|
|
27
|
-
- user-id: optional, identifies the reference user (alias: 'login')
|
|
28
|
-
- redirect-uri: a parameter to be added to the query part of the returned URL
|
|
29
|
-
|
|
30
|
-
If provided, the user identification will be validated against the authorization data
|
|
31
|
-
returned by *iam_server* upon login. On success, the appropriate URL for invoking
|
|
32
|
-
the IAM server's authentication page is returned.
|
|
33
|
-
|
|
34
|
-
:param iam_server: the reference registered *IAM* server
|
|
35
|
-
:param args: the arguments passed when requesting the service
|
|
36
|
-
:param errors: incidental error messages
|
|
37
|
-
:param logger: optional logger
|
|
38
|
-
:return: the callback URL, with the appropriate parameters, of *None* if error
|
|
39
|
-
"""
|
|
40
|
-
# initialize the return variable
|
|
41
|
-
result: str | None = None
|
|
42
|
-
|
|
43
|
-
# obtain the optional user's identification
|
|
44
|
-
user_id: str = args.get("user-id") or args.get("login")
|
|
45
|
-
|
|
46
|
-
# build the user data
|
|
47
|
-
# ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
|
|
48
|
-
oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
49
|
-
|
|
50
|
-
with _iam_lock:
|
|
51
|
-
# retrieve the user data from the IAM server's registry
|
|
52
|
-
user_data: dict[str, Any] = _get_user_data(iam_server=iam_server,
|
|
53
|
-
user_id=oauth_state,
|
|
54
|
-
errors=errors,
|
|
55
|
-
logger=logger)
|
|
56
|
-
if user_data:
|
|
57
|
-
user_data["login-id"] = user_id
|
|
58
|
-
timeout: int = _get_login_timeout(iam_server=iam_server,
|
|
59
|
-
errors=errors,
|
|
60
|
-
logger=logger)
|
|
61
|
-
if not errors:
|
|
62
|
-
user_data["login-expiration"] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout \
|
|
63
|
-
if timeout else None
|
|
64
|
-
redirect_uri: str = args.get("redirect-uri")
|
|
65
|
-
user_data["redirect-uri"] = redirect_uri
|
|
66
|
-
|
|
67
|
-
# build the login url
|
|
68
|
-
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
69
|
-
errors=errors,
|
|
70
|
-
logger=logger)
|
|
71
|
-
if registry:
|
|
72
|
-
result = (f"{registry["base-url"]}/protocol/openid-connect/auth"
|
|
73
|
-
f"?response_type=code&scope=openid"
|
|
74
|
-
f"&client_id={registry["client-id"]}"
|
|
75
|
-
f"&redirect_uri={redirect_uri}"
|
|
76
|
-
f"&state={oauth_state}")
|
|
77
|
-
return result
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
def user_logout(iam_server: IamServer,
|
|
81
|
-
args: dict[str, Any],
|
|
82
|
-
errors: list[str] = None,
|
|
83
|
-
logger: Logger = None) -> None:
|
|
84
|
-
"""
|
|
85
|
-
Logout the user, by removing all data associating it from *iam_server*'s registry.
|
|
86
|
-
|
|
87
|
-
The user is identified by the attribute *user-id* or "login", provided in *args*.
|
|
88
|
-
If successful, remove all data relating to the user from the *IAM* server's registry.
|
|
89
|
-
Otherwise, this operation fails silently, unless an error has ocurred.
|
|
90
|
-
|
|
91
|
-
:param iam_server: the reference registered *IAM* server
|
|
92
|
-
:param args: the arguments passed when requesting the service
|
|
93
|
-
:param errors: incidental error messages
|
|
94
|
-
:param logger: optional logger
|
|
95
|
-
"""
|
|
96
|
-
# obtain the user's identification
|
|
97
|
-
user_id: str = args.get("user-id") or args.get("login")
|
|
98
|
-
|
|
99
|
-
if user_id:
|
|
100
|
-
with _iam_lock:
|
|
101
|
-
# retrieve the data for all users in the IAM server's registry
|
|
102
|
-
users: dict[str, dict[str, Any]] = _get_iam_users(iam_server=iam_server,
|
|
103
|
-
errors=errors,
|
|
104
|
-
logger=logger) or {}
|
|
105
|
-
if user_id in users:
|
|
106
|
-
users.pop(user_id)
|
|
107
|
-
if logger:
|
|
108
|
-
logger.debug(msg=f"User '{user_id}' removed from {iam_server}'s registry")
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def user_token(iam_server: IamServer,
|
|
112
|
-
args: dict[str, Any],
|
|
113
|
-
errors: list[str] = None,
|
|
114
|
-
logger: Logger = None) -> str:
|
|
115
|
-
"""
|
|
116
|
-
Retrieve the authentication token for the user, from *iam_server*.
|
|
117
|
-
|
|
118
|
-
The user is identified by the attribute *user-id* or *login*, provided in *args*.
|
|
119
|
-
|
|
120
|
-
:param iam_server: the reference registered *IAM* server
|
|
121
|
-
:param args: the arguments passed when requesting the service
|
|
122
|
-
:param errors: incidental error messages
|
|
123
|
-
:param logger: optional logger
|
|
124
|
-
:return: the token for user indicated, or *None* if error
|
|
125
|
-
"""
|
|
126
|
-
# initialize the return variable
|
|
127
|
-
result: str | None = None
|
|
128
|
-
|
|
129
|
-
# obtain the user's identification
|
|
130
|
-
user_id: str = args.get("user-id") or args.get("login")
|
|
131
|
-
|
|
132
|
-
err_msg: str | None = None
|
|
133
|
-
if user_id:
|
|
134
|
-
with _iam_lock:
|
|
135
|
-
# retrieve the user data in the IAM server's registry
|
|
136
|
-
user_data: dict[str, Any] = _get_user_data(iam_server=iam_server,
|
|
137
|
-
user_id=user_id,
|
|
138
|
-
errors=errors,
|
|
139
|
-
logger=logger)
|
|
140
|
-
token: str = user_data["access-token"] if user_data else None
|
|
141
|
-
if token:
|
|
142
|
-
access_expiration: int = user_data.get("access-expiration")
|
|
143
|
-
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
144
|
-
if now < access_expiration:
|
|
145
|
-
result = token
|
|
146
|
-
else:
|
|
147
|
-
# access token has expired
|
|
148
|
-
refresh_token: str = user_data["refresh-token"]
|
|
149
|
-
if refresh_token:
|
|
150
|
-
refresh_expiration = user_data["refresh-expiration"]
|
|
151
|
-
if now < refresh_expiration:
|
|
152
|
-
body_data: dict[str, str] = {
|
|
153
|
-
"grant_type": "refresh_token",
|
|
154
|
-
"refresh_token": refresh_token
|
|
155
|
-
}
|
|
156
|
-
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
157
|
-
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
158
|
-
body_data=body_data,
|
|
159
|
-
errors=errors,
|
|
160
|
-
logger=logger)
|
|
161
|
-
# validate and store the token data
|
|
162
|
-
if token_data:
|
|
163
|
-
token_info: tuple[str, str] = __validate_and_store(iam_server=iam_server,
|
|
164
|
-
user_data=user_data,
|
|
165
|
-
token_data=token_data,
|
|
166
|
-
now=now,
|
|
167
|
-
errors=errors,
|
|
168
|
-
logger=logger)
|
|
169
|
-
result = token_info[1]
|
|
170
|
-
else:
|
|
171
|
-
# refresh token is no longer valid
|
|
172
|
-
user_data["refresh-token"] = None
|
|
173
|
-
else:
|
|
174
|
-
# refresh token has expired
|
|
175
|
-
err_msg = "Access and refresh tokens expired"
|
|
176
|
-
if logger:
|
|
177
|
-
logger.error(msg=err_msg)
|
|
178
|
-
else:
|
|
179
|
-
err_msg = "Access token expired, no refresh token available"
|
|
180
|
-
if logger:
|
|
181
|
-
logger.error(msg=err_msg)
|
|
182
|
-
else:
|
|
183
|
-
err_msg = f"User '{user_id}' not authenticated"
|
|
184
|
-
if logger:
|
|
185
|
-
logger.error(msg=err_msg)
|
|
186
|
-
else:
|
|
187
|
-
err_msg = "User identification not provided"
|
|
188
|
-
if logger:
|
|
189
|
-
logger.error(msg=err_msg)
|
|
190
|
-
|
|
191
|
-
if err_msg and isinstance(errors, list):
|
|
192
|
-
errors.append(err_msg)
|
|
193
|
-
|
|
194
|
-
return result
|
|
195
13
|
|
|
196
14
|
|
|
197
|
-
def
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
15
|
+
def iam_setup(flask_app: Flask,
|
|
16
|
+
iam_server: IamServer,
|
|
17
|
+
base_url: str,
|
|
18
|
+
client_id: str,
|
|
19
|
+
client_realm: str,
|
|
20
|
+
client_secret: str | None,
|
|
21
|
+
recipient_attribute: str,
|
|
22
|
+
admin_id: str = None,
|
|
23
|
+
admin_secret: str = None,
|
|
24
|
+
login_timeout: int = None,
|
|
25
|
+
public_key_lifetime: int = None,
|
|
26
|
+
callback_endpoint: str = None,
|
|
27
|
+
exchange_endpoint: str = None,
|
|
28
|
+
login_endpoint: str = None,
|
|
29
|
+
logout_endpoint: str = None,
|
|
30
|
+
token_endpoint: str = None) -> None:
|
|
201
31
|
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
The
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
:
|
|
32
|
+
Establish the provided parameters for configuring the *IAM* server *iam_server*.
|
|
33
|
+
|
|
34
|
+
The parameters *admin_id* and *admin_* are required only if administrative are task are planned.
|
|
35
|
+
The optional parameter *client_timeout* refers to the maximum time in seconds allowed for the
|
|
36
|
+
user to login at the *IAM* server's login page, and defaults to no time limit.
|
|
37
|
+
|
|
38
|
+
The parameter *client_secret* is required in most requests to the *IAM* server. In the case
|
|
39
|
+
it is not provided, but *admin_id* and *admin_secret* are, it is obtained from the *IAM* server itself
|
|
40
|
+
the first time it is needed.
|
|
41
|
+
|
|
42
|
+
:param flask_app: the Flask application
|
|
43
|
+
:param iam_server: identifies the supported *IAM* server (currently, *jusbr* or *keycloak*)
|
|
44
|
+
:param base_url: base URL to request services
|
|
45
|
+
:param client_id: the client's identification with the *IAM* server
|
|
46
|
+
:param client_realm: the client realm
|
|
47
|
+
:param client_secret: the client's password with the *IAM* server
|
|
48
|
+
:param recipient_attribute: attribute in the token's payload holding the token's subject
|
|
49
|
+
:param admin_id: identifies the realm administrator
|
|
50
|
+
:param admin_secret: password for the realm administrator
|
|
51
|
+
:param login_timeout: timeout for login authentication (in seconds,defaults to no timeout)
|
|
52
|
+
:param public_key_lifetime: how long to use *IAM* server's public key, before refreshing it (in seconds)
|
|
53
|
+
:param callback_endpoint: endpoint for the callback from the front end
|
|
54
|
+
:param exchange_endpoint: endpoint for requesting token exchange
|
|
55
|
+
:param login_endpoint: endpoint for redirecting user to the *IAM* server's login page
|
|
56
|
+
:param logout_endpoint: endpoint for terminating user access
|
|
57
|
+
:param token_endpoint: endpoint for retrieving authentication token
|
|
213
58
|
"""
|
|
214
|
-
# initialize the return variable
|
|
215
|
-
result: tuple[str, str] | None = None
|
|
216
59
|
|
|
60
|
+
# configure the Keycloak registry
|
|
217
61
|
with _iam_lock:
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
# exchange 'code' received for the token
|
|
232
|
-
if user_data:
|
|
233
|
-
expiration: int = user_data["login-expiration"] or sys.maxsize
|
|
234
|
-
if int(datetime.now(tz=TZ_LOCAL).timestamp()) > expiration:
|
|
235
|
-
errors.append("Operation timeout")
|
|
236
|
-
else:
|
|
237
|
-
users.pop(oauth_state)
|
|
238
|
-
code: str = args.get("code")
|
|
239
|
-
body_data: dict[str, Any] = {
|
|
240
|
-
"grant_type": "authorization_code",
|
|
241
|
-
"code": code,
|
|
242
|
-
"redirect_uri": user_data.pop("redirect-uri")
|
|
243
|
-
}
|
|
244
|
-
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
245
|
-
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
246
|
-
body_data=body_data,
|
|
247
|
-
errors=errors,
|
|
248
|
-
logger=logger)
|
|
249
|
-
# validate and store the token data
|
|
250
|
-
if token_data:
|
|
251
|
-
result = __validate_and_store(iam_server=iam_server,
|
|
252
|
-
user_data=user_data,
|
|
253
|
-
token_data=token_data,
|
|
254
|
-
now=now,
|
|
255
|
-
errors=errors,
|
|
256
|
-
logger=logger)
|
|
257
|
-
else:
|
|
258
|
-
msg: str = f"State '{oauth_state}' not found in {iam_server}'s registry"
|
|
259
|
-
if logger:
|
|
260
|
-
logger.error(msg=msg)
|
|
261
|
-
if isinstance(errors, list):
|
|
262
|
-
errors.append(msg)
|
|
263
|
-
|
|
264
|
-
return result
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def token_exchange(iam_server: IamServer,
|
|
268
|
-
args: dict[str, Any],
|
|
269
|
-
errors: list[str] = None,
|
|
270
|
-
logger: Logger = None) -> dict[str, Any]:
|
|
271
|
-
"""
|
|
272
|
-
Request *iam_server* to issue a token in exchange for the token obtained from another *IAM* server.
|
|
273
|
-
|
|
274
|
-
The expected parameters in *args* are:
|
|
275
|
-
- user-id: identification for the reference user (alias: 'login')
|
|
276
|
-
- token: the token to be exchanged
|
|
277
|
-
|
|
278
|
-
The typical data set returned contains the following attributes:
|
|
279
|
-
{
|
|
280
|
-
"token_type": "Bearer",
|
|
281
|
-
"access_token": <str>,
|
|
282
|
-
"expires_in": <number-of-seconds>,
|
|
283
|
-
"refresh_token": <str>,
|
|
284
|
-
"refesh_expires_in": <number-of-seconds>
|
|
62
|
+
_IAM_SERVERS[iam_server] = {
|
|
63
|
+
IamParam.URL_BASE: base_url,
|
|
64
|
+
IamParam.CLIENT_ID: client_id,
|
|
65
|
+
IamParam.CLIENT_REALM: client_realm,
|
|
66
|
+
IamParam.CLIENT_SECRET: client_secret,
|
|
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,
|
|
72
|
+
IamParam.PK_EXPIRATION: 0,
|
|
73
|
+
IamParam.PUBLIC_KEY: None,
|
|
74
|
+
IamParam.USERS: {}
|
|
285
75
|
}
|
|
286
76
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
"subject_issuer": "oidc"
|
|
317
|
-
}
|
|
318
|
-
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
319
|
-
token_data: dict[str, Any] = __post_for_token(iam_server=IamServer.IAM_KEYCLOAK,
|
|
320
|
-
body_data=body_data,
|
|
321
|
-
errors=errors,
|
|
322
|
-
logger=logger)
|
|
323
|
-
# validate and store the token data
|
|
324
|
-
if token_data:
|
|
325
|
-
user_data: dict[str, Any] = {}
|
|
326
|
-
result = __validate_and_store(iam_server=iam_server,
|
|
327
|
-
user_data=user_data,
|
|
328
|
-
token_data=token_data,
|
|
329
|
-
now=now,
|
|
330
|
-
errors=errors,
|
|
331
|
-
logger=logger)
|
|
332
|
-
else:
|
|
333
|
-
msg: str = "User identification or token not provided"
|
|
334
|
-
if logger:
|
|
335
|
-
logger.error(msg=msg)
|
|
336
|
-
if isinstance(errors, list):
|
|
337
|
-
errors.append(msg)
|
|
338
|
-
|
|
339
|
-
return result
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
def __post_for_token(iam_server: IamServer,
|
|
343
|
-
body_data: dict[str, Any],
|
|
344
|
-
errors: list[str] | None,
|
|
345
|
-
logger: Logger | None) -> dict[str, Any] | None:
|
|
77
|
+
# establish the endpoints
|
|
78
|
+
if callback_endpoint:
|
|
79
|
+
flask_app.add_url_rule(rule=callback_endpoint,
|
|
80
|
+
endpoint=f"{iam_server}-callback",
|
|
81
|
+
view_func=service_callback,
|
|
82
|
+
methods=["GET"])
|
|
83
|
+
if login_endpoint:
|
|
84
|
+
flask_app.add_url_rule(rule=login_endpoint,
|
|
85
|
+
endpoint=f"{iam_server}-login",
|
|
86
|
+
view_func=service_login,
|
|
87
|
+
methods=["GET"])
|
|
88
|
+
if logout_endpoint:
|
|
89
|
+
flask_app.add_url_rule(rule=logout_endpoint,
|
|
90
|
+
endpoint=f"{iam_server}-logout",
|
|
91
|
+
view_func=service_logout,
|
|
92
|
+
methods=["GET"])
|
|
93
|
+
if token_endpoint:
|
|
94
|
+
flask_app.add_url_rule(rule=token_endpoint,
|
|
95
|
+
endpoint=f"{iam_server}-token",
|
|
96
|
+
view_func=service_token,
|
|
97
|
+
methods=["GET"])
|
|
98
|
+
if exchange_endpoint:
|
|
99
|
+
flask_app.add_url_rule(rule=exchange_endpoint,
|
|
100
|
+
endpoint=f"{iam_server}-exchange",
|
|
101
|
+
view_func=service_exchange,
|
|
102
|
+
methods=["POST"])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def iam_get_env_parameters(iam_prefix: str = None) -> dict[str, Any]:
|
|
346
106
|
"""
|
|
347
|
-
|
|
107
|
+
Retrieve the set parameters for a *IAM* server from the environment.
|
|
348
108
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
- "code": <16-character-random-code>
|
|
352
|
-
- "redirect_uri": <redirect-uri>
|
|
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.
|
|
353
111
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
- "refresh_token": <current-refresh-token>
|
|
357
|
-
|
|
358
|
-
For token exchange, *body_data* will have the attributes:
|
|
359
|
-
- "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
360
|
-
- "subject_token": <token-to-be-exchanged>,
|
|
361
|
-
- "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
362
|
-
- "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
363
|
-
- "audience": <client-id>,
|
|
364
|
-
- "subject_issuer": "oidc"
|
|
365
|
-
|
|
366
|
-
These attributes are then added to *body_data*:
|
|
367
|
-
- "client_id": <client-id>,
|
|
368
|
-
- "client_secret": <client-secret>,
|
|
369
|
-
|
|
370
|
-
If the operation is successful, the token data is stored in the *IAM* server's registry, and returned.
|
|
371
|
-
Otherwise, *errors* will contain the appropriate error message.
|
|
372
|
-
|
|
373
|
-
The typical data set returned contains the following attributes:
|
|
374
|
-
{
|
|
375
|
-
"token_type": "Bearer",
|
|
376
|
-
"access_token": <str>,
|
|
377
|
-
"expires_in": <number-of-seconds>,
|
|
378
|
-
"refresh_token": <str>,
|
|
379
|
-
"refesh_expires_in": <number-of-seconds>
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
:param iam_server: the reference registered *IAM* server
|
|
383
|
-
:param body_data: the data to send in the body of the request
|
|
384
|
-
:param errors: incidental errors
|
|
385
|
-
:param logger: optional logger
|
|
386
|
-
:return: the token data, or *None* if error
|
|
112
|
+
:param iam_prefix: the prefix classifying the parameters
|
|
113
|
+
:return: the sorted parameters classified by *prefix*
|
|
387
114
|
"""
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
body_data["client_secret"] = client_secret
|
|
411
|
-
try:
|
|
412
|
-
# typical return on a token request:
|
|
413
|
-
# {
|
|
414
|
-
# "token_type": "Bearer",
|
|
415
|
-
# "access_token": <str>,
|
|
416
|
-
# "expires_in": <number-of-seconds>,
|
|
417
|
-
# "refresh_token": <str>,
|
|
418
|
-
# "refesh_expires_in": <number-of-seconds>
|
|
419
|
-
# }
|
|
420
|
-
response: requests.Response = requests.post(url=url,
|
|
421
|
-
data=body_data)
|
|
422
|
-
if response.status_code == 200:
|
|
423
|
-
# request succeeded
|
|
424
|
-
result = response.json()
|
|
425
|
-
if logger:
|
|
426
|
-
logger.debug(msg=f"POST success, {json.dumps(obj=result,
|
|
427
|
-
ensure_ascii=False)}")
|
|
428
|
-
else:
|
|
429
|
-
# request resulted in error
|
|
430
|
-
err_msg = f"POST failure, status {response.status_code}, reason {response.reason}"
|
|
431
|
-
if hasattr(response, "content") and response.content:
|
|
432
|
-
err_msg += f", content '{response.content}'"
|
|
433
|
-
if logger:
|
|
434
|
-
logger.error(msg=err_msg)
|
|
435
|
-
except Exception as e:
|
|
436
|
-
# the operation raised an exception
|
|
437
|
-
err_msg = exc_format(exc=e,
|
|
438
|
-
exc_info=sys.exc_info())
|
|
439
|
-
if logger:
|
|
440
|
-
logger.error(msg=err_msg)
|
|
441
|
-
|
|
442
|
-
if err_msg and isinstance(errors, list):
|
|
443
|
-
errors.append(err_msg)
|
|
444
|
-
|
|
445
|
-
return result
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
def __validate_and_store(iam_server: IamServer,
|
|
449
|
-
user_data: dict[str, Any],
|
|
450
|
-
token_data: dict[str, Any],
|
|
451
|
-
now: int,
|
|
452
|
-
errors: list[str] | None,
|
|
453
|
-
logger: Logger) -> tuple[str, str] | None:
|
|
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
|
+
|
|
133
|
+
def iam_get_token(iam_server: IamServer,
|
|
134
|
+
user_id: str,
|
|
135
|
+
errors: list[str] = None,
|
|
136
|
+
logger: Logger = None) -> str:
|
|
454
137
|
"""
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
The typical *token_data* contains the following attributes:
|
|
458
|
-
{
|
|
459
|
-
"token_type": "Bearer",
|
|
460
|
-
"access_token": <str>,
|
|
461
|
-
"expires_in": <number-of-seconds>,
|
|
462
|
-
"refresh_token": <str>,
|
|
463
|
-
"refesh_expires_in": <number-of-seconds>
|
|
464
|
-
}
|
|
138
|
+
Retrieve an authentication token for *user_id*.
|
|
465
139
|
|
|
466
|
-
:param iam_server: the
|
|
467
|
-
:param
|
|
468
|
-
:param token_data: the token data
|
|
140
|
+
:param iam_server: identifies the *IAM* server
|
|
141
|
+
:param user_id: identifies the user
|
|
469
142
|
:param errors: incidental errors
|
|
470
143
|
:param logger: optional logger
|
|
471
|
-
:return:
|
|
144
|
+
:return: the uthentication tokem
|
|
472
145
|
"""
|
|
473
|
-
#
|
|
474
|
-
result:
|
|
146
|
+
# declare the return variable
|
|
147
|
+
result: str
|
|
475
148
|
|
|
149
|
+
# retrieve the token
|
|
150
|
+
args: dict[str, Any] = {"user-id": user_id}
|
|
476
151
|
with _iam_lock:
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if registry:
|
|
482
|
-
token: str = token_data.get("access_token")
|
|
483
|
-
user_data["access-token"] = token
|
|
484
|
-
# keep current refresh token if a new one is not provided
|
|
485
|
-
if token_data.get("refresh_token"):
|
|
486
|
-
user_data["refresh-token"] = token_data.get("refresh_token")
|
|
487
|
-
user_data["access-expiration"] = now + token_data.get("expires_in")
|
|
488
|
-
refresh_exp: int = user_data.get("refresh_expires_in")
|
|
489
|
-
user_data["refresh-expiration"] = (now + refresh_exp) if refresh_exp else sys.maxsize
|
|
490
|
-
# public_key: str = _get_public_key(iam_server=iam_server,
|
|
491
|
-
# errors=errors,
|
|
492
|
-
# logger=logger)
|
|
493
|
-
recipient_attr = registry["recipient-attr"]
|
|
494
|
-
login_id = user_data.pop("login-id", None)
|
|
495
|
-
claims: dict[str, dict[str, Any]] = token_validate(token=token,
|
|
496
|
-
issuer=registry["base-url"],
|
|
497
|
-
recipient_id=login_id,
|
|
498
|
-
recipient_attr=recipient_attr,
|
|
499
|
-
# public_key=public_key,
|
|
500
|
-
errors=errors,
|
|
501
|
-
logger=logger)
|
|
502
|
-
if claims:
|
|
503
|
-
users: dict[str, dict[str, Any]] = _get_iam_users(iam_server=iam_server,
|
|
504
|
-
errors=errors,
|
|
505
|
-
logger=logger)
|
|
506
|
-
# must test with 'not errors'
|
|
507
|
-
if not errors:
|
|
508
|
-
user_id: str = login_id if login_id else claims["payload"][recipient_attr]
|
|
509
|
-
users[user_id] = user_data
|
|
510
|
-
result = (user_id, token)
|
|
152
|
+
result = action_token(iam_server=iam_server,
|
|
153
|
+
args=args,
|
|
154
|
+
errors=errors,
|
|
155
|
+
logger=logger)
|
|
511
156
|
return result
|