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.
@@ -3,17 +3,102 @@ from flask import Request, Response, request, jsonify
3
3
  from logging import Logger
4
4
  from typing import Any
5
5
 
6
- from .iam_common import IamServer, _iam_lock, _get_iam_server
7
- from .iam_pomes import (
8
- user_login, user_logout,
9
- user_token, token_exchange, login_callback
6
+ from .iam_common import (
7
+ IamServer, IamParam, _iam_lock,
8
+ _get_iam_registry, _get_public_key,
9
+ _iam_server_from_endpoint, _iam_server_from_issuer
10
10
  )
11
+ from .iam_actions import (
12
+ action_login, action_logout,
13
+ action_token, action_exchange, action_callback
14
+ )
15
+ from .token_pomes import token_get_claims, token_validate
11
16
 
12
17
  # the logger for IAM service operations
13
18
  # (used exclusively at the HTTP endpoints - all other functions receive the logger as parameter)
14
19
  __IAM_LOGGER: Logger | None = None
15
20
 
16
21
 
22
+ def jwt_required(func: callable) -> callable:
23
+ """
24
+ Create a decorator to authenticate service endpoints with JWT tokens.
25
+
26
+ :param func: the function being decorated
27
+ """
28
+ # ruff: noqa: ANN003 - Missing type annotation for *{name}
29
+ def wrapper(*args, **kwargs) -> Response:
30
+ response: Response = __request_validate(request=request)
31
+ return response if response else func(*args, **kwargs)
32
+
33
+ # prevent a rogue error ("View function mapping is overwriting an existing endpoint function")
34
+ wrapper.__name__ = func.__name__
35
+
36
+ return wrapper
37
+
38
+
39
+ def __request_validate(request: Request) -> Response:
40
+ """
41
+ Verify whether the HTTP *request* has the proper authorization, as per the JWT standard.
42
+
43
+ This implementation assumes that HTTP requests are handled with the *Flask* framework.
44
+ Because this code has a high usage frequency, only authentication failures are logged.
45
+
46
+ :param request: the *request* to be verified
47
+ :return: *None* if the *request* is valid, otherwise a *Response* reporting the error
48
+ """
49
+ # initialize the return variable
50
+ result: Response | None = None
51
+
52
+ # retrieve the authorization from the request header
53
+ auth_header: str = request.headers.get("Authorization")
54
+
55
+ # validate the authorization token
56
+ bad_token: bool = True
57
+ if auth_header and auth_header.startswith("Bearer "):
58
+ # extract and validate the JWT access token
59
+ token: str = auth_header.split(" ")[1]
60
+ claims: dict[str, Any] = token_get_claims(token=token)
61
+ if claims:
62
+ issuer: str = claims["payload"].get("iss")
63
+ recipient_attr: str | None = None
64
+ recipient_id: str = request.values.get("user-id") or request.values.get("login")
65
+ with _iam_lock:
66
+ iam_server: IamServer = _iam_server_from_issuer(issuer=issuer,
67
+ errors=None,
68
+ logger=__IAM_LOGGER)
69
+ if iam_server:
70
+ # validate the token's recipient only if a user identification is provided
71
+ if recipient_id:
72
+ registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
73
+ errors=None,
74
+ logger=__IAM_LOGGER)
75
+ if registry:
76
+ recipient_attr = registry[IamParam.RECIPIENT_ATTR]
77
+ public_key: str = _get_public_key(iam_server=iam_server,
78
+ errors=None,
79
+ logger=__IAM_LOGGER)
80
+ # validate the token (log errors, only)
81
+ errors: list[str] = []
82
+ if public_key and token_validate(token=token,
83
+ issuer=issuer,
84
+ recipient_id=recipient_id,
85
+ recipient_attr=recipient_attr,
86
+ public_key=public_key,
87
+ errors=errors):
88
+ # token is valid
89
+ bad_token = False
90
+ elif __IAM_LOGGER:
91
+ __IAM_LOGGER.error("; ".join(errors))
92
+ if bad_token and __IAM_LOGGER:
93
+ __IAM_LOGGER.error(f"Authorization refused for token {token}")
94
+
95
+ # deny the authorization
96
+ if bad_token:
97
+ result = Response(response="Authorization failed",
98
+ status=401)
99
+ return result
100
+
101
+
17
102
  def logger_register(logger: Logger) -> None:
