pypomes-iam 0.2.9__py3-none-any.whl → 0.7.0__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.

@@ -7,8 +7,49 @@ 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,
51
+ recipient_id: str = None,
52
+ recipient_attr: str = None,
12
53
  public_key: str | bytes | PyJWK | RSAPublicKey = None,
13
54
  errors: list[str] = None,
14
55
  logger: Logger = None) -> dict[str, dict[str, Any]] | None:
@@ -24,15 +65,21 @@ def token_validate(token: str,
24
65
  If an asymmetric algorithm was used to sign the token and *public_key* is provided, then
25
66
  the token is validated, by using the data in its *signature* section.
26
67
 
68
+ The parameters *recipient_id* and *recipient_attr* refer the token's expected subject, respectively,
69
+ the subject's identification and the attribute in the token's payload data identifying its subject.
70
+ If both are provided, *recipient_id* is validated.
71
+
27
72
  On failure, *errors* will contain the reason(s) for rejecting *token*.
28
73
  On success, return the token's claims (*header* and *payload*).
29
74
 
30
75
  :param token: the token to be validated
31
76
  :param public_key: optional public key used to sign the token, in *PEM* format
32
77
  :param issuer: optional value to compare with the token's *iss* (issuer) attribute in its *payload*
78
+ :param recipient_id: identification of the expected token subject
79
+ :param recipient_attr: attribute in the token's payload holding the expected subject's identification
33
80
  :param errors: incidental error messages
34
81
  :param logger: optional logger
35
- :return: The token's claims (*header* and *payload*) if it is valid, *None* otherwise
82
+ :return: The token's claims (*header* and *payload*), or *None* if error
36
83
  """
37
84
  # initialize the return variable
38
85
  result: dict[str, dict[str, Any]] | None = None
@@ -70,8 +117,6 @@ def token_validate(token: str,
70
117
  "verify_nbf": False,
71
118
  "verify_signature": token_alg in ["RS256", "RS512"] and public_key is not None
72
119
  }
73
- if issuer:
74
- options["require"].append("iss")
75
120
  try:
76
121
  # raises:
77
122
  # InvalidTokenError: token is invalid
@@ -87,10 +132,17 @@ def token_validate(token: str,
87
132
  algorithms=[token_alg],
88
133
  options=options,
89
134
  issuer=issuer)
90
- result = {
91
- "header": token_header,
92
- "payload": payload
93
- }
135
+ if recipient_id and recipient_attr and \
136
+ payload.get(recipient_attr) and recipient_id != payload.get(recipient_attr):
137
+ msg: str = f"Token was issued to '{payload.get(recipient_attr)}', not to '{recipient_id}'"
138
+ if logger:
139
+ logger.error(msg=msg)
140
+ errors.append(msg)
141
+ else:
142
+ result = {
143
+ "header": token_header,
144
+ "payload": payload
145
+ }
94
146
  except Exception as e:
95
147
  exc_err: str = exc_format(exc=e,
96
148
  exc_info=sys.exc_info())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_iam
3
- Version: 0.2.9
3
+ Version: 0.7.0
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=5nomjeylTUSEtLCAvRnM1ayblsVx2hGDYzQn2twk8kk,42727
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.7.0.dist-info/METADATA,sha256=H2XjOEqG8t1umbwLIj8CM4AU0G9ufNN0mbEPIHfH4ko,661
9
+ pypomes_iam-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ pypomes_iam-0.7.0.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
11
+ pypomes_iam-0.7.0.dist-info/RECORD,,
@@ -1,114 +0,0 @@
1
- from cachetools import Cache, FIFOCache
2
- from datetime import datetime
3
- from flask import Flask
4
- from logging import Logger
5
- from pypomes_core import (
6
- APP_PREFIX, TZ_LOCAL, env_get_int, env_get_str
7
- )
8
- from typing import Any, Final
9
-
10
- from .iam_common import IAM_SERVERS, IamServer, _service_token
11
-
12
- JUSBR_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_ID")
13
- JUSBR_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_SECRET")
14
- JUSBR_CLIENT_TIMEOUT: Final[int] = env_get_int(key=f"{APP_PREFIX}_JUSBR_CLIENT_TIMEOUT")
15
-
16
- JUSBR_ENDPOINT_CALLBACK: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT_CALLBACK",
17
- def_value="/iam/jusbr:callback")
18
- JUSBR_ENDPOINT_LOGIN: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT_LOGIN",
19
- def_value="/iam/jusbr:login")
20
- JUSBR_ENDPOINT_LOGOUT: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT_LOGOUT",
21
- def_value="/iam/jusbr:logout")
22
- JUSBR_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT_TOKEN",
23
- def_value="/iam/jusbr:get-token")
24
-
25
- JUSBR_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_JUSBR_PUBLIC_KEY_LIFETIME",
26
- def_value=86400) # 24 hours
27
- JUSBR_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_BASE")
28
-
29
-
30
- def jusbr_setup(flask_app: Flask,
31
- client_id: str = JUSBR_CLIENT_ID,
32
- client_secret: str = JUSBR_CLIENT_SECRET,
33
- client_timeout: int = JUSBR_CLIENT_TIMEOUT,
34
- public_key_lifetime: int = JUSBR_PUBLIC_KEY_LIFETIME,
35
- callback_endpoint: str = JUSBR_ENDPOINT_CALLBACK,
36
- token_endpoint: str = JUSBR_ENDPOINT_TOKEN,
37
- login_endpoint: str = JUSBR_ENDPOINT_LOGIN,
38
- logout_endpoint: str = JUSBR_ENDPOINT_LOGOUT,
39
- base_url: str = JUSBR_URL_AUTH_BASE,
40
- logger: Logger = None) -> None:
41
- """
42
- Configure the JusBR IAM.
43
-
44
- This should be invoked only once, before the first access to a JusBR service.
45
-
46
- :param flask_app: the Flask application
47
- :param client_id: the client's identification with JusBR
48
- :param client_secret: the client's password with JusBR
49
- :param client_timeout: timeout for login authentication (in seconds,defaults to no timeout)
50
- :param public_key_lifetime: how long to use JusBR's public key, before refreshing it (in seconds)
51
- :param callback_endpoint: endpoint for the callback from JusBR
52
- :param token_endpoint: endpoint for retrieving the JusBR authentication token
53
- :param login_endpoint: endpoint for redirecting user to JusBR login page
54
- :param logout_endpoint: endpoint for terminating user access to JusBR
55
- :param base_url: base URL to request the JusBR services
56
- :param logger: optional logger
57
- """
58
- from .iam_pomes import service_login, service_logout, service_callback, service_token
59
-
60
- # configure the JusBR registry
61
- cache: Cache = FIFOCache(maxsize=1048576)
62
- cache["users"] = {}
63
- IAM_SERVERS[IamServer.IAM_JUSRBR] = {
64
- "client-id": client_id,
65
- "client-secret": client_secret,
66
- "client-timeout": client_timeout,
67
- "base-url": base_url,
68
- "pk-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
69
- "pk-lifetime": public_key_lifetime,
70
- "cache": cache,
71
- "logger": logger,
72
- "redirect-uri": None
73
- }
74
-
75
- # establish the endpoints
76
- if login_endpoint:
77
- flask_app.add_url_rule(rule=login_endpoint,
78
- endpoint="jusbr-login",
79
- view_func=service_login,
80
- methods=["GET"])
81
- if logout_endpoint:
82
- flask_app.add_url_rule(rule=logout_endpoint,
83
- endpoint="jusbr-logout",
84
- view_func=service_logout,
85
- methods=["GET"])
86
- if callback_endpoint:
87
- flask_app.add_url_rule(rule=callback_endpoint,
88
- endpoint="jusbr-callback",
89
- view_func=service_callback,
90
- methods=["GET", "POST"])
91
- if token_endpoint:
92
- flask_app.add_url_rule(rule=token_endpoint,
93
- endpoint="jusbr-token",
94
- view_func=service_token,
95
- methods=["GET"])
96
-
97
-
98
- def jusbr_get_token(user_id: str,
99
- errors: list[str] = None,
100
- logger: Logger = None) -> str:
101
- """
102
- Retrieve a JusBR authentication token for *user_id*.
103
-
104
- :param user_id: the user's identification
105
- :param errors: incidental errors
106
- :param logger: optional logger
107
- :return: the uthentication tokem
108
- """
109
- # retrieve the token
110
- args: dict[str, Any] = {"user-id": user_id}
111
- return _service_token(registry=IAM_SERVERS[IamServer.IAM_JUSRBR],
112
- args=args,
113
- errors=errors,
114
- logger=logger)
@@ -1,117 +0,0 @@
1
- from cachetools import Cache, FIFOCache
2
- from datetime import datetime
3
- from flask import Flask
4
- from logging import Logger
5
- from pypomes_core import (
6
- APP_PREFIX, TZ_LOCAL, env_get_int, env_get_str
7
- )
8
- from typing import Any, Final
9
-
10
- from .iam_common import IAM_SERVERS, IamServer, _service_token
11
-
12
- KEYCLOAK_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_ID")
13
- KEYCLOAK_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_SECRET")
14
- KEYCLOAK_CLIENT_TIMEOUT: Final[int] = env_get_int(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_TIMEOUT")
15
-
16
- KEYCLOAK_ENDPOINT_CALLBACK: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_CALLBACK",
17
- def_value="/iam/keycloak:callback")
18
- KEYCLOAK_ENDPOINT_LOGIN: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_LOGIN",
19
- def_value="/iam/keycloak:login")
20
- KEYCLOAK_ENDPOINT_LOGOUT: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_LOGOUT",
21
- def_value="/iam/keycloak:logout")
22
- KEYCLOAK_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_TOKEN",
23
- def_value="/iam/keycloak:get-token")
24
-
25
- KEYCLOAK_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_KEYCLOAK_PUBLIC_KEY_LIFETIME",
26
- def_value=86400) # 24 hours
27
- KEYCLOAK_REALM: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_REALM")
28
- KEYCLOAK_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_URL_AUTH_BASE")
29
-
30
-
31
- def keycloak_setup(flask_app: Flask,
32
- client_id: str = KEYCLOAK_CLIENT_ID,
33
- client_secret: str = KEYCLOAK_CLIENT_SECRET,
34
- client_timeout: int = KEYCLOAK_CLIENT_TIMEOUT,
35
- public_key_lifetime: int = KEYCLOAK_PUBLIC_KEY_LIFETIME,
36
- realm: str = KEYCLOAK_REALM,
37
- callback_endpoint: str = KEYCLOAK_ENDPOINT_CALLBACK,
38
- token_endpoint: str = KEYCLOAK_ENDPOINT_TOKEN,
39
- login_endpoint: str = KEYCLOAK_ENDPOINT_LOGIN,
40
- logout_endpoint: str = KEYCLOAK_ENDPOINT_LOGOUT,
41
- base_url: str = KEYCLOAK_URL_AUTH_BASE,
42
- logger: Logger = None) -> None:
43
- """
44
- Configure the Keycloak IAM.
45
-
46
- This should be invoked only once, before the first access to a Keycloak service.
47
-
48
- :param flask_app: the Flask application
49
- :param client_id: the client's identification with JusBR
50
- :param client_secret: the client's password with JusBR
51
- :param client_timeout: timeout for login authentication (in seconds,defaults to no timeout)
52
- :param public_key_lifetime: how long to use Keycloak's public key, before refreshing it (in seconds)
53
- :param realm: the Keycloak realm
54
- :param callback_endpoint: endpoint for the callback from JusBR
55
- :param token_endpoint: endpoint for retrieving the JusBR authentication token
56
- :param login_endpoint: endpoint for redirecting user to JusBR login page
57
- :param logout_endpoint: endpoint for terminating user access to JusBR
58
- :param base_url: base URL to request the JusBR services
59
- :param logger: optional logger
60
- """
61
- from .iam_pomes import service_login, service_logout, service_callback, service_token
62
-
63
- # configure the Keycloak registry
64
- cache: Cache = FIFOCache(maxsize=1048576)
65
- cache["users"] = {}
66
- IAM_SERVERS[IamServer.IAM_KEYCLOAK] = {
67
- "client-id": client_id,
68
- "client-secret": client_secret,
69
- "client-timeout": client_timeout,
70
- "base-url": f"{base_url}/realms/{realm}",
71
- "pk-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
72
- "pk-lifetime": public_key_lifetime,
73
- "cache": cache,
74
- "logger": logger,
75
- "redirect-uri": None
76
- }
77
-
78
- # establish the endpoints
79
- if login_endpoint:
80
- flask_app.add_url_rule(rule=login_endpoint,
81
- endpoint="keycloak-login",
82
- view_func=service_login,
83
- methods=["GET"])
84
- if logout_endpoint:
85
- flask_app.add_url_rule(rule=logout_endpoint,
86
- endpoint="keycloak-logout",
87
- view_func=service_logout,
88
- methods=["GET"])
89
- if callback_endpoint:
90
- flask_app.add_url_rule(rule=callback_endpoint,
91
- endpoint="keycloak-callback",
92
- view_func=service_callback,
93
- methods=["POST"])
94
- if token_endpoint:
95
- flask_app.add_url_rule(rule=token_endpoint,
96
- endpoint="keycloak-token",
97
- view_func=service_token,
98
- methods=["GET"])
99
-
100
-
101
- def keycloak_get_token(user_id: str,
102
- errors: list[str] = None,
103
- logger: Logger = None) -> str:
104
- """
105
- Retrieve a Keycloak authentication token for *user_id*.
106
-
107
- :param user_id: the user's identification
108
- :param errors: incidental errors
109
- :param logger: optional logger
110
- :return: the uthentication tokem
111
- """
112
- # retrieve the token
113
- args: dict[str, Any] = {"user-id": user_id}
114
- return _service_token(registry=IAM_SERVERS[IamServer.IAM_KEYCLOAK],
115
- args=args,
116
- errors=errors,
117
- logger=logger)
@@ -1,11 +0,0 @@
1
- pypomes_iam/__init__.py,sha256=u-gNGbsayMf-2SWTB8VcoTCADoczZuwNEH50BPxTZZ8,682
2
- pypomes_iam/iam_common.py,sha256=_3Fx8kAJCthRTvQtlZg1gx0hWlUn4ko_ASROwsU9dJM,17017
3
- pypomes_iam/iam_pomes.py,sha256=iiJrqS_np2aUyY4JS0Vi1QEcGHBQfnPkdMpTyctceUk,7722
4
- pypomes_iam/jusbr_pomes.py,sha256=Zh4nbKiBD2c4slYxgXoricCSScI8SROQproFfZ_55BI,5322
5
- pypomes_iam/keycloak_pomes.py,sha256=zVoLG0yNKGW1CP1bCf3yQiFw-8ZtOG7YG3VGZDq9_3c,5681
6
- pypomes_iam/provider_pomes.py,sha256=eP8XzjTUEpwejTkO0wmDiqKjqbIEOzRNCR2ju5E15og,5856
7
- pypomes_iam/token_pomes.py,sha256=OSllw00XnU-sE9EKXo8jZAto0zfFLBi83dvllLs-Sc0,4402
8
- pypomes_iam-0.2.9.dist-info/METADATA,sha256=fBxgk8sTkXy8Sky-zZMj6V0ZKE2Af3ksVZt94WtDFhk,694
9
- pypomes_iam-0.2.9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- pypomes_iam-0.2.9.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
11
- pypomes_iam-0.2.9.dist-info/RECORD,,