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 +2 -2
- pypomes_iam/iam_jusbr.py +107 -41
- {pypomes_iam-0.0.2.dist-info → pypomes_iam-0.0.4.dist-info}/METADATA +1 -1
- pypomes_iam-0.0.4.dist-info/RECORD +7 -0
- pypomes_iam-0.0.2.dist-info/RECORD +0 -7
- {pypomes_iam-0.0.2.dist-info → pypomes_iam-0.0.4.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.0.2.dist-info → pypomes_iam-0.0.4.dist-info}/licenses/LICENSE +0 -0
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
|
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
|
-
|
|
148
|
-
|
|
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
|
-
|
|
153
|
+
timeout: int = __get_login_timeout()
|
|
156
154
|
safe_cache: Cache
|
|
157
|
-
if
|
|
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
|
|
175
|
+
def service_logout() -> Response:
|
|
178
176
|
"""
|
|
179
177
|
Entry point for the JusBR logout service.
|
|
180
178
|
|
|
181
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
194
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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=
|
|
232
|
+
logger=_logger)
|
|
235
233
|
else:
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
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=
|
|
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
|
|
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
|
|
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
|
|
359
|
-
# request
|
|
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
|
-
|
|
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
|
|
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.
|
|
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,,
|
|
File without changes
|
|
File without changes
|