18
103
  """
19
104
  Register the logger for HTTP services.
@@ -55,20 +140,20 @@ def service_login() -> Response:
55
140
  errors: list[str] = []
56
141
  with _iam_lock:
57
142
  # retrieve the IAM server
58
- iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
59
- errors=errors,
60
- logger=__IAM_LOGGER)
143
+ iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
144
+ errors=errors,
145
+ logger=__IAM_LOGGER)
61
146
  if iam_server:
62
147
  # obtain the login URL
63
- login_url: str = user_login(iam_server=iam_server,
64
- args=request.args,
65
- errors=errors,
66
- logger=__IAM_LOGGER)
148
+ login_url: str = action_login(iam_server=iam_server,
149
+ args=request.args,
150
+ errors=errors,
151
+ logger=__IAM_LOGGER)
67
152
  if login_url:
68
153
  result = jsonify({"login-url": login_url})
69
154
  if errors:
70
- result = Response("; ".join(errors))
71
- result.status_code = 400
155
+ result = Response(response="; ".join(errors),
156
+ status=400)
72
157
 
73
158
  # log the response
74
159
  if __IAM_LOGGER:
@@ -83,7 +168,7 @@ def service_login() -> Response:
83
168
  # methods=["GET"])
84
169
  def service_logout() -> Response:
85
170
  """
86
- Entry point for the JusBR logout service.
171
+ Entry point for the IAM server's logout service.
87
172
 
88
173
  The user is identified by the attribute *user-id* or "login", provided as a request parameter.
89
174
  If successful, remove all data relating to the user from the *IAM* server's registry.
@@ -101,18 +186,18 @@ def service_logout() -> Response:
101
186
  errors: list[str] = []
102
187
  with _iam_lock:
103
188
  # retrieve the IAM server
104
- iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
105
- errors=errors,
106
- logger=__IAM_LOGGER)
189
+ iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
190
+ errors=errors,
191
+ logger=__IAM_LOGGER)
107
192
  if iam_server:
108
193
  # logout the user
109
- user_logout(iam_server=iam_server,
110
- args=request.args,
111
- errors=errors,
112
- logger=__IAM_LOGGER)
194
+ action_logout(iam_server=iam_server,
195
+ args=request.args,
196
+ errors=errors,
197
+ logger=__IAM_LOGGER)
113
198
  if errors:
114
- result = Response("; ".join(errors))
115
- result.status_code = 400
199
+ result = Response(response="; ".join(errors),
200
+ status=400)
116
201
  else:
117
202
  result = Response(status=204)
118
203
 
@@ -129,7 +214,7 @@ def service_logout() -> Response:
129
214
  # methods=["POST"])
130
215
  def service_callback() -> Response:
