pypomes-iam 0.7.4__py3-none-any.whl → 0.8.2__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/__init__.py +20 -12
- pypomes_iam/iam_actions.py +227 -62
- pypomes_iam/iam_common.py +71 -29
- pypomes_iam/iam_pomes.py +122 -99
- pypomes_iam/iam_services.py +327 -121
- pypomes_iam/provider_pomes.py +197 -30
- pypomes_iam/token_pomes.py +27 -0
- {pypomes_iam-0.7.4.dist-info → pypomes_iam-0.8.2.dist-info}/METADATA +2 -2
- pypomes_iam-0.8.2.dist-info/RECORD +11 -0
- pypomes_iam-0.7.4.dist-info/RECORD +0 -11
- {pypomes_iam-0.7.4.dist-info → pypomes_iam-0.8.2.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.7.4.dist-info → pypomes_iam-0.8.2.dist-info}/licenses/LICENSE +0 -0
pypomes_iam/__init__.py
CHANGED
|
@@ -1,37 +1,45 @@
|
|
|
1
1
|
from .iam_actions import (
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
iam_callback, iam_exchange,
|
|
3
|
+
iam_login, iam_logout, iam_get_token, iam_userinfo
|
|
4
4
|
)
|
|
5
5
|
from .iam_common import (
|
|
6
6
|
IamServer, IamParam
|
|
7
7
|
)
|
|
8
8
|
from .iam_pomes import (
|
|
9
|
-
|
|
9
|
+
iam_setup_server, iam_setup_endpoints
|
|
10
10
|
)
|
|
11
11
|
from .iam_services import (
|
|
12
|
-
|
|
12
|
+
jwt_required, iam_setup_logger,
|
|
13
|
+
service_setup_server, service_login, service_logout,
|
|
14
|
+
service_get_token, service_userinfo, service_callback,
|
|
15
|
+
service_exchange, service_callback_exchange
|
|
13
16
|
)
|
|
14
17
|
from .provider_pomes import (
|
|
15
|
-
|
|
18
|
+
service_get_token, provider_get_token,
|
|
19
|
+
provider_setup_endpoint, provider_setup_logger, provider_setup_server
|
|
16
20
|
)
|
|
17
21
|
from .token_pomes import (
|
|
18
|
-
token_validate
|
|
22
|
+
token_get_claims, token_get_values, token_validate
|
|
19
23
|
)
|
|
20
24
|
|
|
21
25
|
__all__ = [
|
|
22
26
|
# iam_actions
|
|
23
|
-
"
|
|
24
|
-
"
|
|
27
|
+
"iam_callback", "iam_exchange",
|
|
28
|
+
"iam_login", "iam_logout", "iam_get_token", "iam_userinfo",
|
|
25
29
|
# iam_commons
|
|
26
30
|
"IamServer", "IamParam",
|
|
27
31
|
# iam_pomes
|
|
28
|
-
"
|
|
32
|
+
"iam_setup_server", "iam_setup_endpoints",
|
|
29
33
|
# iam_services
|
|
30
|
-
"jwt_required", "
|
|
34
|
+
"jwt_required", "iam_setup_logger",
|
|
35
|
+
"service_setup_server", "service_login", "service_logout",
|
|
36
|
+
"service_get_token", "service_userinfo", "service_callback",
|
|
37
|
+
"service_exchange", "service_callback_exchange",
|
|
31
38
|
# provider_pomes
|
|
32
|
-
"
|
|
39
|
+
"provider_setup_server", "provider_get_token",
|
|
40
|
+
"provider_setup_endpoint", "provider_setup_logger", "provider_setup_server",
|
|
33
41
|
# token_pomes
|
|
34
|
-
"token_validate"
|
|
42
|
+
"token_get_claims", "token_get_values", "token_validate"
|
|
35
43
|
]
|
|
36
44
|
|
|
37
45
|
from importlib.metadata import version
|
pypomes_iam/iam_actions.py
CHANGED
|
@@ -13,24 +13,30 @@ from .iam_common import (
|
|
|
13
13
|
_get_iam_users, _get_iam_registry, _get_public_key,
|
|
14
14
|
_get_login_timeout, _get_user_data, _iam_server_from_issuer
|
|
15
15
|
)
|
|
16
|
-
from .token_pomes import
|
|
16
|
+
from .token_pomes import token_get_values, token_validate
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
def iam_login(iam_server: IamServer,
|
|
20
|
+
args: dict[str, Any],
|
|
21
|
+
errors: list[str] = None,
|
|
22
|
+
logger: Logger = None) -> str:
|
|
23
23
|
"""
|
|
24
24
|
Build the URL for redirecting the request to *iam_server*'s authentication page.
|
|
25
25
|
|
|
26
26
|
These are the expected attributes in *args*:
|
|
27
27
|
- user-id: optional, identifies the reference user (alias: 'login')
|
|
28
28
|
- redirect-uri: a parameter to be added to the query part of the returned URL
|
|
29
|
+
-target-idp: optionally, identify a target identity provider for the login operation
|
|
29
30
|
|
|
30
31
|
If provided, the user identification will be validated against the authorization data
|
|
31
32
|
returned by *iam_server* upon login. On success, the appropriate URL for invoking
|
|
32
33
|
the IAM server's authentication page is returned.
|
|
33
34
|
|
|
35
|
+
if 'target_idp' is provided as an attribute in *args*, the OAuth2 state variable included in the
|
|
36
|
+
returned URL will be postfixed with the string *#idp=<target-idp>*. At the callback endpoint,
|
|
37
|
+
this instructs *iam_server* to act as a broker, forwading the authentication process to the
|
|
38
|
+
*IAM* server *target-idp*.
|
|
39
|
+
|
|
34
40
|
:param iam_server: the reference registered *IAM* server
|
|
35
41
|
:param args: the arguments passed when requesting the service
|
|
36
42
|
:param errors: incidental error messages
|
|
@@ -43,9 +49,14 @@ def action_login(iam_server: IamServer,
|
|
|
43
49
|
# obtain the optional user's identification
|
|
44
50
|
user_id: str = args.get("user-id") or args.get("login")
|
|
45
51
|
|
|
52
|
+
# obtain the optional target identity provider
|
|
53
|
+
target_idp: str = args.get("target-idp")
|
|
54
|
+
|
|
46
55
|
# build the user data
|
|
47
56
|
# ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
|
|
48
57
|
oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
58
|
+
if target_idp:
|
|
59
|
+
oauth_state += f"#idp={target_idp}"
|
|
49
60
|
|
|
50
61
|
with _iam_lock:
|
|
51
62
|
# retrieve the user data from the IAM server's registry
|
|
@@ -75,19 +86,23 @@ def action_login(iam_server: IamServer,
|
|
|
75
86
|
f"&client_id={registry[IamParam.CLIENT_ID]}"
|
|
76
87
|
f"&redirect_uri={redirect_uri}"
|
|
77
88
|
f"&state={oauth_state}")
|
|
89
|
+
if target_idp:
|
|
90
|
+
# HAZARD: the name 'kc_idp_hint' is Keycloak-specific
|
|
91
|
+
result += f"&kc_idp_hint={target_idp}"
|
|
92
|
+
|
|
78
93
|
return result
|
|
79
94
|
|
|
80
95
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
def iam_logout(iam_server: IamServer,
|
|
97
|
+
args: dict[str, Any],
|
|
98
|
+
errors: list[str] = None,
|
|
99
|
+
logger: Logger = None) -> None:
|
|
85
100
|
"""
|
|
86
101
|
Logout the user, by removing all data associating it from *iam_server*'s registry.
|
|
87
102
|
|
|
88
|
-
The user is identified by the attribute *user-id* or
|
|
89
|
-
|
|
90
|
-
|
|
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.
|
|
91
106
|
|
|
92
107
|
:param iam_server: the reference registered *IAM* server
|
|
93
108
|
:param args: the arguments passed when requesting the service
|
|
@@ -99,33 +114,90 @@ def action_logout(iam_server: IamServer,
|
|
|
99
114
|
|
|
100
115
|
if user_id:
|
|
101
116
|
with _iam_lock:
|
|
102
|
-
# retrieve the data for all users
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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)
|
|
110
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)
|
|
111
176
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
177
|
+
|
|
178
|
+
def iam_get_token(iam_server: IamServer,
|
|
179
|
+
args: dict[str, Any],
|
|
180
|
+
errors: list[str] = None,
|
|
181
|
+
logger: Logger = None) -> dict[str, str]:
|
|
116
182
|
"""
|
|
117
183
|
Retrieve the authentication token for the user, from *iam_server*.
|
|
118
184
|
|
|
119
185
|
The user is identified by the attribute *user-id* or *login*, provided in *args*.
|
|
120
186
|
|
|
187
|
+
On success, the returned *dict* will contain the following JSON:
|
|
188
|
+
{
|
|
189
|
+
"access-token": <token>,
|
|
190
|
+
"user-id": <user-identification
|
|
191
|
+
}
|
|
192
|
+
|
|
121
193
|
:param iam_server: the reference registered *IAM* server
|
|
122
194
|
:param args: the arguments passed when requesting the service
|
|
123
195
|
:param errors: incidental error messages
|
|
124
196
|
:param logger: optional logger
|
|
125
|
-
:return: the
|
|
197
|
+
:return: the user identification and token issued, or *None* if error
|
|
126
198
|
"""
|
|
127
199
|
# initialize the return variable
|
|
128
|
-
result: str | None = None
|
|
200
|
+
result: dict[str, str] | None = None
|
|
129
201
|
|
|
130
202
|
# obtain the user's identification
|
|
131
203
|
user_id: str = args.get("user-id") or args.get("login")
|
|
@@ -144,7 +216,10 @@ def action_token(iam_server: IamServer,
|
|
|
144
216
|
access_expiration: int = user_data.get(UserParam.ACCESS_EXPIRATION)
|
|
145
217
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
146
218
|
if now < access_expiration:
|
|
147
|
-
result =
|
|
219
|
+
result = {
|
|
220
|
+
"access-token": token,
|
|
221
|
+
"user-id": user_id
|
|
222
|
+
}
|
|
148
223
|
else:
|
|
149
224
|
# access token has expired
|
|
150
225
|
refresh_token: str = user_data[UserParam.REFRESH_TOKEN]
|
|
@@ -152,7 +227,7 @@ def action_token(iam_server: IamServer,
|
|
|
152
227
|
refresh_expiration: int = user_data[UserParam.REFRESH_EXPIRATION]
|
|
153
228
|
if now < refresh_expiration:
|
|
154
229
|
header_data: dict[str, str] = {
|
|
155
|
-
"Content-Type": "application/
|
|
230
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
156
231
|
}
|
|
157
232
|
body_data: dict[str, str] = {
|
|
158
233
|
"grant_type": "refresh_token",
|
|
@@ -172,7 +247,10 @@ def action_token(iam_server: IamServer,
|
|
|
172
247
|
now=now,
|
|
173
248
|
errors=errors,
|
|
174
249
|
logger=logger)
|
|
175
|
-
result =
|
|
250
|
+
result = {
|
|
251
|
+
"access-token": token_info[1],
|
|
252
|
+
"user-id": user_id
|
|
253
|
+
}
|
|
176
254
|
else:
|
|
177
255
|
# refresh token is no longer valid
|
|
178
256
|
user_data[UserParam.REFRESH_TOKEN] = None
|
|
@@ -200,10 +278,10 @@ def action_token(iam_server: IamServer,
|
|
|
200
278
|
return result
|
|
201
279
|
|
|
202
280
|
|
|
203
|
-
def
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
281
|
+
def iam_callback(iam_server: IamServer,
|
|
282
|
+
args: dict[str, Any],
|
|
283
|
+
errors: list[str] = None,
|
|
284
|
+
logger: Logger = None) -> tuple[str, str] | None:
|
|
207
285
|
"""
|
|
208
286
|
Entry point for the callback from *iam_server* via the front-end application, on authentication operations.
|
|
209
287
|
|
|
@@ -211,6 +289,10 @@ def action_callback(iam_server: IamServer,
|
|
|
211
289
|
- *state*: used to enhance security during the authorization process, typically to provide *CSRF* protection
|
|
212
290
|
- *code*: the temporary authorization code provided by *iam_server*, to be exchanged for the token
|
|
213
291
|
|
|
292
|
+
if *state* is postfixed with the string *#idp=<target-idp>*, this instructs *iam_server* to act as a broker,
|
|
293
|
+
forwarding the authentication process to the *IAM* server *target-idp*. This mechanism fully dispenses with
|
|
294
|
+
the flows 'callback-exchange', and 'callback' followed by 'exchange'.
|
|
295
|
+
|
|
214
296
|
:param iam_server: the reference registered *IAM* server
|
|
215
297
|
:param args: the arguments passed when requesting the service
|
|
216
298
|
:param errors: incidental errors
|
|
@@ -240,6 +322,10 @@ def action_callback(iam_server: IamServer,
|
|
|
240
322
|
if int(datetime.now(tz=TZ_LOCAL).timestamp()) > expiration:
|
|
241
323
|
errors.append("Operation timeout")
|
|
242
324
|
else:
|
|
325
|
+
pos: int = oauth_state.rfind("#idp=")
|
|
326
|
+
target_idp: str = oauth_state[pos+4:] if pos > 0 else None
|
|
327
|
+
target_iam = IamServer(target_idp) if target_idp in IamServer else None
|
|
328
|
+
target_data: dict[str, Any] = user_data.copy() if target_iam else None
|
|
243
329
|
users.pop(oauth_state)
|
|
244
330
|
code: str = args.get("code")
|
|
245
331
|
header_data: dict[str, str] = {
|
|
@@ -264,6 +350,33 @@ def action_callback(iam_server: IamServer,
|
|
|
264
350
|
now=now,
|
|
265
351
|
errors=errors,
|
|
266
352
|
logger=logger)
|
|
353
|
+
if target_iam:
|
|
354
|
+
if logger:
|
|
355
|
+
logger.debug(msg=f"Requesting to IAM server '{iam_server}' "
|
|
356
|
+
f"the token issued by '{target_iam}' ")
|
|
357
|
+
registry: dict[str, Any] = _get_iam_registry(iam_server,
|
|
358
|
+
errors=errors,
|
|
359
|
+
logger=logger)
|
|
360
|
+
url: str = (f"{registry[IamParam.URL_BASE]}/realms/"
|
|
361
|
+
f"{registry[IamParam.CLIENT_REALM]}/broker/{target_idp}/token")
|
|
362
|
+
header_data: dict[str, str] = {
|
|
363
|
+
"Authorization": f"Bearer {result[1]}",
|
|
364
|
+
"Content-Type": "application/json"
|
|
365
|
+
}
|
|
366
|
+
token_data = __get_for_data(url=url,
|
|
367
|
+
header_data=header_data,
|
|
368
|
+
params=None,
|
|
369
|
+
errors=errors,
|
|
370
|
+
logger=logger)
|
|
371
|
+
if not errors:
|
|
372
|
+
token_info: tuple[str, str] = __validate_and_store(iam_server=target_iam,
|
|
373
|
+
user_data=target_data,
|
|
374
|
+
token_data=token_data,
|
|
375
|
+
now=now,
|
|
376
|
+
errors=errors,
|
|
377
|
+
logger=logger)
|
|
378
|
+
if token_info and logger:
|
|
379
|
+
logger.debug(msg=f"Token obtained: {json.dumps(obj=token_info)}")
|
|
267
380
|
else:
|
|
268
381
|
msg: str = f"State '{oauth_state}' not found in {iam_server}'s registry"
|
|
269
382
|
if logger:
|
|
@@ -274,10 +387,10 @@ def action_callback(iam_server: IamServer,
|
|
|
274
387
|
return result
|
|
275
388
|
|
|
276
389
|
|
|
277
|
-
def
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
390
|
+
def iam_exchange(iam_server: IamServer,
|
|
391
|
+
args: dict[str, Any],
|
|
392
|
+
errors: list[str] = None,
|
|
393
|
+
logger: Logger = None) -> tuple[str, str]:
|
|
281
394
|
"""
|
|
282
395
|
Request *iam_server* to issue a token in exchange for the token obtained from another *IAM* server.
|
|
283
396
|
|
|
@@ -308,12 +421,10 @@ def action_exchange(iam_server: IamServer,
|
|
|
308
421
|
|
|
309
422
|
# obtain the token to be exchanged
|
|
310
423
|
token: str = args.get("access-token") if user_id else None
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
logger=logger) if token else None
|
|
314
|
-
token_issuer: str = _iam_server_from_issuer(issuer=token_claims["payload"]["iss"],
|
|
424
|
+
token_issuer: tuple[str] = token_get_values(token=token,
|
|
425
|
+
keys=("iss",),
|
|
315
426
|
errors=errors,
|
|
316
|
-
logger=logger)
|
|
427
|
+
logger=logger)
|
|
317
428
|
if not errors:
|
|
318
429
|
# HAZARD: only 'IAM_KEYCLOAK' is currently supported
|
|
319
430
|
with _iam_lock:
|
|
@@ -326,6 +437,7 @@ def action_exchange(iam_server: IamServer,
|
|
|
326
437
|
__assert_link(iam_server=iam_server,
|
|
327
438
|
user_id=user_id,
|
|
328
439
|
token=token,
|
|
440
|
+
token_issuer=token_issuer[0],
|
|
329
441
|
errors=errors,
|
|
330
442
|
logger=logger)
|
|
331
443
|
if not errors:
|
|
@@ -368,9 +480,64 @@ def action_exchange(iam_server: IamServer,
|
|
|
368
480
|
return result
|
|
369
481
|
|
|
370
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
|
+
|
|
371
537
|
def __assert_link(iam_server: IamServer,
|
|
372
538
|
user_id: str,
|
|
373
539
|
token: str,
|
|
540
|
+
token_issuer: str,
|
|
374
541
|
errors: list[str] | None,
|
|
375
542
|
logger: Logger | None) -> None:
|
|
376
543
|
"""
|
|
@@ -398,7 +565,7 @@ def __assert_link(iam_server: IamServer,
|
|
|
398
565
|
# obtain the internal user identification for 'user_id'
|
|
399
566
|
if logger:
|
|
400
567
|
logger.debug(msg="Obtaining internal identification "
|
|
401
|
-
f"for user {user_id} in IAM server {iam_server}")
|
|
568
|
+
f"for user '{user_id}' in IAM server '{iam_server}'")
|
|
402
569
|
url: str = f"{registry[IamParam.URL_BASE]}/admin/realms/{registry[IamParam.CLIENT_REALM]}/users"
|
|
403
570
|
header_data: dict[str, str] = {
|
|
404
571
|
"Authorization": f"Bearer {admin_token}",
|
|
@@ -414,12 +581,12 @@ def __assert_link(iam_server: IamServer,
|
|
|
414
581
|
errors=errors,
|
|
415
582
|
logger=logger)
|
|
416
583
|
if users:
|
|
417
|
-
# verify whether the
|
|
418
|
-
#
|
|
584
|
+
# verify whether the IAM server that issued the token is a federated identity provider
|
|
585
|
+
# in the associations between 'user_id' and the internal user identification
|
|
419
586
|
internal_id: str = users[0].get("id")
|
|
420
587
|
if logger:
|
|
421
|
-
logger.debug(msg="Obtaining the providers federated
|
|
422
|
-
f"
|
|
588
|
+
logger.debug(msg="Obtaining the providers federated in IAM server "
|
|
589
|
+
f"'{iam_server}', for internal identification '{internal_id}'")
|
|
423
590
|
url = (f"{registry[IamParam.URL_BASE]}/admin/realms/"
|
|
424
591
|
f"{registry[IamParam.CLIENT_REALM]}/users/{internal_id}/federated-identity")
|
|
425
592
|
providers: list[dict[str, Any]] = __get_for_data(url=url,
|
|
@@ -428,13 +595,9 @@ def __assert_link(iam_server: IamServer,
|
|
|
428
595
|
errors=errors,
|
|
429
596
|
logger=logger)
|
|
430
597
|
no_link: bool = True
|
|
431
|
-
|
|
432
|
-
errors=errors,
|
|
433
|
-
logger=logger)
|
|
434
|
-
issuer: str = claims["payload"]["iss"] if claims else None
|
|
435
|
-
provider_name: str = _iam_server_from_issuer(issuer=issuer,
|
|
598
|
+
provider_name: str = _iam_server_from_issuer(issuer=token_issuer,
|
|
436
599
|
errors=errors,
|
|
437
|
-
logger=logger)
|
|
600
|
+
logger=logger)
|
|
438
601
|
if provider_name:
|
|
439
602
|
for provider in providers:
|
|
440
603
|
if provider.get("identityProvider") == provider_name:
|
|
@@ -442,17 +605,17 @@ def __assert_link(iam_server: IamServer,
|
|
|
442
605
|
break
|
|
443
606
|
if no_link:
|
|
444
607
|
# link the identities
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
608
|
+
token_sub: tuple[str] = token_get_values(token=token,
|
|
609
|
+
keys=("sub",),
|
|
610
|
+
errors=errors,
|
|
611
|
+
logger=logger)
|
|
612
|
+
if token_sub:
|
|
450
613
|
if logger:
|
|
451
614
|
logger.debug(msg="Creating an association between identifications "
|
|
452
|
-
f"'{user_id}' and '{token_sub}' in IAM server {iam_server}")
|
|
615
|
+
f"'{user_id}' and '{token_sub}' in IAM server '{iam_server}'")
|
|
453
616
|
url += f"/{provider_name}"
|
|
454
617
|
json_data: dict[str, Any] = {
|
|
455
|
-
"userId": token_sub,
|
|
618
|
+
"userId": token_sub[0],
|
|
456
619
|
"userName": user_id
|
|
457
620
|
}
|
|
458
621
|
__post_json(url=url,
|
|
@@ -756,8 +919,8 @@ def __post_for_token(iam_server: IamServer,
|
|
|
756
919
|
body_data["client_id"] = registry[IamParam.CLIENT_ID]
|
|
757
920
|
|
|
758
921
|
# build the URL
|
|
759
|
-
|
|
760
|
-
|
|
922
|
+
url: str = (f"{registry[IamParam.URL_BASE]}/realms/"
|
|
923
|
+
f"{registry[IamParam.CLIENT_REALM]}/protocol/openid-connect/token")
|
|
761
924
|
# 'client_secret' data must not be shown in log
|
|
762
925
|
msg: str = f"POST {url}, {json.dumps(obj=body_data,
|
|
763
926
|
ensure_ascii=False)}"
|
|
@@ -839,6 +1002,8 @@ def __validate_and_store(iam_server: IamServer,
|
|
|
839
1002
|
# initialize the return variable
|
|
840
1003
|
result: tuple[str, str] | None = None
|
|
841
1004
|
|
|
1005
|
+
if logger:
|
|
1006
|
+
logger.debug(msg=f"Validating and storing the token")
|
|
842
1007
|
with _iam_lock:
|
|
843
1008
|
# retrieve the IAM server's registry
|
|
844
1009
|
registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
|