pypomes-iam 0.2.4__tar.gz → 0.2.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.2.4
3
+ Version: 0.2.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.2.4"
9
+ version = "0.2.6"
10
10
  authors = [
11
11
  { name="GT Nunes", email="wisecoder01@gmail.com" }
12
12
  ]
@@ -5,41 +5,55 @@ import string
5
5
  import sys
6
6
  from cachetools import Cache
7
7
  from datetime import datetime
8
+ from enum import StrEnum
8
9
  from flask import Request
9
10
  from logging import Logger
10
11
  from pypomes_core import TZ_LOCAL, exc_format
11
12
  from pypomes_crypto import crypto_jwk_convert
12
- from typing import Any
13
+ from typing import Any, Final
14
+
15
+
16
+ class IamServer(StrEnum):
17
+ IAM_JUSRBR = "iam-jusbr",
18
+ IAM_KEYCLOAK = "iam-keycloak"
19
+
13
20
 
14
21
  # registry structure:
15
- # {
16
- # "client-id": <str>,
17
- # "client-secret": <str>,
18
- # "client-timeout": <int>,
19
- # "public_key": <str>,
20
- # "key-lifetime": <int>,
21
- # "key-expiration": <int>,
22
- # "base-url": <str>,
23
- # "callback-url": <str>,
24
- # "safe-cache": <FIFOCache>
22
+ # { <IamServer>:
23
+ # {
24
+ # "client-id": <str>,
25
+ # "client-secret": <str>,
26
+ # "client-timeout": <int>,
27
+ # "public_key": <str>,
28
+ # "pk-lifetime": <int>,
29
+ # "pk-expiration": <int>,
30
+ # "base-url": <str>,
31
+ # "logger": <Logger>,
32
+ # "cache": <FIFOCache>,
33
+ # "redirect-uri": <str> <-- transient
34
+ # },
35
+ # ...
25
36
  # }
26
- # data in "safe-cache":
37
+ # data in "cache":
27
38
  # {
28
39
  # "users": {
29
40
  # "<user-id>": {
30
- # "access-token": <str>
31
- # "refresh-token": <str>
32
- # "access-expiration": <timestamp>,
33
- # "login-expiration": <timestamp>, <-- transient
34
- # "login-id": <str>, <-- transient
41
+ # "access-token": <str>
42
+ # "refresh-token": <str>
43
+ # "access-expiration": <timestamp>,
44
+ # "refresh-expiration": <timestamp>,
45
+ # "login-expiration": <timestamp>, <-- transient
46
+ # "login-id": <str>, <-- transient
35
47
  # }
36
- # }
48
+ # },
49
+ # ...
37
50
  # }
51
+ IAM_SERVERS: Final[dict[IamServer, dict[str, Any]]] = {}
38
52
 
39
53
 
40
54
  def _service_login(registry: dict[str, Any],
41
55
  args: dict[str, Any],
42
- logger: Logger | None) -> str:
56
+ logger: Logger | None) -> dict[str, str]:
43
57
  """
44
58
  Build the callback URL for redirecting the request to the IAM's authentication page.
45
59
 
@@ -48,7 +62,6 @@ def _service_login(registry: dict[str, Any],
48
62
  :param logger: optional logger
49
63
  :return: the callback URL, with the appropriate parameters
50
64
  """
51
-
52
65
  # retrieve user data
53
66
  oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
54
67
 