131
216
  """
132
- Entry point for the callback from JusBR on authentication operation.
217
+ Entry point for the callback from the IAM server on authentication operation.
133
218
 
134
219
  This callback is invoked from a front-end application after a successful login at the
135
220
  *IAM* server's login page, forwarding the data received. In a typical OAuth2 flow faction,
@@ -155,15 +240,15 @@ def service_callback() -> Response:
155
240
  token_data: tuple[str, str] | None = None
156
241
  with _iam_lock:
157
242
  # retrieve the IAM server
158
- iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
159
- errors=errors,
160
- logger=__IAM_LOGGER)
243
+ iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
244
+ errors=errors,
245
+ logger=__IAM_LOGGER)
161
246
  if iam_server:
162
247
  # process the callback operation
163
- token_data = login_callback(iam_server=iam_server,
164
- args=request.args,
165
- errors=errors,
166
- logger=__IAM_LOGGER)
248
+ token_data = action_callback(iam_server=iam_server,
249
+ args=request.args,
250
+ errors=errors,
251
+ logger=__IAM_LOGGER)
167
252
  result: Response
168
253
  if errors:
169
254
  result = jsonify({"errors": "; ".join(errors)})
@@ -171,9 +256,9 @@ def service_callback() -> Response:
171
256
  else:
172
257
  result = jsonify({"user-id": token_data[0],
173
258
  "access-token": token_data[1]})
174
- # log the response
175
259
  if __IAM_LOGGER:
176
- __IAM_LOGGER.debug(msg=f"Response {result}, {result.get_data(as_text=True)}")
260
+ # log the response (the returned data is not logged, as it contains the token)
261
+ __IAM_LOGGER.debug(msg=f"Response {result}")
177
262
 
178
263
  return result
179
264
 
@@ -209,16 +294,16 @@ def service_token() -> Response:
209
294
  if user_id:
210
295
  with _iam_lock:
211
296
  # retrieve the IAM server
212
- iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
213
- errors=errors,
214
- logger=__IAM_LOGGER)
297
+ iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
298
+ errors=errors,
299
+ logger=__IAM_LOGGER)
215
300
  if iam_server:
216
301
  # retrieve the token
217
302
  errors: list[str] = []
218
- token: str = user_token(iam_server=iam_server,
219
- args=args,
220
- errors=errors,
221
- logger=__IAM_LOGGER)
303
+ token: str = action_token(iam_server=iam_server,
304
+ args=args,
305
+ errors=errors,
306
+ logger=__IAM_LOGGER)
222
307
  else:
223
308
  msg: str = "User identification not provided"
224
309
  errors.append(msg)
@@ -227,14 +312,14 @@ def service_token() -> Response:
227
312
 
228
313
  result: Response
229
314
  if errors:
230
- result = Response("; ".join(errors))
231
- result.status_code = 400
315
+ result = Response(response="; ".join(errors),
316
+ status=400)
232
317
  else:
233
318
  result = jsonify({"user-id": user_id,
234
319
  "access-token": token})
235
- # log the response
236
320
  if __IAM_LOGGER:
237
- __IAM_LOGGER.debug(msg=f"Response {result}, {result.get_data(as_text=True)}")
321
+ # log the response (the returned data is not logged, as it contains the token)
322
+ __IAM_LOGGER.debug(msg=f"Response {result}")
238
323
 
239
324
  return result
240
325
 
@@ -271,21 +356,21 @@ def service_exchange() -> Response:
271
356
  errors: list[str] = []
272
357
  with _iam_lock:
273
358
  # retrieve the IAM server (currently, only 'IAM_KEYCLOAK' is supported)
274
- iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
275
- errors=errors,
276
- logger=__IAM_LOGGER)
359
+ iam_server: IamServer = _iam_server_from_endpoint(endpoint=request.endpoint,
360
+ errors=errors,
361
+ logger=__IAM_LOGGER)
277
362
  # exchange the token
278
363
  token_data: dict[str, Any] | None = None
279
364
  if iam_server:
280
365
  errors: list[str] = []
281
- token_data = token_exchange(iam_server=iam_server,
282
- args=request.args,
283
- errors=errors,
284
- logger=__IAM_LOGGER)
366
+ token_data = action_exchange(iam_server=iam_server,
367
+ args=request.args,
368
+ errors=errors,
369
+ logger=__IAM_LOGGER)
285
370
  result: Response
286
371
  if errors:
287
- result = Response("; ".join(errors))
288
- result.status_code = 400
372
+ result = Response(response="; ".join(errors),
373
+ status=400)
289
374
  else:
290
375
  result = jsonify(token_data)
291
376
 
@@ -3,29 +3,49 @@ import requests
3
3
  import sys
4
4
  from base64 import b64encode
5
5
  from datetime import datetime
6
+ from enum import StrEnum
6
7
  from logging import Logger
7
8
  from pypomes_core import TZ_LOCAL, exc_format
8
- from threading import RLock
9
+ from threading import Lock
9
10
  from typing import Any, Final
10
11
 
12
+
13
+ class ProviderParam(StrEnum):
14
+ """
15
+ Parameters for configuring a *JWT* token provider.
16
+ """
17
+ URL = "url"
18
+ USER = "user"
19
+ PWD = "pwd"
20
+ CUSTOM_AUTH = "custom-auth"
21
+ HEADER_DATA = "headers-data"
22
+ BODY_DATA = "body-data"
23
+ ACCESS_TOKEN = "access-token"
24
+ ACCESS_EXPIRATION = "access-expiration"
25
+ REFRESH_TOKEN = "refresh-token"
26
+ REFRESH_EXPIRATION = "refresh-expiration"
27
+
28
+
11
29
  # structure:
12
30
  # {
13
31
  # <provider-id>: {
14
32
  # "url": <strl>,
15
33
  # "user": <str>,
16
34
  # "pwd": <str>,
17
- # "basic-auth": <bool>,
35
+ # "custom-auth": <bool>,
18
36
  # "headers-data": <dict[str, str]>,
19
37
  # "body-data": <dict[str, str],
20
38
  # "access-token": <str>,
21
- # "access-expiration": <timestamp>
39
+ # "access-expiration": <timestamp>,
40
+ # "refresh-token": <str>,
41
+ # "refresh-expiration": <timestamp>
22
42
  # }
23
43
  # }
24
44
  _provider_registry: Final[dict[str, dict[str, Any]]] = {}
25
45
 
26
46
  # the lock protecting the data in '_provider_registry'
27
47
  # (because it is 'Final' and set at declaration time, it can be accessed through simple imports)
28
- _provider_lock: Final[RLock] = RLock()
48
+ _provider_lock: Final[Lock] = Lock()
29
49
 
30
50
 
31
51
  def provider_register(provider_id: str,
@@ -58,16 +78,16 @@ def provider_register(provider_id: str,
58
78
 
59
79
  with _provider_lock:
60
80
  _provider_registry[provider_id] = {
61
- "url": auth_url,
62
- "user": auth_user,
63
- "pwd": auth_pwd,
64
- "custom-auth": custom_auth,
65
- "headers-data": headers_data,
66
- "body-data": body_data,
67
- "access-token": None,
68
- "access-expiration": 0,
69
- "refresh-token": None,
70
- "refresh-expiration": 0
81
+ ProviderParam.URL: auth_url,
82
+ ProviderParam.USER: auth_user,
83
+ ProviderParam.PWD: auth_pwd,
84
+ ProviderParam.CUSTOM_AUTH: custom_auth,
85
+ ProviderParam.HEADER_DATA: headers_data,
86
+ ProviderParam.BODY_DATA: body_data,
87
+ ProviderParam.ACCESS_TOKEN: None,
88
+ ProviderParam.ACCESS_EXPIRATION: 0,
89
+ ProviderParam.REFRESH_TOKEN: None,
90
+ ProviderParam.REFRESH_EXPIRATION: 0
71
91
  }
72
92
 
73
93
 
@@ -86,76 +106,137 @@ def provider_get_token(provider_id: str,
86
106
  # initialize the return variable
87
107
  result: str | None = None
88
108
 
89
- err_msg: str | None = None
90
109
  with _provider_lock:
91
110
  provider: dict[str, Any] = _provider_registry.get(provider_id)
92
111
  if provider:
93
- now: float = datetime.now(tz=TZ_LOCAL).timestamp()
94
- if now > provider.get("access-expiration"):
95
- user: str = provider.get("user")
96
- pwd: str = provider.get("pwd")
97
- headers_data: dict[str, str] = provider.get("headers-data") or {}
98
- body_data: dict[str, str] = provider.get("body-data") or {}
99
- custom_auth: tuple[str, str] = provider.get("custom-auth")
100
- if custom_auth:
101
- body_data[custom_auth[0]] = user
102
- body_data[custom_auth[1]] = pwd
103
- else:
104
- enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
105
- headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
106
- url: str = provider.get("url")
107
- if logger:
108
- logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
109
- ensure_ascii=False)}")
110
- try:
111
- # typical return on a token request:
112
- # {
113
- # "token_type": "Bearer",
114
- # "access_token": <str>,
115
- # "expires_in": <number-of-seconds>,
116
- # optional data:
117
- # "refresh_token": <str>,
118
- # "refresh_expires_in": <number-of-seconds>
119
- # }
120
- response: requests.Response = requests.post(url=url,
121
- data=body_data,
122
- headers=headers_data,
123
- timeout=None)
124
- if response.status_code < 200 or response.status_code >= 300:
125
- # request resulted in error, report the problem
126
- err_msg = (f"POST failure, "
127
- f"status {response.status_code}, reason {response.reason}")
112
+ now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
113
+ if now < provider.get(ProviderParam.ACCESS_EXPIRATION):
114
+ # retrieve the stored access token
115
+ result = provider.get(ProviderParam.ACCESS_TOKEN)
116
+ else:
117
+ # access token has expired
118
+ header_data: dict[str, str] | None = None
119
+ body_data: dict[str, str] | None = None
120
+ url: str = provider.get(ProviderParam.URL)
121
+ refresh_token: str = provider.get(ProviderParam.REFRESH_TOKEN)
122
+ if refresh_token:
123
+ # refresh token exists
124
+ refresh_expiration: int = provider.get(ProviderParam.REFRESH_EXPIRATION)
125
+ if now < refresh_expiration:
126
+ # refresh token has not expired
127
+ header_data: dict[str, str] = {
128
+ "Content-Type": "application/json"
129
+ }
130
+ body_data: dict[str, str] = {
131
+ "grant_type": "refresh_token",
132
+ "refresh_token": refresh_token
133
+ }
134
+ if not body_data:
135
+ # refresh token does not exist or has expired
136
+ user: str = provider.get(ProviderParam.USER)
137
+ pwd: str = provider.get(ProviderParam.PWD)
138
+ headers_data: dict[str, str] = provider.get(ProviderParam.HEADER_DATA) or {}
139
+ body_data: dict[str, str] = provider.get(ProviderParam.BODY_DATA) or {}
140
+ custom_auth: tuple[str, str] = provider.get(ProviderParam.CUSTOM_AUTH)
141
+ if custom_auth:
142
+ body_data[custom_auth[0]] = user
143
+ body_data[custom_auth[1]] = pwd
128
144
  else:
129
- # request succeeded
130
- if logger:
131
- logger.debug(msg=f"POST success, status {response.status_code}")
132
- reply: dict[str, Any] = response.json()
133
- provider["access-token"] = reply.get("access_token")
134
- provider["access-expiration"] = now + int(reply.get("expires_in"))
135
- if reply.get("refresh_token"):
136
- provider["refresh-token"] = reply["refesh_token"]
137
- if reply.get("refresh_expires_in"):
138
- provider["refresh-expiration"] = now + int(reply.get("refresh_expires_in"))
139
- else:
140
- provider["refresh-expiration"] = sys.maxsize
141
- if logger:
142
- logger.debug(msg=f"POST {url}: status {response.status_code}")
143
- except Exception as e:
144
- # the operation raised an exception
145
- err_msg = exc_format(exc=e,
146
- exc_info=sys.exc_info())
147
- err_msg = f"POST error, '{err_msg}'"
148
- else:
149
- err_msg: str = f"Provider '{provider_id}' not registered"
150
-
151
- if err_msg:
152
- if isinstance(errors, list):
153
- errors.append(err_msg)
154
- if logger:
155
- logger.error(msg=err_msg)
156
- else:
157
- result = provider.get("access-token")
145
+ enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
146
+ headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
147
+
148
+ # obtain the token
149
+ token_data: dict[str, Any] = __post_for_token(url=url,
150
+ header_data=header_data,
151
+ body_data=body_data,
152
+ errors=errors,
153
+ logger=logger)
154
+ if token_data:
155
+ result = token_data.get("access_token")
156
+ provider[ProviderParam.ACCESS_TOKEN] = result
157
+ provider[ProviderParam.ACCESS_EXPIRATION] = now + token_data.get("expires_in")
158
+ refresh_token = token_data.get("refresh_token")
159
+ if refresh_token:
160
+ provider[ProviderParam.REFRESH_TOKEN] = refresh_token
161
+ refresh_exp: int = token_data.get("refresh_expires_in")
162
+ provider[ProviderParam.REFRESH_EXPIRATION] = (now + refresh_exp) \
163
+ if refresh_exp else sys.maxsize
164
+
165
+ elif logger or isinstance(errors, list):
166
+ msg: str = f"Unknown provider '{provider_id}'"
167
+ if logger:
168
+ logger.error(msg=msg)
169
+ if isinstance(errors, list):
170
+ errors.append(msg)
158
171
 
159
172
  return result
160
173
 
161
174
 
175
+ def __post_for_token(url: str,
176
+ header_data: dict[str, str],
177
+ body_data: dict[str, Any],
178
+ errors: list[str] | None,
179
+ logger: Logger | None) -> dict[str, Any] | None:
180
+ """
181
+ Send a *POST* request to *url* and return the token data obtained.
182
+
183
+ Token acquisition and token refresh are the two types of requests contemplated herein.
184
+ For the former, *header_data* and *body_data* will have contents customized to the specific provider,
185
+ whereas the latter's *body_data* will contain these two attributes:
186
+ - "grant_type": "refresh_token"
187
+ - "refresh_token": <current-refresh-token>
188
+
189
+ The typical data set returned contains the following attributes:
190
+ {
191
+ "token_type": "Bearer",
192
+ "access_token": <str>,
193
+ "expires_in": <number-of-seconds>,
194
+ "refresh_token": <str>,
195
+ "refesh_expires_in": <number-of-seconds>
196
+ }
197
+
198
+ :param url: the target URL
199
+ :param header_data: the data to send in the header of the request
200
+ :param body_data: the data to send in the body of the request
201
+ :param errors: incidental errors
202
+ :param logger: optional logger
203
+ :return: the token data, or *None* if error
204
+ """
205
+ # initialize the return variable
206
+ result: dict[str, Any] | None = None
207
+
208
+ # log the POST
209
+ if logger:
210
+ logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
211
+ ensure_ascii=False)}")
212
+ try:
213
+ response: requests.Response = requests.post(url=url,
214
+ data=body_data,
215
+ headers=header_data,
216
+ timeout=None)
217
+ if response.status_code == 200:
218
+ # request succeeded
219
+ result = response.json()
220
+ if logger:
221
+ logger.debug(msg=f"POST success, status {response.status_code}")
222
+ else:
223
+ # request failed, report the problem
224
+ msg: str = (f"POST failure, "
225
+ f"status {response.status_code}, reason {response.reason}")
226
+ if hasattr(response, "content") and response.content:
227
+ msg += f", content '{response.content}'"
228
+ if logger:
229
+ logger.error(msg=msg)
230
+ if isinstance(errors, list):
231
+ errors.append(msg)
232
+ except Exception as e:
233
+ # the operation raised an exception
234
+ err_msg = exc_format(exc=e,
235
+ exc_info=sys.exc_info())
236
+ msg: str = f"POST error, {err_msg}"
237
+ if logger:
238
+ logger.debug(msg=msg)
239
+ if isinstance(errors, list):
240
+ errors.append(msg)
241
+
242
+ return result
@@ -7,6 +7,45 @@ from pypomes_core import exc_format
7
7
  from typing import Any
8
8
 
9
9
 
10
+ def token_get_claims(token: str,
11
+ errors: list[str] = None,
12
+ logger: Logger = None) -> dict[str, dict[str, Any]] | None:
13
+ """
14
+ Retrieve the claims set of a JWT *token*.
15
+
16
+ Any well-constructed JWT token may be provided in *token*.
17
+ Note that neither the token's signature nor its expiration is verified.
18
+
19
+ :param token: the refrence token
20
+ :param errors: incidental error messages
21
+ :param logger: optional logger
22
+ :return: the token's claimset, or *None* if error
23
+ """
24
+ # initialize the return variable
25
+ result: dict[str, dict[str, Any]] | None = None
26
+
27
+ if logger:
28
+ logger.debug(msg="Retrieve claims for token")
29
+
30
+ try:
31
+ header: dict[str, Any] = jwt.get_unverified_header(jwt=token)
32
+ payload: dict[str, Any] = jwt.decode(jwt=token,
33
+ options={"verify_signature": False})
34
+ result = {
35
+ "header": header,
36
+ "payload": payload
37
+ }
38
+ except Exception as e:
39
+ exc_err: str = exc_format(exc=e,
40
+ exc_info=sys.exc_info())
41
+ if logger:
42
+ logger.error(msg=f"Error retrieving the token's claims: {exc_err}")
43
+ if isinstance(errors, list):
44
+ errors.append(exc_err)
45
+
46
+ return result
47
+
48
+
10
49
  def token_validate(token: str,
11
50
  issuer: str = None,
12
51
  recipient_id: str = None,
@@ -78,8 +117,6 @@ def token_validate(token: str,
78
117
  "verify_nbf": False,
79
118
  "verify_signature": token_alg in ["RS256", "RS512"] and public_key is not None
80
119
  }
81
- if issuer:
82
- options["require"].append("iss")
83
120
  try:
84
121
  # raises:
85
122
  # InvalidTokenError: token is invalid
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_iam
3
- Version: 0.5.1
3
+ Version: 0.6.9
4
4
  Summary: A collection of Python pomes, penyeach (IAM modules)
5
5
  Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-IAM
6
6
  Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-IAM/issues
@@ -10,7 +10,6 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.12
13
- Requires-Dist: cachetools>=6.2.1
14
13
  Requires-Dist: flask>=3.1.2
15
14
  Requires-Dist: pyjwt>=2.10.1
16
15
  Requires-Dist: pypomes-core>=2.8.1
@@ -0,0 +1,11 @@
1
+ pypomes_iam/__init__.py,sha256=_6tSFfjuU-5p6TAMqNLHSL6IQmaJMSYuEW-TG3ybhTI,1044
2
+ pypomes_iam/iam_actions.py,sha256=0PC7Z7xsepxynmgjMvKZLjLdvC3s5kGk_dqCFrG4Qs8,42083
3
+ pypomes_iam/iam_common.py,sha256=ki_-m6fqJqUbGjgTD41r9zaE-FOXgA_c_tLisIYYTfU,15457
4
+ pypomes_iam/iam_pomes.py,sha256=_kLnrZG25XhJsIv3wqDl_2sIJ2ho_2TIMKrPCyPmA7Q,7362
5
+ pypomes_iam/iam_services.py,sha256=uUD333SaTbo8MGRyIp5GGil7HAupK73ym4_bKtGkPFg,15878
6
+ pypomes_iam/provider_pomes.py,sha256=3mMj5LQs53YEINUEOfFBAxOwOP3aOR_szlE4daEBLK0,10523
7
+ pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
8
+ pypomes_iam-0.6.9.dist-info/METADATA,sha256=tPsAu2TWT9hb3vu-KHnYT77TYZytoCQfs78uxWLlLH8,661
9
+ pypomes_iam-0.6.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ pypomes_iam-0.6.9.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
11
+ pypomes_iam-0.6.9.dist-info/RECORD,,