digitalhub 0.12.0__py3-none-any.whl → 0.13.0b1__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 digitalhub might be problematic. Click here for more details.
- digitalhub/__init__.py +1 -1
- digitalhub/entities/_base/executable/entity.py +105 -57
- digitalhub/entities/_base/material/entity.py +8 -15
- digitalhub/entities/_base/material/utils.py +1 -1
- digitalhub/entities/_processors/context.py +3 -3
- digitalhub/entities/artifact/crud.py +4 -0
- digitalhub/entities/dataitem/crud.py +4 -0
- digitalhub/entities/model/crud.py +4 -0
- digitalhub/entities/project/_base/entity.py +0 -2
- digitalhub/entities/run/_base/entity.py +2 -2
- digitalhub/entities/trigger/_base/entity.py +11 -0
- digitalhub/stores/client/dhcore/client.py +57 -20
- digitalhub/stores/client/dhcore/configurator.py +280 -183
- digitalhub/stores/client/dhcore/enums.py +3 -0
- digitalhub/stores/client/dhcore/utils.py +8 -8
- digitalhub/stores/{configurator → credentials}/api.py +3 -3
- digitalhub/stores/credentials/configurator.py +37 -0
- digitalhub/stores/credentials/enums.py +54 -0
- digitalhub/stores/{configurator/configurator.py → credentials/handler.py} +23 -77
- digitalhub/stores/{configurator → credentials}/ini_module.py +1 -1
- digitalhub/stores/credentials/store.py +49 -0
- digitalhub/stores/data/_base/store.py +19 -6
- digitalhub/stores/data/api.py +37 -1
- digitalhub/stores/data/builder.py +46 -53
- digitalhub/stores/data/local/store.py +4 -7
- digitalhub/stores/data/remote/store.py +4 -7
- digitalhub/stores/data/s3/configurator.py +36 -92
- digitalhub/stores/data/s3/store.py +42 -57
- digitalhub/stores/data/s3/utils.py +1 -1
- digitalhub/stores/data/sql/configurator.py +35 -83
- digitalhub/stores/data/sql/store.py +15 -15
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0b1.dist-info}/METADATA +1 -1
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0b1.dist-info}/RECORD +37 -39
- digitalhub/stores/configurator/credentials_store.py +0 -69
- digitalhub/stores/configurator/enums.py +0 -25
- digitalhub/stores/data/s3/enums.py +0 -20
- digitalhub/stores/data/sql/enums.py +0 -20
- digitalhub/stores/data/utils.py +0 -38
- /digitalhub/stores/{configurator → credentials}/__init__.py +0 -0
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0b1.dist-info}/WHEEL +0 -0
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0b1.dist-info}/licenses/AUTHORS +0 -0
- {digitalhub-0.12.0.dist-info → digitalhub-0.13.0b1.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,14 +5,13 @@
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import typing
|
|
8
|
-
from warnings import warn
|
|
9
8
|
|
|
10
9
|
from requests import request
|
|
11
10
|
|
|
12
|
-
from digitalhub.stores.client.dhcore.enums import AuthType
|
|
13
|
-
from digitalhub.stores.
|
|
14
|
-
from digitalhub.stores.
|
|
15
|
-
from digitalhub.stores.
|
|
11
|
+
from digitalhub.stores.client.dhcore.enums import AuthType
|
|
12
|
+
from digitalhub.stores.credentials.configurator import Configurator
|
|
13
|
+
from digitalhub.stores.credentials.enums import CredsEnvVar, CredsOrigin
|
|
14
|
+
from digitalhub.stores.credentials.handler import creds_handler
|
|
16
15
|
from digitalhub.utils.exceptions import ClientError
|
|
17
16
|
from digitalhub.utils.generic_utils import list_enum
|
|
18
17
|
from digitalhub.utils.uri_utils import has_remote_scheme
|
|
@@ -21,191 +20,282 @@ if typing.TYPE_CHECKING:
|
|
|
21
20
|
from requests import Response
|
|
22
21
|
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
AUTH_KEY = "_auth"
|
|
26
|
-
|
|
27
|
-
# API levels that are supported
|
|
28
|
-
MAX_API_LEVEL = 20
|
|
29
|
-
MIN_API_LEVEL = 11
|
|
30
|
-
LIB_VERSION = 11
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class ClientDHCoreConfigurator:
|
|
23
|
+
class ClientDHCoreConfigurator(Configurator):
|
|
34
24
|
"""
|
|
35
25
|
Configurator object used to configure the client.
|
|
26
|
+
|
|
27
|
+
The configurator starts reading the credentials from the
|
|
28
|
+
environment and from the ini file and stores them into the
|
|
29
|
+
creds_handler object.
|
|
30
|
+
|
|
31
|
+
While reading the credentials from the two sources (environment and file),
|
|
32
|
+
the configurator evaluate if the required keys are present in both sources.
|
|
33
|
+
If the required keys are not present in both sources, the configurator
|
|
34
|
+
will rise an error, otherwise decide which source to use.
|
|
35
|
+
|
|
36
|
+
Once the credentials are read, the configurator check the current profile
|
|
37
|
+
name from the ini file, and set it. The default one is __default. The
|
|
38
|
+
profile is used to discriminate a set of credentials inside the ini file.
|
|
39
|
+
|
|
40
|
+
The configurator finally set the authentication type based on the credentials.
|
|
41
|
+
The logic is the following:
|
|
42
|
+
|
|
43
|
+
1. Check for a personal access token. Use it immediately to
|
|
44
|
+
require a timed access token in an exchange endpoint.
|
|
45
|
+
Switche then the origin to file and .
|
|
46
|
+
Set the auth type to EXCHANGE.
|
|
47
|
+
2. Check for an access token and a refresh token.
|
|
48
|
+
Set the auth type to OAUTH2.
|
|
49
|
+
3. Check for username and password.
|
|
50
|
+
Set the auth type to BASIC.
|
|
51
|
+
4. If none of the above is true, leave the auth type to None.
|
|
36
52
|
"""
|
|
37
53
|
|
|
54
|
+
keys = [*list_enum(CredsEnvVar)]
|
|
55
|
+
required_keys = [CredsEnvVar.DHCORE_ENDPOINT.value]
|
|
56
|
+
keys_to_unprefix = [
|
|
57
|
+
CredsEnvVar.DHCORE_REFRESH_TOKEN.value,
|
|
58
|
+
CredsEnvVar.DHCORE_ACCESS_TOKEN.value,
|
|
59
|
+
CredsEnvVar.DHCORE_ISSUER.value,
|
|
60
|
+
CredsEnvVar.DHCORE_CLIENT_ID.value,
|
|
61
|
+
]
|
|
62
|
+
|
|
38
63
|
def __init__(self) -> None:
|
|
39
|
-
|
|
64
|
+
super().__init__()
|
|
65
|
+
self.load_configs()
|
|
66
|
+
self._origin = self.set_origin()
|
|
67
|
+
self._current_profile = creds_handler.get_current_env()
|
|
68
|
+
self._auth_type: str | None = None
|
|
69
|
+
self.set_auth_type()
|
|
40
70
|
|
|
41
71
|
##############################
|
|
42
|
-
#
|
|
72
|
+
# Credentials methods
|
|
43
73
|
##############################
|
|
44
74
|
|
|
45
|
-
def
|
|
75
|
+
def load_configs(self) -> str:
|
|
46
76
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
config : dict
|
|
52
|
-
Configuration dictionary.
|
|
77
|
+
Load the configuration from the environment and from the file.
|
|
78
|
+
"""
|
|
79
|
+
self.load_env_vars()
|
|
80
|
+
self.load_file_vars()
|
|
53
81
|
|
|
54
|
-
|
|
55
|
-
-------
|
|
56
|
-
None
|
|
82
|
+
def load_env_vars(self) -> None:
|
|
57
83
|
"""
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
Load the credentials from the environment.
|
|
85
|
+
"""
|
|
86
|
+
env_creds = {var: self._creds_handler.load_from_env(var) for var in self.keys}
|
|
87
|
+
env_creds = self._sanitize_env_vars(env_creds)
|
|
88
|
+
self._creds_handler.set_credentials(self._env, env_creds)
|
|
60
89
|
|
|
61
|
-
def
|
|
90
|
+
def _sanitize_env_vars(self, creds: dict) -> dict:
|
|
62
91
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Regarding authentication parameters, the config parameter
|
|
66
|
-
takes precedence over the env variables, and the token
|
|
67
|
-
over the basic auth. Furthermore, the config parameter is
|
|
68
|
-
validated against the proper pydantic model.
|
|
92
|
+
Sanitize the env vars. We expect issuer to have the
|
|
93
|
+
form "DHCORE_ISSUER" in env.
|
|
69
94
|
|
|
70
95
|
Parameters
|
|
71
96
|
----------
|
|
72
|
-
|
|
73
|
-
|
|
97
|
+
creds : dict
|
|
98
|
+
Credentials dictionary.
|
|
74
99
|
|
|
75
100
|
Returns
|
|
76
101
|
-------
|
|
77
|
-
|
|
102
|
+
dict
|
|
78
103
|
"""
|
|
79
|
-
self.
|
|
80
|
-
self.
|
|
104
|
+
creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
|
|
105
|
+
creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ISSUER.value])
|
|
106
|
+
return creds
|
|
81
107
|
|
|
82
|
-
def
|
|
108
|
+
def load_file_vars(self) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Load the credentials from the file.
|
|
83
111
|
"""
|
|
84
|
-
|
|
112
|
+
keys = [*self._remove_prefix_dhcore()]
|
|
113
|
+
file_creds = {var: self._creds_handler.load_from_file(var) for var in keys}
|
|
85
114
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
115
|
+
# Because in the response there is no endpoint
|
|
116
|
+
if file_creds[CredsEnvVar.DHCORE_ENDPOINT.value] is None:
|
|
117
|
+
file_creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._creds_handler.load_from_env(
|
|
118
|
+
CredsEnvVar.DHCORE_ENDPOINT.value
|
|
119
|
+
)
|
|
90
120
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if LIB_VERSION < core_api_level:
|
|
100
|
-
warn("Backend API level is higher than library version. You should consider updating the library.")
|
|
121
|
+
# Because in the response there is no personal access token
|
|
122
|
+
if file_creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is None:
|
|
123
|
+
file_creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] = self._creds_handler.load_from_env(
|
|
124
|
+
CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
file_creds = self._sanitize_file_vars(file_creds)
|
|
128
|
+
self._creds_handler.set_credentials(self._file, file_creds)
|
|
101
129
|
|
|
102
|
-
def
|
|
130
|
+
def _sanitize_file_vars(self, creds: dict) -> dict:
|
|
103
131
|
"""
|
|
104
|
-
|
|
132
|
+
Sanitize the file vars. We expect issuer, client_id and access_token and
|
|
133
|
+
refresh_token to not have the form "DHCORE_" in the file.
|
|
105
134
|
|
|
106
135
|
Parameters
|
|
107
136
|
----------
|
|
108
|
-
|
|
109
|
-
|
|
137
|
+
creds : dict
|
|
138
|
+
Credentials dictionary.
|
|
110
139
|
|
|
111
140
|
Returns
|
|
112
141
|
-------
|
|
113
|
-
|
|
114
|
-
The url.
|
|
142
|
+
dict
|
|
115
143
|
"""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
creds[CredsEnvVar.DHCORE_ENDPOINT.value] = self._sanitize_endpoint(creds[CredsEnvVar.DHCORE_ENDPOINT.value])
|
|
145
|
+
creds[CredsEnvVar.DHCORE_ISSUER.value] = self._sanitize_endpoint(
|
|
146
|
+
creds[CredsEnvVar.DHCORE_ISSUER.value.removeprefix("DHCORE_")]
|
|
147
|
+
)
|
|
148
|
+
creds[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] = creds[
|
|
149
|
+
CredsEnvVar.DHCORE_REFRESH_TOKEN.value.removeprefix("DHCORE_")
|
|
150
|
+
]
|
|
151
|
+
creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] = creds[
|
|
152
|
+
CredsEnvVar.DHCORE_ACCESS_TOKEN.value.removeprefix("DHCORE_")
|
|
153
|
+
]
|
|
154
|
+
creds[CredsEnvVar.DHCORE_CLIENT_ID.value] = creds[CredsEnvVar.DHCORE_CLIENT_ID.value.removeprefix("DHCORE_")]
|
|
155
|
+
return {k: v for k, v in creds.items() if k in self.keys}
|
|
122
156
|
|
|
123
157
|
@staticmethod
|
|
124
|
-
def _sanitize_endpoint(endpoint: str) -> str:
|
|
158
|
+
def _sanitize_endpoint(endpoint: str | None = None) -> str | None:
|
|
125
159
|
"""
|
|
126
160
|
Sanitize the endpoint.
|
|
127
161
|
|
|
128
162
|
Returns
|
|
129
163
|
-------
|
|
130
|
-
None
|
|
164
|
+
str | None
|
|
165
|
+
The sanitized endpoint.
|
|
131
166
|
"""
|
|
167
|
+
if endpoint is None:
|
|
168
|
+
return
|
|
132
169
|
if not has_remote_scheme(endpoint):
|
|
133
170
|
raise ClientError("Invalid endpoint scheme. Must start with http:// or https://.")
|
|
134
171
|
|
|
135
172
|
endpoint = endpoint.strip()
|
|
136
173
|
return endpoint.removesuffix("/")
|
|
137
174
|
|
|
138
|
-
def
|
|
175
|
+
def check_config(self) -> None:
|
|
139
176
|
"""
|
|
140
|
-
|
|
177
|
+
Check if the config is valid.
|
|
178
|
+
|
|
179
|
+
Parameters
|
|
180
|
+
----------
|
|
181
|
+
config : dict
|
|
182
|
+
Configuration dictionary.
|
|
141
183
|
|
|
142
184
|
Returns
|
|
143
185
|
-------
|
|
144
186
|
None
|
|
187
|
+
"""
|
|
188
|
+
if (current := creds_handler.get_current_env()) != self._current_profile:
|
|
189
|
+
self.load_file_vars()
|
|
190
|
+
self._current_profile = current
|
|
145
191
|
|
|
146
|
-
|
|
147
|
-
------
|
|
148
|
-
Exception
|
|
149
|
-
If the endpoint of DHCore is not set in the env variables.
|
|
192
|
+
def get_endpoint(self) -> str:
|
|
150
193
|
"""
|
|
151
|
-
|
|
152
|
-
if endpoint is None:
|
|
153
|
-
raise ClientError("Endpoint not set as environment variables.")
|
|
154
|
-
endpoint = self._sanitize_endpoint(endpoint)
|
|
155
|
-
configurator.set_credential(DhcoreEnvVar.ENDPOINT.value, endpoint)
|
|
194
|
+
Get the DHCore endpoint.
|
|
156
195
|
|
|
157
|
-
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
str
|
|
199
|
+
The endpoint.
|
|
158
200
|
"""
|
|
159
|
-
|
|
201
|
+
creds = self._creds_handler.get_credentials(self._origin)
|
|
202
|
+
return creds[CredsEnvVar.DHCORE_ENDPOINT.value]
|
|
203
|
+
|
|
204
|
+
##############################
|
|
205
|
+
# Origin methods
|
|
206
|
+
##############################
|
|
207
|
+
|
|
208
|
+
def set_origin(self) -> str:
|
|
209
|
+
"""
|
|
210
|
+
Evaluate the default origin from the credentials.
|
|
160
211
|
|
|
161
212
|
Returns
|
|
162
213
|
-------
|
|
163
|
-
|
|
214
|
+
str
|
|
215
|
+
The origin.
|
|
164
216
|
"""
|
|
165
|
-
|
|
166
|
-
access_token = self._load_dhcore_oauth_vars(DhcoreEnvVar.ACCESS_TOKEN.value)
|
|
167
|
-
if access_token is not None:
|
|
168
|
-
configurator.set_credential(AUTH_KEY, AuthType.OAUTH2.value)
|
|
169
|
-
configurator.set_credential(DhcoreEnvVar.ACCESS_TOKEN.value.removeprefix("DHCORE_"), access_token)
|
|
217
|
+
origin = CredsOrigin.ENV.value
|
|
170
218
|
|
|
171
|
-
|
|
219
|
+
env_creds = self._creds_handler.get_credentials(self._env)
|
|
220
|
+
missing_env = self._check_credentials(env_creds)
|
|
221
|
+
|
|
222
|
+
file_creds = self._creds_handler.get_credentials(self._file)
|
|
223
|
+
missing_file = self._check_credentials(file_creds)
|
|
224
|
+
|
|
225
|
+
msg = ""
|
|
226
|
+
if missing_env:
|
|
227
|
+
msg = f"Missing required vars in env: {', '.join(missing_env)}"
|
|
228
|
+
origin = CredsOrigin.FILE.value
|
|
229
|
+
elif missing_file:
|
|
230
|
+
msg += f"Missing required vars in .dhcore.ini file: {', '.join(missing_file)}"
|
|
231
|
+
|
|
232
|
+
if missing_env and missing_file:
|
|
233
|
+
raise ClientError(msg)
|
|
234
|
+
|
|
235
|
+
return origin
|
|
236
|
+
|
|
237
|
+
def change_origin(self) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Change the origin of the credentials.
|
|
240
|
+
"""
|
|
241
|
+
if self._origin == CredsOrigin.ENV.value:
|
|
242
|
+
self.change_to_file()
|
|
172
243
|
else:
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
244
|
+
self.change_to_env()
|
|
245
|
+
|
|
246
|
+
# Re-evaluate the auth type
|
|
247
|
+
self.set_auth_type()
|
|
248
|
+
|
|
249
|
+
def change_to_file(self) -> None:
|
|
250
|
+
"""
|
|
251
|
+
Change the origin to file. Re-evaluate the auth type.
|
|
252
|
+
"""
|
|
253
|
+
self._origin = CredsOrigin.FILE.value
|
|
254
|
+
|
|
255
|
+
def change_to_env(self) -> None:
|
|
256
|
+
"""
|
|
257
|
+
Change the origin to env. Re-evaluate the auth type.
|
|
258
|
+
"""
|
|
259
|
+
self._origin = CredsOrigin.ENV.value
|
|
179
260
|
|
|
180
261
|
##############################
|
|
181
262
|
# Auth methods
|
|
182
263
|
##############################
|
|
183
264
|
|
|
184
|
-
def
|
|
265
|
+
def set_auth_type(self) -> None:
|
|
185
266
|
"""
|
|
186
|
-
|
|
267
|
+
Evaluate the auth type from the credentials.
|
|
187
268
|
|
|
188
269
|
Returns
|
|
189
270
|
-------
|
|
190
|
-
|
|
271
|
+
None
|
|
191
272
|
"""
|
|
192
|
-
|
|
193
|
-
|
|
273
|
+
creds = creds_handler.get_credentials(self._origin)
|
|
274
|
+
self._auth_type = self._eval_auth_type(creds)
|
|
275
|
+
# If we have an exchange token, we need to get a new access token.
|
|
276
|
+
# Therefore, we change the origin to file, where the refresh token is written.
|
|
277
|
+
# We also try to fetch the PAT from both env and file
|
|
278
|
+
if self._auth_type == AuthType.EXCHANGE.value:
|
|
279
|
+
self.get_new_access_token(change_origin=True)
|
|
280
|
+
# Just to ensure we get the right source from file
|
|
281
|
+
self.change_to_file()
|
|
194
282
|
|
|
195
|
-
def
|
|
283
|
+
def refreshable_auth_types(self) -> bool:
|
|
196
284
|
"""
|
|
197
|
-
|
|
285
|
+
Check if the auth type is refreshable.
|
|
198
286
|
|
|
199
287
|
Returns
|
|
200
288
|
-------
|
|
201
289
|
bool
|
|
290
|
+
True if the auth type is refreshable, False otherwise.
|
|
202
291
|
"""
|
|
203
|
-
|
|
204
|
-
return auth_type == AuthType.OAUTH2.value
|
|
292
|
+
return self._auth_type in [AuthType.OAUTH2.value, AuthType.EXCHANGE.value]
|
|
205
293
|
|
|
206
|
-
def
|
|
294
|
+
def get_auth_parameters(self, kwargs: dict) -> dict:
|
|
207
295
|
"""
|
|
208
|
-
Get the authentication header.
|
|
296
|
+
Get the authentication header for the request.
|
|
297
|
+
It is given for granted that the auth type is set and that,
|
|
298
|
+
if the auth type is EXCHANGE, the refresh token is set.
|
|
209
299
|
|
|
210
300
|
Parameters
|
|
211
301
|
----------
|
|
@@ -215,50 +305,78 @@ class ClientDHCoreConfigurator:
|
|
|
215
305
|
Returns
|
|
216
306
|
-------
|
|
217
307
|
dict
|
|
218
|
-
Authentication
|
|
219
|
-
"""
|
|
220
|
-
creds =
|
|
221
|
-
if
|
|
222
|
-
|
|
223
|
-
if self.basic_auth():
|
|
224
|
-
user = creds[DhcoreEnvVar.USER.value]
|
|
225
|
-
password = creds[DhcoreEnvVar.PASSWORD.value]
|
|
226
|
-
kwargs["auth"] = (user, password)
|
|
227
|
-
elif self.oauth2_auth():
|
|
308
|
+
Authentication parameters.
|
|
309
|
+
"""
|
|
310
|
+
creds = creds_handler.get_credentials(self._origin)
|
|
311
|
+
if self._auth_type in (AuthType.EXCHANGE.value, AuthType.OAUTH2.value):
|
|
312
|
+
access_token = creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value]
|
|
228
313
|
if "headers" not in kwargs:
|
|
229
314
|
kwargs["headers"] = {}
|
|
230
|
-
access_token = creds[DhcoreEnvVar.ACCESS_TOKEN.value.removeprefix("DHCORE_")]
|
|
231
315
|
kwargs["headers"]["Authorization"] = f"Bearer {access_token}"
|
|
316
|
+
elif self._auth_type == AuthType.BASIC.value:
|
|
317
|
+
user = creds[CredsEnvVar.DHCORE_USER.value]
|
|
318
|
+
password = creds[CredsEnvVar.DHCORE_PASSWORD.value]
|
|
319
|
+
kwargs["auth"] = (user, password)
|
|
232
320
|
return kwargs
|
|
233
321
|
|
|
234
|
-
def get_new_access_token(self) -> None:
|
|
322
|
+
def get_new_access_token(self, change_origin: bool = False) -> None:
|
|
235
323
|
"""
|
|
236
324
|
Get a new access token.
|
|
237
325
|
|
|
326
|
+
Parameters
|
|
327
|
+
----------
|
|
328
|
+
change_origin : bool, optional
|
|
329
|
+
Whether to change the origin of the credentials, by default False
|
|
330
|
+
|
|
238
331
|
Returns
|
|
239
332
|
-------
|
|
240
333
|
None
|
|
241
334
|
"""
|
|
242
|
-
|
|
243
|
-
|
|
335
|
+
if not self.refreshable_auth_types():
|
|
336
|
+
raise ClientError(f"Auth type {self._auth_type} does not support refresh.")
|
|
337
|
+
|
|
338
|
+
# Get refresh endpoint
|
|
244
339
|
url = self._get_refresh_endpoint()
|
|
245
340
|
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
341
|
+
# Get credentials
|
|
342
|
+
creds = self._creds_handler.get_credentials(self._origin)
|
|
343
|
+
|
|
344
|
+
# Get client id
|
|
345
|
+
if (client_id := creds.get(CredsEnvVar.DHCORE_CLIENT_ID.value)) is None:
|
|
346
|
+
raise ClientError("Client id not set.")
|
|
250
347
|
|
|
251
|
-
#
|
|
348
|
+
# Handling of token exchange or refresh
|
|
349
|
+
if self._auth_type == AuthType.OAUTH2.value:
|
|
350
|
+
response = self._call_refresh_token_endpoint(
|
|
351
|
+
url,
|
|
352
|
+
client_id=client_id,
|
|
353
|
+
refresh_token=creds.get(CredsEnvVar.DHCORE_REFRESH_TOKEN.value),
|
|
354
|
+
grant_type="refresh_token",
|
|
355
|
+
scope="credentials",
|
|
356
|
+
)
|
|
357
|
+
elif self._auth_type == AuthType.EXCHANGE.value:
|
|
358
|
+
response = self._call_refresh_token_endpoint(
|
|
359
|
+
url,
|
|
360
|
+
client_id=client_id,
|
|
361
|
+
subject_token=creds.get(CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value),
|
|
362
|
+
subject_token_type="urn:ietf:params:oauth:token-type:pat",
|
|
363
|
+
grant_type="urn:ietf:params:oauth:grant-type:token-exchange",
|
|
364
|
+
scope="credentials",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
# Change origin of creds if needed
|
|
252
368
|
if response.status_code in (400, 401, 403):
|
|
253
|
-
|
|
254
|
-
|
|
369
|
+
if not change_origin:
|
|
370
|
+
raise ClientError("Unable to refresh token. Please check your credentials.")
|
|
371
|
+
self.change_origin()
|
|
372
|
+
self.get_new_access_token(change_origin=False)
|
|
255
373
|
|
|
256
374
|
response.raise_for_status()
|
|
257
375
|
|
|
258
376
|
# Read new credentials and propagate to config file
|
|
259
|
-
self.
|
|
377
|
+
self._export_new_creds(response.json())
|
|
260
378
|
|
|
261
|
-
def
|
|
379
|
+
def _export_new_creds(self, response: dict) -> None:
|
|
262
380
|
"""
|
|
263
381
|
Set new credentials.
|
|
264
382
|
|
|
@@ -271,17 +389,13 @@ class ClientDHCoreConfigurator:
|
|
|
271
389
|
-------
|
|
272
390
|
None
|
|
273
391
|
"""
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
*list_enum(S3StoreEnv),
|
|
277
|
-
*list_enum(SqlStoreEnv),
|
|
278
|
-
]
|
|
279
|
-
for key in keys:
|
|
280
|
-
if (value := response.get(key.lower())) is not None:
|
|
281
|
-
configurator.set_credential(key, value)
|
|
282
|
-
configurator.write_env(keys)
|
|
392
|
+
creds_handler.write_env(response)
|
|
393
|
+
self.load_file_vars()
|
|
283
394
|
|
|
284
|
-
|
|
395
|
+
# Change current origin to file because of refresh
|
|
396
|
+
self._origin = CredsOrigin.FILE.value
|
|
397
|
+
|
|
398
|
+
def _remove_prefix_dhcore(self) -> list[str]:
|
|
285
399
|
"""
|
|
286
400
|
Remove prefix from selected keys. (Compatibility with CLI)
|
|
287
401
|
|
|
@@ -296,13 +410,8 @@ class ClientDHCoreConfigurator:
|
|
|
296
410
|
List of keys without prefix.
|
|
297
411
|
"""
|
|
298
412
|
new_list = []
|
|
299
|
-
for key in keys:
|
|
300
|
-
if key in
|
|
301
|
-
DhcoreEnvVar.REFRESH_TOKEN.value,
|
|
302
|
-
DhcoreEnvVar.ACCESS_TOKEN.value,
|
|
303
|
-
DhcoreEnvVar.ISSUER.value,
|
|
304
|
-
DhcoreEnvVar.CLIENT_ID.value,
|
|
305
|
-
):
|
|
413
|
+
for key in self.keys:
|
|
414
|
+
if key in self.keys_to_unprefix:
|
|
306
415
|
new_list.append(key.removeprefix("DHCORE_"))
|
|
307
416
|
else:
|
|
308
417
|
new_list.append(key)
|
|
@@ -318,11 +427,10 @@ class ClientDHCoreConfigurator:
|
|
|
318
427
|
Refresh endpoint.
|
|
319
428
|
"""
|
|
320
429
|
# Get issuer endpoint
|
|
321
|
-
|
|
430
|
+
creds = self._creds_handler.get_credentials(self._origin)
|
|
431
|
+
endpoint_issuer = creds.get(CredsEnvVar.DHCORE_ISSUER.value)
|
|
322
432
|
if endpoint_issuer is None:
|
|
323
433
|
raise ClientError("Issuer endpoint not set.")
|
|
324
|
-
endpoint_issuer = self._sanitize_endpoint(endpoint_issuer)
|
|
325
|
-
configurator.set_credential(DhcoreEnvVar.ISSUER.value.removeprefix("DHCORE_"), endpoint_issuer)
|
|
326
434
|
|
|
327
435
|
# Standard issuer endpoint path
|
|
328
436
|
url = endpoint_issuer + "/.well-known/openid-configuration"
|
|
@@ -332,7 +440,11 @@ class ClientDHCoreConfigurator:
|
|
|
332
440
|
r.raise_for_status()
|
|
333
441
|
return r.json().get("token_endpoint")
|
|
334
442
|
|
|
335
|
-
def _call_refresh_token_endpoint(
|
|
443
|
+
def _call_refresh_token_endpoint(
|
|
444
|
+
self,
|
|
445
|
+
url: str,
|
|
446
|
+
**kwargs,
|
|
447
|
+
) -> Response:
|
|
336
448
|
"""
|
|
337
449
|
Call the refresh token endpoint.
|
|
338
450
|
|
|
@@ -340,44 +452,29 @@ class ClientDHCoreConfigurator:
|
|
|
340
452
|
----------
|
|
341
453
|
url : str
|
|
342
454
|
Refresh token endpoint.
|
|
343
|
-
|
|
344
|
-
|
|
455
|
+
kwargs : dict
|
|
456
|
+
Keyword arguments to pass to the request.
|
|
345
457
|
|
|
346
458
|
Returns
|
|
347
459
|
-------
|
|
348
460
|
Response
|
|
349
461
|
Response object.
|
|
350
462
|
"""
|
|
351
|
-
# Get client id
|
|
352
|
-
client_id = self._load_dhcore_oauth_vars(DhcoreEnvVar.CLIENT_ID.value)
|
|
353
|
-
if client_id is None:
|
|
354
|
-
raise ClientError("Client id not set.")
|
|
355
|
-
|
|
356
463
|
# Send request to get new access token
|
|
357
|
-
payload = {
|
|
358
|
-
"grant_type": "refresh_token",
|
|
359
|
-
"client_id": client_id,
|
|
360
|
-
"refresh_token": refresh_token,
|
|
361
|
-
"scope": "openid credentials offline_access",
|
|
362
|
-
}
|
|
464
|
+
payload = {**kwargs}
|
|
363
465
|
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
|
364
466
|
return request("POST", url, data=payload, headers=headers, timeout=60)
|
|
365
467
|
|
|
366
|
-
def
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
"""
|
|
380
|
-
read_var = configurator.load_from_env(oauth_var)
|
|
381
|
-
if read_var is None:
|
|
382
|
-
read_var = configurator.load_from_file(oauth_var.removeprefix("DHCORE_"))
|
|
383
|
-
return read_var
|
|
468
|
+
def _eval_auth_type(self, creds: dict) -> str | None:
|
|
469
|
+
if creds[CredsEnvVar.DHCORE_PERSONAL_ACCESS_TOKEN.value] is not None:
|
|
470
|
+
return AuthType.EXCHANGE.value
|
|
471
|
+
if (
|
|
472
|
+
creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] is not None
|
|
473
|
+
and creds[CredsEnvVar.DHCORE_REFRESH_TOKEN.value] is not None
|
|
474
|
+
):
|
|
475
|
+
return AuthType.OAUTH2.value
|
|
476
|
+
if creds[CredsEnvVar.DHCORE_ACCESS_TOKEN.value] is not None:
|
|
477
|
+
return AuthType.ACCESS_TOKEN.value
|
|
478
|
+
if creds[CredsEnvVar.DHCORE_USER.value] is not None and creds[CredsEnvVar.DHCORE_PASSWORD.value] is not None:
|
|
479
|
+
return AuthType.BASIC.value
|
|
480
|
+
return None
|
|
@@ -19,6 +19,7 @@ class DhcoreEnvVar(Enum):
|
|
|
19
19
|
CLIENT_ID = "DHCORE_CLIENT_ID"
|
|
20
20
|
ACCESS_TOKEN = "DHCORE_ACCESS_TOKEN"
|
|
21
21
|
REFRESH_TOKEN = "DHCORE_REFRESH_TOKEN"
|
|
22
|
+
PERSONAL_ACCESS_TOKEN = "DHCORE_PERSONAL_ACCESS_TOKEN"
|
|
22
23
|
WORKFLOW_IMAGE = "DHCORE_WORKFLOW_IMAGE"
|
|
23
24
|
|
|
24
25
|
|
|
@@ -29,3 +30,5 @@ class AuthType(Enum):
|
|
|
29
30
|
|
|
30
31
|
BASIC = "basic"
|
|
31
32
|
OAUTH2 = "oauth2"
|
|
33
|
+
EXCHANGE = "exchange"
|
|
34
|
+
ACCESS_TOKEN = "access_token_only"
|