pypomes-iam 0.3.0__py3-none-any.whl → 0.3.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/iam_common.py CHANGED
@@ -1,12 +1,9 @@
1
1
  import json
2
2
  import requests
3
- import secrets
4
- import string
5
3
  import sys
6
4
  from cachetools import Cache
7
5
  from datetime import datetime
8
6
  from enum import StrEnum
9
- from flask import Request
10
7
  from logging import Logger
11
8
  from pypomes_core import TZ_LOCAL, exc_format
12
9
  from pypomes_crypto import crypto_jwk_convert
@@ -14,21 +11,27 @@ from typing import Any, Final
14
11
 
15
12
 
16
13
  class IamServer(StrEnum):
14
+ """
15
+ Supported IAM servers.
16
+ """
17
17
  IAM_JUSRBR = "iam-jusbr",
18
18
  IAM_KEYCLOAK = "iam-keycloak"
19
19
 
20
20
 
21
+ # the logger for IAM operations
22
+ __IAM_LOGGER: Logger | None = None
23
+
21
24
  # registry structure:
22
25
  # { <IamServer>:
23
26
  # {
24
27
  # "client-id": <str>,
25
28
  # "client-secret": <str>,
26
29
  # "client-timeout": <int>,
30
+ # "recipient-attr": <str>,
27
31
  # "public_key": <str>,
28
32
  # "pk-lifetime": <int>,
29
33
  # "pk-expiration": <int>,
30
34
  # "base-url": <str>,
31
- # "logger": <Logger>,
32
35
  # "cache": <FIFOCache>,
33
36
  # "redirect-uri": <str> <-- transient
34
37
  # },
@@ -48,380 +51,316 @@ class IamServer(StrEnum):
48
51
  # },
49
52
  # ...
50
53
  # }
51
- IAM_SERVERS: Final[dict[IamServer, dict[str, Any]]] = {}
54
+ _IAM_SERVERS: Final[dict[IamServer, dict[str, Any]]] = {}
52
55
 
53
56
 
54
- def _service_login(registry: dict[str, Any],
55
- args: dict[str, Any],
56
- logger: Logger | None) -> dict[str, str]:
57
+ def _get_logger() -> Logger | None:
57
58
  """
58
- Build the callback URL for redirecting the request to the IAM's authentication page.
59
+ Retrieve the registered logger for *IAM* operations.
59
60
 
60
- :param registry: the registry holding the authentication data
61
- :param args: the arguments passed when requesting the service
62
- :param logger: optional logger
63
- :return: the callback URL, with the appropriate parameters
61
+ :return: the registered logger for *IAM* operations.
64
62
  """
65
- # retrieve user data
66
- oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
67
-
68
- # build the user data
69
- # ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
70
- user_data: dict[str, Any] = _get_user_data(registry=registry,
71
- user_id=oauth_state,
72
- logger=logger)
73
- user_id: str = args.get("user-id") or args.get("user_id") or args.get("login")
74
- user_data["login-id"] = user_id
75
- timeout: int = _get_login_timeout(registry=registry)
76
- user_data["login-expiration"] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout if timeout else None
77
- redirect_uri: str = args.get("redirect-uri")
78
- registry["redirect-uri"] = redirect_uri
79
-
80
- # build the login url
81
- return {
82
- "login-url": (f"{registry["base-url"]}/protocol/openid-connect/auth"
83
- f"?response_type=code&scope=openid"
84
- f"&client_id={registry["client-id"]}"
85
- f"&redirect_uri={redirect_uri}"
86
- f"&state={oauth_state}")
87
- }
88
-
89
-
90
- def _service_logout(registry: dict[str, Any],
91
- args: dict[str, Any],
92
- logger: Logger | None) -> None:
63
+ return __IAM_LOGGER
64
+
65
+
66
+ def _register_logger(logger: Logger) -> None:
93
67
  """
94
- Remove all data associating *user_id* from *registry*.
68
+ Register the logger for *IAM* operations
95
69
 
96
- :param registry: the registry holding the authentication data
97
- :param args: the arguments passed when requesting the service
98
- :param logger: optional logger
70
+ :param logger: the logger to be rergistered
99
71
  """
