pypomes-iam 0.5.5__tar.gz → 0.5.6__tar.gz

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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_iam
3
- Version: 0.5.5
3
+ Version: 0.5.6
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
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
6
6
 
7
7
  [project]
8
8
  name = "pypomes_iam"
9
- version = "0.5.5"
9
+ version = "0.5.6"
10
10
  authors = [
11
11
  { name="GT Nunes", email="wisecoder01@gmail.com" }
12
12
  ]
File without changes
@@ -1,7 +1,12 @@
1
+ from .iam_actions import (
2
+ action_callback, action_exchange,
3
+ action_login, action_logout, action_token
4
+ )
5
+ from .iam_common import (
6
+ IamServer
7
+ )
1
8
  from .iam_pomes import (
2
- IamServer,
3
- login_callback, token_exchange,
4
- user_login, user_logout, user_token
9
+ jwt_required
5
10
  )
6
11
  from .iam_services import (
7
12
  logger_register
@@ -20,10 +25,13 @@ from .token_pomes import (
20
25
  )
21
26
 
22
27
  __all__ = [
23
- # iam_pomes
28
+ # iam_actions
29
+ "action_callback", "action_exchange",
30
+ "action_login", "action_logout", "action_token",
31
+ # iam_commons
24
32
  "IamServer",
25
- "login_callback", "token_exchange",
26
- "user_login", "user_logout", "user_token",
33
+ # iam_pomes
34
+ "jwt_required",
27
35
  # iam_services
28
36
  "logger_register",
29
37
  # jusbr_pomes
@@ -4,7 +4,6 @@ import secrets
4
4
  import string
5
5
  import sys
6
6
  from datetime import datetime
7
- from flask import Request, Response, request
8
7
  from logging import Logger
9
8
  from pypomes_core import TZ_LOCAL, exc_format
10
9
  from typing import Any
@@ -12,32 +11,15 @@ from typing import Any
12
11
  from .iam_common import (
13
12
  IamServer, _iam_lock,
14
13
  _get_iam_users, _get_iam_registry, # _get_public_key,
15
- _get_login_timeout, _get_user_data, _iam_server_from_issuer
14
+ _get_login_timeout, _get_user_data
16
15
  )
17
- from .token_pomes import token_get_claims, token_validate
16
+ from .token_pomes import token_validate
18
17
 
19
18
 
20
- def jwt_required(func: callable) -> callable:
21
- """
22
- Create a decorator to authenticate service endpoints with JWT tokens.
23
-
24
- :param func: the function being decorated
25
- """
26
- # ruff: noqa: ANN003 - Missing type annotation for *{name}
27
- def wrapper(*args, **kwargs) -> Response:
28
- response: Response = __request_validate(request=request)
29
- return response if response else func(*args, **kwargs)
30
-
31
- # prevent a rogue error ("View function mapping is overwriting an existing endpoint function")
32
- wrapper.__name__ = func.__name__
33
-
34
- return wrapper
35
-
36
-
37
- def user_login(iam_server: IamServer,
38
- args: dict[str, Any],
39
- errors: list[str] = None,
40
- logger: Logger = None) -> str:
19
+ def action_login(iam_server: IamServer,
20
+ args: dict[str, Any],
21
+ errors: list[str] = None,
22
+ logger: Logger = None) -> str:
41
23
  """
42
24
  Build the URL for redirecting the request to *iam_server*'s authentication page.
43
25
 
@@ -95,10 +77,10 @@ def user_login(iam_server: IamServer,
95
77
  return result
96
78
 
97
79
 
98
- def user_logout(iam_server: IamServer,
99
- args: dict[str, Any],
100
- errors: list[str] = None,
101
- logger: Logger = None) -> None:
80
+ def action_logout(iam_server: IamServer,
81
+ args: dict[str, Any],
82
+ errors: list[str] = None,
83
+ logger: Logger = None) -> None:
102
84
  """
103
85
  Logout the user, by removing all data associating it from *iam_server*'s registry.
104
86
 
@@ -126,10 +108,10 @@ def user_logout(iam_server: IamServer,
126
108
  logger.debug(msg=f"User '{user_id}' removed from {iam_server}'s registry")
127
109
 
128
110
 
129
- def user_token(iam_server: IamServer,
130
- args: dict[str, Any],
131
- errors: list[str] = None,
132
- logger: Logger = None) -> str:
111
+ def action_token(iam_server: IamServer,
112
+ args: dict[str, Any],
113
+ errors: list[str] = None,
114
+ logger: Logger = None) -> str:
133
115
  """
134
116
  Retrieve the authentication token for the user, from *iam_server*.
135
117
 
@@ -212,10 +194,10 @@ def user_token(iam_server: IamServer,
212
194
  return result
213
195
 
214
196
 
215
- def login_callback(iam_server: IamServer,
216
- args: dict[str, Any],
217
- errors: list[str] = None,
218
- logger: Logger = None) -> tuple[str, str] | None:
197
+ def action_callback(iam_server: IamServer,
198
+ args: dict[str, Any],
199
+ errors: list[str] = None,
200
+ logger: Logger = None) -> tuple[str, str] | None:
219
201
  """
220
202
  Entry point for the callback from *iam_server* via the front-end application, on authentication operations.
221
203
 
@@ -282,10 +264,10 @@ def login_callback(iam_server: IamServer,
282
264
  return result
283
265
 
284
266
 
285
- def token_exchange(iam_server: IamServer,
286
- args: dict[str, Any],
287
- errors: list[str] = None,
288
- logger: Logger = None) -> dict[str, Any]:
267
+ def action_exchange(iam_server: IamServer,
268
+ args: dict[str, Any],
269
+ errors: list[str] = None,
270
+ logger: Logger = None) -> dict[str, Any]:
289
271
  """
290
272
  Request *iam_server* to issue a token in exchange for the token obtained from another *IAM* server.
291
273
 
@@ -357,63 +339,6 @@ def token_exchange(iam_server: IamServer,
357
339
  return result
358
340
 
359
341
 
360
- def __request_validate(request: Request) -> Response:
361
- """
362
- Verify whether the HTTP *request* has the proper authorization, as per the JWT standard.
363
-
364
- This implementation assumes that HTTP requests are handled with the *Flask* framework.
365
-
366
- :param request: the *request* to be verified
367
- :return: *None* if the *request* is valid, otherwise a *Response* reporting the error
368
- """
369
- # initialize the return variable
370
- result: Response | None = None
371
-
372
- # retrieve the authorization from the request header
373
- auth_header: str = request.headers.get("Authorization")
374
-
375
- # validate the authorization token
376
- bad_token: bool = True
377
- if auth_header and auth_header.startswith("Bearer "):
378
- # extract and validate the JWT access token
379
- token: str = auth_header.split(" ")[1]
380
- claims: dict[str, Any] = token_get_claims(token=token)
381
- if claims:
382
- issuer: str = claims["payload"].get("iss")
383
- recipient_attr: str | None = None
384
- recipient_id: str = request.values.get("user-id") or request.values.get("login")
385
- with _iam_lock:
386
- iam_server: IamServer = _iam_server_from_issuer(issuer=issuer,
387
- errors=None,
388
- logger=None)
389
- # public_key: str = _get_public_key(iam_server=iam_server,
390
- # errors=errors,
391
- # logger=logger)
392
- public_key = None
393
-
394
- # validate the token's recipient only if a user identification is provided
395
- if recipient_id:
396
- registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
397
- errors=None,
398
- logger=None)
399
- recipient_attr = registry["recipient-attr"]
400
-
401
- # validate the token
402
- if token_validate(token=token,
403
- issuer=issuer,
404
- recipient_id=recipient_id,
405
- recipient_attr=recipient_attr,
406
- public_key=public_key):
407
- # token is valid
408
- bad_token = False
409
-
410
- # deny the authorization
411
- if bad_token:
412
- result = Response(response="Authorization failed",
413
- status=401)
414
- return result
415
-
416
-
417
342
  def __post_for_token(iam_server: IamServer,
418
343
  body_data: dict[str, Any],
419
344
  errors: list[str] | None,
@@ -50,7 +50,7 @@ class IamServer(StrEnum):
50
50
  # }
51
51
  _IAM_SERVERS: Final[dict[IamServer, dict[str, Any]]] = {}
52
52
 
53
- # the lock protecting the data in '_IAM_SERVER'
53
+ # the lock protecting the data in '_IAM_SERVERS'
54
54
  # (because it is 'Final' and set at declaration time, it can be accessed through simple imports)
55
55
  _iam_lock: Final[RLock] = RLock()
56
56
 
@@ -0,0 +1,82 @@
1
+ from flask import Request, Response, request
2
+ from typing import Any
3
+
4
+ from .iam_common import (
5
+ IamServer, _iam_lock, _get_iam_registry,
6
+ _iam_server_from_issuer # _get_public_key
7
+ )
8
+ from .token_pomes import token_get_claims, token_validate
9
+
10
+
11
+ def jwt_required(func: callable) -> callable:
12
+ """
13
+ Create a decorator to authenticate service endpoints with JWT tokens.
14
+
15
+ :param func: the function being decorated
16
+ """
17
+ # ruff: noqa: ANN003 - Missing type annotation for *{name}
18
+ def wrapper(*args, **kwargs) -> Response:
19
+ response: Response = __request_validate(request=request)
20
+ return response if response else func(*args, **kwargs)
21
+
22
+ # prevent a rogue error ("View function mapping is overwriting an existing endpoint function")
23
+ wrapper.__name__ = func.__name__
24
+
25
+ return wrapper
26
+
27
+
28
+ def __request_validate(request: Request) -> Response:
29
+ """
30
+ Verify whether the HTTP *request* has the proper authorization, as per the JWT standard.
31
+
32
+ This implementation assumes that HTTP requests are handled with the *Flask* framework.
33
+
34
+ :param request: the *request* to be verified
35
+ :return: *None* if the *request* is valid, otherwise a *Response* reporting the error
36
+ """
37
+ # initialize the return variable
38
+ result: Response | None = None
39
+
40
+ # retrieve the authorization from the request header
41
+ auth_header: str = request.headers.get("Authorization")
42
+
43
+ # validate the authorization token
44
+ bad_token: bool = True
45
+ if auth_header and auth_header.startswith("Bearer "):
46
+ # extract and validate the JWT access token
47
+ token: str = auth_header.split(" ")[1]
48
+ claims: dict[str, Any] = token_get_claims(token=token)
49
+ if claims:
50
+ issuer: str = claims["payload"].get("iss")
51
+ recipient_attr: str | None = None
52
+ recipient_id: str = request.values.get("user-id") or request.values.get("login")
53
+ with _iam_lock:
54
+ iam_server: IamServer = _iam_server_from_issuer(issuer=issuer,
55
+ errors=None,
56
+ logger=None)
57
+ # public_key: str = _get_public_key(iam_server=iam_server,
58
+ # errors=errors,
59
+ # logger=logger)
60
+ public_key = None
61
+
62
+ # validate the token's recipient only if a user identification is provided
63
+ if recipient_id:
64
+ registry: dict[str, Any] = _get_iam_registry(iam_server=iam_server,
65
+ errors=None,
66
+ logger=None)
67
+ recipient_attr = registry["recipient-attr"]
68
+
69
+ # validate the token
70
+ if token_validate(token=token,
71
+ issuer=issuer,
72
+ recipient_id=recipient_id,
73
+ recipient_attr=recipient_attr,
74
+ public_key=public_key):
75
+ # token is valid
76
+ bad_token = False
77
+
78
+ # deny the authorization
79
+ if bad_token:
80
+ result = Response(response="Authorization failed",
81
+ status=401)
82
+ return result
@@ -4,9 +4,9 @@ from logging import Logger
4
4
  from typing import Any
5
5
 
6
6
  from .iam_common import IamServer, _iam_lock, _iam_server_from_endpoint
7
- from .iam_pomes import (
8
- user_login, user_logout,
9
- user_token, token_exchange, login_callback
7
+ from .iam_actions import (
8
+ action_login, action_logout,
9
+ action_token, action_exchange, action_callback
10
10
  )
11
11
 
12
12
  # the logger for IAM service operations
@@ -60,10 +60,10 @@ def service_login() -> Response:
60
60
  logger=__IAM_LOGGER)
61
61
  if iam_server:
62
62
  # 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)
63
+ login_url: str = action_login(iam_server=iam_server,
64
+ args=request.args,
65
+ errors=errors,
66
+ logger=__IAM_LOGGER)
67
67
  if login_url:
68
68
  result = jsonify({"login-url": login_url})
69
69
  if errors:
@@ -106,10 +106,10 @@ def service_logout() -> Response:
106
106
  logger=__IAM_LOGGER)
107
107
  if iam_server:
108
108
  # logout the user
109
- user_logout(iam_server=iam_server,
110
- args=request.args,
111
- errors=errors,
112
- logger=__IAM_LOGGER)
109
+ action_logout(iam_server=iam_server,
110
+ args=request.args,
111
+ errors=errors,
112
+ logger=__IAM_LOGGER)
113
113
  if errors:
114
114
  result = Response(response="; ".join(errors),
115
115
  status=400)
@@ -160,10 +160,10 @@ def service_callback() -> Response:
160
160
  logger=__IAM_LOGGER)
161
161
  if iam_server:
162
162
  # process the callback operation
163
- token_data = login_callback(iam_server=iam_server,
164
- args=request.args,
165
- errors=errors,
166
- logger=__IAM_LOGGER)
163
+ token_data = action_callback(iam_server=iam_server,
164
+ args=request.args,
165
+ errors=errors,
166
+ logger=__IAM_LOGGER)
167
167
  result: Response
168
168
  if errors:
169
169
  result = jsonify({"errors": "; ".join(errors)})
@@ -215,10 +215,10 @@ def service_token() -> Response:
215
215
  if iam_server:
216
216
  # retrieve the token
217
217
  errors: list[str] = []
218
- token: str = user_token(iam_server=iam_server,
219
- args=args,
220
- errors=errors,
221
- logger=__IAM_LOGGER)
218
+ token: str = action_token(iam_server=iam_server,
219
+ args=args,
220
+ errors=errors,
221
+ logger=__IAM_LOGGER)
222
222
  else:
223
223
  msg: str = "User identification not provided"
224
224
  errors.append(msg)
@@ -278,10 +278,10 @@ def service_exchange() -> Response:
278
278
  token_data: dict[str, Any] | None = None
279
279
  if iam_server:
280
280
  errors: list[str] = []
281
- token_data = token_exchange(iam_server=iam_server,
282
- args=request.args,
283
- errors=errors,
284
- logger=__IAM_LOGGER)
281
+ token_data = action_exchange(iam_server=iam_server,
282
+ args=request.args,
283
+ errors=errors,
284
+ logger=__IAM_LOGGER)
285
285
  result: Response
286
286
  if errors:
287
287
  result = Response(response="; ".join(errors),
@@ -7,7 +7,7 @@ from pypomes_core import (
7
7
  from typing import Any, Final
8
8
 
9
9
  from .iam_common import _IAM_SERVERS, IamServer, _iam_lock
10
- from .iam_pomes import user_token
10
+ from .iam_actions import action_token
11
11
 
12
12
  JUSBR_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_ID")
13
13
  JUSBR_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_SECRET")
@@ -118,8 +118,8 @@ def jusbr_get_token(user_id: str,
118
118
  # retrieve the token
119
119
  args: dict[str, Any] = {"user-id": user_id}
120
120
  with _iam_lock:
121
- result = user_token(iam_server=IamServer.IAM_JUSRBR,
122
- args=args,
123
- errors=errors,
124
- logger=logger)
121
+ result = action_token(iam_server=IamServer.IAM_JUSRBR,
122
+ args=args,
123
+ errors=errors,
124
+ logger=logger)
125
125
  return result
@@ -7,7 +7,7 @@ from pypomes_core import (
7
7
  from typing import Any, Final
8
8
 
9
9
  from .iam_common import _IAM_SERVERS, IamServer, _iam_lock
10
- from .iam_pomes import user_token
10
+ from .iam_actions import action_token
11
11
 
12
12
  KEYCLOAK_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_ID")
13
13
  KEYCLOAK_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_SECRET")
@@ -129,8 +129,8 @@ def keycloak_get_token(user_id: str,
129
129
  # retrieve the token
130
130
  args: dict[str, Any] = {"user-id": user_id}
131
131
  with _iam_lock:
132
- result = user_token(iam_server=IamServer.IAM_KEYCLOAK,
133
- args=args,
134
- errors=errors,
135
- logger=logger)
132
+ result = action_token(iam_server=IamServer.IAM_KEYCLOAK,
133
+ args=args,
134
+ errors=errors,
135
+ logger=logger)
136
136
  return result
@@ -5,7 +5,7 @@ from base64 import b64encode
5
5
  from datetime import datetime
6
6
  from logging import Logger
7
7
  from pypomes_core import TZ_LOCAL, exc_format
8
- from threading import RLock
8
+ from threading import Lock
9
9
  from typing import Any, Final
10
10
 
11
11
  # structure:
@@ -25,7 +25,7 @@ _provider_registry: Final[dict[str, dict[str, Any]]] = {}
25
25
 
26
26
  # the lock protecting the data in '_provider_registry'
27
27
  # (because it is 'Final' and set at declaration time, it can be accessed through simple imports)
28
- _provider_lock: Final[RLock] = RLock()
28
+ _provider_lock: Final[Lock] = Lock()
29
29
 
30
30
 
31
31
  def provider_register(provider_id: str,
File without changes
File without changes
File without changes