pypomes-iam 0.3.0__py3-none-any.whl → 0.3.2__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.
- pypomes_iam/__init__.py +5 -0
- pypomes_iam/iam_common.py +241 -302
- pypomes_iam/iam_pomes.py +279 -169
- pypomes_iam/iam_services.py +243 -0
- pypomes_iam/jusbr_pomes.py +20 -14
- pypomes_iam/keycloak_pomes.py +38 -21
- pypomes_iam/token_pomes.py +19 -4
- {pypomes_iam-0.3.0.dist-info → pypomes_iam-0.3.2.dist-info}/METADATA +1 -1
- pypomes_iam-0.3.2.dist-info/RECORD +12 -0
- pypomes_iam-0.3.0.dist-info/RECORD +0 -11
- {pypomes_iam-0.3.0.dist-info → pypomes_iam-0.3.2.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.3.0.dist-info → pypomes_iam-0.3.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from flask import Request, Response, request, jsonify
|
|
3
|
+
from logging import Logger
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from .iam_common import IamServer, _get_logger, _get_iam_server
|
|
7
|
+
from .iam_pomes import user_login, user_logout, user_token, token_exchange, login_callback
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# @flask_app.route(rule=<login_endpoint>, # JUSBR_LOGIN_ENDPOINT: /iam/jusbr:login
|
|
11
|
+
# methods=["GET"])
|
|
12
|
+
# @flask_app.route(rule=<login_endpoint>, # KEYCLOAK_LOGIN_ENDPOINT: /iam/keycloak:logout
|
|
13
|
+
# methods=["GET"])
|
|
14
|
+
def service_login() -> Response:
|
|
15
|
+
"""
|
|
16
|
+
Entry point for the IAM server's login service.
|
|
17
|
+
|
|
18
|
+
Return the URL for the IAM server's authentication page, with the appropriate parameters.
|
|
19
|
+
|
|
20
|
+
:return: the response from the operation
|
|
21
|
+
"""
|
|
22
|
+
# declare the return variable
|
|
23
|
+
result: Response | None = None
|
|
24
|
+
|
|
25
|
+
# retrieve the operations's logger
|
|
26
|
+
logger: Logger = _get_logger()
|
|
27
|
+
if logger:
|
|
28
|
+
# log the request
|
|
29
|
+
logger.debug(msg=_log_init(request=request))
|
|
30
|
+
|
|
31
|
+
# retrieve the IAM server
|
|
32
|
+
errors: list[str] = []
|
|
33
|
+
iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
|
|
34
|
+
errors=errors,
|
|
35
|
+
logger=logger)
|
|
36
|
+
if iam_server:
|
|
37
|
+
# obtain the login URL
|
|
38
|
+
login_data: dict[str, str] = user_login(iam_server=iam_server,
|
|
39
|
+
args=request.args,
|
|
40
|
+
errors=errors,
|
|
41
|
+
logger=logger)
|
|
42
|
+
if login_data:
|
|
43
|
+
result = jsonify(login_data)
|
|
44
|
+
|
|
45
|
+
if errors:
|
|
46
|
+
result = Response("; ".join(errors))
|
|
47
|
+
result.status_code = 400
|
|
48
|
+
|
|
49
|
+
# log the response
|
|
50
|
+
if logger:
|
|
51
|
+
logger.debug(msg=f"Response {result}")
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# @flask_app.route(rule=<logout_endpoint>, # JUSBR_LOGOUT_ENDPOINT
|
|
57
|
+
# methods=["GET"])
|
|
58
|
+
# @flask_app.route(rule=<login_endpoint>, # KEYCLOAK_LOGOUT_ENDPOINT
|
|
59
|
+
# methods=["GET"])
|
|
60
|
+
def service_logout() -> Response:
|
|
61
|
+
"""
|
|
62
|
+
Entry point for the JusBR logout service.
|
|
63
|
+
|
|
64
|
+
Remove all data associating the user from the *IAM* server's registry.
|
|
65
|
+
|
|
66
|
+
:return: response *OK*, or response *BAD REQUEST* if error
|
|
67
|
+
"""
|
|
68
|
+
# declare the return variable
|
|
69
|
+
result: Response | None
|
|
70
|
+
|
|
71
|
+
# retrieve the operations's logger
|
|
72
|
+
logger: Logger = _get_logger()
|
|
73
|
+
if logger:
|
|
74
|
+
# log the request
|
|
75
|
+
logger.debug(msg=_log_init(request=request))
|
|
76
|
+
|
|
77
|
+
# retrieve the IAM server
|
|
78
|
+
errors: list[str] = []
|
|
79
|
+
iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
|
|
80
|
+
errors=errors,
|
|
81
|
+
logger=logger)
|
|
82
|
+
if iam_server:
|
|
83
|
+
# logout the user
|
|
84
|
+
user_logout(iam_server=iam_server,
|
|
85
|
+
args=request.args,
|
|
86
|
+
errors=errors,
|
|
87
|
+
logger=logger)
|
|
88
|
+
if errors:
|
|
89
|
+
result = Response("; ".join(errors))
|
|
90
|
+
result.status_code = 400
|
|
91
|
+
else:
|
|
92
|
+
result = Response(status=200)
|
|
93
|
+
|
|
94
|
+
# log the response
|
|
95
|
+
if logger:
|
|
96
|
+
logger.debug(msg=f"Response {result}")
|
|
97
|
+
|
|
98
|
+
return result
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# @flask_app.route(rule=<callback_endpoint>, # JUSBR_CALLBACK_ENDPOINT
|
|
102
|
+
# methods=["GET", "POST"])
|
|
103
|
+
# @flask_app.route(rule=<callback_endpoint>, # KEYCLOAK_CALLBACK_ENDPOINT
|
|
104
|
+
# methods=["POST"])
|
|
105
|
+
def service_callback() -> Response:
|
|
106
|
+
"""
|
|
107
|
+
Entry point for the callback from JusBR on authentication operation.
|
|
108
|
+
|
|
109
|
+
This callback is typically invoked from a front-end application after a successful login at the
|
|
110
|
+
JusBR login page, forwarding the data received.
|
|
111
|
+
|
|
112
|
+
:return: the response containing the token, or *BAD REQUEST*
|
|
113
|
+
"""
|
|
114
|
+
# retrieve the operations's logger
|
|
115
|
+
logger: Logger = _get_logger()
|
|
116
|
+
if logger:
|
|
117
|
+
# log the request
|
|
118
|
+
logger.debug(msg=_log_init(request=request))
|
|
119
|
+
|
|
120
|
+
# retrieve the IAM server
|
|
121
|
+
errors: list[str] = []
|
|
122
|
+
token_data: tuple[str, str] | None = None
|
|
123
|
+
iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
|
|
124
|
+
errors=errors,
|
|
125
|
+
logger=logger)
|
|
126
|
+
if iam_server:
|
|
127
|
+
# process the callback operation
|
|
128
|
+
token_data = login_callback(iam_server=iam_server,
|
|
129
|
+
args=request.args,
|
|
130
|
+
errors=errors,
|
|
131
|
+
logger=logger)
|
|
132
|
+
result: Response
|
|
133
|
+
if errors:
|
|
134
|
+
result = jsonify({"errors": "; ".join(errors)})
|
|
135
|
+
result.status_code = 400
|
|
136
|
+
if logger:
|
|
137
|
+
logger.error(msg=json.dumps(obj=result))
|
|
138
|
+
else:
|
|
139
|
+
result = jsonify({
|
|
140
|
+
"user-id": token_data[0],
|
|
141
|
+
"access-token": token_data[1]})
|
|
142
|
+
|
|
143
|
+
# log the response
|
|
144
|
+
if logger:
|
|
145
|
+
logger.debug(msg=f"Response {result}")
|
|
146
|
+
|
|
147
|
+
return result
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# @flask_app.route(rule=<token_endpoint>, # JUSBR_ENDPOINT_TOKEN
|
|
151
|
+
# @flask_app.route(rule=<token_endpoint>, # KEYCLOAK_ENDPOINT_TOKEN
|
|
152
|
+
# methods=["GET"])
|
|
153
|
+
# methods=["GET"])
|
|
154
|
+
def service_token() -> Response:
|
|
155
|
+
"""
|
|
156
|
+
Entry point for retrieving token from the *IAM* server.
|
|
157
|
+
|
|
158
|
+
:return: the response containing the token, or *UNAUTHORIZED*
|
|
159
|
+
"""
|
|
160
|
+
# retrieve the operations's logger
|
|
161
|
+
logger: Logger = _get_logger()
|
|
162
|
+
if logger:
|
|
163
|
+
# log the request
|
|
164
|
+
logger.debug(msg=_log_init(request=request))
|
|
165
|
+
|
|
166
|
+
# retrieve the IAM server
|
|
167
|
+
errors: list[str] = []
|
|
168
|
+
iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
|
|
169
|
+
errors=errors,
|
|
170
|
+
logger=logger)
|
|
171
|
+
# retrieve the token
|
|
172
|
+
token: str | None = None
|
|
173
|
+
if iam_server:
|
|
174
|
+
errors: list[str] = []
|
|
175
|
+
token: str = user_token(iam_server=iam_server,
|
|
176
|
+
args=request.args,
|
|
177
|
+
errors=errors,
|
|
178
|
+
logger=logger)
|
|
179
|
+
result: Response
|
|
180
|
+
if errors:
|
|
181
|
+
result = Response("; ".join(errors))
|
|
182
|
+
result.status_code = 400
|
|
183
|
+
else:
|
|
184
|
+
result = jsonify({"token": token})
|
|
185
|
+
|
|
186
|
+
# log the response
|
|
187
|
+
if logger:
|
|
188
|
+
logger.debug(msg=f"Response {result}")
|
|
189
|
+
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# @flask_app.route(rule=<callback_endpoint>, # KEYCLOAK_ENDPOINT_EXCHANGE
|
|
194
|
+
# methods=["POST"])
|
|
195
|
+
def service_exchange() -> Response:
|
|
196
|
+
"""
|
|
197
|
+
Entry point for requesting the *IAM* server to exchange the token.
|
|
198
|
+
|
|
199
|
+
This is currently limit to the *Keycloak* server
|
|
200
|
+
|
|
201
|
+
:return: the response containing the token, or *UNAUTHORIZED*
|
|
202
|
+
"""
|
|
203
|
+
# retrieve the operations's logger
|
|
204
|
+
logger: Logger = _get_logger()
|
|
205
|
+
|
|
206
|
+
# retrieve the IAM server (currently, only 'Keycloak' is supported)
|
|
207
|
+
errors: list[str] = []
|
|
208
|
+
iam_server: IamServer = _get_iam_server(endpoint=request.endpoint,
|
|
209
|
+
errors=errors,
|
|
210
|
+
logger=logger)
|
|
211
|
+
# exchange the token
|
|
212
|
+
token_data: dict[str, Any] | None = None
|
|
213
|
+
if iam_server:
|
|
214
|
+
errors: list[str] = []
|
|
215
|
+
token_data = token_exchange(iam_server=iam_server,
|
|
216
|
+
args=request.args,
|
|
217
|
+
errors=errors,
|
|
218
|
+
logger=logger)
|
|
219
|
+
result: Response
|
|
220
|
+
if errors:
|
|
221
|
+
result = Response("; ".join(errors))
|
|
222
|
+
result.status_code = 400
|
|
223
|
+
else:
|
|
224
|
+
result = jsonify(token_data)
|
|
225
|
+
|
|
226
|
+
# log the response
|
|
227
|
+
if logger:
|
|
228
|
+
logger.debug(msg=f"Response {result}")
|
|
229
|
+
|
|
230
|
+
return result
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _log_init(request: Request) -> str:
|
|
234
|
+
"""
|
|
235
|
+
Build the messages for logging the request entry.
|
|
236
|
+
|
|
237
|
+
:param request: the Request object
|
|
238
|
+
:return: the log message
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
params: str = json.dumps(obj=request.args,
|
|
242
|
+
ensure_ascii=False)
|
|
243
|
+
return f"Request {request.method}:{request.path}, params {params}"
|
pypomes_iam/jusbr_pomes.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from cachetools import Cache, FIFOCache
|
|
2
|
-
from datetime import datetime
|
|
3
3
|
from flask import Flask
|
|
4
4
|
from logging import Logger
|
|
5
5
|
from pypomes_core import (
|
|
6
|
-
APP_PREFIX,
|
|
6
|
+
APP_PREFIX, env_get_int, env_get_str
|
|
7
7
|
)
|
|
8
8
|
from typing import Any, Final
|
|
9
9
|
|
|
10
|
-
from .iam_common import
|
|
10
|
+
from .iam_common import _IAM_SERVERS, IamServer
|
|
11
|
+
from .iam_pomes import user_token
|
|
11
12
|
|
|
12
13
|
JUSBR_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_ID")
|
|
13
14
|
JUSBR_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_CLIENT_SECRET")
|
|
@@ -24,6 +25,8 @@ JUSBR_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_ENDPOINT
|
|
|
24
25
|
|
|
25
26
|
JUSBR_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_JUSBR_PUBLIC_KEY_LIFETIME",
|
|
26
27
|
def_value=86400) # 24 hours
|
|
28
|
+
JUSBR_RECIPIENT_ATTR: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_RECIPIENT_ATTR",
|
|
29
|
+
def_value="preferred_username")
|
|
27
30
|
JUSBR_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_JUSBR_URL_AUTH_BASE")
|
|
28
31
|
|
|
29
32
|
|
|
@@ -32,6 +35,7 @@ def jusbr_setup(flask_app: Flask,
|
|
|
32
35
|
client_secret: str = JUSBR_CLIENT_SECRET,
|
|
33
36
|
client_timeout: int = JUSBR_CLIENT_TIMEOUT,
|
|
34
37
|
public_key_lifetime: int = JUSBR_PUBLIC_KEY_LIFETIME,
|
|
38
|
+
recipient_attribute: str = JUSBR_RECIPIENT_ATTR,
|
|
35
39
|
callback_endpoint: str = JUSBR_ENDPOINT_CALLBACK,
|
|
36
40
|
token_endpoint: str = JUSBR_ENDPOINT_TOKEN,
|
|
37
41
|
login_endpoint: str = JUSBR_ENDPOINT_LOGIN,
|
|
@@ -48,24 +52,26 @@ def jusbr_setup(flask_app: Flask,
|
|
|
48
52
|
:param client_secret: the client's password with JusBR
|
|
49
53
|
:param client_timeout: timeout for login authentication (in seconds,defaults to no timeout)
|
|
50
54
|
:param public_key_lifetime: how long to use JusBR's public key, before refreshing it (in seconds)
|
|
55
|
+
:param recipient_attribute: attribute in the token's payload holding the token's subject
|
|
51
56
|
:param callback_endpoint: endpoint for the callback from JusBR
|
|
52
|
-
:param token_endpoint: endpoint for retrieving
|
|
53
|
-
:param login_endpoint: endpoint for redirecting user to JusBR login page
|
|
57
|
+
:param token_endpoint: endpoint for retrieving JusBR's authentication token
|
|
58
|
+
:param login_endpoint: endpoint for redirecting user to JusBR's login page
|
|
54
59
|
:param logout_endpoint: endpoint for terminating user access to JusBR
|
|
55
|
-
:param base_url: base URL to request
|
|
60
|
+
:param base_url: base URL to request JusBR services
|
|
56
61
|
:param logger: optional logger
|
|
57
62
|
"""
|
|
58
|
-
from .
|
|
63
|
+
from .iam_services import service_login, service_logout, service_callback, service_token
|
|
59
64
|
|
|
60
65
|
# configure the JusBR registry
|
|
61
66
|
cache: Cache = FIFOCache(maxsize=1048576)
|
|
62
67
|
cache["users"] = {}
|
|
63
|
-
|
|
68
|
+
_IAM_SERVERS[IamServer.IAM_JUSRBR] = {
|
|
64
69
|
"client-id": client_id,
|
|
65
70
|
"client-secret": client_secret,
|
|
66
71
|
"client-timeout": client_timeout,
|
|
72
|
+
"recipient-attr": recipient_attribute,
|
|
67
73
|
"base-url": base_url,
|
|
68
|
-
"pk-expiration":
|
|
74
|
+
"pk-expiration": sys.maxsize,
|
|
69
75
|
"pk-lifetime": public_key_lifetime,
|
|
70
76
|
"cache": cache,
|
|
71
77
|
"logger": logger,
|
|
@@ -87,7 +93,7 @@ def jusbr_setup(flask_app: Flask,
|
|
|
87
93
|
flask_app.add_url_rule(rule=callback_endpoint,
|
|
88
94
|
endpoint="jusbr-callback",
|
|
89
95
|
view_func=service_callback,
|
|
90
|
-
methods=["GET"
|
|
96
|
+
methods=["GET"])
|
|
91
97
|
if token_endpoint:
|
|
92
98
|
flask_app.add_url_rule(rule=token_endpoint,
|
|
93
99
|
endpoint="jusbr-token",
|
|
@@ -108,7 +114,7 @@ def jusbr_get_token(user_id: str,
|
|
|
108
114
|
"""
|
|
109
115
|
# retrieve the token
|
|
110
116
|
args: dict[str, Any] = {"user-id": user_id}
|
|
111
|
-
return
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
return user_token(iam_server=IamServer.IAM_JUSRBR,
|
|
118
|
+
args=args,
|
|
119
|
+
errors=errors,
|
|
120
|
+
logger=logger)
|
pypomes_iam/keycloak_pomes.py
CHANGED
|
@@ -1,30 +1,35 @@
|
|
|
1
|
+
import sys
|
|
1
2
|
from cachetools import Cache, FIFOCache
|
|
2
|
-
from datetime import datetime
|
|
3
3
|
from flask import Flask
|
|
4
4
|
from logging import Logger
|
|
5
5
|
from pypomes_core import (
|
|
6
|
-
APP_PREFIX,
|
|
6
|
+
APP_PREFIX, env_get_int, env_get_str
|
|
7
7
|
)
|
|
8
8
|
from typing import Any, Final
|
|
9
9
|
|
|
10
|
-
from .iam_common import
|
|
10
|
+
from .iam_common import _IAM_SERVERS, IamServer
|
|
11
|
+
from .iam_pomes import user_token
|
|
11
12
|
|
|
12
13
|
KEYCLOAK_CLIENT_ID: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_ID")
|
|
13
14
|
KEYCLOAK_CLIENT_SECRET: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_SECRET")
|
|
14
15
|
KEYCLOAK_CLIENT_TIMEOUT: Final[int] = env_get_int(key=f"{APP_PREFIX}_KEYCLOAK_CLIENT_TIMEOUT")
|
|
15
16
|
|
|
16
17
|
KEYCLOAK_ENDPOINT_CALLBACK: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_CALLBACK",
|
|
17
|
-
def_value="/iam/
|
|
18
|
+
def_value="/iam/ijud:callback")
|
|
19
|
+
KEYCLOAK_ENDPOINT_EXCHANGE: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_EXCHANGE",
|
|
20
|
+
def_value="/iam/ijud:exchange-token")
|
|
18
21
|
KEYCLOAK_ENDPOINT_LOGIN: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_LOGIN",
|
|
19
|
-
def_value="/iam/
|
|
22
|
+
def_value="/iam/ijud:login")
|
|
20
23
|
KEYCLOAK_ENDPOINT_LOGOUT: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_LOGOUT",
|
|
21
|
-
def_value="/iam/
|
|
24
|
+
def_value="/iam/ijud:logout")
|
|
22
25
|
KEYCLOAK_ENDPOINT_TOKEN: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_ENDPOINT_TOKEN",
|
|
23
|
-
def_value="/iam/
|
|
26
|
+
def_value="/iam/ijud:get-token")
|
|
24
27
|
|
|
25
28
|
KEYCLOAK_PUBLIC_KEY_LIFETIME: Final[int] = env_get_int(key=f"{APP_PREFIX}_KEYCLOAK_PUBLIC_KEY_LIFETIME",
|
|
26
29
|
def_value=86400) # 24 hours
|
|
27
30
|
KEYCLOAK_REALM: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_REALM")
|
|
31
|
+
KEYCLOAK_RECIPIENT_ATTR: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_RECIPIENT_ATTR",
|
|
32
|
+
def_value="preferred_username")
|
|
28
33
|
KEYCLOAK_URL_AUTH_BASE: Final[str] = env_get_str(key=f"{APP_PREFIX}_KEYCLOAK_URL_AUTH_BASE")
|
|
29
34
|
|
|
30
35
|
|
|
@@ -33,11 +38,13 @@ def keycloak_setup(flask_app: Flask,
|
|
|
33
38
|
client_secret: str = KEYCLOAK_CLIENT_SECRET,
|
|
34
39
|
client_timeout: int = KEYCLOAK_CLIENT_TIMEOUT,
|
|
35
40
|
public_key_lifetime: int = KEYCLOAK_PUBLIC_KEY_LIFETIME,
|
|
41
|
+
recipient_attribute: str = KEYCLOAK_RECIPIENT_ATTR,
|
|
36
42
|
realm: str = KEYCLOAK_REALM,
|
|
37
43
|
callback_endpoint: str = KEYCLOAK_ENDPOINT_CALLBACK,
|
|
38
|
-
|
|
44
|
+
exchange_endpoint: str = KEYCLOAK_ENDPOINT_EXCHANGE,
|
|
39
45
|
login_endpoint: str = KEYCLOAK_ENDPOINT_LOGIN,
|
|
40
46
|
logout_endpoint: str = KEYCLOAK_ENDPOINT_LOGOUT,
|
|
47
|
+
token_endpoint: str = KEYCLOAK_ENDPOINT_TOKEN,
|
|
41
48
|
base_url: str = KEYCLOAK_URL_AUTH_BASE,
|
|
42
49
|
logger: Logger = None) -> None:
|
|
43
50
|
"""
|
|
@@ -50,25 +57,30 @@ def keycloak_setup(flask_app: Flask,
|
|
|
50
57
|
:param client_secret: the client's password with JusBR
|
|
51
58
|
:param client_timeout: timeout for login authentication (in seconds,defaults to no timeout)
|
|
52
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
|
|
53
61
|
:param realm: the Keycloak realm
|
|
54
|
-
:param callback_endpoint: endpoint for the callback from
|
|
55
|
-
:param
|
|
56
|
-
:param
|
|
57
|
-
:param
|
|
58
|
-
:param
|
|
62
|
+
:param callback_endpoint: endpoint for the callback from the front end
|
|
63
|
+
:param exchange_endpoint: endpoint fro requesting token exchange
|
|
64
|
+
:param token_endpoint: endpoint for retrieving Keycloak's authentication token
|
|
65
|
+
:param login_endpoint: endpoint for redirecting user to Keycloak's login page
|
|
66
|
+
:param logout_endpoint: endpoint for terminating user access to Keycloak
|
|
67
|
+
:param base_url: base URL to request Keycloak services
|
|
59
68
|
:param logger: optional logger
|
|
60
69
|
"""
|
|
61
|
-
from .
|
|
70
|
+
from .iam_services import (
|
|
71
|
+
service_login, service_logout, service_callback, service_exchange, service_token
|
|
72
|
+
)
|
|
62
73
|
|
|
63
74
|
# configure the Keycloak registry
|
|
64
75
|
cache: Cache = FIFOCache(maxsize=1048576)
|
|
65
76
|
cache["users"] = {}
|
|
66
|
-
|
|
77
|
+
_IAM_SERVERS[IamServer.IAM_KEYCLOAK] = {
|
|
67
78
|
"client-id": client_id,
|
|
68
79
|
"client-secret": client_secret,
|
|
69
80
|
"client-timeout": client_timeout,
|
|
81
|
+
"recipient-attr": recipient_attribute,
|
|
70
82
|
"base-url": f"{base_url}/realms/{realm}",
|
|
71
|
-
"pk-expiration":
|
|
83
|
+
"pk-expiration": sys.maxsize,
|
|
72
84
|
"pk-lifetime": public_key_lifetime,
|
|
73
85
|
"cache": cache,
|
|
74
86
|
"logger": logger,
|
|
@@ -90,12 +102,17 @@ def keycloak_setup(flask_app: Flask,
|
|
|
90
102
|
flask_app.add_url_rule(rule=callback_endpoint,
|
|
91
103
|
endpoint="keycloak-callback",
|
|
92
104
|
view_func=service_callback,
|
|
93
|
-
methods=["
|
|
105
|
+
methods=["GET"])
|
|
94
106
|
if token_endpoint:
|
|
95
107
|
flask_app.add_url_rule(rule=token_endpoint,
|
|
96
108
|
endpoint="keycloak-token",
|
|
97
109
|
view_func=service_token,
|
|
98
110
|
methods=["GET"])
|
|
111
|
+
if exchange_endpoint:
|
|
112
|
+
flask_app.add_url_rule(rule=exchange_endpoint,
|
|
113
|
+
endpoint="keycloak-exchange",
|
|
114
|
+
view_func=service_exchange,
|
|
115
|
+
methods=["POST"])
|
|
99
116
|
|
|
100
117
|
|
|
101
118
|
def keycloak_get_token(user_id: str,
|
|
@@ -111,7 +128,7 @@ def keycloak_get_token(user_id: str,
|
|
|
111
128
|
"""
|
|
112
129
|
# retrieve the token
|
|
113
130
|
args: dict[str, Any] = {"user-id": user_id}
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
return user_token(iam_server=IamServer.IAM_KEYCLOAK,
|
|
132
|
+
args=args,
|
|
133
|
+
errors=errors,
|
|
134
|
+
logger=logger)
|
pypomes_iam/token_pomes.py
CHANGED
|
@@ -9,6 +9,8 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
def token_validate(token: str,
|
|
11
11
|
issuer: str = None,
|
|
12
|
+
recipient_id: str = None,
|
|
13
|
+
recipient_attr: str = None,
|
|
12
14
|
public_key: str | bytes | PyJWK | RSAPublicKey = None,
|
|
13
15
|
errors: list[str] = None,
|
|
14
16
|
logger: Logger = None) -> dict[str, dict[str, Any]] | None:
|
|
@@ -24,12 +26,18 @@ def token_validate(token: str,
|
|
|
24
26
|
If an asymmetric algorithm was used to sign the token and *public_key* is provided, then
|
|
25
27
|
the token is validated, by using the data in its *signature* section.
|
|
26
28
|
|
|
29
|
+
The parameters *recipient_id* and *recipient_attr* refer the token's expected subject, respectively,
|
|
30
|
+
the subject's identification and the attribute in the token's payload data identifying its subject.
|
|
31
|
+
If both are provided, *recipient_id* is validated.
|
|
32
|
+
|
|
27
33
|
On failure, *errors* will contain the reason(s) for rejecting *token*.
|
|
28
34
|
On success, return the token's claims (*header* and *payload*).
|
|
29
35
|
|
|
30
36
|
:param token: the token to be validated
|
|
31
37
|
:param public_key: optional public key used to sign the token, in *PEM* format
|
|
32
38
|
:param issuer: optional value to compare with the token's *iss* (issuer) attribute in its *payload*
|
|
39
|
+
:param recipient_id: identification of the expected token subject
|
|
40
|
+
:param recipient_attr: attribute in the token's payload holding the expected subject's identification
|
|
33
41
|
:param errors: incidental error messages
|
|
34
42
|
:param logger: optional logger
|
|
35
43
|
:return: The token's claims (*header* and *payload*) if it is valid, *None* otherwise
|
|
@@ -87,10 +95,17 @@ def token_validate(token: str,
|
|
|
87
95
|
algorithms=[token_alg],
|
|
88
96
|
options=options,
|
|
89
97
|
issuer=issuer)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
"payload"
|
|
93
|
-
|
|
98
|
+
if recipient_id and recipient_attr and \
|
|
99
|
+
payload.get(recipient_attr) and recipient_id != payload.get(recipient_attr):
|
|
100
|
+
msg: str = f"Token was issued to '{payload.get(recipient_attr)}', not to '{recipient_id}'"
|
|
101
|
+
if logger:
|
|
102
|
+
logger.error(msg=msg)
|
|
103
|
+
errors.append(msg)
|
|
104
|
+
else:
|
|
105
|
+
result = {
|
|
106
|
+
"header": token_header,
|
|
107
|
+
"payload": payload
|
|
108
|
+
}
|
|
94
109
|
except Exception as e:
|
|
95
110
|
exc_err: str = exc_format(exc=e,
|
|
96
111
|
exc_info=sys.exc_info())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_iam
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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,12 @@
|
|
|
1
|
+
pypomes_iam/__init__.py,sha256=NB5ALh_kihQhfzl-7x_od4icrRBuSmYCSG5QSJujkuo,887
|
|
2
|
+
pypomes_iam/iam_common.py,sha256=IWH9DVykF7K-Z9Sn3oP7To9_I2TkO2AzjVod238zUWc,13645
|
|
3
|
+
pypomes_iam/iam_pomes.py,sha256=MqPcz39u5yBFGCnDBA8zFLMRZQyH6U4iZvG0q0XL0h8,15216
|
|
4
|
+
pypomes_iam/iam_services.py,sha256=4NQR5agj5zNnFymldHaTURGYDEHMkK0nX8PqkAldFXQ,8221
|
|
5
|
+
pypomes_iam/jusbr_pomes.py,sha256=M47h_PUUgbCmFQyKz2sN1H9T00BC5v_oPgwl5ATWMSA,5625
|
|
6
|
+
pypomes_iam/keycloak_pomes.py,sha256=GtXJb4TZb-a_5b9ExYdJGetBcU1pEP96ONO6prA_vDo,6638
|
|
7
|
+
pypomes_iam/provider_pomes.py,sha256=eP8XzjTUEpwejTkO0wmDiqKjqbIEOzRNCR2ju5E15og,5856
|
|
8
|
+
pypomes_iam/token_pomes.py,sha256=cfHdv2qYbsciY-3aEuDYUwCM479uMRSm2uwr4-hCaBQ,5345
|
|
9
|
+
pypomes_iam-0.3.2.dist-info/METADATA,sha256=8E8b97RrTn4eLYueJum1UugCVC-230nEoWBOQK2QkyM,694
|
|
10
|
+
pypomes_iam-0.3.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
11
|
+
pypomes_iam-0.3.2.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
|
|
12
|
+
pypomes_iam-0.3.2.dist-info/RECORD,,
|
|
@@ -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=O1acRMyisiZuB9rIkSboxwzGoLU_1hq_gNrg39CE9NQ,7835
|
|
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.3.0.dist-info/METADATA,sha256=-hmCE_h6DbGx97J44czCa2OD2wh4gXQkQ8HQzDmy6E0,694
|
|
9
|
-
pypomes_iam-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
10
|
-
pypomes_iam-0.3.0.dist-info/licenses/LICENSE,sha256=YvUELgV8qvXlaYsy9hXG5EW3Bmsrkw-OJmmILZnonAc,1086
|
|
11
|
-
pypomes_iam-0.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|