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.
- pypomes_iam/__init__.py +6 -16
- pypomes_iam/iam_actions.py +357 -47
- pypomes_iam/iam_common.py +213 -52
- pypomes_iam/iam_pomes.py +111 -63
- pypomes_iam/iam_services.py +90 -5
- pypomes_iam/provider_pomes.py +46 -26
- pypomes_iam/token_pomes.py +0 -2
- {pypomes_iam-0.5.6.dist-info → pypomes_iam-0.5.8.dist-info}/METADATA +1 -1
- pypomes_iam-0.5.8.dist-info/RECORD +11 -0
- pypomes_iam/jusbr_pomes.py +0 -125
- pypomes_iam/keycloak_pomes.py +0 -136
- pypomes_iam-0.5.6.dist-info/RECORD +0 -13
- {pypomes_iam-0.5.6.dist-info → pypomes_iam-0.5.8.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.5.6.dist-info → pypomes_iam-0.5.8.dist-info}/licenses/LICENSE +0 -0
pypomes_iam/iam_services.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
pypomes_iam/provider_pomes.py
CHANGED
|
@@ -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
|
-
# "
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
95
|
-
user: str = provider.get(
|
|
96
|
-
pwd: str = provider.get(
|
|
97
|
-
headers_data: dict[str, str] = provider.get(
|
|
98
|
-
body_data: dict[str, str] = provider.get(
|
|
99
|
-
custom_auth: tuple[str, str] = provider.get(
|
|
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(
|
|
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[
|
|
134
|
-
provider[
|
|
135
|
-
if reply.get(
|
|
136
|
-
provider[
|
|
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[
|
|
158
|
+
provider[ProviderParam.REFRESH_EXPIRATION] = now + int(reply.get("refresh_expires_in"))
|
|
139
159
|
else:
|
|
140
|
-
provider[
|
|
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(
|
|
177
|
+
result = provider.get(ProviderParam.ACCESS_TOKEN)
|
|
158
178
|
|
|
159
179
|
return result
|
|
160
180
|
|
pypomes_iam/token_pomes.py
CHANGED
|
@@ -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.
|
|
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,,
|
pypomes_iam/jusbr_pomes.py
DELETED
|
@@ -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
|
pypomes_iam/keycloak_pomes.py
DELETED
|
@@ -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,,
|
|
File without changes
|
|
File without changes
|