100
- # remove the user data
101
- user_id: str = args.get("user-id") or args.get("login")
102
- if user_id:
103
- cache: Cache = registry["cache"]
104
- users: dict[str, dict[str, Any]] = cache.get("users")
105
- if user_id in users:
106
- users.pop(user_id)
107
- if logger:
108
- logger.debug(msg=f"User '{user_id}' removed from the registry")
72
+ global __IAM_LOGGER
73
+ __IAM_LOGGER = logger
109
74
 
110
75
 
111
- def _service_callback(registry: dict[str, Any],
112
- args: dict[str, Any],
113
- errors: list[str],
114
- logger: Logger | None) -> tuple[str, str]:
76
+ def _get_public_key(iam_server: IamServer,
77
+ errors: list[str] | None,
78
+ logger: Logger | None) -> str:
115
79
  """
116
- Entry point for the callback from JusBR via the front-end application on authentication operation.
80
+ Obtain the public key used by *iam_server* to sign the authentication tokens.
117
81
 
118
- :param registry: the registry holding the authentication data
119
- :param args: the arguments passed when requesting the service
120
- :param errors: incidental errors
82
+ The public key is saved in *iam_server*'s registry.
83
+
84
+ :param iam_server: the reference registered *IAM* server
85
+ :param errors: incidental error messages
121
86
  :param logger: optional logger
87
+ :return: the public key in *PEM* format, or *None* if the server is unknown
122
88
  """
123
- from .token_pomes import token_validate
124
-
125
89
  # initialize the return variable
126
- result: tuple[str, str] | None = None
127
-
128
- # retrieve the users authentication data
129
- cache: Cache = registry["cache"]
130
- users: dict[str, dict[str, Any]] = cache.get("users")
131
-
132
- # validate the OAuth2 state
133
- oauth_state: str = args.get("state")
134
- user_data: dict[str, Any] | None = None
135
- if oauth_state:
136
- for user, data in users.items():
137
- if user == oauth_state:
138
- user_data = data
139
- break
140
-
141
- # exchange 'code' for the token
142
- if user_data:
143
- expiration: int = user_data["login-expiration"] or sys.maxsize
144
- if int(datetime.now(tz=TZ_LOCAL).timestamp()) > expiration:
145
- errors.append("Operation timeout")
146
- else:
147
- users.pop(oauth_state)
148
- code: str = args.get("code")
149
- body_data: dict[str, Any] = {
150
- "grant_type": "authorization_code",
151
- "code": code,
152
- "redirect_uri": registry.get("redirect-uri"),
153
- }
154
- token = _post_for_token(registry=registry,
155
- user_data=user_data,
156
- body_data=body_data,
157
- errors=errors,
158
- logger=logger)
159
- # retrieve the token's claims
160
- if not errors:
161
- public_key: str = _get_public_key(registry=registry,
162
- logger=logger)
163
- token_claims: dict[str, dict[str, Any]] = token_validate(token=token,
164
- issuer=registry["base-url"],
165
- public_key=public_key,
166
- errors=errors,
167
- logger=logger)
168
- if not errors:
169
- token_user: str = token_claims["payload"].get("preferred_username")
170
- login_id = user_data.pop("login-id", None)
171
- if not login_id or (login_id == token_user):
172
- users[token_user] = user_data
173
- result = (token_user, token)
174
- else:
175
- errors.append(f"Token was issued to user '{token_user}'")
176
- else:
177
- errors.append("Unknown state received")
90
+ result: str | None = None
178
91
 
179
- if errors and logger:
180
- logger.error(msg="; ".join(errors))
92
+ registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
93
+ errors=errors,
94
+ logger=logger)
95
+ if registry:
96
+ now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
97
+ if now > registry["pk-expiration"]:
98
+ # obtain a new public key
99
+ url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
100
+ if logger:
101
+ logger.debug(msg=f"GET '{url}'")
102
+ try:
103
+ response: requests.Response = requests.get(url=url)
104
+ if response.status_code == 200:
105
+ # request succeeded
106
+ if logger:
107
+ logger.debug(msg=f"GET success, status {response.status_code}")
108
+ reply: dict[str, Any] = response.json()
109
+ result = crypto_jwk_convert(jwk=reply["keys"][0],
110
+ fmt="PEM")
111
+ registry["public-key"] = result
112
+ lifetime: int = registry["pk-lifetime"] or 0
113
+ registry["pk-expiration"] = now + lifetime
114
+ elif logger:
115
+ msg: str = f"GET failure, status {response.status_code}, reason '{response.reason}'"
116
+ if hasattr(response, "content") and response.content:
117
+ msg += f", content '{response.content}'"
118
+ logger.error(msg=msg)
119
+ if isinstance(errors, list):
120
+ errors.append(msg)
121
+ except Exception as e:
122
+ # the operation raised an exception
123
+ msg = exc_format(exc=e,
124
+ exc_info=sys.exc_info())
125
+ if logger:
126
+ logger.error(msg=msg)
127
+ if isinstance(errors, list):
128
+ errors.append(msg)
129
+ else:
130
+ result = registry["public-key"]
181
131
 
