pypomes-iam 0.5.6__py3-none-any.whl → 0.5.8__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, _iam_server_from_endpoint
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
+ )
7
11
  from .iam_actions import (
8
12
  action_login, action_logout,
9
13
  action_token, action_exchange, action_callback
10
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.
@@ -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
 
@@ -232,9 +317,9 @@ def service_token() -> Response:
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
 
@@ -3,22 +3,42 @@ 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
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]]] = {}
@@ -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
 
@@ -91,19 +111,19 @@ def provider_get_token(provider_id: str,
91
111
  provider: dict[str, Any] = _provider_registry.get(provider_id)
92
112
  if provider:
93
113
  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")
114
+ if now > provider.get(ProviderParam.ACCESS_EXPIRATION):
115
+ user: str = provider.get(ProviderParam.USER)
116
+ pwd: str = provider.get(ProviderParam.PWD)
117
+ headers_data: dict[str, str] = provider.get(ProviderParam.HEADER_DATA) or {}
118
+ body_data: dict[str, str] = provider.get(ProviderParam.BODY_DATA) or {}
119
+ custom_auth: tuple[str, str] = provider.get(ProviderParam.CUSTOM_AUTH)
100
120
  if custom_auth:
101
121
  body_data[custom_auth[0]] = user
102
122
  body_data[custom_auth[1]] = pwd
103
123
  else:
104
124
  enc_bytes: bytes = b64encode(f"{user}:{pwd}".encode())
105
125
  headers_data["Authorization"] = f"Basic {enc_bytes.decode()}"
106
- url: str = provider.get("url")
126
+ url: str = provider.get(ProviderParam.URL)
107
127
  if logger:
108
128
  logger.debug(msg=f"POST {url}, {json.dumps(obj=body_data,
109
129
  ensure_ascii=False)}")
