pypomes-iam 0.3.4__py3-none-any.whl → 0.3.6__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.
Potentially problematic release.
This version of pypomes-iam might be problematic. Click here for more details.
- pypomes_iam/iam_common.py +17 -104
- pypomes_iam/iam_pomes.py +325 -142
- pypomes_iam/iam_services.py +110 -71
- pypomes_iam/jusbr_pomes.py +23 -17
- pypomes_iam/keycloak_pomes.py +23 -17
- pypomes_iam/provider_pomes.py +66 -59
- pypomes_iam/token_pomes.py +1 -1
- {pypomes_iam-0.3.4.dist-info → pypomes_iam-0.3.6.dist-info}/METADATA +1 -1
- pypomes_iam-0.3.6.dist-info/RECORD +12 -0
- pypomes_iam-0.3.4.dist-info/RECORD +0 -12
- {pypomes_iam-0.3.4.dist-info → pypomes_iam-0.3.6.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.3.4.dist-info → pypomes_iam-0.3.6.dist-info}/licenses/LICENSE +0 -0
pypomes_iam/iam_pomes.py
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
1
3
|
import secrets
|
|
2
4
|
import string
|
|
3
5
|
import sys
|
|
4
|
-
from cachetools import Cache
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from logging import Logger
|
|
7
|
-
from pypomes_core import TZ_LOCAL
|
|
8
|
+
from pypomes_core import TZ_LOCAL, exc_format
|
|
8
9
|
from typing import Any
|
|
9
10
|
|
|
10
11
|
from .iam_common import (
|
|
11
|
-
IamServer,
|
|
12
|
-
_register_logger,
|
|
13
|
-
_get_iam_cache, _get_iam_registry,
|
|
12
|
+
IamServer, _iam_lock,
|
|
13
|
+
_register_logger, _get_iam_users, _get_iam_registry,
|
|
14
14
|
_get_login_timeout, _get_user_data, _get_public_key
|
|
15
15
|
)
|
|
16
|
+
from .token_pomes import token_validate
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
def register_logger(logger: Logger) -> None:
|
|
@@ -46,30 +47,34 @@ def user_login(iam_server: IamServer,
|
|
|
46
47
|
# build the user data
|
|
47
48
|
# ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
|
|
48
49
|
oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
registry[
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
50
|
+
|
|
51
|
+
with _iam_lock:
|
|
52
|
+
# retrieve the user data from the IAM server's registry
|
|
53
|
+
user_data: dict[str, Any] = _get_user_data(iam_server=iam_server,
|
|
54
|
+
user_id=oauth_state,
|
|
55
|
+
errors=errors,
|
|
56
|
+
logger=logger)
|
|
57
|
+
if user_data:
|
|
58
|
+
user_data["login-id"] = user_id
|
|
59
|
+
timeout: int = _get_login_timeout(iam_server=iam_server,
|
|
60
|
+
errors=errors,
|
|
61
|
+
logger=logger)
|
|
62
|
+
if not errors:
|
|
63
|
+
user_data["login-expiration"] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout \
|
|
64
|
+
if timeout else None
|
|
65
|
+
redirect_uri: str = args.get("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
|
+
registry["redirect-uri"] = redirect_uri
|
|
73
|
+
result = {"login-url": (f"{registry["base-url"]}/protocol/openid-connect/auth"
|
|
74
|
+
f"?response_type=code&scope=openid"
|
|
75
|
+
f"&client_id={registry["client-id"]}"
|
|
76
|
+
f"&redirect_uri={redirect_uri}"
|
|
77
|
+
f"&state={oauth_state}")}
|
|
73
78
|
return result
|
|
74
79
|
|
|
75
80
|
|
|
@@ -92,12 +97,11 @@ def user_logout(iam_server: IamServer,
|
|
|
92
97
|
user_id: str = args.get("user-id") or args.get("user_id") or args.get("login")
|
|
93
98
|
|
|
94
99
|
if user_id:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
users: dict[str, dict[str, Any]] = cache.get("users") or {}
|
|
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 {}
|
|
101
105
|
if user_id in users:
|
|
102
106
|
users.pop(user_id)
|
|
103
107
|
if logger:
|
|
@@ -111,7 +115,7 @@ def user_token(iam_server: IamServer,
|
|
|
111
115
|
"""
|
|
112
116
|
Retrieve the authentication token for the user, from *iam_server*.
|
|
113
117
|
|
|
114
|
-
The user is identified by the attribute *user-id*, *user_id*, or
|
|
118
|
+
The user is identified by the attribute *user-id*, *user_id*, or *login*, provided in *args*.
|
|
115
119
|
|
|
116
120
|
:param iam_server: the reference registered *IAM* server
|
|
117
121
|
:param args: the arguments passed when requesting the service
|
|
@@ -127,56 +131,58 @@ def user_token(iam_server: IamServer,
|
|
|
127
131
|
|
|
128
132
|
err_msg: str | None = None
|
|
129
133
|
if user_id:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
|
164
173
|
else:
|
|
165
|
-
# refresh token
|
|
166
|
-
|
|
174
|
+
# refresh token has expired
|
|
175
|
+
err_msg = "Access and refresh tokens expired"
|
|
176
|
+
if logger:
|
|
177
|
+
logger.error(msg=err_msg)
|
|
167
178
|
else:
|
|
168
|
-
|
|
169
|
-
err_msg = "Access and refresh tokens expired"
|
|
179
|
+
err_msg = "Access token expired, no refresh token available"
|
|
170
180
|
if logger:
|
|
171
181
|
logger.error(msg=err_msg)
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
else:
|
|
177
|
-
err_msg = f"User '{user_id}' not authenticated"
|
|
178
|
-
if logger:
|
|
179
|
-
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)
|
|
180
186
|
else:
|
|
181
187
|
err_msg = "User identification not provided"
|
|
182
188
|
if logger:
|
|
@@ -193,7 +199,11 @@ def login_callback(iam_server: IamServer,
|
|
|
193
199
|
errors: list[str] = None,
|
|
194
200
|
logger: Logger = None) -> tuple[str, str] | None:
|
|
195
201
|
"""
|
|
196
|
-
Entry point for the callback from *iam_server* via the front-end application, on authentication
|
|
202
|
+
Entry point for the callback from *iam_server* via the front-end application, on authentication operations.
|
|
203
|
+
|
|
204
|
+
The relevant arguments received are:
|
|
205
|
+
- *state*: used to enhance security during the authorization process, typically to provide *CSRF* protection
|
|
206
|
+
- *code*: the temporary authorization code, to be exchanged for the token
|
|
197
207
|
|
|
198
208
|
:param iam_server: the reference registered *IAM* server
|
|
199
209
|
:param args: the arguments passed when requesting the service
|
|
@@ -201,20 +211,16 @@ def login_callback(iam_server: IamServer,
|
|
|
201
211
|
:param logger: optional logger
|
|
202
212
|
:return: a tuple containing the reference user identification and the token obtained, or *None* if error
|
|
203
213
|
"""
|
|
204
|
-
from .token_pomes import token_validate
|
|
205
|
-
|
|
206
214
|
# initialize the return variable
|
|
207
215
|
result: tuple[str, str] | None = None
|
|
208
216
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# validate the OAuth2 state
|
|
217
|
+
with _iam_lock:
|
|
218
|
+
# retrieve the IAM server's registry and the data for all users therein
|
|
219
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
220
|
+
errors=errors,
|
|
221
|
+
logger=logger)
|
|
222
|
+
users: dict[str, dict[str, Any]] = (registry["cache"]["users"] or {}) if registry else {}
|
|
223
|
+
# retrieve the OAuth2 state
|
|
218
224
|
oauth_state: str = args.get("state")
|
|
219
225
|
user_data: dict[str, Any] | None = None
|
|
220
226
|
if oauth_state:
|
|
@@ -223,7 +229,7 @@ def login_callback(iam_server: IamServer,
|
|
|
223
229
|
user_data = data
|
|
224
230
|
break
|
|
225
231
|
|
|
226
|
-
# exchange 'code' for the token
|
|
232
|
+
# exchange 'code' received for the token
|
|
227
233
|
if user_data:
|
|
228
234
|
expiration: int = user_data["login-expiration"] or sys.maxsize
|
|
229
235
|
if int(datetime.now(tz=TZ_LOCAL).timestamp()) > expiration:
|
|
@@ -237,35 +243,18 @@ def login_callback(iam_server: IamServer,
|
|
|
237
243
|
"redirect_uri": registry["redirect-uri"]
|
|
238
244
|
}
|
|
239
245
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
240
|
-
token_data: dict[str, Any] =
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
#
|
|
246
|
+
token_data: dict[str, Any] = __post_for_token(iam_server=iam_server,
|
|
247
|
+
body_data=body_data,
|
|
248
|
+
errors=errors,
|
|
249
|
+
logger=logger)
|
|
250
|
+
# validate and store the token data
|
|
245
251
|
if token_data:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
user_data["refresh-expiration"] = (now + refresh_exp) if refresh_exp else sys.maxsize
|
|
253
|
-
public_key: str = _get_public_key(iam_server=iam_server,
|
|
254
|
-
errors=errors,
|
|
255
|
-
logger=logger)
|
|
256
|
-
if public_key:
|
|
257
|
-
recipient_attr = registry["recipient_attr"]
|
|
258
|
-
login_id = user_data.pop("login-id", None)
|
|
259
|
-
token_claims: dict[str, dict[str, Any]] = token_validate(token=token,
|
|
260
|
-
issuer=registry["base-url"],
|
|
261
|
-
recipient_id=login_id,
|
|
262
|
-
recipient_attr=recipient_attr,
|
|
263
|
-
public_key=public_key,
|
|
264
|
-
errors=errors,
|
|
265
|
-
logger=logger)
|
|
266
|
-
if token_claims:
|
|
267
|
-
token_user: str = token_claims["payload"].get(recipient_attr)
|
|
268
|
-
result = (token_user, token)
|
|
252
|
+
result = __validate_and_store(iam_server=iam_server,
|
|
253
|
+
user_data=user_data,
|
|
254
|
+
token_data=token_data,
|
|
255
|
+
now=now,
|
|
256
|
+
errors=errors,
|
|
257
|
+
logger=logger)
|
|
269
258
|
else:
|
|
270
259
|
msg: str = "Unknown state received"
|
|
271
260
|
if logger:
|
|
@@ -281,7 +270,20 @@ def token_exchange(iam_server: IamServer,
|
|
|
281
270
|
errors: list[str] = None,
|
|
282
271
|
logger: Logger = None) -> dict[str, Any]:
|
|
283
272
|
"""
|
|
284
|
-
|
|
273
|
+
Request *iam_server* to issue a token in exchange for the token obtained from another *IAM* server.
|
|
274
|
+
|
|
275
|
+
The expected parameters in *args* are:
|
|
276
|
+
- client-id: identification for the reference user (aliases: 'client_id', 'login')
|
|
277
|
+
- token: the token to be exchanged
|
|
278
|
+
|
|
279
|
+
The typical data set returned contains the following attributes:
|
|
280
|
+
{
|
|
281
|
+
"token_type": "Bearer",
|
|
282
|
+
"access_token": <str>,
|
|
283
|
+
"expires_in": <number-of-seconds>,
|
|
284
|
+
"refresh_token": <str>,
|
|
285
|
+
"refesh_expires_in": <number-of-seconds>
|
|
286
|
+
}
|
|
285
287
|
|
|
286
288
|
:param iam_server: the reference registered *IAM* server
|
|
287
289
|
:param args: the arguments passed when requesting the service
|
|
@@ -295,32 +297,213 @@ def token_exchange(iam_server: IamServer,
|
|
|
295
297
|
# obtain the user's identification
|
|
296
298
|
user_id: str = args.get("user-id") or args.get("user_id") or args.get("login")
|
|
297
299
|
|
|
298
|
-
#
|
|
300
|
+
# obtain the token to be exchanges
|
|
299
301
|
token: str = args.get("token")
|
|
300
302
|
|
|
301
303
|
if user_id and token:
|
|
302
304
|
# HAZARD: only 'IAM_KEYCLOAK' is currently supported
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
305
|
+
with _iam_lock:
|
|
306
|
+
# retrieve the IAM server's registry
|
|
307
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
308
|
+
errors=errors,
|
|
309
|
+
logger=logger)
|
|
310
|
+
if registry:
|
|
311
|
+
body_data: dict[str, str] = {
|
|
312
|
+
"grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
313
|
+
"subject_token": token,
|
|
314
|
+
"subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
315
|
+
"requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
316
|
+
"audience": registry["client-id"],
|
|
317
|
+
"subject_issuer": "oidc"
|
|
318
|
+
}
|
|
319
|
+
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
320
|
+
token_data: dict[str, Any] = __post_for_token(iam_server=IamServer.IAM_KEYCLOAK,
|
|
321
|
+
body_data=body_data,
|
|
322
|
+
errors=errors,
|
|
323
|
+
logger=logger)
|
|
324
|
+
# validate and store the token data
|
|
325
|
+
if token_data:
|
|
326
|
+
user_data: dict[str, Any] = {}
|
|
327
|
+
result = __validate_and_store(iam_server=iam_server,
|
|
328
|
+
user_data=user_data,
|
|
329
|
+
token_data=token_data,
|
|
330
|
+
now=now,
|
|
331
|
+
errors=errors,
|
|
332
|
+
logger=logger)
|
|
319
333
|
else:
|
|
320
|
-
msg: str = "User identification
|
|
334
|
+
msg: str = "User identification or token not provided"
|
|
321
335
|
if logger:
|
|
322
336
|
logger.error(msg=msg)
|
|
323
337
|
if isinstance(errors, list):
|
|
324
338
|
errors.append(msg)
|
|
325
339
|
|
|
326
340
|
return result
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def __post_for_token(iam_server: IamServer,
|
|
344
|
+
body_data: dict[str, Any],
|
|
345
|
+
errors: list[str] | None,
|
|
346
|
+
logger: Logger | None) -> dict[str, Any] | None:
|
|
347
|
+
"""
|
|
348
|
+
Send a POST request to obtain the authentication token data, and return the data received.
|
|
349
|
+
|
|
350
|
+
For token acquisition, *body_data* will have the attributes:
|
|
351
|
+
- "grant_type": "authorization_code"
|
|
352
|
+
- "code": <16-character-random-code>
|
|
353
|
+
- "redirect_uri": <redirect-uri>
|
|
354
|
+
|
|
355
|
+
For token refresh, *body_data* will have the attributes:
|
|
356
|
+
- "grant_type": "refresh_token"
|
|
357
|
+
- "refresh_token": <current-refresh-token>
|
|
358
|
+
|
|
359
|
+
For token exchange, *body_data* will have the attributes:
|
|
360
|
+
- "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
|
|
361
|
+
- "subject_token": <token-to-be-exchanged>,
|
|
362
|
+
- "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
363
|
+
- "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
|
|
364
|
+
- "audience": <client-id>,
|
|
365
|
+
- "subject_issuer": "oidc"
|
|
366
|
+
|
|
367
|
+
These attributes are then added to *body_data*:
|
|
368
|
+
- "client_id": <client-id>,
|
|
369
|
+
- "client_secret": <client-secret>,
|
|
370
|
+
|
|
371
|
+
If the operation is successful, the token data is stored in the *IAM* server's registry, and returned.
|
|
372
|
+
Otherwise, *errors* will contain the appropriate error message.
|
|
373
|
+
|
|
374
|
+
The typical data set returned contains the following attributes:
|
|
375
|
+
{
|
|
376
|
+
"token_type": "Bearer",
|
|
377
|
+
"access_token": <str>,
|
|
378
|
+
"expires_in": <number-of-seconds>,
|
|
379
|
+
"refresh_token": <str>,
|
|
380
|
+
"refesh_expires_in": <number-of-seconds>
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
:param iam_server: the reference registered *IAM* server
|
|
384
|
+
:param body_data: the data to send in the body of the request
|
|
385
|
+
:param errors: incidental errors
|
|
386
|
+
:param logger: optional logger
|
|
387
|
+
:return: the token data, or *None* if error
|
|
388
|
+
"""
|
|
389
|
+
# initialize the return variable
|
|
390
|
+
result: dict[str, Any] | None = None
|
|
391
|
+
|
|
392
|
+
err_msg: str | None = None
|
|
393
|
+
with _iam_lock:
|
|
394
|
+
# retrieve the IAM server's registry
|
|
395
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
396
|
+
errors=errors,
|
|
397
|
+
logger=logger)
|
|
398
|
+
if registry:
|
|
399
|
+
# complete the data to send in body of request
|
|
400
|
+
body_data["client_id"] = registry["client-id"]
|
|
401
|
+
client_secret: str = registry["client-secret"]
|
|
402
|
+
if client_secret:
|
|
403
|
+
body_data["client_secret"] = client_secret
|
|
404
|
+
|
|
405
|
+
# obtain the token
|
|
406
|
+
url: str = registry["base-url"] + "/protocol/openid-connect/token"
|
|
407
|
+
if logger:
|
|
408
|
+
logger.debug(msg=f"POST '{url}', data {json.dumps(obj=body_data,
|
|
409
|
+
ensure_ascii=False)}")
|
|
410
|
+
try:
|
|
411
|
+
# typical return on a token request:
|
|
412
|
+
# {
|
|
413
|
+
# "token_type": "Bearer",
|
|
414
|
+
# "access_token": <str>,
|
|
415
|
+
# "expires_in": <number-of-seconds>,
|
|
416
|
+
# "refresh_token": <str>,
|
|
417
|
+
# "refesh_expires_in": <number-of-seconds>
|
|
418
|
+
# }
|
|
419
|
+
response: requests.Response = requests.post(url=url,
|
|
420
|
+
data=body_data)
|
|
421
|
+
if response.status_code == 200:
|
|
422
|
+
# request succeeded
|
|
423
|
+
if logger:
|
|
424
|
+
logger.debug(msg=f"POST success, status {response.status_code}")
|
|
425
|
+
result = response.json()
|
|
426
|
+
else:
|
|
427
|
+
# request resulted in error
|
|
428
|
+
err_msg = f"POST failure, status {response.status_code}, reason '{response.reason}'"
|
|
429
|
+
if hasattr(response, "content") and response.content:
|
|
430
|
+
err_msg += f", content '{response.content}'"
|
|
431
|
+
if logger:
|
|
432
|
+
logger.error(msg=err_msg)
|
|
433
|
+
except Exception as e:
|
|
434
|
+
# the operation raised an exception
|
|
435
|
+
err_msg = exc_format(exc=e,
|
|
436
|
+
exc_info=sys.exc_info())
|
|
437
|
+
if logger:
|
|
438
|
+
logger.error(msg=err_msg)
|
|
439
|
+
|
|
440
|
+
if err_msg and isinstance(errors, list):
|
|
441
|
+
errors.append(err_msg)
|
|
442
|
+
|
|
443
|
+
return result
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def __validate_and_store(iam_server: IamServer,
|
|
447
|
+
user_data: dict[str, Any],
|
|
448
|
+
token_data: dict[str, Any],
|
|
449
|
+
now: int,
|
|
450
|
+
errors: list[str] | None,
|
|
451
|
+
logger: Logger) -> tuple[str, str] | None:
|
|
452
|
+
"""
|
|
453
|
+
Validate and store the token data.
|
|
454
|
+
|
|
455
|
+
The typical *token_data* contains the following attributes:
|
|
456
|
+
{
|
|
457
|
+
"token_type": "Bearer",
|
|
458
|
+
"access_token": <str>,
|
|
459
|
+
"expires_in": <number-of-seconds>,
|
|
460
|
+
"refresh_token": <str>,
|
|
461
|
+
"refesh_expires_in": <number-of-seconds>
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
:param iam_server: the reference registered *IAM* server
|
|
465
|
+
:param user_data: the aurthentication data kepth in *iam_server*'s registry
|
|
466
|
+
:param token_data: the token data
|
|
467
|
+
:param errors: incidental errors
|
|
468
|
+
:param logger: optional logger
|
|
469
|
+
:return: tuple containing the user identification and the validated and stored token, or *None* if error
|
|
470
|
+
"""
|
|
471
|
+
# initialize the return variable
|
|
472
|
+
result: tuple[str, str] | None = None
|
|
473
|
+
|
|
474
|
+
with _iam_lock:
|
|
475
|
+
# retrieve the IAM server's registry
|
|
476
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|
|
477
|
+
errors=errors,
|
|
478
|
+
logger=logger)
|
|
479
|
+
if registry:
|
|
480
|
+
token: str = token_data.get("access_token")
|
|
481
|
+
user_data["access-token"] = token
|
|
482
|
+
# keep current refresh token if a new one is not provided
|
|
483
|
+
if token_data.get("refresh_token"):
|
|
484
|
+
user_data["refresh-token"] = token_data.get("refresh_token")
|
|
485
|
+
user_data["access-expiration"] = now + token_data.get("expires_in")
|
|
486
|
+
refresh_exp: int = user_data.get("refresh_expires_in")
|
|
487
|
+
user_data["refresh-expiration"] = (now + refresh_exp) if refresh_exp else sys.maxsize
|
|
488
|
+
public_key: str = _get_public_key(iam_server=iam_server,
|
|
489
|
+
errors=errors,
|
|
490
|
+
logger=logger)
|
|
491
|
+
if public_key:
|
|
492
|
+
recipient_attr = registry["recipient_attr"]
|
|
493
|
+
login_id = user_data.pop("login-id", None)
|
|
494
|
+
claims: dict[str, dict[str, Any]] = token_validate(token=token,
|
|
495
|
+
issuer=registry["base-url"],
|
|
496
|
+
recipient_id=login_id,
|
|
497
|
+
recipient_attr=recipient_attr,
|
|
498
|
+
public_key=public_key,
|
|
499
|
+
errors=errors,
|
|
500
|
+
logger=logger)
|
|
501
|
+
if claims:
|
|
502
|
+
users: dict[str, dict[str, Any]] = _get_iam_users(iam_server=iam_server,
|
|
503
|
+
errors=errors,
|
|
504
|
+
logger=logger)
|
|
505
|
+
if users:
|
|
506
|
+
user_id: str = login_id if login_id else claims["payload"][recipient_attr]
|
|
507
|
+
users[user_id] = user_data
|
|
508
|
+
result = (user_id, token)
|
|
509
|
+
return result
|