182
132
  return result
183
133
 
184
134
 
185
- def _service_token(registry: dict[str, Any],
186
- args: dict[str, Any],
187
- errors: list[str] = None,
188
- logger: Logger = None) -> str:
135
+ def _get_login_timeout(iam_server: IamServer,
136
+ errors: list[str] | None,
137
+ logger: Logger) -> int | None:
189
138
  """
190
- Retrieve the authentication token for user *user_id*.
139
+ Retrieve the timeout currently applicable for the login operation.
191
140
 
192
- :param registry: the registry holding the authentication data
193
- :param args: the arguments passed when requesting the service
141
+ :param iam_server: the reference registered *IAM* server
194
142
  :param errors: incidental error messages
195
143
  :param logger: optional logger
196
- :return: the token for *user_id*, or *None* if error
144
+ :return: the current login timeout, or *None* if the server is unknown or none has been set.
197
145
  """
198
146
  # initialize the return variable
199
- result: str | None = None
147
+ result: int | None = None
200
148
 
201
- user_id: str = args.get("user-id") or args.get("user_id") or args.get("login")
202
- user_data: dict[str, Any] = _get_user_data(registry=registry,
203
- user_id=user_id,
204
- logger=logger)
205
- err_msg: str | None = None
206
- token: str = user_data["access-token"]
207
- if token:
208
- access_expiration: int = user_data.get("access-expiration")
209
- now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
210
- if now < access_expiration:
211
- result = token
212
- else:
213
- # access token has expired
214
- refresh_token: str = user_data["refresh-token"]
215
- if refresh_token:
216
- refresh_expiration = user_data["refresh-expiration"]
217
- if now < refresh_expiration:
218
- body_data: dict[str, str] = {
219
- "grant_type": "refresh_token",
220
- "refresh_token": refresh_token
221
- }
222
- result = _post_for_token(registry=registry,
223
- user_data=user_data,
224
- body_data=body_data,
225
- errors=errors,
226
- logger=logger)
227
- else:
228
- # refresh token has expired
229
- err_msg = "Access and refresh tokens expired"
230
- else:
231
- err_msg = "Access token expired, no refresh token available"
232
- else:
233
- err_msg = f"User '{user_id}' not authenticated"
234
-
235
- if err_msg and (logger or isinstance(errors, list)):
236
- err_msg: str = f"User '{user_id}' not authenticated"
237
- if isinstance(errors, list):
238
- errors.append(err_msg)
239
- if logger:
240
- logger.error(msg=err_msg)
241
- logger.error(msg=err_msg)
149
+ registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
150
+ errors=errors,
151
+ logger=logger)
152
+ if registry:
153
+ timeout: int = registry.get("client-timeout")
154
+ if isinstance(timeout, int) and timeout > 0:
155
+ result = timeout
242
156
 
243
157
  return result
244
158
 
245
159
 
246
- def _get_public_key(registry: dict[str, Any],
247
- logger: Logger | None) -> str:
160
+ def _get_user_data(iam_server: IamServer,
161
+ user_id: str,
162
+ errors: list[str] | None,
163
+ logger: Logger | None) -> dict[str, Any] | None:
248
164
  """
249
- Obtain the public key used by the *IAM* to sign the authentication tokens.
165
+ Retrieve the data for *user_id* from *iam_server*'s registry.
250
166
 
251
- The public key is saved in *registry*.
167
+ If an entry is not found for *user_id* in the registry, it is created.
168
+ It will remain there until the user is logged out.
252
169
 
253
- :param registry: the registry holding the authentication data
254
- :return: the public key, in *PEM* format
170
+ :param iam_server: the reference registered *IAM* server
171
+ :param errors: incidental error messages
172
+ :param logger: optional logger
173
+ :return: the data for *user_id* in *iam_server*'s registry, or *None* if the server is unknown
255
174
  """
