pypomes-iam 0.0.2__py3-none-any.whl → 0.0.4__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 CHANGED
@@ -1,5 +1,5 @@
1
1
  from iam_jusbr import (
2
- jusbr_setup, jusbr_get_token
2
+ jusbr_setup, jusbr_get_token, jusbr_set_scope
3
3
  )
4
4
  from .iam_provider import (
5
5
  provider_register, provider_get_token
@@ -7,7 +7,7 @@ from .iam_provider import (
7
7
 
8
8
  __all__ = [
9
9
  # iam_jusbr
10
- "jusbr_setup", "jusbr_get_token",
10
+ "jusbr_setup", "jusbr_get_token", "jusbr_set_scope",
11
11
  # jwt_provider
12
12
  "provider_register", "provider_get_token"
13
13
  ]
pypomes_iam/iam_jusbr.py CHANGED
@@ -110,28 +110,28 @@ def jusbr_setup(flask_app: Flask,
110
110
  if token_endpoint:
111
111
  flask_app.add_url_rule(rule=token_endpoint,
112
112
  endpoint="jusbr-token",
113
- view_func=jusbr_token,
113
+ view_func=service_token,
114
114
  methods=["GET"])
115
115
  if login_endpoint:
116
116
  flask_app.add_url_rule(rule=login_endpoint,
117
117
  endpoint="jusbr-login",
118
- view_func=jusbr_login,
118
+ view_func=service_login,
119
119
  methods=["GET"])
120
120
  if logout_endpoint:
121
121
  flask_app.add_url_rule(rule=logout_endpoint,
122
122
  endpoint="jusbr-logout",
123
- view_func=jusbr_logout,
123
+ view_func=service_logout,
124
124
  methods=["GET"])
125
125
  if callback_endpoint:
126
126
  flask_app.add_url_rule(rule=callback_endpoint,
127
127
  endpoint="jusbr-callback",
128
- view_func=jusbr_callback,
128
+ view_func=service_callback,
129
129
  methods=["POST"])
130
130
 
131
131
 
132
132
  # @flask_app.route(rule=<login_endpoint>, # JUSBR_LOGIN_ENDPOINT: /iam/jusbr:login
133
133
  # methods=["GET"])
134
- def jusbr_login() -> Response:
134
+ def service_login() -> Response:
135
135
  """
136
136
  Entry point for the JusBR login service.
137
137
 
@@ -139,22 +139,20 @@ def jusbr_login() -> Response:
139
139
 
140
140
  :return: the response from the redirect operation
141
141
  """
142
+ global _jusbr_registry
143
+
142
144
  # retrieve user id
143
145
  input_params: dict[str, Any] = request.values
144
146
  user_id: str = input_params.get("user-id") or input_params.get("login")
145
147
 
146
148
  # retrieve user data
147
- global _jusbr_registry
148
- user_data: dict[str, Any] = _jusbr_registry["users"].get(user_id)
149
- if not user_data:
150
- user_data = {"access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp())}
151
- _jusbr_registry["users"][user_id] = user_data
152
-
149
+ user_data: dict[str, Any] = __get_user_data(user_id=user_id,
150
+ logger=_logger)
153
151
  # build redirect url
154
152
  oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
155
- login_timeout: int = _jusbr_registry.get("login-timeout")
153
+ timeout: int = __get_login_timeout()
156
154
  safe_cache: Cache
157
- if isinstance(login_timeout, int) and login_timeout > 0:
155
+ if timeout:
158
156
  safe_cache = TTLCache(maxsize=16,
159
157
  ttl=600)
160
158
  else:
@@ -174,32 +172,32 @@ def jusbr_login() -> Response:
174
172
 
175
173
  # @flask_app.route(rule=<login_endpoint>, # JUSBR_LOGIN_ENDPOINT: /iam/jusbr:logout
176
174
  # methods=["GET"])
177
- def jusbr_logout() -> Response:
175
+ def service_logout() -> Response:
178
176
  """
179
177
  Entry point for the JusBR logout service.
180
178
 
181
- Delete all data associating the user with JusBR.
179
+ Remove all data associating the user with JusBR from the registry.
182
180
 
183
181
  :return: the response from the redirect operation
184
182
  """
183
+ global _jusbr_registry
184
+
185
185
  # retrieve user id
186
186
  input_params: dict[str, Any] = request.values
187
187
  user_id: str = input_params.get("user-id") or input_params.get("login")
188
188
 
189
- # retrieve user data
190
- global _jusbr_registry
189
+ # remove user data
191
190
  if user_id in _jusbr_registry.get("users"):
192
191
  _jusbr_registry.pop(user_id)
193
- logger: Logger = _jusbr_registry.get("logger")
194
- if logger:
195
- logger.debug(f"User '{user_id}' removed from the registry")
192
+ if _logger:
193
+ _logger.debug(f"User '{user_id}' removed from the registry")
196
194
 
197
195
  return Response(status=200)
198
196
 
199
197
 
200
198
  # @flask_app.route(rule=<callback_endpoint>, # JUSBR_CALLBACK_ENDPOINT: /iam/jusbr:callback
201
199
  # methods=["POST"])
202
- def jusbr_callback() -> Response:
200
+ def service_callback() -> Response:
203
201
  """
204
202
  Entry point for the callback from JusBR on authentication.
205
203
 
@@ -211,10 +209,10 @@ def jusbr_callback() -> Response:
211
209
  oauth_state: str = request.args.get("state")
212
210
  user_data: dict[str, Any] | None = None
213
211
  if oauth_state:
214
- for user in _jusbr_registry.get("users"):
212
+ for data in _jusbr_registry.get("users"):
215
213
  safe_cache: Cache = user_data.get("cache-obj")
216
214
  if safe_cache and oauth_state == safe_cache.get("oauth-state"):
217
- user_data = user
215
+ user_data = data
218
216
  # 'oauth-state' is to be used only once
219
217
  safe_cache["oauth-state"] = None
220
218
  break
@@ -231,10 +229,12 @@ def jusbr_callback() -> Response:
231
229
  __post_jusbr(user_data=user_data,
232
230
  body_data=body_data,
233
231
  errors=errors,
234
- logger=_jusbr_registry.get("logger"))
232
+ logger=_logger)
235
233
  else:
236
- # login operation timed-out
237
- errors.append("Operation timeout")
234
+ msg: str = "Unknown OAuth2 code received"
235
+ if __get_login_timeout():
236
+ msg += " - possible operation timeout"
237
+ errors.append(msg)
238
238
 
239
239
  result: Response
240
240
  if errors:
@@ -248,7 +248,7 @@ def jusbr_callback() -> Response:
248
248
 
249
249
  # @flask_app.route(rule=<token_endpoint>, # JUSBR_TOKEN_ENDPOINT: /iam/jusbr:get-token
250
250
  # methods=["GET"])
251
- def jusbr_token() -> Response:
251
+ def service_token() -> Response:
252
252
  """
253
253
  Entry point for retrieving the JusBR token.
254
254
 
@@ -260,7 +260,7 @@ def jusbr_token() -> Response:
260
260
 
261
261
  # retrieve the token
262
262
  token: str = jusbr_get_token(user_id=user_id,
263
- logger=_jusbr_registry.get("logger"))
263
+ logger=_logger)
264
264
  result: Response
265
265
  if token:
266
266
  result = jsonify({"token": token})
@@ -318,17 +318,81 @@ def jusbr_get_token(user_id: str,
318
318
  return result
319
319
 
320
320
 
321
+ def jusbr_set_scope(user_id: str,
322
+ scope: str,
323
+ logger: Logger | None) -> None:
324
+ """
325
+ Set the OAuth2 scope of *user_id* to *scope*.
326
+
327
+ :param user_id: the user's identification
328
+ :param scope: the OAuth2 scope to set to the user
329
+ :param logger: optional logger
330
+ """
331
+ global _jusbr_registry
332
+
333
+ # retrieve user data
334
+ user_data: dict[str, Any] = __get_user_data(user_id=user_id,
335
+ logger=logger)
336
+ # set the OAuth2 scope
337
+ user_data["oauth-scope"] = scope
338
+ if logger:
339
+ logger.debug(f"Scope for user '{user_id}' set to '{scope}'")
340
+
341
+
342
+ def __get_login_timeout() -> int | None:
343
+ """
344
+ Retrieve the timeout currently applicable for the login operation.
345
+
346
+ :return: the current login timeout, or *None* if none has been set.
347
+ """
348
+ global _jusbr_registry
349
+
350
+ timeout: int = _jusbr_registry.get("login-timeout")
351
+ return timeout if isinstance(timeout, int) and timeout > 0 else None
352
+
353
+
354
+ def __get_user_data(user_id: str,
355
+ logger: Logger | None) -> dict[str, Any]:
356
+ """
357
+ Retrieve the data for *user_id* from the registry.
358
+
359
+ If an entry is not found for *user_id* in the registry, it is created.
360
+ It will remain there until the user is logged out.
361
+
362
+ :param user_id:
363
+ :return: the data for *user_id* in the registry
364
+ """
365
+ global _jusbr_registry
366
+
367
+ result: dict[str, Any] = _jusbr_registry["users"].get(user_id)
368
+ if not result:
369
+ result = {"access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp())}
370
+ _jusbr_registry["users"][user_id] = result
371
+ if logger:
372
+ logger.debug(f"Entry for user '{user_id}' added to registry")
373
+
374
+ return result
375
+
376
+
321
377
  def __post_jusbr(user_data: dict[str, Any],
322
378
  body_data: dict[str, Any],
323
379
  errors: list[str] | None,
324
380
  logger: Logger | None) -> None:
325
381
  """
326
- Send a POST request to JusBR to obtain the authorization token.
382
+ Send a POST request to JusBR to obtain the authentication tokens.
383
+
384
+ For code for token exchange, *body_data* will have the attributes
385
+ - "grant_type": "authorization_code"
386
+ - "code": <16-character-random-code>
387
+ - "redirect_url": <callback-url>
388
+ For token refresh, *body_data* will have the attributes
389
+ - "grant_type": "refresh_token"
390
+ - "refresh_token": <current-refresh-token>
327
391
 
328
- If successful, the token data is stored in the registry, and the token itself is returned.
392
+ If the operation is successful, the token data is stored in the registry.
329
393
  Otherwise, *errors* will contain the appropriate error message.
330
394
 
331
- :param user_data: the user data in the registry
395
+ :param user_data: the user's data in the registry
332
396
  :param body_data: the data to send in the body of the request
333
397
  :param errors: incidental errors
334
398
  :param logger: optional logger
@@ -355,28 +419,30 @@ def __post_jusbr(user_data: dict[str, Any],
355
419
  # }
356
420
  response: requests.Response = requests.post(url=url,
357
421
  data=body_data)
358
- if response.status_code < 200 or response.status_code >= 300:
359
- # request resulted in error, report the problem
360
- err_msg = (f"POST '{url}': failed, "
361
- f"status {response.status_code}, reason '{response.reason}'")
362
- if response.status_code == 401 and "refresh_token" in body_data:
363
- # refresh token is no longer valid
364
- safe_cache["refresh-token"] = None
365
- else:
422
+ if response.status_code == 200:
423
+ # request succeeded
366
424
  reply: dict[str, Any] = response.json()
367
425
  result = reply.get("access_token")
368
426
  safe_cache: Cache = FIFOCache(maxsize=1024)
369
427
  safe_cache["access-token"] = result
370
- safe_cache["refresh-token"] = reply.get("refresh_token")
428
+ # on token refresh, keep current refresh token if a new one is not provided
429
+ safe_cache["refresh-token"] = reply.get("refresh_token") or body_data.get("refresh_token")
371
430
  user_data["cache-obj"] = safe_cache
372
431
  user_data["access-expiration"] = now + reply.get("expires_in")
373
432
  if logger:
374
433
  logger.debug(msg=f"POST '{url}': status {response.status_code}")
434
+ else:
435
+ # request resulted in error
436
+ err_msg = (f"POST '{url}': failed, "
437
+ f"status {response.status_code}, reason '{response.reason}'")
438
+ if response.status_code == 401 and "refresh_token" in body_data:
439
+ # refresh token is no longer valid
440
+ safe_cache["refresh-token"] = None
375
441
  except Exception as e:
376
442
  # the operation raised an exception
377
443
  err_msg = exc_format(exc=e,
378
444
  exc_info=sys.exc_info())
379
- err_msg = f"POST '{url}': error, '{err_msg}'"
445
+ err_msg = f"POST '{url}': error '{err_msg}'"
380
446
 
381
447
  if err_msg:
382
448
  if isinstance(errors, list):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_iam
3
- Version: 0.0.2
3
+ Version: 0.0.4
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
@@ -0,0 +1,7 @@
1
+ pypomes_iam/__init__.py,sha256=mOQCIgHLA7a-_7gkWoDCzaPYtaj48KPhcAlLJKe6lPo,475
2
+ pypomes_iam/iam_jusbr.py,sha256=uAozBLp7FjZhLyLYrh-QLX_ero5odeRdN0larJ_C28k,17000
3
+ pypomes_iam/iam_provider.py,sha256=eP8XzjTUEpwejTkO0wmDiqKjqbIEOzRNCR2ju5E15og,5856
4
+ pypomes_iam-0.0.4.dist-info/METADATA,sha256=28FoyyBfzPQdngdU_Jxe6C7n8OQAdSA9k2_pcj-aHhc,628
5
+ pypomes_iam-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
+ pypomes_iam-0.0.4.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
7
+ pypomes_iam-0.0.4.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- pypomes_iam/__init__.py,sha256=LoBYDB2Jl5urH8HK2S5bPCPwb4O417xmCkoz6rF0VG4,439
2
- pypomes_iam/iam_jusbr.py,sha256=VSXWTOhdXU-UwBY2swU8FSu90iaRJeZtGkyqai0bMVQ,14897
3
- pypomes_iam/iam_provider.py,sha256=eP8XzjTUEpwejTkO0wmDiqKjqbIEOzRNCR2ju5E15og,5856
4
- pypomes_iam-0.0.2.dist-info/METADATA,sha256=292VOclyq-Y0kyrPDGsBEb8tUhxGcK_Mxz8CCslr_1U,628
5
- pypomes_iam-0.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
- pypomes_iam-0.0.2.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
7
- pypomes_iam-0.0.2.dist-info/RECORD,,