digitalhub 0.14.0b5__py3-none-any.whl → 0.14.9__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.
- digitalhub/__init__.py +2 -2
- digitalhub/context/api.py +43 -6
- digitalhub/context/builder.py +1 -1
- digitalhub/context/context.py +3 -6
- digitalhub/entities/_base/context/entity.py +0 -3
- digitalhub/entities/_base/executable/entity.py +29 -11
- digitalhub/entities/_base/material/entity.py +2 -2
- digitalhub/entities/_base/material/utils.py +0 -4
- digitalhub/entities/_commons/enums.py +1 -0
- digitalhub/entities/_commons/utils.py +19 -0
- digitalhub/entities/_processors/base/crud.py +15 -24
- digitalhub/entities/_processors/base/import_export.py +3 -7
- digitalhub/entities/_processors/base/processor.py +4 -7
- digitalhub/entities/_processors/base/special_ops.py +4 -8
- digitalhub/entities/_processors/context/crud.py +27 -29
- digitalhub/entities/_processors/context/import_export.py +7 -7
- digitalhub/entities/_processors/context/material.py +2 -2
- digitalhub/entities/_processors/context/special_ops.py +25 -25
- digitalhub/entities/_processors/utils.py +7 -116
- digitalhub/entities/artifact/crud.py +3 -3
- digitalhub/entities/artifact/utils.py +2 -2
- digitalhub/entities/builders.py +2 -0
- digitalhub/entities/dataitem/crud.py +3 -3
- digitalhub/entities/dataitem/utils.py +10 -14
- digitalhub/entities/function/_base/entity.py +0 -3
- digitalhub/entities/function/crud.py +3 -3
- digitalhub/entities/model/crud.py +3 -3
- digitalhub/entities/model/mlflow/utils.py +29 -20
- digitalhub/entities/model/utils.py +2 -2
- digitalhub/entities/project/_base/builder.py +0 -6
- digitalhub/entities/project/_base/entity.py +264 -114
- digitalhub/entities/project/_base/spec.py +4 -4
- digitalhub/entities/project/crud.py +16 -51
- digitalhub/entities/project/utils.py +7 -3
- digitalhub/entities/secret/crud.py +2 -2
- digitalhub/entities/task/_base/models.py +13 -16
- digitalhub/entities/trigger/crud.py +28 -9
- digitalhub/entities/workflow/_base/entity.py +0 -5
- digitalhub/entities/workflow/crud.py +3 -6
- digitalhub/stores/client/{dhcore/api_builder.py → api_builder.py} +2 -3
- digitalhub/stores/client/builder.py +20 -32
- digitalhub/stores/client/client.py +322 -0
- digitalhub/stores/client/{dhcore/configurator.py → configurator.py} +148 -195
- digitalhub/stores/client/{_base/enums.py → enums.py} +11 -0
- digitalhub/stores/client/header_manager.py +61 -0
- digitalhub/stores/client/http_handler.py +152 -0
- digitalhub/stores/client/{_base/key_builder.py → key_builder.py} +14 -14
- digitalhub/stores/client/{dhcore/params_builder.py → params_builder.py} +51 -12
- digitalhub/stores/client/response_processor.py +102 -0
- digitalhub/stores/client/utils.py +35 -0
- digitalhub/stores/{credentials → configurator}/api.py +5 -9
- digitalhub/stores/configurator/configurator.py +123 -0
- digitalhub/stores/{credentials → configurator}/enums.py +26 -10
- digitalhub/stores/configurator/handler.py +213 -0
- digitalhub/stores/{credentials → configurator}/ini_module.py +31 -6
- digitalhub/stores/data/_base/store.py +0 -4
- digitalhub/stores/data/api.py +4 -6
- digitalhub/stores/data/builder.py +6 -38
- digitalhub/stores/data/s3/configurator.py +30 -114
- digitalhub/stores/data/s3/store.py +9 -22
- digitalhub/stores/data/sql/configurator.py +49 -71
- digitalhub/stores/data/sql/store.py +26 -61
- digitalhub/utils/generic_utils.py +0 -12
- digitalhub/utils/git_utils.py +0 -8
- digitalhub/utils/io_utils.py +0 -8
- digitalhub/utils/store_utils.py +1 -1
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/METADATA +3 -3
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/RECORD +73 -86
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/WHEEL +1 -1
- digitalhub/stores/client/_base/api_builder.py +0 -34
- digitalhub/stores/client/_base/client.py +0 -243
- digitalhub/stores/client/_base/params_builder.py +0 -82
- digitalhub/stores/client/api.py +0 -32
- digitalhub/stores/client/dhcore/__init__.py +0 -3
- digitalhub/stores/client/dhcore/client.py +0 -553
- digitalhub/stores/client/dhcore/enums.py +0 -18
- digitalhub/stores/client/dhcore/key_builder.py +0 -62
- digitalhub/stores/client/dhcore/utils.py +0 -86
- digitalhub/stores/client/local/__init__.py +0 -3
- digitalhub/stores/client/local/api_builder.py +0 -116
- digitalhub/stores/client/local/client.py +0 -605
- digitalhub/stores/client/local/enums.py +0 -15
- digitalhub/stores/client/local/key_builder.py +0 -62
- digitalhub/stores/client/local/params_builder.py +0 -97
- digitalhub/stores/credentials/__init__.py +0 -3
- digitalhub/stores/credentials/configurator.py +0 -185
- digitalhub/stores/credentials/handler.py +0 -164
- digitalhub/stores/credentials/store.py +0 -77
- digitalhub/stores/data/enums.py +0 -15
- /digitalhub/stores/client/{dhcore/error_parser.py → error_parser.py} +0 -0
- /digitalhub/stores/{client/_base → configurator}/__init__.py +0 -0
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/licenses/AUTHORS +0 -0
- {digitalhub-0.14.0b5.dist-info → digitalhub-0.14.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import typing
|
|
8
|
+
from warnings import warn
|
|
8
9
|
|
|
9
10
|
from requests import request
|
|
10
11
|
|
|
11
|
-
from digitalhub.stores.client.
|
|
12
|
-
from digitalhub.stores.
|
|
13
|
-
from digitalhub.stores.
|
|
14
|
-
from digitalhub.stores.credentials.handler import creds_handler
|
|
12
|
+
from digitalhub.stores.client.enums import AuthType
|
|
13
|
+
from digitalhub.stores.configurator.configurator import configurator
|
|
14
|
+
from digitalhub.stores.configurator.enums import ConfigurationVars, CredentialsVars
|
|
15
15
|
from digitalhub.utils.exceptions import ClientError
|
|
16
16
|
from digitalhub.utils.generic_utils import list_enum
|
|
17
17
|
from digitalhub.utils.uri_utils import has_remote_scheme
|
|
@@ -20,7 +20,10 @@ if typing.TYPE_CHECKING:
|
|
|
20
20
|
from requests import Response
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
DEFAULT_TIMEOUT = 60
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ClientConfigurator:
|
|
24
27
|
"""
|
|
25
28
|
DHCore client configurator for credential management and authentication.
|
|
26
29
|
|
|
@@ -35,20 +38,13 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
35
38
|
credential storage.
|
|
36
39
|
"""
|
|
37
40
|
|
|
38
|
-
keys = [*list_enum(
|
|
39
|
-
required_keys = [CredsEnvVar.DHCORE_ENDPOINT.value]
|
|
40
|
-
keys_to_prefix = [
|
|
41
|
-
CredsEnvVar.DHCORE_REFRESH_TOKEN.value,
|
|
42
|
-
CredsEnvVar.DHCORE_ACCESS_TOKEN.value,
|
|
43
|
-
CredsEnvVar.DHCORE_ISSUER.value,
|
|
44
|
-
CredsEnvVar.DHCORE_CLIENT_ID.value,
|
|
45
|
-
]
|
|
41
|
+
keys = [*list_enum(ConfigurationVars), *list_enum(CredentialsVars)]
|
|
46
42
|
|
|
47
43
|
def __init__(self) -> None:
|
|
48
44
|
"""
|
|
49
45
|
Initialize DHCore configurator and evaluate authentication type.
|
|
50
46
|
"""
|
|
51
|
-
|
|
47
|
+
self._validate()
|
|
52
48
|
self._auth_type: str | None = None
|
|
53
49
|
self.set_auth_type()
|
|
54
50
|
|
|
@@ -56,92 +52,6 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
56
52
|
# Credentials methods
|
|
57
53
|
##############################
|
|
58
54
|
|
|
59
|
-
def load_env_vars(self) -> None:
|
|
60
|
-
"""
|
|
61
|
-
Load and sanitize credentials from environment variables.
|
|
62
|
-
|
|
63
|
-
Sanitizes endpoint and issuer URLs to ensure proper HTTP/HTTPS schemes
|
|
64
|
-
and removes trailing slashes.
|
|
65
|
-
"""
|
|
66
|
-
env_creds = self._creds_handler.load_from_env(self.keys)
|
|
67
|
-
env_creds = self._sanitize_env_vars(env_creds)
|
|
68
|
-
self._creds_handler.set_credentials(self._env, env_creds)
|
|
69
|
-
|
|
70
|
-
def _sanitize_env_vars(self, creds: dict) -> dict:
|
|
71
|
-
"""
|
|
72
|
-
Sanitize environment variable credentials.
|
|
73
|
-
|
|
74
|
-
Validates and normalizes endpoint and issuer URLs. Environment variables
|
|
75
|
-
use full "DHCORE_" prefixes.
|
|
76
|
-
|
|
77
|
-
Parameters
|
|
78
|
-
----------
|
|
79
|
-
creds : dict
|
|
80
|
-
Raw credentials from environment variables.
|
|
81
|
-
|
|
82
|
-
Returns
|
|
83
|
-
-------
|
|
84
|
-
dict
|
|
85
|
-
Sanitized credentials with normalized URLs.
|
|
86
|
-
|
|
87
|
-
Raises
|
|
88
|
-
------
|
|
89
|
-
ClientError
|
|
90
|
-
If endpoint or issuer URLs have invalid schemes.
|
|
91
|
-
"""
|
|
92
|
-
creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
|
|
93
|
-
creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
|
|
94
|
-
return creds
|
|
95
|
-
|
|
96
|
-
def load_file_vars(self) -> None:
|
|
97
|
-
"""
|
|
98
|
-
Load credentials from configuration file with CLI compatibility.
|
|
99
|
-
|
|
100
|
-
Handles keys without "DHCORE_" prefix for CLI compatibility. Falls back
|
|
101
|
-
to environment variables for missing endpoint and personal access token values.
|
|
102
|
-
"""
|
|
103
|
-
file_creds = self._creds_handler.load_from_file(self.keys)
|
|
104
|
-
|
|
105
|
-
# Because in the response there is no personal access token
|
|
106
|
-
pat = CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value
|
|
107
|
-
if file_creds[pat] is None:
|
|
108
|
-
file_creds[pat] = self._creds_handler.load_from_env([pat]).get(pat)
|
|
109
|
-
|
|
110
|
-
# Because in the response there is no endpoint
|
|
111
|
-
endpoint = CredsEnvVar.DHCORE_ENDPOINT.value
|
|
112
|
-
if file_creds[endpoint] is None:
|
|
113
|
-
file_creds[endpoint] = self._creds_handler.load_from_env([endpoint]).get(endpoint)
|
|
114
|
-
|
|
115
|
-
file_creds = self._sanitize_file_vars(file_creds)
|
|
116
|
-
self._creds_handler.set_credentials(self._file, file_creds)
|
|
117
|
-
|
|
118
|
-
def _sanitize_file_vars(self, creds: dict) -> dict:
|
|
119
|
-
"""
|
|
120
|
-
Sanitize configuration file credentials.
|
|
121
|
-
|
|
122
|
-
Handles different key formats between file and environment variables.
|
|
123
|
-
File format omits "DHCORE_" prefix for: issuer, client_id, access_token,
|
|
124
|
-
refresh_token. Full names used for: endpoint, user, password, personal_access_token.
|
|
125
|
-
|
|
126
|
-
Parameters
|
|
127
|
-
----------
|
|
128
|
-
creds : dict
|
|
129
|
-
Raw credentials from configuration file.
|
|
130
|
-
|
|
131
|
-
Returns
|
|
132
|
-
-------
|
|
133
|
-
dict
|
|
134
|
-
Sanitized credentials with standardized keys and normalized URLs.
|
|
135
|
-
|
|
136
|
-
Raises
|
|
137
|
-
------
|
|
138
|
-
ClientError
|
|
139
|
-
If endpoint or issuer URLs have invalid schemes.
|
|
140
|
-
"""
|
|
141
|
-
creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
|
|
142
|
-
creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
|
|
143
|
-
return {k: v for k, v in creds.items() if k in self.keys}
|
|
144
|
-
|
|
145
55
|
@staticmethod
|
|
146
56
|
def _sanitize_endpoint(endpoint: str | None = None) -> str | None:
|
|
147
57
|
"""
|
|
@@ -151,7 +61,7 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
151
61
|
|
|
152
62
|
Parameters
|
|
153
63
|
----------
|
|
154
|
-
endpoint : str
|
|
64
|
+
endpoint : str
|
|
155
65
|
Endpoint URL to sanitize.
|
|
156
66
|
|
|
157
67
|
Returns
|
|
@@ -188,24 +98,9 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
188
98
|
KeyError
|
|
189
99
|
If endpoint not configured in current credential source.
|
|
190
100
|
"""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
##############################
|
|
195
|
-
# Origin methods
|
|
196
|
-
##############################
|
|
197
|
-
|
|
198
|
-
def change_origin(self) -> None:
|
|
199
|
-
"""
|
|
200
|
-
Switch credential source and re-evaluate authentication type.
|
|
201
|
-
|
|
202
|
-
Changes between environment and file credential sources, then re-evaluates
|
|
203
|
-
authentication type based on the new credentials.
|
|
204
|
-
"""
|
|
205
|
-
super().change_origin()
|
|
206
|
-
|
|
207
|
-
# Re-evaluate the auth type
|
|
208
|
-
self.set_auth_type()
|
|
101
|
+
config = configurator.get_configuration()
|
|
102
|
+
endpoint = config[ConfigurationVars.DHCORE_ENDPOINT.value]
|
|
103
|
+
return self._sanitize_endpoint(endpoint)
|
|
209
104
|
|
|
210
105
|
##############################
|
|
211
106
|
# Auth methods
|
|
@@ -220,15 +115,13 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
220
115
|
(username + password). For EXCHANGE type, automatically exchanges the
|
|
221
116
|
personal access token and switches to file-based credentials storage.
|
|
222
117
|
"""
|
|
223
|
-
creds =
|
|
118
|
+
creds = configurator.get_credentials()
|
|
224
119
|
self._auth_type = self._eval_auth_type(creds)
|
|
225
120
|
# If we have an exchange token, we need to get a new access token.
|
|
226
121
|
# Therefore, we change the origin to file, where the refresh token is written.
|
|
227
122
|
# We also try to fetch the PAT from both env and file
|
|
228
123
|
if self._auth_type == AuthType.EXCHANGE.value:
|
|
229
|
-
self.refresh_credentials(
|
|
230
|
-
# Just to ensure we get the right source from file
|
|
231
|
-
self.change_to_file()
|
|
124
|
+
self.refresh_credentials()
|
|
232
125
|
|
|
233
126
|
def refreshable_auth_types(self) -> bool:
|
|
234
127
|
"""
|
|
@@ -261,84 +154,106 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
261
154
|
dict
|
|
262
155
|
Modified kwargs with authentication parameters.
|
|
263
156
|
"""
|
|
264
|
-
creds =
|
|
157
|
+
creds = configurator.get_credentials()
|
|
265
158
|
if self._auth_type in (
|
|
266
159
|
AuthType.EXCHANGE.value,
|
|
267
160
|
AuthType.OAUTH2.value,
|
|
268
161
|
AuthType.ACCESS_TOKEN.value,
|
|
269
162
|
):
|
|
270
|
-
access_token = creds[
|
|
163
|
+
access_token = creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value]
|
|
271
164
|
if "headers" not in kwargs:
|
|
272
165
|
kwargs["headers"] = {}
|
|
273
166
|
kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
|
|
274
167
|
elif self._auth_type == AuthType.BASIC.value:
|
|
275
|
-
user = creds[
|
|
276
|
-
password = creds[
|
|
168
|
+
user = creds[CredentialsVars.DHCORE_USER.value]
|
|
169
|
+
password = creds[CredentialsVars.DHCORE_PASSWORD.value]
|
|
277
170
|
kwargs["auth"] = (user, password)
|
|
278
171
|
return kwargs
|
|
279
172
|
|
|
280
|
-
def
|
|
173
|
+
def _evaluate_auth_flow(self, url: str, creds: dict) -> Response:
|
|
281
174
|
"""
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
Uses refresh_token grant for OAUTH2 or token exchange for EXCHANGE authentication.
|
|
285
|
-
On 400/401/403 errors with change_origin=True, attempts to switch credential
|
|
286
|
-
sources and retry. Saves new credentials to configuration file.
|
|
175
|
+
Evaluate the auth flow to execute.
|
|
287
176
|
|
|
288
177
|
Parameters
|
|
289
178
|
----------
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
------
|
|
295
|
-
ClientError
|
|
296
|
-
If auth type doesn't support refresh or credentials missing.
|
|
179
|
+
url : str
|
|
180
|
+
Token endpoint URL.
|
|
181
|
+
creds : dict
|
|
182
|
+
Available credential values.
|
|
297
183
|
"""
|
|
298
|
-
if
|
|
299
|
-
raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
|
|
300
|
-
|
|
301
|
-
# Get refresh endpoint
|
|
302
|
-
url = self._get_refresh_endpoint()
|
|
303
|
-
|
|
304
|
-
# Get credentials
|
|
305
|
-
creds = self._creds_handler.get_credentials(self._origin)
|
|
306
|
-
|
|
307
|
-
# Get client id
|
|
308
|
-
if (client_id := creds.get(CredsEnvVar.DHCORE_CLIENT_ID.value)) is None:
|
|
184
|
+
if (client_id := creds.get(ConfigurationVars.DHCORE_CLIENT_ID.value)) is None:
|
|
309
185
|
raise ClientError("Client id not set.")
|
|
310
186
|
|
|
311
|
-
# Handling of token
|
|
187
|
+
# Handling of token refresh
|
|
312
188
|
if self._auth_type == AuthType.OAUTH2.value:
|
|
313
|
-
|
|
189
|
+
return self._call_refresh_endpoint(
|
|
314
190
|
url,
|
|
315
191
|
client_id=client_id,
|
|
316
|
-
refresh_token=creds.get(
|
|
192
|
+
refresh_token=creds.get(CredentialsVars.DHCORE_REFRESH_TOKEN.value),
|
|
317
193
|
grant_type="refresh_token",
|
|
318
194
|
scope="credentials",
|
|
319
195
|
)
|
|
320
|
-
elif self._auth_type == AuthType.EXCHANGE.value:
|
|
321
|
-
response = self._call_refresh_endpoint(
|
|
322
|
-
url,
|
|
323
|
-
client_id=client_id,
|
|
324
|
-
subject_token=creds.get(CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value),
|
|
325
|
-
subject_token_type="urn:ietf:params:oauth:token-type:pat",
|
|
326
|
-
grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
|
|
327
|
-
scope="credentials",
|
|
328
|
-
)
|
|
329
196
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
197
|
+
## Handling of token exchange
|
|
198
|
+
return self._call_refresh_endpoint(
|
|
199
|
+
url,
|
|
200
|
+
client_id=client_id,
|
|
201
|
+
subject_token=creds.get(CredentialsVars.DHCORE_PERSONAL_ACCESS_TOKEN.value),
|
|
202
|
+
subject_token_type="urn:ietf:params:oauth:token-type:pat",
|
|
203
|
+
grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
|
|
204
|
+
scope="credentials",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
def refresh_credentials(self) -> None:
|
|
208
|
+
"""
|
|
209
|
+
Refresh authentication tokens using OAuth2 flows.
|
|
210
|
+
"""
|
|
211
|
+
if not self.refreshable_auth_types():
|
|
212
|
+
raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
|
|
213
|
+
|
|
214
|
+
# Get credentials and configuration
|
|
215
|
+
creds = configurator.get_config_creds()
|
|
216
|
+
|
|
217
|
+
# Get token refresh from creds
|
|
218
|
+
if (url := creds.get(ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value)) is None:
|
|
219
|
+
url = self._get_refresh_endpoint()
|
|
220
|
+
url = self._sanitize_endpoint(url)
|
|
336
221
|
|
|
222
|
+
# Execute the appropriate auth flow
|
|
223
|
+
response = self._evaluate_auth_flow(url, creds)
|
|
224
|
+
|
|
225
|
+
# Raise an error if the response indicates failure
|
|
337
226
|
response.raise_for_status()
|
|
338
227
|
|
|
339
|
-
#
|
|
228
|
+
# Export new credentials to file
|
|
340
229
|
self._export_new_creds(response.json())
|
|
341
230
|
|
|
231
|
+
configurator.reload_credentials()
|
|
232
|
+
|
|
233
|
+
def evaluate_refresh(self) -> bool:
|
|
234
|
+
"""
|
|
235
|
+
Check if token refresh should be attempted.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
bool
|
|
240
|
+
True if token refresh is applicable, otherwise False.
|
|
241
|
+
"""
|
|
242
|
+
try:
|
|
243
|
+
self.refresh_credentials()
|
|
244
|
+
return True
|
|
245
|
+
except Exception:
|
|
246
|
+
if not configurator.eval_retry():
|
|
247
|
+
warn(
|
|
248
|
+
"Failed to refresh credentials after retry"
|
|
249
|
+
" (checked credentials from file and env)."
|
|
250
|
+
" Please check your credentials"
|
|
251
|
+
" and make sure they are up to date."
|
|
252
|
+
" (refresh tokens, password, etc.)."
|
|
253
|
+
)
|
|
254
|
+
return False
|
|
255
|
+
return self.evaluate_refresh()
|
|
256
|
+
|
|
342
257
|
def _get_refresh_endpoint(self) -> str:
|
|
343
258
|
"""
|
|
344
259
|
Discover OAuth2 token endpoint from issuer well-known configuration.
|
|
@@ -346,28 +261,25 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
346
261
|
Queries /.well-known/openid-configuration to extract token_endpoint for
|
|
347
262
|
credential refresh operations.
|
|
348
263
|
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
creds : dict
|
|
267
|
+
Available credential values.
|
|
268
|
+
|
|
349
269
|
Returns
|
|
350
270
|
-------
|
|
351
271
|
str
|
|
352
272
|
Token endpoint URL for credential refresh.
|
|
353
|
-
|
|
354
|
-
Raises
|
|
355
|
-
------
|
|
356
|
-
ClientError
|
|
357
|
-
If issuer endpoint not configured.
|
|
358
|
-
HTTPError
|
|
359
|
-
If well-known configuration endpoint inaccessible.
|
|
360
|
-
KeyError
|
|
361
|
-
If token_endpoint not found in issuer configuration.
|
|
362
273
|
"""
|
|
274
|
+
config = configurator.get_configuration()
|
|
275
|
+
|
|
363
276
|
# Get issuer endpoint
|
|
364
|
-
|
|
365
|
-
endpoint_issuer = creds.get(CredsEnvVar.DHCORE_ISSUER.value)
|
|
366
|
-
if endpoint_issuer is None:
|
|
277
|
+
if (endpoint_issuer := config.get(ConfigurationVars.DHCORE_ISSUER.value)) is None:
|
|
367
278
|
raise ClientError("Issuer endpoint not set.")
|
|
368
279
|
|
|
369
280
|
# Standard issuer endpoint path
|
|
370
281
|
url = endpoint_issuer + "/.well-known/openid-configuration"
|
|
282
|
+
url = self._sanitize_endpoint(url)
|
|
371
283
|
|
|
372
284
|
# Call issuer to get refresh endpoint
|
|
373
285
|
r = request("GET", url, timeout=60)
|
|
@@ -400,7 +312,13 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
400
312
|
# Send request to get new access token
|
|
401
313
|
payload = {**kwargs}
|
|
402
314
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
403
|
-
return request(
|
|
315
|
+
return request(
|
|
316
|
+
"POST",
|
|
317
|
+
url,
|
|
318
|
+
data=payload,
|
|
319
|
+
headers=headers,
|
|
320
|
+
timeout=DEFAULT_TIMEOUT,
|
|
321
|
+
)
|
|
404
322
|
|
|
405
323
|
def _eval_auth_type(self, creds: dict) -> str | None:
|
|
406
324
|
"""
|
|
@@ -419,16 +337,19 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
419
337
|
str or None
|
|
420
338
|
Authentication type from AuthType enum, or None if no valid credentials.
|
|
421
339
|
"""
|
|
422
|
-
if creds[
|
|
340
|
+
if creds[CredentialsVars.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
|
|
423
341
|
return AuthType.EXCHANGE.value
|
|
424
342
|
if (
|
|
425
|
-
creds[
|
|
426
|
-
and creds[
|
|
343
|
+
creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value] is not None
|
|
344
|
+
and creds[CredentialsVars.DHCORE_REFRESH_TOKEN.value] is not None
|
|
427
345
|
):
|
|
428
346
|
return AuthType.OAUTH2.value
|
|
429
|
-
if creds[
|
|
347
|
+
if creds[CredentialsVars.DHCORE_ACCESS_TOKEN.value] is not None:
|
|
430
348
|
return AuthType.ACCESS_TOKEN.value
|
|
431
|
-
if
|
|
349
|
+
if (
|
|
350
|
+
creds[CredentialsVars.DHCORE_USER.value] is not None
|
|
351
|
+
and creds[CredentialsVars.DHCORE_PASSWORD.value] is not None
|
|
352
|
+
):
|
|
432
353
|
return AuthType.BASIC.value
|
|
433
354
|
return None
|
|
434
355
|
|
|
@@ -444,12 +365,44 @@ class ClientDHCoreConfigurator(Configurator):
|
|
|
444
365
|
response : dict
|
|
445
366
|
OAuth2 token response with new credentials.
|
|
446
367
|
"""
|
|
447
|
-
|
|
368
|
+
keys_to_prefix = [
|
|
369
|
+
CredentialsVars.DHCORE_REFRESH_TOKEN.value,
|
|
370
|
+
CredentialsVars.DHCORE_ACCESS_TOKEN.value,
|
|
371
|
+
ConfigurationVars.DHCORE_CLIENT_ID.value,
|
|
372
|
+
ConfigurationVars.DHCORE_ISSUER.value,
|
|
373
|
+
ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value,
|
|
374
|
+
]
|
|
375
|
+
for key in keys_to_prefix:
|
|
376
|
+
if key == ConfigurationVars.OAUTH2_TOKEN_ENDPOINT.value:
|
|
377
|
+
prefix = "oauth2_"
|
|
378
|
+
else:
|
|
379
|
+
prefix = "dhcore_"
|
|
448
380
|
key = key.lower()
|
|
449
|
-
if key.removeprefix(
|
|
450
|
-
response[key] = response.pop(key.removeprefix(
|
|
451
|
-
|
|
452
|
-
|
|
381
|
+
if key.removeprefix(prefix) in response:
|
|
382
|
+
response[key] = response.pop(key.removeprefix(prefix))
|
|
383
|
+
configurator.write_file(response)
|
|
384
|
+
|
|
385
|
+
def _validate(self) -> None:
|
|
386
|
+
"""
|
|
387
|
+
Validate if all required keys are present in the configuration.
|
|
388
|
+
"""
|
|
389
|
+
required_keys = [ConfigurationVars.DHCORE_ENDPOINT.value]
|
|
390
|
+
current_keys = configurator.get_config_creds()
|
|
391
|
+
for key in required_keys:
|
|
392
|
+
if current_keys.get(key) is None:
|
|
393
|
+
raise ClientError(f"Required configuration key '{key}' is missing.")
|
|
453
394
|
|
|
454
|
-
|
|
455
|
-
|
|
395
|
+
###############################
|
|
396
|
+
# Utility methods
|
|
397
|
+
###############################
|
|
398
|
+
|
|
399
|
+
def get_credentials_and_config(self) -> dict:
|
|
400
|
+
"""
|
|
401
|
+
Get current authentication credentials and configuration.
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
dict
|
|
406
|
+
Current authentication credentials and configuration.
|
|
407
|
+
"""
|
|
408
|
+
return configurator.get_config_creds()
|
|
@@ -7,6 +7,17 @@ from __future__ import annotations
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
class AuthType(Enum):
|
|
11
|
+
"""
|
|
12
|
+
Authentication types.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
BASIC = "basic"
|
|
16
|
+
OAUTH2 = "oauth2"
|
|
17
|
+
EXCHANGE = "exchange"
|
|
18
|
+
ACCESS_TOKEN = "access_token_only"
|
|
19
|
+
|
|
20
|
+
|
|
10
21
|
class ApiCategories(Enum):
|
|
11
22
|
"""
|
|
12
23
|
API categories.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: © 2025 DSLab - Fondazione Bruno Kessler
|
|
2
|
+
#
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HeaderManager:
|
|
9
|
+
"""
|
|
10
|
+
Manages HTTP headers for DHCore client requests.
|
|
11
|
+
|
|
12
|
+
Provides utilities for setting and managing common HTTP headers
|
|
13
|
+
like Content-Type for JSON requests.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
@staticmethod
|
|
17
|
+
def ensure_headers(**kwargs) -> dict:
|
|
18
|
+
"""
|
|
19
|
+
Initialize headers dictionary in kwargs.
|
|
20
|
+
|
|
21
|
+
Ensures parameter dictionary has 'headers' key for HTTP headers,
|
|
22
|
+
guaranteeing consistent structure for all parameter building methods.
|
|
23
|
+
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
**kwargs : dict
|
|
27
|
+
Keyword arguments to format. May be empty or contain various
|
|
28
|
+
parameters for API operations.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
dict
|
|
33
|
+
Dictionary with guaranteed 'headers' key containing
|
|
34
|
+
empty dict if not already present.
|
|
35
|
+
"""
|
|
36
|
+
if "headers" not in kwargs:
|
|
37
|
+
kwargs["headers"] = {}
|
|
38
|
+
return kwargs
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def set_json_content_type(**kwargs) -> dict:
|
|
42
|
+
"""
|
|
43
|
+
Set Content-Type header to application/json.
|
|
44
|
+
|
|
45
|
+
Ensures that the 'Content-Type' header is set to 'application/json'
|
|
46
|
+
for requests that require JSON payloads.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
**kwargs : dict
|
|
51
|
+
Keyword arguments to format. May be empty or contain various
|
|
52
|
+
parameters for API operations.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
dict
|
|
57
|
+
Dictionary with 'Content-Type' header set to 'application/json'.
|
|
58
|
+
"""
|
|
59
|
+
kwargs = HeaderManager.ensure_headers(**kwargs)
|
|
60
|
+
kwargs["headers"]["Content-Type"] = "application/json"
|
|
61
|
+
return kwargs
|