256
175
  # initialize the return variable
257
- result: str | None = None
176
+ result: dict[str, Any] | None = None
258
177
 
259
- now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
260
- if now > registry["pk-expiration"]:
261
- # obtain a new public key
262
- url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
263
- if logger:
264
- logger.debug(msg=f"GET '{url}'")
265
- response: requests.Response = requests.get(url=url)
266
- if response.status_code == 200:
267
- # request succeeded
178
+ cache: Cache = _get_iam_cache(iam_server=iam_server,
179
+ errors=errors,
180
+ logger=logger)
181
+ if cache:
182
+ users: dict[str, dict[str, Any]] = cache.get("users")
183
+ result = users.get(user_id)
184
+ if not result:
185
+ result = {
186
+ "access-token": None,
187
+ "refresh-token": None,
188
+ "access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
189
+ "refresh-expiration": sys.maxsize
190
+ }
191
+ users[user_id] = result
268
192
  if logger:
269
- logger.debug(msg=f"GET success, status {response.status_code}")
270
- reply: dict[str, Any] = response.json()
271
- result = crypto_jwk_convert(jwk=reply["keys"][0],
272
- fmt="PEM")
273
- registry["public-key"] = result
274
- lifetime: int = registry["pk-lifetime"] or 0
275
- registry["pk-expiration"] = now + lifetime
193
+ logger.debug(msg=f"Entry for '{user_id}' added to {iam_server}'s registry")
276
194
  elif logger:
277
- msg: str = f"GET failure, status {response.status_code}, reason '{response.reason}'"
278
- if hasattr(response, "content") and response.content:
279
- msg += f", content '{response.content}'"
280
- logger.error(msg=msg)
281
- else:
282
- result = registry["public-key"]
195
+ logger.debug(msg=f"Entry for '{user_id}' obtained from {iam_server}'s registry")
283
196
 
284
197
  return result
285
198
 
286
199
 
287
- def _get_login_timeout(registry: dict[str, Any]) -> int | None:
200
+ def _get_iam_server(endpoint: str,
201
+ errors: list[str] | None,
202
+ logger: Logger | None) -> IamServer | None:
288
203
  """
289
- Retrieve from *registry* the timeout currently applicable for the login operation.
204
+ Retrieve the registered *IAM* server associated with the service's invocation *endpoint*.
290
205
 
291
- :param registry: the registry holding the authentication data
292
- :return: the current login timeout, or *None* if none has been set.
206
+ :param endpoint: the service's invocation endpoint
207
+ :param errors: incidental error messages
208
+ :param logger: optional logger
209
+ :return: the corresponding *IAM* server, or *None* if one could not be obtained
293
210
  """
294
- timeout: int = registry.get("client-timeout")
295
- return timeout if isinstance(timeout, int) and timeout > 0 else None
211
+ # declare the return variable
212
+ result: IamServer | None
296
213
 
214
+ if endpoint.startswith("jusbr"):
215
+ result = IamServer.IAM_JUSRBR
216
+ elif endpoint.startswith("keycloak"):
217
+ result = IamServer.IAM_KEYCLOAK
218
+ else:
219
+ result = None
220
+ msg: str = f"Unknown endpoind {endpoint}"
221
+ if logger:
222
+ logger.error(msg=msg)
223
+ if isinstance(errors, list):
224
+ errors.append(msg)
297
225
 
298
- def _get_user_data(registry: dict[str, Any],
299
- user_id: str,
300
- logger: Logger | None) -> dict[str, Any]:
301
- """
302
- Retrieve the data for *user_id* from *registry*.
226
+ return result
303
227
 
304
- If an entry is not found for *user_id* in the registry, it is created.
305
- It will remain there until the user is logged out.
306
228
 
307
- :param registry: the registry holding the authentication data
308
- :return: the data for *user_id* in the registry
229
+ def _get_iam_registry(iam_server: IamServer,
230
+ errors: list[str] | None,
231
+ logger: Logger | None) -> dict[str, Any]:
309
232
  """
