pypomes-iam 0.1.8__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pypomes-iam might be problematic. Click here for more details.
- pypomes_iam/__init__.py +5 -0
- pypomes_iam/common_pomes.py +331 -19
- pypomes_iam/iam_pomes.py +176 -0
- pypomes_iam/jusbr_pomes.py +34 -358
- pypomes_iam/keycloak_pomes.py +43 -208
- {pypomes_iam-0.1.8.dist-info → pypomes_iam-0.2.0.dist-info}/METADATA +2 -2
- pypomes_iam-0.2.0.dist-info/RECORD +11 -0
- pypomes_iam-0.1.8.dist-info/RECORD +0 -10
- {pypomes_iam-0.1.8.dist-info → pypomes_iam-0.2.0.dist-info}/WHEEL +0 -0
- {pypomes_iam-0.1.8.dist-info → pypomes_iam-0.2.0.dist-info}/licenses/LICENSE +0 -0
pypomes_iam/__init__.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from .jusbr_pomes import (
|
|
2
2
|
jusbr_setup, jusbr_get_token, jusbr_set_scope
|
|
3
3
|
)
|
|
4
|
+
from .keycloak_pomes import (
|
|
5
|
+
keycloak_setup, keycloak_get_token, keycloak_set_scope
|
|
6
|
+
)
|
|
4
7
|
from .provider_pomes import (
|
|
5
8
|
provider_register, provider_get_token
|
|
6
9
|
)
|
|
@@ -11,6 +14,8 @@ from .token_pomes import (
|
|
|
11
14
|
__all__ = [
|
|
12
15
|
# jusbr_pomes
|
|
13
16
|
"jusbr_setup", "jusbr_get_token", "jusbr_set_scope",
|
|
17
|
+
# keycloak_pomes
|
|
18
|
+
"keycloak_setup", "keycloak_get_token", "keycloak_set_scope",
|
|
14
19
|
# provider_pomes
|
|
15
20
|
"provider_register", "provider_get_token",
|
|
16
21
|
# token_pomes
|
pypomes_iam/common_pomes.py
CHANGED
|
@@ -1,21 +1,233 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import requests
|
|
3
|
+
import secrets
|
|
4
|
+
import string
|
|
5
|
+
import sys
|
|
6
|
+
from cachetools import Cache
|
|
3
7
|
from datetime import datetime
|
|
4
8
|
from flask import Request
|
|
5
9
|
from logging import Logger
|
|
6
|
-
from pypomes_core import TZ_LOCAL
|
|
10
|
+
from pypomes_core import TZ_LOCAL, exc_format
|
|
7
11
|
from typing import Any
|
|
8
12
|
|
|
13
|
+
# registry structure:
|
|
14
|
+
# {
|
|
15
|
+
# "client-id": <str>,
|
|
16
|
+
# "client-secret": <str>,
|
|
17
|
+
# "client-timeout": <int>,
|
|
18
|
+
# "public_key": <str>,
|
|
19
|
+
# "key-lifetime": <int>,
|
|
20
|
+
# "key-expiration": <int>,
|
|
21
|
+
# "base-url": <str>,
|
|
22
|
+
# "callback-url": <str>,
|
|
23
|
+
# "safe-cache": <FIFOCache>
|
|
24
|
+
# }
|
|
25
|
+
# data in "safe-cache":
|
|
26
|
+
# {
|
|
27
|
+
# "users": {
|
|
28
|
+
# "<user-id>": {
|
|
29
|
+
# "access-token": <str>
|
|
30
|
+
# "refresh-token": <str>
|
|
31
|
+
# "access-expiration": <timestamp>,
|
|
32
|
+
# "login-expiration": <timestamp>, <-- transient
|
|
33
|
+
# "login-id": <str>, <-- transient
|
|
34
|
+
# "oauth-scope": <str> <-- optional
|
|
35
|
+
# }
|
|
36
|
+
# }
|
|
37
|
+
# }
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _service_callback(registry: dict[str, Any],
|
|
41
|
+
args: dict[str, Any],
|
|
42
|
+
errors: list[str],
|
|
43
|
+
logger: Logger | None) -> tuple[str, str]:
|
|
44
|
+
"""
|
|
45
|
+
Entry point for the callback from JusBR on authentication operation.
|
|
46
|
+
|
|
47
|
+
:param registry: the registry holding the authentication data
|
|
48
|
+
:param args: the arguments passed when requesting the service
|
|
49
|
+
:param errors: incidental errors
|
|
50
|
+
:param logger: optional logger
|
|
51
|
+
"""
|
|
52
|
+
from .token_pomes import token_validate
|
|
53
|
+
|
|
54
|
+
# initialize the return variable
|
|
55
|
+
result: tuple[str, str] | None = None
|
|
56
|
+
|
|
57
|
+
# retrieve the users authentication data
|
|
58
|
+
cache: Cache = registry["safe-cache"]
|
|
59
|
+
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
60
|
+
|
|
61
|
+
# validate the OAuth2 state
|
|
62
|
+
oauth_state: str = args.get("state")
|
|
63
|
+
user_data: dict[str, Any] | None = None
|
|
64
|
+
if oauth_state:
|
|
65
|
+
for user, data in users.items():
|
|
66
|
+
if user == oauth_state:
|
|
67
|
+
user_data = data
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
# exchange 'code' for the token
|
|
71
|
+
if user_data:
|
|
72
|
+
users.pop(oauth_state)
|
|
73
|
+
code: str = args.get("code")
|
|
74
|
+
body_data: dict[str, Any] = {
|
|
75
|
+
"grant_type": "authorization_code",
|
|
76
|
+
"code": code,
|
|
77
|
+
"redirect_uri": registry.get("callback-url"),
|
|
78
|
+
}
|
|
79
|
+
token = _post_for_token(registry=registry,
|
|
80
|
+
user_data=user_data,
|
|
81
|
+
body_data=body_data,
|
|
82
|
+
errors=errors,
|
|
83
|
+
logger=logger)
|
|
84
|
+
# retrieve the token's claims
|
|
85
|
+
if not errors:
|
|
86
|
+
public_key: bytes = _get_public_key(registry=registry,
|
|
87
|
+
logger=logger)
|
|
88
|
+
token_claims: dict[str, dict[str, Any]] = token_validate(token=token,
|
|
89
|
+
issuer=registry["base-url"],
|
|
90
|
+
public_key=public_key,
|
|
91
|
+
errors=errors,
|
|
92
|
+
logger=logger)
|
|
93
|
+
if not errors:
|
|
94
|
+
token_user: str = token_claims["payload"].get("preferred_username")
|
|
95
|
+
if token_user == oauth_state:
|
|
96
|
+
users[token_user] = user_data
|
|
97
|
+
result = (token_user, token)
|
|
98
|
+
else:
|
|
99
|
+
errors.append(f"Token was issued to user '{token_user}'")
|
|
100
|
+
else:
|
|
101
|
+
msg: str = "Unknown OAuth2 code received"
|
|
102
|
+
if _get_login_timeout(registry=registry):
|
|
103
|
+
msg += " - possible operation timeout"
|
|
104
|
+
errors.append(msg)
|
|
105
|
+
|
|
106
|
+
return result
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _service_login(registry: dict[str, Any],
|
|
110
|
+
args: dict[str, Any],
|
|
111
|
+
logger: Logger | None) -> str:
|
|
112
|
+
"""
|
|
113
|
+
Build the callback URL for redirecting the request to the IAM's authentication page.
|
|
114
|
+
|
|
115
|
+
:param registry: the registry holding the authentication data
|
|
116
|
+
:param args: the arguments passed when requesting the service
|
|
117
|
+
:param logger: optional logger
|
|
118
|
+
:return: the callback URL, with the appropriate parameters
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
# retrieve user data
|
|
122
|
+
oauth_state: str = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(16))
|
|
123
|
+
|
|
124
|
+
# build the user data
|
|
125
|
+
# ('oauth_state' is a randomly-generated string, thus 'user_data' is always a new entry)
|
|
126
|
+
user_data: dict[str, Any] = _get_user_data(registry=registry,
|
|
127
|
+
user_id=oauth_state,
|
|
128
|
+
logger=logger)
|
|
129
|
+
user_id: str = args.get("user-id") or args.get("user_id") or args.get("login")
|
|
130
|
+
user_data["login-id"] = user_id
|
|
131
|
+
timeout: int = _get_login_timeout(registry=registry)
|
|
132
|
+
user_data["login-expiration"] = int(datetime.now(tz=TZ_LOCAL).timestamp()) + timeout if timeout else None
|
|
133
|
+
|
|
134
|
+
# build the redirect url
|
|
135
|
+
result: str = (f"{registry["base-url"]}/protocol/openid-connect/auth?response_type=code"
|
|
136
|
+
f"&client_id={registry["client-id"]}"
|
|
137
|
+
f"&redirect_uri={registry["callback-url"]}"
|
|
138
|
+
f"&state={oauth_state}")
|
|
139
|
+
scope: str = _get_user_scope(registry=registry,
|
|
140
|
+
user_id=user_id)
|
|
141
|
+
if scope:
|
|
142
|
+
user_data["oauth-scope"] = scope
|
|
143
|
+
result += f"&scope={scope}"
|
|
144
|
+
|
|
145
|
+
# logout the user
|
|
146
|
+
_service_logout(registry=registry,
|
|
147
|
+
args=args,
|
|
148
|
+
logger=logger)
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _service_logout(registry: dict[str, Any],
|
|
153
|
+
args: dict[str, Any],
|
|
154
|
+
logger: Logger | None) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Remove all data associating *user_id* from *registry*.
|
|
157
|
+
|
|
158
|
+
:param registry: the registry holding the authentication data
|
|
159
|
+
:param args: the arguments passed when requesting the service
|
|
160
|
+
:param logger: optional logger
|
|
161
|
+
"""
|
|
162
|
+
# remove the user data
|
|
163
|
+
user_id: str = args.get("user-id") or args.get("login")
|
|
164
|
+
if user_id:
|
|
165
|
+
cache: Cache = registry["safe-cache"]
|
|
166
|
+
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
167
|
+
if user_id in users:
|
|
168
|
+
users.pop(user_id)
|
|
169
|
+
if logger:
|
|
170
|
+
logger.debug(msg=f"User '{user_id}' removed from the registry")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _service_token(registry: dict[str, Any],
|
|
174
|
+
args: dict[str, Any],
|
|
175
|
+
errors: list[str] = None,
|
|
176
|
+
logger: Logger = None) -> str:
|
|
177
|
+
"""
|
|
178
|
+
Retrieve the authentication token for user *user_id*.
|
|
179
|
+
|
|
180
|
+
:param registry: the registry holding the authentication data
|
|
181
|
+
:param args: the arguments passed when requesting the service
|
|
182
|
+
:param errors: incidental error messages
|
|
183
|
+
:param logger: optional logger
|
|
184
|
+
:return: the token for *user_id*, or *None* if error
|
|
185
|
+
"""
|
|
186
|
+
# initialize the return variable
|
|
187
|
+
result: str | None = None
|
|
188
|
+
|
|
189
|
+
user_id: str = args.get("user-id") or args.get("user_id") or args.get("login")
|
|
190
|
+
user_data: dict[str, Any] = _get_user_data(registry=registry,
|
|
191
|
+
user_id=user_id,
|
|
192
|
+
logger=logger)
|
|
193
|
+
token: str = user_data["access-token"]
|
|
194
|
+
if token:
|
|
195
|
+
access_expiration: int = user_data.get("access-expiration")
|
|
196
|
+
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
197
|
+
if now < access_expiration:
|
|
198
|
+
result = token
|
|
199
|
+
else:
|
|
200
|
+
# access token has expired
|
|
201
|
+
refresh_token: str = user_data["refresh-token"]
|
|
202
|
+
if refresh_token:
|
|
203
|
+
body_data: dict[str, str] = {
|
|
204
|
+
"grant_type": "refresh_token",
|
|
205
|
+
"refresh_token": refresh_token
|
|
206
|
+
}
|
|
207
|
+
result = _post_for_token(registry=registry,
|
|
208
|
+
user_data=user_data,
|
|
209
|
+
body_data=body_data,
|
|
210
|
+
errors=errors,
|
|
211
|
+
logger=logger)
|
|
212
|
+
|
|
213
|
+
elif logger or isinstance(errors, list):
|
|
214
|
+
err_msg: str = f"User '{user_id}' not authenticated"
|
|
215
|
+
if isinstance(errors, list):
|
|
216
|
+
errors.append(err_msg)
|
|
217
|
+
if logger:
|
|
218
|
+
logger.error(msg=err_msg)
|
|
219
|
+
|
|
220
|
+
return result
|
|
221
|
+
|
|
9
222
|
|
|
10
223
|
def _get_public_key(registry: dict[str, Any],
|
|
11
|
-
url: str,
|
|
12
224
|
logger: Logger | None) -> bytes:
|
|
13
225
|
"""
|
|
14
226
|
Obtain the public key used by the *IAM* to sign the authentication tokens.
|
|
15
227
|
|
|
16
228
|
The public key is saved in *registry*.
|
|
17
229
|
|
|
18
|
-
:param
|
|
230
|
+
:param registry: the registry holding the authentication data
|
|
19
231
|
:return: the public key, in *DER* format
|
|
20
232
|
"""
|
|
21
233
|
from pypomes_crypto import crypto_jwk_convert
|
|
@@ -24,9 +236,9 @@ def _get_public_key(registry: dict[str, Any],
|
|
|
24
236
|
result: bytes | None = None
|
|
25
237
|
|
|
26
238
|
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
27
|
-
if now > registry
|
|
239
|
+
if now > registry["key-expiration"]:
|
|
28
240
|
# obtain a new public key
|
|
29
|
-
url: str = f"{url}/protocol/openid-connect/certs"
|
|
241
|
+
url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
|
|
30
242
|
if logger:
|
|
31
243
|
logger.debug(msg=f"GET '{url}'")
|
|
32
244
|
response: requests.Response = requests.get(url=url)
|
|
@@ -38,7 +250,7 @@ def _get_public_key(registry: dict[str, Any],
|
|
|
38
250
|
result = crypto_jwk_convert(jwk=reply["keys"][0],
|
|
39
251
|
fmt="DER")
|
|
40
252
|
registry["public-key"] = result
|
|
41
|
-
duration: int = registry
|
|
253
|
+
duration: int = registry["key-lifetime"] or 0
|
|
42
254
|
registry["key-expiration"] = now + duration
|
|
43
255
|
elif logger:
|
|
44
256
|
msg: str = f"GET failure, status {response.status_code}, reason '{response.reason}'"
|
|
@@ -46,7 +258,7 @@ def _get_public_key(registry: dict[str, Any],
|
|
|
46
258
|
msg += f", content '{response.content}'"
|
|
47
259
|
logger.error(msg=msg)
|
|
48
260
|
else:
|
|
49
|
-
result = registry
|
|
261
|
+
result = registry["public-key"]
|
|
50
262
|
|
|
51
263
|
return result
|
|
52
264
|
|
|
@@ -55,6 +267,7 @@ def _get_login_timeout(registry: dict[str, Any]) -> int | None:
|
|
|
55
267
|
"""
|
|
56
268
|
Retrieve from *registry* the timeout currently applicable for the login operation.
|
|
57
269
|
|
|
270
|
+
:param registry: the registry holding the authentication data
|
|
58
271
|
:return: the current login timeout, or *None* if none has been set.
|
|
59
272
|
"""
|
|
60
273
|
timeout: int = registry.get("client-timeout")
|
|
@@ -70,13 +283,19 @@ def _get_user_data(registry: dict[str, Any],
|
|
|
70
283
|
If an entry is not found for *user_id* in the registry, it is created.
|
|
71
284
|
It will remain there until the user is logged out.
|
|
72
285
|
|
|
73
|
-
:param
|
|
286
|
+
:param registry: the registry holding the authentication data
|
|
74
287
|
:return: the data for *user_id* in the registry
|
|
75
288
|
"""
|
|
76
|
-
|
|
289
|
+
cache: Cache = registry["safe-cache"]
|
|
290
|
+
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
291
|
+
result: dict[str, Any] = users.get(user_id)
|
|
77
292
|
if not result:
|
|
78
|
-
result = {
|
|
79
|
-
|
|
293
|
+
result = {
|
|
294
|
+
"access-token": None,
|
|
295
|
+
"refresh-token": None,
|
|
296
|
+
"access-expiration": int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
297
|
+
}
|
|
298
|
+
users[user_id] = result
|
|
80
299
|
if logger:
|
|
81
300
|
logger.debug(msg=f"Entry for user '{user_id}' added to the registry")
|
|
82
301
|
elif logger:
|
|
@@ -85,17 +304,110 @@ def _get_user_data(registry: dict[str, Any],
|
|
|
85
304
|
return result
|
|
86
305
|
|
|
87
306
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
logger: Logger | None) -> None:
|
|
307
|
+
def _get_user_scope(registry: dict[str, Any],
|
|
308
|
+
user_id: str) -> str | None:
|
|
91
309
|
"""
|
|
92
|
-
|
|
310
|
+
Retrieve the OAuth2 scope associated with *user_id*.
|
|
311
|
+
|
|
312
|
+
:param registry: the registry holding the authentication data
|
|
313
|
+
:param user_id:
|
|
314
|
+
:return: the OAuth2 scope associated with *user_id*, or *None* if it does not exist
|
|
93
315
|
"""
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
316
|
+
# initialize the return variable
|
|
317
|
+
result: str | None = None
|
|
318
|
+
|
|
319
|
+
if user_id:
|
|
320
|
+
cache: Cache = registry["safe-cache"]
|
|
321
|
+
users: dict[str, dict[str, Any]] = cache.get("users")
|
|
322
|
+
if user_id in users:
|
|
323
|
+
result = users[user_id].get("oauth2-scope")
|
|
324
|
+
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _post_for_token(registry: dict[str, Any],
|
|
329
|
+
user_data: dict[str, Any],
|
|
330
|
+
body_data: dict[str, Any],
|
|
331
|
+
errors: list[str] | None,
|
|
332
|
+
logger: Logger | None) -> str | None:
|
|
333
|
+
"""
|
|
334
|
+
Send a POST request to obtain the authentication token data, and return the access token.
|
|
335
|
+
|
|
336
|
+
For token exchange, *body_data* will have the attributes
|
|
337
|
+
- "grant_type": "authorization_code"
|
|
338
|
+
- "code": <16-character-random-code>
|
|
339
|
+
- "redirect_uri": <callback-url>
|
|
340
|
+
For token refresh, *body_data* will have the attributes
|
|
341
|
+
- "grant_type": "refresh_token"
|
|
342
|
+
- "refresh_token": <current-refresh-token>
|
|
343
|
+
|
|
344
|
+
If the operation is successful, the token data is stored in the registry.
|
|
345
|
+
Otherwise, *errors* will contain the appropriate error message.
|
|
346
|
+
|
|
347
|
+
:param registry: the registry holding the authentication data
|
|
348
|
+
:param user_data: the user's data in the registry
|
|
349
|
+
:param body_data: the data to send in the body of the request
|
|
350
|
+
:param errors: incidental errors
|
|
351
|
+
:param logger: optional logger
|
|
352
|
+
:return: the access token obtained, or *None* if error
|
|
353
|
+
"""
|
|
354
|
+
# initialize the return variable
|
|
355
|
+
result: str | None = None
|
|
356
|
+
|
|
357
|
+
# complete the data to send in body of request
|
|
358
|
+
body_data["client_id"] = registry["client-id"]
|
|
359
|
+
client_secret: str = registry["client-secret"]
|
|
360
|
+
if client_secret:
|
|
361
|
+
body_data["client_secret"] = client_secret
|
|
362
|
+
|
|
363
|
+
# obtain the token
|
|
364
|
+
err_msg: str | None = None
|
|
365
|
+
url: str = registry["base-url"] + "/protocol/openid-connect/token"
|
|
366
|
+
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
367
|
+
if logger:
|
|
368
|
+
logger.debug(msg=f"POST '{url}', data {json.dumps(obj=body_data,
|
|
369
|
+
ensure_ascii=False)}")
|
|
370
|
+
try:
|
|
371
|
+
# typical return on a token request:
|
|
372
|
+
# {
|
|
373
|
+
# "token_type": "Bearer",
|
|
374
|
+
# "access_token": <str>,
|
|
375
|
+
# "expires_in": <number-of-seconds>,
|
|
376
|
+
# "refresh_token": <str>
|
|
377
|
+
# }
|
|
378
|
+
response: requests.Response = requests.post(url=url,
|
|
379
|
+
data=body_data)
|
|
380
|
+
if response.status_code == 200:
|
|
381
|
+
# request succeeded
|
|
382
|
+
if logger:
|
|
383
|
+
logger.debug(msg=f"POST success, status {response.status_code}")
|
|
384
|
+
reply: dict[str, Any] = response.json()
|
|
385
|
+
result = reply.get("access_token")
|
|
386
|
+
user_data["access-token"] = result
|
|
387
|
+
# on token refresh, keep current refresh token if a new one is not provided
|
|
388
|
+
user_data["refresh-token"] = reply.get("refresh_token") or body_data.get("refresh_token")
|
|
389
|
+
user_data["access-expiration"] = now + reply.get("expires_in")
|
|
390
|
+
else:
|
|
391
|
+
# request resulted in error
|
|
392
|
+
err_msg = f"POST failure, status {response.status_code}, reason '{response.reason}'"
|
|
393
|
+
if hasattr(response, "content") and response.content:
|
|
394
|
+
err_msg += f", content '{response.content}'"
|
|
395
|
+
if response.status_code == 400 and body_data.get("grant_type") == "refresh_token":
|
|
396
|
+
# refresh token is no longer valid
|
|
397
|
+
user_data["refresh-token"] = None
|
|
398
|
+
except Exception as e:
|
|
399
|
+
# the operation raised an exception
|
|
400
|
+
err_msg = exc_format(exc=e,
|
|
401
|
+
exc_info=sys.exc_info())
|
|
402
|
+
err_msg = f"POST '{url}': error '{err_msg}'"
|
|
403
|
+
|
|
404
|
+
if err_msg:
|
|
405
|
+
if isinstance(errors, list):
|
|
406
|
+
errors.append(err_msg)
|
|
97
407
|
if logger:
|
|
98
|
-
logger.
|
|
408
|
+
logger.error(msg=err_msg)
|
|
409
|
+
|
|
410
|
+
return result
|
|
99
411
|
|
|
100
412
|
|
|
101
413
|
def _log_init(request: Request) -> str:
|
pypomes_iam/iam_pomes.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from flask import Response, redirect, request, jsonify
|
|
2
|
+
from logging import Logger
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .common_pomes import (
|
|
6
|
+
_service_login, _service_logout,
|
|
7
|
+
_service_callback, _service_token, _log_init
|
|
8
|
+
)
|
|
9
|
+
from .jusbr_pomes import _jusbr_logger, _jusbr_registry
|
|
10
|
+
from .keycloak_pomes import _keycloak_logger, _keycloak_registry
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# @flask_app.route(rule=<login_endpoint>, # JUSBR_LOGIN_ENDPOINT: /iam/jusbr:login
|
|
14
|
+
# methods=["GET"])
|
|
15
|
+
# @flask_app.route(rule=<login_endpoint>, # KEYCLOAK_LOGIN_ENDPOINT: /iam/keycloak:logout
|
|
16
|
+
# methods=["GET"])
|
|
17
|
+
def service_login() -> Response:
|
|
18
|
+
"""
|
|
19
|
+
Entry point for the JusBR login service.
|
|
20
|
+
|
|
21
|
+
Redirect the request to the JusBR authentication page, with the appropriate parameters.
|
|
22
|
+
|
|
23
|
+
:return: the response from the redirect operation
|
|
24
|
+
"""
|
|
25
|
+
logger: Logger
|
|
26
|
+
registry: dict[str, Any]
|
|
27
|
+
if request.endpoint == "jusbr-login":
|
|
28
|
+
logger = _jusbr_logger
|
|
29
|
+
registry = _jusbr_registry
|
|
30
|
+
else:
|
|
31
|
+
logger = _keycloak_logger
|
|
32
|
+
registry = _keycloak_registry
|
|
33
|
+
|
|
34
|
+
# log the request
|
|
35
|
+
if logger:
|
|
36
|
+
logger.debug(msg=_log_init(request=request))
|
|
37
|
+
|
|
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)
|
|
44
|
+
|
|
45
|
+
# log the response
|
|
46
|
+
if logger:
|
|
47
|
+
logger.debug(msg=f"Response {result}")
|
|
48
|
+
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# @flask_app.route(rule=<logout_endpoint>, # JUSBR_LOGOUT_ENDPOINT: /iam/jusbr:logout
|
|
53
|
+
# methods=["GET"])
|
|
54
|
+
# @flask_app.route(rule=<login_endpoint>, # KEYCLOAK_LOGOUT_ENDPOINT: /iam/keycloak:logout
|
|
55
|
+
# methods=["GET"])
|
|
56
|
+
def service_logout() -> Response:
|
|
57
|
+
"""
|
|
58
|
+
Entry point for the JusBR logout service.
|
|
59
|
+
|
|
60
|
+
Remove all data associating the user with JusBR from the registry.
|
|
61
|
+
|
|
62
|
+
:return: response *OK*
|
|
63
|
+
"""
|
|
64
|
+
logger: Logger
|
|
65
|
+
registry: dict[str, Any]
|
|
66
|
+
if request.endpoint == "jusbr-logout":
|
|
67
|
+
logger = _jusbr_logger
|
|
68
|
+
registry = _jusbr_registry
|
|
69
|
+
else:
|
|
70
|
+
logger = _keycloak_logger
|
|
71
|
+
registry = _keycloak_registry
|
|
72
|
+
|
|
73
|
+
# log the request
|
|
74
|
+
if logger:
|
|
75
|
+
logger.debug(msg=_log_init(request=request))
|
|
76
|
+
|
|
77
|
+
# logout the user
|
|
78
|
+
_service_logout(registry=registry,
|
|
79
|
+
args=request.args,
|
|
80
|
+
logger=logger)
|
|
81
|
+
|
|
82
|
+
result: Response = Response(status=200)
|
|
83
|
+
|
|
84
|
+
# log the response
|
|
85
|
+
if logger:
|
|
86
|
+
logger.debug(msg=f"Response {result}")
|
|
87
|
+
|
|
88
|
+
return result
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# @flask_app.route(rule=<callback_endpoint>, # JUSBR_CALLBACK_ENDPOINT: /iam/jusbr:callback
|
|
92
|
+
# methods=["GET", "POST"])
|
|
93
|
+
# @flask_app.route(rule=<callback_endpoint>, # KEYCLOAK_CALLBACK_ENDPOINT: /iam/keycloak:callback
|
|
94
|
+
# methods=["POST"])
|
|
95
|
+
def service_callback() -> Response:
|
|
96
|
+
"""
|
|
97
|
+
Entry point for the callback from JusBR on authentication operation.
|
|
98
|
+
|
|
99
|
+
:return: the response containing the token, or *BAD REQUEST*
|
|
100
|
+
"""
|
|
101
|
+
logger: Logger
|
|
102
|
+
registry: dict[str, Any]
|
|
103
|
+
if request.endpoint == "jusbr-callback":
|
|
104
|
+
logger = _jusbr_logger
|
|
105
|
+
registry = _jusbr_registry
|
|
106
|
+
else:
|
|
107
|
+
logger = _keycloak_logger
|
|
108
|
+
registry = _keycloak_registry
|
|
109
|
+
|
|
110
|
+
# log the request
|
|
111
|
+
if logger:
|
|
112
|
+
logger.debug(msg=_log_init(request=request))
|
|
113
|
+
|
|
114
|
+
# process the callback operation
|
|
115
|
+
errors: list[str] = []
|
|
116
|
+
token_data: tuple[str, str] = _service_callback(registry=registry,
|
|
117
|
+
args=request.args,
|
|
118
|
+
errors=errors,
|
|
119
|
+
logger=logger)
|
|
120
|
+
result: Response
|
|
121
|
+
if errors:
|
|
122
|
+
result = jsonify({"errors": "; ".join(errors)})
|
|
123
|
+
result.status_code = 400
|
|
124
|
+
else:
|
|
125
|
+
result = jsonify({
|
|
126
|
+
"user_id": token_data[0],
|
|
127
|
+
"access_token": token_data[1]})
|
|
128
|
+
|
|
129
|
+
# log the response
|
|
130
|
+
if logger:
|
|
131
|
+
logger.debug(msg=f"Response {result}")
|
|
132
|
+
|
|
133
|
+
return result
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# @flask_app.route(rule=<token_endpoint>, # JUSBR_TOKEN_ENDPOINT: /iam/jusbr:get-token
|
|
137
|
+
# methods=["GET"])
|
|
138
|
+
# @flask_app.route(rule=<token_endpoint>, # JUSBR_TOKEN_ENDPOINT: /iam/jusbr:get-token
|
|
139
|
+
# methods=["GET"])
|
|
140
|
+
def service_token() -> Response:
|
|
141
|
+
"""
|
|
142
|
+
Entry point for retrieving the JusBR token.
|
|
143
|
+
|
|
144
|
+
:return: the response containing the token, or *UNAUTHORIZED*
|
|
145
|
+
"""
|
|
146
|
+
logger: Logger
|
|
147
|
+
registry: dict[str, Any]
|
|
148
|
+
if request.endpoint == "jusbr-token":
|
|
149
|
+
logger = _jusbr_logger
|
|
150
|
+
registry = _jusbr_registry
|
|
151
|
+
else:
|
|
152
|
+
logger = _keycloak_logger
|
|
153
|
+
registry = _keycloak_registry
|
|
154
|
+
|
|
155
|
+
# log the request
|
|
156
|
+
if logger:
|
|
157
|
+
logger.debug(msg=_log_init(request=request))
|
|
158
|
+
|
|
159
|
+
# retrieve the token
|
|
160
|
+
errors: list[str] = []
|
|
161
|
+
token: str = _service_token(registry=registry,
|
|
162
|
+
args=request.args,
|
|
163
|
+
errors=errors,
|
|
164
|
+
logger=logger)
|
|
165
|
+
result: Response
|
|
166
|
+
if token:
|
|
167
|
+
result = jsonify({"token": token})
|
|
168
|
+
else:
|
|
169
|
+
result = Response("; ".join(errors))
|
|
170
|
+
result.status_code = 401
|
|
171
|
+
|
|
172
|
+
# log the response
|
|
173
|
+
if logger:
|
|
174
|
+
logger.debug(msg=f"Response {result}")
|
|
175
|
+
|
|
176
|
+
return result
|