@@ -61,19 +74,17 @@ def _service_login(registry: dict[str, Any],
61
74
  user_data["login-id"] = user_id
62
75
  timeout: int = _get_login_timeout(registry=registry)
63
76
  user_data["login-expiration"] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout if timeout else None
77
+ redirect_uri: str = args.get("redirect-uri")
78
+ registry["redirect-uri"] = redirect_uri
64
79
 
65
- # build the redirect url
66
- result: str = (f"{registry["base-url"]}/protocol/openid-connect/auth"
67
- f"?response_type=code&scope=openid"
68
- f"&client_id={registry["client-id"]}"
69
- f"&redirect_uri={registry["callback-url"]}"
70
- f"&state={oauth_state}")
71
-
72
- # logout the user
73
- _service_logout(registry=registry,
74
- args=args,
75
- logger=logger)
76
- return result
80
+ # build the login url
81
+ return {
82
+ "login-url": (f"{registry["base-url"]}/protocol/openid-connect/auth"
83
+ f"?response_type=code&scope=openid"
84
+ f"&client_id={registry["client-id"]}"
85
+ f"&redirect_uri={redirect_uri}"
86
+ f"&state={oauth_state}")
87
+ }
77
88
 
78
89
 
79
90
  def _service_logout(registry: dict[str, Any],
@@ -89,7 +100,7 @@ def _service_logout(registry: dict[str, Any],
89
100
  # remove the user data
90
101
  user_id: str = args.get("user-id") or args.get("login")
91
102
  if user_id:
92
- cache: Cache = registry["safe-cache"]
103
+ cache: Cache = registry["cache"]
93
104
  users: dict[str, dict[str, Any]] = cache.get("users")
94
105
  if user_id in users:
95
106
  users.pop(user_id)
@@ -102,7 +113,7 @@ def _service_callback(registry: dict[str, Any],
102
113
  errors: list[str],
103
114
  logger: Logger | None) -> tuple[str, str]:
104
115
  """
105
- Entry point for the callback from JusBR on authentication operation.
116
+ Entry point for the callback from JusBR via the front-end application on authentication operation.
106
117
 
107
118
  :param registry: the registry holding the authentication data
108
119
  :param args: the arguments passed when requesting the service
@@ -115,7 +126,7 @@ def _service_callback(registry: dict[str, Any],
115
126
  result: tuple[str, str] | None = None
116
127
 
117
128
  # retrieve the users authentication data
118
- cache: Cache = registry["safe-cache"]
129
+ cache: Cache = registry["cache"]
119
130
  users: dict[str, dict[str, Any]] = cache.get("users")
120
131
 
121
132
  # validate the OAuth2 state
@@ -138,7 +149,7 @@ def _service_callback(registry: dict[str, Any],
138
149
  body_data: dict[str, Any] = {
139
150
  "grant_type": "authorization_code",
140
151
  "code": code,
141
- "redirect_uri": registry.get("callback-url"),
152
+ "redirect_uri": registry.get("redirect-uri"),
142
153
  }
143
154
  token = _post_for_token(registry=registry,
144
155
  user_data=user_data,
@@ -147,8 +158,8 @@ def _service_callback(registry: dict[str, Any],
147
158
  logger=logger)
148
159
  # retrieve the token's claims
149
160
  if not errors:
150
- public_key: bytes = _get_public_key(registry=registry,
151
- logger=logger)
161
+ public_key: str = _get_public_key(registry=registry,
162
+ logger=logger)
152
163
  token_claims: dict[str, dict[str, Any]] = token_validate(token=token,
153
164
  issuer=registry["base-url"],
154
165
  public_key=public_key,
@@ -187,6 +198,7 @@ def _service_token(registry: dict[str, Any],
187
198
  user_data: dict[str, Any] = _get_user_data(registry=registry,
188
199
  user_id=user_id,
189
200
  logger=logger)
201
+ err_msg: str | None = None
190
202
  token: str = user_data["access-token"]
191
203
  if token:
192
204
  access_expiration: int = user_data.get("access-expiration")
@@ -197,41 +209,51 @@ def _service_token(registry: dict[str, Any],
197
209
  # access token has expired
198
210
  refresh_token: str = user_data["refresh-token"]
199
211
  if refresh_token:
200
- body_data: dict[str, str] = {
201
- "grant_type": "refresh_token",
202
- "refresh_token": refresh_token
203
- }
204
- result = _post_for_token(registry=registry,
205
- user_data=user_data,
206
- body_data=body_data,
207
- errors=errors,
208
- logger=logger)
209
-
210
- elif logger or isinstance(errors, list):
212
+ refresh_expiration = user_data["refresh-expiration"]
213
+ if now < refresh_expiration:
214
+ body_data: dict[str, str] = {
215
+ "grant_type": "refresh_token",
216
+ "refresh_token": refresh_token
217
+ }
218
+ result = _post_for_token(registry=registry,
219
+ user_data=user_data,
220
+ body_data=body_data,
221
+ errors=errors,
222
+ logger=logger)
223
+ else:
224
+ # refresh token has expired
225
+ err_msg = "Access and refresh tokens expired"
226
+ else:
227
+ err_msg = "Access token expired, no refresh token available"
228
+ else:
229
+ err_msg = f"User '{user_id}' not authenticated"
230
+
231
+ if err_msg and (logger or isinstance(errors, list)):
211
232
  err_msg: str = f"User '{user_id}' not authenticated"
212
233
  if isinstance(errors, list):
213
234
  errors.append(err_msg)
214
235
  if logger:
215
236
  logger.error(msg=err_msg)
237
+ logger.error(msg=err_msg)
216
238
 
217
239
  return result
218
240
 
219
241
 
220
242
  def _get_public_key(registry: dict[str, Any],
221
- logger: Logger | None) -> bytes:
243
+ logger: Logger | None) -> str:
222
244
  """
223
245
  Obtain the public key used by the *IAM* to sign the authentication tokens.
224
246
 
225
247
  The public key is saved in *registry*.
226
248
 
227
249
  :param registry: the registry holding the authentication data
228
- :return: the public key, in *DER* format
250
+ :return: the public key, in *PEM* format
229
251
  """
230
252
  # initialize the return variable
231
- result: bytes | None = None
253
+ result: str | None = None
232
254
 
233
255
  now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
234
- if now > registry["key-expiration"]:
256
+ if now > registry["pk-expiration"]:
235
257
  # obtain a new public key
236
258
  url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
237
259
  if logger:
@@ -243,10 +265,10 @@ def _get_public_key(registry: dict[str, Any],
243
265
  logger.debug(msg=f"GET success, status {response.status_code}")
244
266
  reply: dict[str, Any] = response.json()
245
267
  result = crypto_jwk_convert(jwk=reply["keys"][0],
246
- fmt="DER")
268
+ fmt="PEM")
247
269
  registry["public-key"] = result
248
- duration: int = registry["key-lifetime"] or 0
249
- registry["key-expiration"] = now + duration
270
+ lifetime: int = registry["pk-lifetime"] or 0
271
+ registry["pk-expiration"] = now + lifetime
250
272
  elif logger:
251
273
  msg: str = f"GET failure, status {response.status_code}, reason '{response.reason}'"
252
274
  if hasattr(response, "content") and response.content:
@@ -281,14 +303,15 @@ def _get_user_data(registry: dict[str, Any],
281
303
  :param registry: the registry holding the authentication data
282
304
  :return: the data for *user_id* in the registry
283
305
  """
284
- cache: Cache = registry["safe-cache"]
306
+ cache: Cache = registry["cache"]
285
307
  users: dict[str, dict[str, Any]] = cache.get("users")
286
308
  result: dict[str, Any] = users.get(user_id)
287
309
  if not result:
288
310
  result = {
289
311
  "access-token": None,
290
312
  "refresh-token": None,
291
- "access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp())
313
+ "access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
314
+ "refresh-expiration": sys.maxsize
292
315
  }
293
316
  users[user_id] = result
294
317
  if logger:
@@ -310,7 +333,7 @@ def _post_for_token(registry: dict[str, Any],
310
333
  For token exchange, *body_data* will have the attributes
311
334
  - "grant_type": "authorization_code"
312
335
  - "code": <16-character-random-code>
313
- - "redirect_uri": <callback-url>
336
+ - "redirect_uri": <redirect-uri>
314
337
  For token refresh, *body_data* will have the attributes
315
338
  - "grant_type": "refresh_token"
316
339
  - "refresh_token": <current-refresh-token>
@@ -347,7 +370,8 @@ def _post_for_token(registry: dict[str, Any],
347
370
  # "token_type": "Bearer",
348
371
  # "access_token": <str>,
349
372
  # "expires_in": <number-of-seconds>,
350
- # "refresh_token": <str>
373
+ # "refresh_token": <str>,
374
+ # "refesh_expires_in": <number-of-seconds>
351
375
  # }
352
376
  response: requests.Response = requests.post(url=url,
353
377
  data=body_data)
@@ -361,6 +385,8 @@ def _post_for_token(registry: dict[str, Any],
361
385
  # on token refresh, keep current refresh token if a new one is not provided
362
386
  user_data["refresh-token"] = reply.get("refresh_token") or body_data.get("refresh_token")
363
387
  user_data["access-expiration"] = now + reply.get("expires_in")
388
+ refresh_expiration: int = user_data.get("refresh_expires_in")
389
+ user_data["refresh-expiration"] = (now + refresh_expiration) if refresh_expiration else sys.maxsize
364
390
  else:
365
391
  # request resulted in error
366
392
  err_msg = f"POST failure, status {response.status_code}, reason '{response.reason}'"
@@ -1,13 +1,12 @@
1
- from flask import Response, redirect, request, jsonify
1
+ from flask import Response, request, jsonify
2
2
  from logging import Logger
3
3
  from typing import Any
4
4
 
5
- from .common_pomes import (
5
+ from .iam_common import (
6
+ IAM_SERVERS, IamServer,
6
7
  _service_login, _service_logout,
7
8
  _service_callback, _service_token, _log_init
8
9
  )
9
- from .jusbr_pomes import _jusbr_get_logger, _jusbr_get_registry
10
- from .keycloak_pomes import _keycloak_get_logger, _keycloak_get_registry
11
10
 
12
11
 
13
12
  # @flask_app.route(rule=<login_endpoint>, # JUSBR_LOGIN_ENDPOINT: /iam/jusbr:login
@@ -22,25 +21,19 @@ def service_login() -> Response:
22
21
 
23
22
  :return: the response from the redirect operation
24
23
  """
25
- logger: Logger
26
- registry: dict[str, Any]
27
- if request.endpoint == "jusbr-login":
28
- logger = _jusbr_get_logger()
29
- registry = _jusbr_get_registry()
30
- else:
31
- logger = _keycloak_get_logger()
32
- registry = _keycloak_get_registry()
24
+ # retrieve logger and registry
25
+ registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
26
+ logger: Logger = registry["logger"]
33
27
 
34
28
  # log the request
35
29
  if logger:
36
30
  logger.debug(msg=_log_init(request=request))
37
31
 
38
- # obtain the redirect URL
39
- auth_url: str = _service_login(registry=registry,
40
- args=request.args,
41
- logger=logger)
42
- # redirect the request
43
- result: Response = redirect(location=auth_url)
32
+ # obtain the login URL
33
+ login_data: dict[str, str] = _service_login(registry=registry,
34
+ args=request.args,
35
+ logger=logger)
36
+ result = jsonify(login_data)
44
37
 
45
38
  # log the response
46
39
  if logger:
@@ -61,14 +54,9 @@ def service_logout() -> Response:
61
54
 
62
55
  :return: response *OK*
63
56
  """
64
- logger: Logger
65
- registry: dict[str, Any]
66
- if request.endpoint == "jusbr-logout":
67
- logger = _jusbr_get_logger()
68
- registry = _jusbr_get_registry()
69
- else:
70
- logger = _keycloak_get_logger()
71
- registry = _keycloak_get_registry()
57
+ # retrieve logger and registry
58
+ registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
59
+ logger: Logger = registry["logger"]
72
60
 
73
61
  # log the request
74
62
  if logger:
@@ -96,16 +84,14 @@ def service_callback() -> Response:
96
84
  """
97
85
  Entry point for the callback from JusBR on authentication operation.
98
86
 
87
+ This callback is typically invoked from a front-end application after a successful login at the
88
+ JusBR login page, forwarding the data received.
89
+
99
90
  :return: the response containing the token, or *BAD REQUEST*
100
91
  """
101
- logger: Logger
102
- registry: dict[str, Any]
103
- if request.endpoint == "jusbr-callback":
104
- logger = _jusbr_get_logger()
105
- registry = _jusbr_get_registry()
106
- else:
107
- logger = _keycloak_get_logger()
108
- registry = _keycloak_get_registry()
92
+ # retrieve logger and registry
93
+ registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
94
+ logger: Logger = registry["logger"]
109
95
 
110
96
  # log the request
111
97
  if logger:
@@ -143,14 +129,9 @@ def service_token() -> Response:
143
129
 
144
130
  :return: the response containing the token, or *UNAUTHORIZED*
145
131
  """
146
- logger: Logger
147
- registry: dict[str, Any]
148
- if request.endpoint == "jusbr-token":
149
- logger = _jusbr_get_logger()
150
- registry = _jusbr_get_registry()
151
- else:
152
- logger = _keycloak_get_logger()
153
- registry = _keycloak_get_registry()
132
+ # retrieve logger and registry
133
+ registry: dict[str, Any] = __get_iam_registry(endpoint=request.endpoint)
134
+ logger: Logger = registry["logger"]
154
135
 
155
136
  # log the request
156
137
  if logger:
@@ -174,3 +155,22 @@ def service_token() -> Response:
174
155
  logger.debug(msg=f"Response {result}")
175
156
 
176
157
  return result
158
+
159
+
160
+ def __get_iam_registry(endpoint: str) -> dict[str, Any]:
161
+ """
162
+ Retrieve the registry associated the the IAM identifies by *endpoint*.
163
+
164
+ :param endpoint: the service enpoint identifying the IAM.
165
+ :return: the tuple (*logger*, *registry*) associated with *endpoint*
166
+ """
167
+ # initialize the return variable
168
+ result: dict[str, Any] | None = None
169
+
170
+ if endpoint.startswith("jusbr-"):
171
+ result = IAM_SERVERS[IamServer.IAM_JUSRBR]
172
+ elif endpoint.startswith("keycloak-"):
173
+ result = IAM_SERVERS[IamServer.IAM_KEYCLOAK]
174
+
175
+ return result
176
+
@@ -7,7 +7,7 @@ from pypomes_core import (
7
7
  )
8
8
  from typing import Any, Final
9
9
 
10
- from .common_pomes import _service_token
10
+ from .iam_common import IAM_SERVERS, IamServer, _service_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")
@@ -25,36 +25,6 @@ JUSBR_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT
25
25
  JUSBR_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_JUSBR_PUBLIC_KEY_LIFETIME",
26
26
  def_value=86400) # 24 hours
27
27
  JUSBR_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_BASE")
28
- JUSBR_URL_AUTH_CALLBACK: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_CALLBACK")
29
-
30
- # registry structure:
31
- # {
32
- # "client-id": <str>,
33
- # "client-secret": <str>,
34
- # "client-timeout": <int>,
35
- # "public_key": <str>,
36
- # "key-lifetime": <int>,
37
- # "key-expiration": <int>,
38
- # "base-url": <str>,
39
- # "callback-url": <str>,
40
- # "safe-cache": <FIFOCache>
41
- # }
42
- # data in "safe-cache":
43
- # {
44
- # "users": {
45
- # "<user-id>": {
46
- # "access-token": <str>
47
- # "refresh-token": <str>
48
- # "access-expiration": <timestamp>,
49
- # "login-expiration": <timestamp>, <-- transient
50
- # "login-id": <str>, <-- transient
51
- # }
52
- # }
53
- # }
54
- _jusbr_registry: dict[str, Any] | None = None
55
-
56
- # dafault logger
57
- _jusbr_logger: Logger | None = None
58
28
 
59
29
 
60
30
  def jusbr_setup(flask_app: Flask,
@@ -67,7 +37,6 @@ def jusbr_setup(flask_app: Flask,
67
37
  login_endpoint: str = JUSBR_ENDPOINT_LOGIN,
68
38
  logout_endpoint: str = JUSBR_ENDPOINT_LOGOUT,
69
39
  base_url: str = JUSBR_URL_AUTH_BASE,
70
- callback_url: str = JUSBR_URL_AUTH_CALLBACK,
71
40
  logger: Logger = None) -> None:
72
41
  """
73
42
  Configure the JusBR IAM.
@@ -84,27 +53,23 @@ def jusbr_setup(flask_app: Flask,
84
53
  :param login_endpoint: endpoint for redirecting user to JusBR login page
85
54
  :param logout_endpoint: endpoint for terminating user access to JusBR
86
55
  :param base_url: base URL to request the JusBR services
87
- :param callback_url: URL for JusBR to callback on login
88
56
  :param logger: optional logger
89
57
  """
90
58
  from .iam_pomes import service_login, service_logout, service_callback, service_token
91
- global _jusbr_logger, _jusbr_registry
92
-
93
- # establish the logger
94
- _jusbr_logger = logger
95
59
 
96
60
  # configure the JusBR registry
97
- safe_cache: Cache = FIFOCache(maxsize=1048576)
98
- safe_cache["users"] = {}
99
- _jusbr_registry = {
61
+ cache: Cache = FIFOCache(maxsize=1048576)
62
+ cache["users"] = {}
63
+ IAM_SERVERS[IamServer.IAM_JUSRBR] = {
100
64
  "client-id": client_id,
101
65
  "client-secret": client_secret,
102
66
  "client-timeout": client_timeout,
103
67
  "base-url": base_url,
104
- "callback-url": callback_url,
105
- "key-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
106
- "key-lifetime": public_key_lifetime,
107
- "safe-cache": safe_cache
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
108
73
  }
109
74
 
110
75
  # establish the endpoints
@@ -141,27 +106,9 @@ def jusbr_get_token(user_id: str,
141
106
  :param logger: optional logger
142
107
  :return: the uthentication tokem
143
108
  """
144
- global _jusbr_registry
145
-
146
109
  # retrieve the token
147
110
  args: dict[str, Any] = {"user-id": user_id}
148
- return _service_token(registry=_jusbr_registry,
111
+ return _service_token(registry=IAM_SERVERS[IamServer.IAM_JUSRBR],
149
112
  args=args,
150
113
  errors=errors,
151
114
  logger=logger)
152
-
153
-
154
- def _jusbr_get_logger() -> Logger:
155
- """
156
- Retrieve the logger for JusBR operations.
157
- :return: the Keycloak logger
158
- """
159
- return _jusbr_logger
160
-
161
-
162
- def _jusbr_get_registry() -> dict[str, Any]:
163
- """
164
- Retrieve the registry holding user authentication data related to JusBR operations.
165
- :return: the Keycloak registry
166
- """
167
- return _jusbr_registry
@@ -7,7 +7,7 @@ from pypomes_core import (
7
7
  )
8
8
  from typing import Any, Final
9
9
 
10
- from .common_pomes import _service_token
10
+ from .iam_common import IAM_SERVERS, IamServer, _service_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")
@@ -26,36 +26,6 @@ KEYCLOAK_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_KEYCLO
26
26
  def_value=86400) # 24 hours
27
27
  KEYCLOAK_REALM: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_REALM")
28
28
  KEYCLOAK_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_URL_AUTH_BASE")
29
- KEYCLOAK_URL_AUTH_CALLBACK: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_URL_AUTH_CALLBACK")
30
-
31
- # registry structure:
32
- # {
33
- # "client-id": <str>,
34
- # "client-secret": <str>,
35
- # "client-timeout": <int>,
36
- # "public_key": <str>,
37
- # "key-lifetime": <int>,
38
- # "key-expiration": <int>,
39
- # "base-url": <str>,
40
- # "callback-url": <str>,
41
- # "safe-cache": <FIFOCache>
42
- # }
43
- # data in "safe-cache":
44
- # {
45
- # "users": {
46
- # "<user-id>": {
47
- # "access-token": <str>
48
- # "refresh-token": <str>
49
- # "access-expiration": <timestamp>,
50
- # "login-expiration": <timestamp>, <-- transient
51
- # "login-id": <str>, <-- transient
52
- # }
53
- # }
54
- # }
55
- _keycloak_registry: dict[str, Any] = {}
56
-
57
- # dafault logger
58
- _keycloak_logger: Logger | None = None
59
29
 
60
30
 
61
31
  def keycloak_setup(flask_app: Flask,
@@ -69,7 +39,6 @@ def keycloak_setup(flask_app: Flask,
69
39
  login_endpoint: str = KEYCLOAK_ENDPOINT_LOGIN,
70
40
  logout_endpoint: str = KEYCLOAK_ENDPOINT_LOGOUT,
71
41
  base_url: str = KEYCLOAK_URL_AUTH_BASE,
72
- callback_url: str = KEYCLOAK_URL_AUTH_CALLBACK,
73
42
  logger: Logger = None) -> None:
74
43
  """
75
44
  Configure the Keycloak IAM.
@@ -87,35 +56,26 @@ def keycloak_setup(flask_app: Flask,
87
56
  :param login_endpoint: endpoint for redirecting user to JusBR login page
88
57
  :param logout_endpoint: endpoint for terminating user access to JusBR
89
58
  :param base_url: base URL to request the JusBR services
90
- :param callback_url: URL for Keycloak to callback on login
91
59
  :param logger: optional logger
92
60
  """
93
61
  from .iam_pomes import service_login, service_logout, service_callback, service_token
94
- global _keycloak_logger, _keycloak_registry
95
-
96
- # establish the logger
97
- _keycloak_logger = logger
98
62
 
99
- # configure the JusBR registry
100
- safe_cache: Cache = FIFOCache(maxsize=1048576)
101
- safe_cache["users"] = {}
102
- _keycloak_registry = {
63
+ # configure the Keycloak registry
64
+ cache: Cache = FIFOCache(maxsize=1048576)
65
+ cache["users"] = {}
66
+ IAM_SERVERS[IamServer.IAM_KEYCLOAK] = {
103
67
  "client-id": client_id,
104
68
  "client-secret": client_secret,
105
69
  "client-timeout": client_timeout,
106
70
  "base-url": f"{base_url}/realms/{realm}",
107
- "callback-url": callback_url,
108
- "key-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp()),
109
- "key-lifetime": public_key_lifetime,
110
- "safe-cache": safe_cache
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
111
76
  }
112
77
 
113
78
  # establish the endpoints
114
- if token_endpoint:
115
- flask_app.add_url_rule(rule=token_endpoint,
116
- endpoint="keycloak-token",
117
- view_func=service_token,
118
- methods=["GET"])
119
79
  if login_endpoint:
120
80
  flask_app.add_url_rule(rule=login_endpoint,
121
81
  endpoint="keycloak-login",
@@ -131,6 +91,11 @@ def keycloak_setup(flask_app: Flask,
131
91
  endpoint="keycloak-callback",
132
92
  view_func=service_callback,
133
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"])
134
99
 
135
100
 
136
101
  def keycloak_get_token(user_id: str,
@@ -144,27 +109,9 @@ def keycloak_get_token(user_id: str,
144
109
  :param logger: optional logger
145
110
  :return: the uthentication tokem
146
111
  """
147
- global _keycloak_registry
148
-
149
112
  # retrieve the token
150
113
  args: dict[str, Any] = {"user-id": user_id}
151
- return _service_token(registry=_keycloak_registry,
114
+ return _service_token(registry=IAM_SERVERS[IamServer.IAM_KEYCLOAK],
152
115
  args=args,
153
116
  errors=errors,
154
117
  logger=logger)
155
-
156
-
157
- def _keycloak_get_logger() -> Logger:
158
- """
159
- Retrieve the logger for Keycloak operations.
160
- :return: the Keycloak logger
161
- """
162
- return _keycloak_logger
163
-
164
-
165
- def _keycloak_get_registry() -> dict[str, Any]:
166
- """
167
- Retrieve the registry holding user authentication data related to Keycloak operations.
168
- :return: the Keycloak registry
169
- """
170
- return _keycloak_registry
@@ -58,8 +58,11 @@ def token_validate(token: str,
58
58
  # validate the token
59
59
  if not errors:
60
60
  token_alg: str = token_header.get("alg")
61
+ require: list[str] = ["exp", "iat"]
62
+ if issuer:
63
+ require.append("iss")
61
64
  options: dict[str, Any] = {
62
- "require": ["exp", "iat"],
65
+ "require": require,
63
66
  "verify_aud": False,
64
67
  "verify_exp": True,
65
68
  "verify_iat": True,
File without changes
File without changes
File without changes