310
- cache: Cache = registry["cache"]
311
- users: dict[str, dict[str, Any]] = cache.get("users")
312
- result: dict[str, Any] = users.get(user_id)
313
- if not result:
314
- result = {
315
- "access-token": None,
316
- "refresh-token": None,
317
- "access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
318
- "refresh-expiration": sys.maxsize
319
- }
320
- users[user_id] = result
321
- if logger:
322
- logger.debug(msg=f"Entry for '{user_id}' added to the registry")
323
- elif logger:
324
- logger.debug(msg=f"Entry for '{user_id}' obtained from the registry")
233
+ Retrieve the registry associated with *iam_server*.
234
+
235
+ :param iam_server: the reference registered *IAM* server
236
+ :param errors: incidental error messages
237
+ :param logger: optional logger
238
+ :return: the registry associated with *iam_server*, or *None* if the server is unknown
239
+ """
240
+ # declare the return variable
241
+ result: dict[str, Any] | None
242
+
243
+ match iam_server:
244
+ case IamServer.IAM_JUSRBR:
245
+ result = _IAM_SERVERS[IamServer.IAM_JUSRBR]
246
+ case IamServer.IAM_KEYCLOAK:
247
+ result = _IAM_SERVERS[IamServer.IAM_KEYCLOAK]
248
+ case _:
249
+ result = None
250
+ msg = f"Unknown IAM server '{iam_server}'"
251
+ if logger:
252
+ logger.error(msg=msg)
253
+ if isinstance(errors, list):
254
+ errors.append(msg)
325
255
 
326
256
  return result
327
257
 
328
258
 
329
- def _post_for_token(registry: dict[str, Any],
330
- user_data: dict[str, Any],
259
+ def _get_iam_cache(iam_server: IamServer,
260
+ errors: list[str] | None,
261
+ logger: Logger | None) -> Cache:
262
+ """
263
+ Retrieve the cache storage in *iam_server*'s registry.
264
+
265
+ :param iam_server: the reference registered *IAM* server
266
+ :param errors: incidental error messages
267
+ :param logger: optional logger
268
+ :return: the cache storage in *iam_server*'s registry, or *None* if the server is unknown
269
+ """
270
+ registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
271
+ errors=errors,
272
+ logger=logger)
273
+ return registry["cache"] if registry else None
274
+
275
+
276
+ def _post_for_token(iam_server: IamServer,
331
277
  body_data: dict[str, Any],
332
278
  errors: list[str] | None,
333
- logger: Logger | None) -> str | None:
279
+ logger: Logger | None) -> dict[str, Any] | None:
334
280
  """
335
- Send a POST request to obtain the authentication token data, and return the access token.
281
+ Send a POST request to obtain the authentication token data, and return the data received.
336
282
 
337
- For token exchange, *body_data* will have the attributes
283
+ For token acquisition, *body_data* will have the attributes:
338
284
  - "grant_type": "authorization_code"
339
285
  - "code": <16-character-random-code>
340
286
  - "redirect_uri": <redirect-uri>
341
- For token refresh, *body_data* will have the attributes
287
+
288
+ For token refresh, *body_data* will have the attributes:
342
289
  - "grant_type": "refresh_token"
343
290
  - "refresh_token": <current-refresh-token>
344
291
 
292
+ For token exchange, *body_data* will have the attributes:
293
+ - "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
294
+ - "subject_token": <token-to-be-exchanged>,
295
+ - "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
296
+ - "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
297
+ - "audience": <client-id>,
298
+ - "subject_issuer": "oidc"
299
+
300
+ These attributes are then added to *body_data*:
301
+ - "client_id": <client-id>,
302
+ - "client_secret": <client-secret>,
303
+
345
304
  If the operation is successful, the token data is stored in the registry.
346
305
  Otherwise, *errors* will contain the appropriate error message.
347
306
 
348
- :param registry: the registry holding the authentication data
349
- :param user_data: the user's data in the registry
307
+ :param iam_server: the reference registered *IAM* server
350
308
  :param body_data: the data to send in the body of the request
351
309
  :param errors: incidental errors
352
310
  :param logger: optional logger
353
311
  :return: the access token obtained, or *None* if error