@@ -130,14 +150,14 @@ def provider_get_token(provider_id: str,
130
150
  if logger:
131
151
  logger.debug(msg=f"POST success, status {response.status_code}")
132
152
  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["refresh_token"]
153
+ provider[ProviderParam.ACCESS_TOKEN] = reply.get("access_token")
154
+ provider[ProviderParam.ACCESS_EXPIRATION] = now + int(reply.get("expires_in"))
155
+ if reply.get(ProviderParam.REFRESH_TOKEN):
156
+ provider[ProviderParam.REFRESH_TOKEN] = reply["refresh_token"]
137
157
  if reply.get("refresh_expires_in"):
138
- provider["refresh-expiration"] = now + int(reply.get("refresh_expires_in"))
158
+ provider[ProviderParam.REFRESH_EXPIRATION] = now + int(reply.get("refresh_expires_in"))
139
159
  else:
140
- provider["refresh-expiration"] = sys.maxsize
160
+ provider[ProviderParam.REFRESH_EXPIRATION] = sys.maxsize
141
161
  if logger:
142
162
  logger.debug(msg=f"POST {url}: status {response.status_code}")
143
163
  except Exception as e:
@@ -154,7 +174,7 @@ def provider_get_token(provider_id: str,
154
174
  if logger:
155
175
  logger.error(msg=err_msg)
156
176
  else:
157
- result = provider.get("access-token")
177
+ result = provider.get(ProviderParam.ACCESS_TOKEN)
158
178
 
159
179
  return result
160
180
 
@@ -117,8 +117,6 @@ def token_validate(token: str,
117
117
  "verify_nbf": False,
118
118
  "verify_signature": token_alg in ["RS256", "RS512"] and public_key is not None
119
119
  }
120
- if issuer:
121
- options["require"].append("iss")
122
120
  try:
123
121
  # raises:
124
122
  # InvalidTokenError: token is invalid
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pypomes_iam
3
- Version: 0.5.6
3
+ Version: 0.5.8
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,11 @@
1
+ pypomes_iam/__init__.py,sha256=f-2W_zrCmXExubJPExQrhAwGpiQCmybEC_wguRYFHsw,994
2
+ pypomes_iam/iam_actions.py,sha256=Bmd8rBg3948Afsg10B6B1ZrFY4wYtbxi55rX4Rlqiyk,39167
3
+ pypomes_iam/iam_common.py,sha256=I-HtwpvrhByTbOoSQrMktjpbYgeIPlYM1YC6wkFUhI4,18251
4
+ pypomes_iam/iam_pomes.py,sha256=BetEVGv41wkcP9E1wRvYiQgmJElDXH4Iz8qgf7iH6X0,5617
5
+ pypomes_iam/iam_services.py,sha256=IkCjrKDX1Ix7BiHh-BL3VKz5xogcNC8prXkHyJzQoZ8,15862
6
+ pypomes_iam/provider_pomes.py,sha256=N0nL9_hgHmAjG9JKFoXC33zk8b1ckPG1veu1jTp-2JE,8045
7
+ pypomes_iam/token_pomes.py,sha256=K4nSAotKUoHIE2s3ltc_nVimlNeKS9tnD-IlslkAvkk,6626
8
+ pypomes_iam-0.5.8.dist-info/METADATA,sha256=Q60cQU69Gbay_IjFewESe9P4O4Z6mQ5tz_tYvw7yIMM,694
9
+ pypomes_iam-0.5.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ pypomes_iam-0.5.8.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
11
+ pypomes_iam-0.5.8.dist-info/RECORD,,
@@ -1,125 +0,0 @@
1
- from cachetools import Cache, FIFOCache
2
- from flask import Flask
3
- from logging import Logger
4
- from pypomes_core import (
5
- APP_PREFIX, env_get_int, env_get_str
6
- )
7
- from typing import Any, Final
8
-
9
- from .iam_common import _IAM_SERVERS, IamServer, _iam_lock
10
- from .iam_actions import action_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_REALM: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_REALM")
28
- JUSBR_RECIPIENT_ATTR: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_RECIPIENT_ATTR",
29
- def_value="preferred_username")
30
- JUSBR_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_BASE")
31
-
32
-
33
- def jusbr_setup(flask_app: Flask,
34
- base_url: str = JUSBR_URL_AUTH_BASE,
35
- realm: str = JUSBR_REALM,
36
- client_id: str = JUSBR_CLIENT_ID,
37
- client_secret: str = JUSBR_CLIENT_SECRET,
38
- client_timeout: int = JUSBR_CLIENT_TIMEOUT,
39
- public_key_lifetime: int | None = JUSBR_PUBLIC_KEY_LIFETIME,
40
- recipient_attribute: str | None = JUSBR_RECIPIENT_ATTR,
41
- callback_endpoint: str | None = JUSBR_ENDPOINT_CALLBACK,
42
- login_endpoint: str | None = JUSBR_ENDPOINT_LOGIN,
43
- logout_endpoint: str | None = JUSBR_ENDPOINT_LOGOUT,
44
- token_endpoint: str | None = JUSBR_ENDPOINT_TOKEN) -> None:
45
- """
46
- Configure the JusBR IAM.
47
-
48
- This should be invoked only once, before the first access to a JusBR service.
49
-
50
- :param flask_app: the Flask application
51
- :param base_url: base URL to request JusBR services
52
- :param realm: the JusBR realm
53
- :param client_id: the client's identification with JusBR
54
- :param client_secret: the client's password with JusBR
55
- :param client_timeout: timeout for login authentication (in seconds,defaults to no timeout)
56
- :param public_key_lifetime: how long to use JusBR's public key, before refreshing it (in seconds)
57
- :param recipient_attribute: attribute in the token's payload holding the token's subject
58
- :param callback_endpoint: endpoint for the callback from JusBR
59
- :param login_endpoint: endpoint for redirecting user to JusBR's login page
60
- :param logout_endpoint: endpoint for terminating user access to JusBR
61
- :param token_endpoint: endpoint for retrieving JusBR's authentication token
62
- """
63
- from .iam_services import service_login, service_logout, service_callback, service_token
64
-
65
- # configure the JusBR registry
66
- cache: Cache = FIFOCache(maxsize=1048576)
67
- cache["users"] = {}
68
- with _iam_lock:
69
- _IAM_SERVERS[IamServer.IAM_JUSRBR] = {
70
- "base-url": f"{base_url}/realms/{realm}",
71
- "client-id": client_id,
72
- "client-secret": client_secret,
73
- "client-timeout": client_timeout,
74
- "public-key": None,
75
- "pk-lifetime": public_key_lifetime,
76
- "pk-expiration": 0,
77
- "recipient-attr": recipient_attribute,
78
- "cache": cache
79
- }
80
-
81
- # establish the endpoints
82
- if callback_endpoint:
83
- flask_app.add_url_rule(rule=callback_endpoint,
84
- endpoint="jusbr-callback",
85
- view_func=service_callback,
86
- methods=["GET"])
87
- if login_endpoint:
88
- flask_app.add_url_rule(rule=login_endpoint,
89
- endpoint="jusbr-login",
90
- view_func=service_login,
91
- methods=["GET"])
92
- if logout_endpoint:
93
- flask_app.add_url_rule(rule=logout_endpoint,
94
- endpoint="jusbr-logout",
95
- view_func=service_logout,
96
- methods=["GET"])
97
- if token_endpoint:
98
- flask_app.add_url_rule(rule=token_endpoint,
99
- endpoint="jusbr-token",
100
- view_func=service_token,
101
- methods=["GET"])
102
-
103
-
104
- def jusbr_get_token(user_id: str,
105
- errors: list[str] = None,
106
- logger: Logger = None) -> str:
107
- """
108
- Retrieve a JusBR authentication token for *user_id*.
109
-
110
- :param user_id: the user's identification
111
- :param errors: incidental errors
112
- :param logger: optional logger
113
- :return: the uthentication tokem
114
- """
115
- # declare the return variable
116
- result: str
117
-
118
- # retrieve the token
119
- args: dict[str, Any] = {"user-id": user_id}
120
- with _iam_lock:
121
- result = action_token(iam_server=IamServer.IAM_JUSRBR,
122
- args=args,
123
- errors=errors,
124
- logger=logger)
125
- return result
@@ -1,136 +0,0 @@
1
- from cachetools import Cache, FIFOCache
2
- from flask import Flask
3
- from logging import Logger
4
- from pypomes_core import (
5
- APP_PREFIX, env_get_int, env_get_str
6
- )
7
- from typing import Any, Final
8
-
9
- from .iam_common import _IAM_SERVERS, IamServer, _iam_lock
10
- from .iam_actions import action_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/ijud:callback")
18
- KEYCLOAK_ENDPOINT_EXCHANGE: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_EXCHANGE",
19
- def_value="/iam/ijud:exchange-token")
20
- KEYCLOAK_ENDPOINT_LOGIN: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_LOGIN",
21
- def_value="/iam/ijud:login")
22
- KEYCLOAK_ENDPOINT_LOGOUT: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_LOGOUT",
23
- def_value="/iam/ijud:logout")
24
- KEYCLOAK_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_TOKEN",
25
- def_value="/iam/ijud:get-token")
26
-
27
- KEYCLOAK_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_KEYCLOAK_PUBLIC_KEY_LIFETIME",
28
- def_value=86400) # 24 hours
29
- KEYCLOAK_REALM: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_REALM")
30
- KEYCLOAK_RECIPIENT_ATTR: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_RECIPIENT_ATTR",
31
- def_value="preferred_username")
32
- KEYCLOAK_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_URL_AUTH_BASE")
33
-
34
-
35
- def keycloak_setup(flask_app: Flask,
36
- base_url: str = KEYCLOAK_URL_AUTH_BASE,
37
- realm: str = KEYCLOAK_REALM,
38
- client_id: str = KEYCLOAK_CLIENT_ID,
39
- client_secret: str = KEYCLOAK_CLIENT_SECRET,
40
- client_timeout: int = KEYCLOAK_CLIENT_TIMEOUT,
41
- public_key_lifetime: int | None = KEYCLOAK_PUBLIC_KEY_LIFETIME,
42
- recipient_attribute: str | None = KEYCLOAK_RECIPIENT_ATTR,
43
- callback_endpoint: str | None = KEYCLOAK_ENDPOINT_CALLBACK,
44
- login_endpoint: str | None = KEYCLOAK_ENDPOINT_LOGIN,
45
- logout_endpoint: str | None = KEYCLOAK_ENDPOINT_LOGOUT,
46
- token_endpoint: str | None = KEYCLOAK_ENDPOINT_TOKEN,
47
- exchange_endpoint: str | None = KEYCLOAK_ENDPOINT_EXCHANGE) -> None:
48
- """
49
- Configure the Keycloak IAM.
50
-
51
- This should be invoked only once, before the first access to a Keycloak service.
52
-
53
- :param flask_app: the Flask application
54
- :param base_url: base URL to request Keycloak services
55
- :param realm: the Keycloak realm
56
- :param client_id: the client's identification with JusBR
57
- :param client_secret: the client's password with JusBR
58
- :param client_timeout: timeout for login authentication (in seconds,defaults to no timeout)
59
- :param public_key_lifetime: how long to use Keycloak's public key, before refreshing it (in seconds)
60
- :param recipient_attribute: attribute in the token's payload holding the token's subject
61
- :param callback_endpoint: endpoint for the callback from the front end
62
- :param login_endpoint: endpoint for redirecting user to Keycloak's login page
63
- :param logout_endpoint: endpoint for terminating user access to Keycloak
64
- :param token_endpoint: endpoint for retrieving Keycloak's authentication token
65
- :param exchange_endpoint: endpoint for requesting token exchange
66
- """
67
- from .iam_services import (
68
- service_login, service_logout, service_callback, service_exchange, service_token
69
- )
70
-
71
- # configure the Keycloak registry
72
- cache: Cache = FIFOCache(maxsize=1048576)
73
- cache["users"] = {}
74
- with _iam_lock:
75
- _IAM_SERVERS[IamServer.IAM_KEYCLOAK] = {
76
- "base-url": f"{base_url}/realms/{realm}",
77
- "client-id": client_id,
78
- "client-secret": client_secret,
79
- "client-timeout": client_timeout,
80
- "public-key": None,
81
- "pk-lifetime": public_key_lifetime,
82
- "pk-expiration": 0,
83
- "recipient-attr": recipient_attribute,
84
- "cache": cache
85
- }
86
-
87
- # establish the endpoints
88
- if callback_endpoint:
89
- flask_app.add_url_rule(rule=callback_endpoint,
90
- endpoint="keycloak-callback",
91
- view_func=service_callback,
92
- methods=["GET"])
93
- if login_endpoint:
94
- flask_app.add_url_rule(rule=login_endpoint,
95
- endpoint="keycloak-login",
96
- view_func=service_login,
97
- methods=["GET"])
98
- if logout_endpoint:
99
- flask_app.add_url_rule(rule=logout_endpoint,
100
- endpoint="keycloak-logout",
101
- view_func=service_logout,
102
- methods=["GET"])
103
- if token_endpoint:
104
- flask_app.add_url_rule(rule=token_endpoint,
105
- endpoint="keycloak-token",
106
- view_func=service_token,
107
- methods=["GET"])
108
- if exchange_endpoint:
109
- flask_app.add_url_rule(rule=exchange_endpoint,
110
- endpoint="keycloak-exchange",
111
- view_func=service_exchange,
112
- methods=["POST"])
113
-
114
-
115
- def keycloak_get_token(user_id: str,
116
- errors: list[str] = None,
117
- logger: Logger = None) -> str:
118
- """
119
- Retrieve a Keycloak authentication token for *user_id*.
120
-
121
- :param user_id: the user's identification
122
- :param errors: incidental errors
123
- :param logger: optional logger
124
- :return: the uthentication tokem
125
- """
126
- # declare the return variable
127
- result: str
128
-
129
- # retrieve the token
130
- args: dict[str, Any] = {"user-id": user_id}
131
- with _iam_lock:
132
- result = action_token(iam_server=IamServer.IAM_KEYCLOAK,
133
- args=args,
134
- errors=errors,
135
- logger=logger)
136
- return result
@@ -1,13 +0,0 @@
1
- pypomes_iam/__init__.py,sha256=ip9p9-0qCaRPuMVae2JTLZHq6-OPgNKBIL6t6PaSHWg,1180
2
- pypomes_iam/iam_actions.py,sha256=0x5kPaDor2rHiOznyF9DLzsNRGLleB66K6RJBPaJkBc,24178
3
- pypomes_iam/iam_common.py,sha256=Xu3Jz-wXzYtEk1hi06lFJ997e9n77I_eeRbpRQ2qCy4,10365
4
- pypomes_iam/iam_pomes.py,sha256=yA0ZRaD-fp7aZZ-yDnFlh6CvCsEWd-Tf123twQoTPGg,3456
5
- pypomes_iam/iam_services.py,sha256=ZwSwCiA3XssjG_HgTdkkKtdnQg9UjuqlvFhWVPQfSH8,11871
6
- pypomes_iam/jusbr_pomes.py,sha256=X_YgY45122tflAzQdAMEcEyVbPvzFigjHLal0qL1v_M,5916
7
- pypomes_iam/keycloak_pomes.py,sha256=FGdkPjVGEDp5Pwfav4EIc9uSbT4_pG7oPqaiHeJBSLU,6763
8
- pypomes_iam/provider_pomes.py,sha256=CdEjYjepGXsehn_ujljUQKs0Ws7xNOzBYG6wKp9C7-E,7233
9
- pypomes_iam/token_pomes.py,sha256=Bz9pT2oU6jTEr_ZEZEJ3kUjH3TfxRyY1_vR319v6CEo,6692
10
- pypomes_iam-0.5.6.dist-info/METADATA,sha256=ZowLxR_xl3hWHutcz_hGAhvW0B1vsIyIIrrCnwF5uXg,694
11
- pypomes_iam-0.5.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
12
- pypomes_iam-0.5.6.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
13
- pypomes_iam-0.5.6.dist-info/RECORD,,