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/iam_pomes.py CHANGED
@@ -1,511 +1,156 @@
1
- import json
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 TZ_LOCAL, exc_format
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
- _get_iam_users, _get_iam_registry,
14
- _get_login_timeout, _get_user_data, # _get_public_key
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 login_callback(iam_server: IamServer,
198
- args: dict[str, Any],
199
- errors: list[str] = None,
200
- logger: Logger = None) -> tuple[str, str] | None:
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
- Entry point for the callback from *iam_server* via the front-end application, on authentication operations.
203
-
204
- The relevant expected arguments in *args* are:
205
- - *state*: used to enhance security during the authorization process, typically to provide *CSRF* protection
206
- - *code*: the temporary authorization code provided by *iam_server*, to be exchanged for the token
207
-
208
- :param iam_server: the reference registered *IAM* server
209
- :param args: the arguments passed when requesting the service
210
- :param errors: incidental errors
211
- :param logger: optional logger
212
- :return: a tuple containing the reference user identification and the token obtained, or *None* if error
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
- # retrieve the IAM server's data for all users
219
- users: dict[str, dict[str, Any]] = _get_iam_users(iam_server=iam_server,
220
- errors=errors,
221
- logger=logger) or {}
222
- # retrieve the OAuth2 state
223
- oauth_state: str = args.get("state")
224
- user_data: dict[str, Any] | None = None
225
- if oauth_state:
226
- for user, data in users.items():
227
- if user == oauth_state:
228
- user_data = data
229
- break
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
- :param iam_server: the reference registered *IAM* server
288
- :param args: the arguments passed when requesting the service
289
- :param errors: incidental errors
290
- :param logger: optional logger
291
- :return: the data for the new token, or *None* if error
292
- """
293
- # initialize the return variable
294
- result: dict[str, Any] | None = None
295
-
296
- # obtain the user's identification
297
- user_id: str = args.get("user-id") or args.get("login")
298
-
299
- # obtain the token to be exchanged
300
- token: str = args.get("access-token")
301
-
302
- if user_id and token:
303
- # HAZARD: only 'IAM_KEYCLOAK' is currently supported
304
- with _iam_lock:
305
- # retrieve the IAM server's registry
306
- registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
307
- errors=errors,
308
- logger=logger)
309
- if registry:
310
- body_data: dict[str, str] = {
311
- "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
312
- "subject_token": token,
313
- "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
314
- "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
315
- "audience": registry["client-id"],
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
- Send a POST request to obtain the authentication token data, and return the data received.
107
+ Retrieve the set parameters for a *IAM* server from the environment.
348
108
 
349
- For token acquisition, *body_data* will have the attributes:
350
- - "grant_type": "authorization_code"
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
- For token refresh, *body_data* will have the attributes:
355
- - "grant_type": "refresh_token"
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
- # initialize the return variable
389
- result: dict[str, Any] | None = None
390
-
391
- err_msg: str | None = None
392
- with _iam_lock:
393
- # retrieve the IAM server's registry
394
- registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
395
- errors=errors,
396
- logger=logger)
397
- if registry:
398
- # complete the data to send in body of request
399
- body_data["client_id"] = registry["client-id"]
400
- client_secret: str = registry["client-secret"]
401
-
402
- # obtain the token
403
- url: str = registry["base-url"] + "/protocol/openid-connect/token"
404
-
405
- # log the POST ('client_secret' data must not be shown in log)
406
- if logger:
407
- logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
408
- ensure_ascii=False)}")
409
- if client_secret:
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
- Validate and store the token data.
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 reference registered *IAM* server
467
- :param user_data: the aurthentication data kepth in *iam_server*'s registry
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: tuple containing the user identification and the validated and stored token, or *None* if error
144
+ :return: the uthentication tokem
472
145
  """
473
- # initialize the return variable
474
- result: tuple[str, str] | None = None
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
- # retrieve the IAM server's registry
478
- registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
479
- errors=errors,
480
- logger=logger)
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