354
312
  """
355
313
  # initialize the return variable
356
- result: str | None = None
357
-
358
- # complete the data to send in body of request
359
- body_data["client_id"] = registry["client-id"]
360
- client_secret: str = registry["client-secret"]
361
- if client_secret:
362
- body_data["client_secret"] = client_secret
314
+ result: dict[str, Any] | None = None
363
315
 
364
- # obtain the token
316
+ # PBTAIN THE iam SERVER'S REGISTRY
317
+ registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
318
+ errors=errors,
319
+ logger=logger)
365
320
  err_msg: str | None = None
366
- url: str = registry["base-url"] + "/protocol/openid-connect/token"
367
- now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
368
- if logger:
369
- logger.debug(msg=f"POST '{url}', data {json.dumps(obj=body_data,
370
- ensure_ascii=False)}")
371
- try:
372
- # typical return on a token request:
373
- # {
374
- # "token_type": "Bearer",
375
- # "access_token": <str>,
376
- # "expires_in": <number-of-seconds>,
377
- # "refresh_token": <str>,
378
- # "refesh_expires_in": <number-of-seconds>
379
- # }
380
- response: requests.Response = requests.post(url=url,
381
- data=body_data)
382
- if response.status_code == 200:
383
- # request succeeded
384
- if logger:
385
- logger.debug(msg=f"POST success, status {response.status_code}")
386
- reply: dict[str, Any] = response.json()
387
- result = reply.get("access_token")
388
- user_data["access-token"] = result
389
- # on token refresh, keep current refresh token if a new one is not provided
390
- user_data["refresh-token"] = reply.get("refresh_token") or body_data.get("refresh_token")
391
- user_data["access-expiration"] = now + reply.get("expires_in")
392
- refresh_expiration: int = user_data.get("refresh_expires_in")
393
- user_data["refresh-expiration"] = (now + refresh_expiration) if refresh_expiration else sys.maxsize
394
- else:
395
- # request resulted in error
396
- err_msg = f"POST failure, status {response.status_code}, reason '{response.reason}'"
397
- if hasattr(response, "content") and response.content:
398
- err_msg += f", content '{response.content}'"
399
- if response.status_code == 400 and body_data.get("grant_type") == "refresh_token":
400
- # refresh token is no longer valid
401
- user_data["refresh-token"] = None
402
- except Exception as e:
403
- # the operation raised an exception
404
- err_msg = exc_format(exc=e,
405
- exc_info=sys.exc_info())
406
- err_msg = f"POST '{url}': error '{err_msg}'"
407
-
408
- if err_msg:
409
- if isinstance(errors, list):
410
- errors.append(err_msg)
321
+ if registry:
322
+ # complete the data to send in body of request
323
+ body_data["client_id"] = registry["client-id"]
324
+ client_secret: str = registry["client-secret"]
325
+ if client_secret:
326
+ body_data["client_secret"] = client_secret
327
+
328
+ # obtain the token
329
+ url: str = registry["base-url"] + "/protocol/openid-connect/token"
411
330
  if logger:
412
- logger.error(msg=err_msg)
413
-
414
- return result
415
-
416
-
417
- def _log_init(request: Request) -> str:
418
- """
419
- Build the messages for logging the request entry.
331
+ logger.debug(msg=f"POST '{url}', data {json.dumps(obj=body_data,
332
+ ensure_ascii=False)}")
333
+ try:
334
+ # typical return on a token request:
335
+ # {
336
+ # "token_type": "Bearer",
337
+ # "access_token": <str>,
338
+ # "expires_in": <number-of-seconds>,
339
+ # "refresh_token": <str>,
340
+ # "refesh_expires_in": <number-of-seconds>
341
+ # }
342
+ response: requests.Response = requests.post(url=url,
343
+ data=body_data)
344
+ if response.status_code == 200:
345
+ # request succeeded
346
+ if logger:
347
+ logger.debug(msg=f"POST success, status {response.status_code}")
348
+ result = response.json()
349
+ else:
350
+ # request resulted in error
351
+ err_msg = f"POST failure, status {response.status_code}, reason '{response.reason}'"
352
+ if hasattr(response, "content") and response.content:
353
+ err_msg += f", content '{response.content}'"
354
+ if logger:
355
+ logger.error(msg=err_msg)
356
+ except Exception as e:
357
+ # the operation raised an exception
358
+ err_msg = exc_format(exc=e,
359
+ exc_info=sys.exc_info())
360
+ if logger:
361
+ logger.error(msg=err_msg)
420
362
 
421
- :param request: the Request object
422
- :return: the log message
423
- """
363
+ if err_msg and isinstance(errors, list):
364
+ errors.append(err_msg)
424
365
 
425
- params: str = json.dumps(obj=request.args,
426
- ensure_ascii=False)
427
- return f"Request {request.method}:{request.path}, params {params}"
366
+ return result