pypomes-iam 0.1.7__tar.gz → 0.1.9__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.
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/PKG-INFO +2 -2
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/pyproject.toml +2 -2
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/src/pypomes_iam/__init__.py +5 -0
- pypomes_iam-0.1.9/src/pypomes_iam/common_pomes.py +423 -0
- pypomes_iam-0.1.9/src/pypomes_iam/jusbr_pomes.py +305 -0
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/src/pypomes_iam/keycloak_pomes.py +149 -53
- pypomes_iam-0.1.7/src/pypomes_iam/jusbr_pomes.py +0 -561
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/.gitignore +0 -0
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/LICENSE +0 -0
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/README.md +0 -0
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/src/pypomes_iam/provider_pomes.py +0 -0
- {pypomes_iam-0.1.7 → pypomes_iam-0.1.9}/src/pypomes_iam/token_pomes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pypomes_iam
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
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
|
|
@@ -13,6 +13,6 @@ Requires-Python: >=3.12
|
|
|
13
13
|
Requires-Dist: cachetools>=6.2.1
|
|
14
14
|
Requires-Dist: flask>=3.1.2
|
|
15
15
|
Requires-Dist: pyjwt>=2.10.1
|
|
16
|
-
Requires-Dist: pypomes-core>=2.8.
|
|
16
|
+
Requires-Dist: pypomes-core>=2.8.1
|
|
17
17
|
Requires-Dist: pypomes-crypto>=0.4.8
|
|
18
18
|
Requires-Dist: requests>=2.32.5
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
|
|
7
7
|
[project]
|
|
8
8
|
name = "pypomes_iam"
|
|
9
|
-
version = "0.1.
|
|
9
|
+
version = "0.1.9"
|
|
10
10
|
authors = [
|
|
11
11
|
{ name="GT Nunes", email="wisecoder01@gmail.com" }
|
|
12
12
|
]
|
|
@@ -22,7 +22,7 @@ dependencies = [
|
|
|
22
22
|
"cachetools>=6.2.1",
|
|
23
23
|
"Flask>=3.1.2",
|
|
24
24
|
"PyJWT>=2.10.1",
|
|
25
|
-
"pypomes-core>=2.8.
|
|
25
|
+
"pypomes-core>=2.8.1",
|
|
26
26
|
"pypomes-crypto>=0.4.8",
|
|
27
27
|
"requests>=2.32.5"
|
|
28
28
|
]
|
|
@@ -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
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
import secrets
|
|
4
|
+
import string
|
|
5
|
+
import sys
|
|
6
|
+
from cachetools import Cache
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from flask import Request
|
|
9
|
+
from logging import Logger
|
|
10
|
+
from pypomes_core import TZ_LOCAL, exc_format
|
|
11
|
+
from typing import Any
|
|
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
|
+
|
|
222
|
+
|
|
223
|
+
def _get_public_key(registry: dict[str, Any],
|
|
224
|
+
logger: Logger | None) -> bytes:
|
|
225
|
+
"""
|
|
226
|
+
Obtain the public key used by the *IAM* to sign the authentication tokens.
|
|
227
|
+
|
|
228
|
+
The public key is saved in *registry*.
|
|
229
|
+
|
|
230
|
+
:param registry: the registry holding the authentication data
|
|
231
|
+
:return: the public key, in *DER* format
|
|
232
|
+
"""
|
|
233
|
+
from pypomes_crypto import crypto_jwk_convert
|
|
234
|
+
|
|
235
|
+
# initialize the return variable
|
|
236
|
+
result: bytes | None = None
|
|
237
|
+
|
|
238
|
+
now: int = int(datetime.now(tz=TZ_LOCAL).timestamp())
|
|
239
|
+
if now > registry["key-expiration"]:
|
|
240
|
+
# obtain a new public key
|
|
241
|
+
url: str = f"{registry["base-url"]}/protocol/openid-connect/certs"
|
|
242
|
+
if logger:
|
|
243
|
+
logger.debug(msg=f"GET '{url}'")
|
|
244
|
+
response: requests.Response = requests.get(url=url)
|
|
245
|
+
if response.status_code == 200:
|
|
246
|
+
# request succeeded
|
|
247
|
+
if logger:
|
|
248
|
+
logger.debug(msg=f"GET success, status {response.status_code}")
|
|
249
|
+
reply: dict[str, Any] = response.json()
|
|
250
|
+
result = crypto_jwk_convert(jwk=reply["keys"][0],
|
|
251
|
+
fmt="DER")
|
|
252
|
+
registry["public-key"] = result
|
|
253
|
+
duration: int = registry["key-lifetime"] or 0
|
|
254
|
+
registry["key-expiration"] = now + duration
|
|
255
|
+
elif logger:
|
|
256
|
+
msg: str = f"GET failure, status {response.status_code}, reason '{response.reason}'"
|
|
257
|
+
if hasattr(response, "content") and response.content:
|
|
258
|
+
msg += f", content '{response.content}'"
|
|
259
|
+
logger.error(msg=msg)
|
|
260
|
+
else:
|
|
261
|
+
result = registry["public-key"]
|
|
262
|
+
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _get_login_timeout(registry: dict[str, Any]) -> int | None:
|
|
267
|
+
"""
|
|
268
|
+
Retrieve from *registry* the timeout currently applicable for the login operation.
|
|
269
|
+
|
|
270
|
+
:param registry: the registry holding the authentication data
|
|
271
|
+
:return: the current login timeout, or *None* if none has been set.
|
|
272
|
+
"""
|
|
273
|
+
timeout: int = registry.get("client-timeout")
|
|
274
|
+
return timeout if isinstance(timeout, int) and timeout > 0 else None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _get_user_data(registry: dict[str, Any],
|
|
278
|
+
user_id: str,
|
|
279
|
+
logger: Logger | None) -> dict[str, Any]:
|
|
280
|
+
"""
|
|
281
|
+
Retrieve the data for *user_id* from *registry*.
|
|
282
|
+
|
|
283
|
+
If an entry is not found for *user_id* in the registry, it is created.
|
|
284
|
+
It will remain there until the user is logged out.
|
|
285
|
+
|
|
286
|
+
:param registry: the registry holding the authentication data
|
|
287
|
+
:return: the data for *user_id* in the registry
|
|
288
|
+
"""
|
|
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)
|
|
292
|
+
if not result:
|
|
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
|
|
299
|
+
if logger:
|
|
300
|
+
logger.debug(msg=f"Entry for user '{user_id}' added to the registry")
|
|
301
|
+
elif logger:
|
|
302
|
+
logger.debug(msg=f"Entry for user '{user_id}' obtained from the registry")
|
|
303
|
+
|
|
304
|
+
return result
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _get_user_scope(registry: dict[str, Any],
|
|
308
|
+
user_id: str) -> str | None:
|
|
309
|
+
"""
|
|
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
|
|
315
|
+
"""
|
|
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)
|
|
407
|
+
if logger:
|
|
408
|
+
logger.error(msg=err_msg)
|
|
409
|
+
|
|
410
|
+
return result
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _log_init(request: Request) -> str:
|
|
414
|
+
"""
|
|
415
|
+
Build the messages for logging the request entry.
|
|
416
|
+
|
|
417
|
+
:param request: the Request object
|
|
418
|
+
:return: the log message
|
|
419
|
+
"""
|
|
420
|
+
|
|
421
|
+
params: str = json.dumps(obj=request.args,
|
|
422
|
+
ensure_ascii=False)
|
|
423
|
+
return f"Request {request.method}:{request.path}, params {params}"
|