dataflow-core 2.1.6__py3-none-any.whl → 2.1.8__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 dataflow-core might be problematic. Click here for more details.
- authenticator/dataflowhubauthenticator.py +19 -17
- dataflow/dataflow.py +158 -34
- dataflow/models/__init__.py +2 -1
- dataflow/models/dataflow_zone.py +19 -0
- dataflow/models/role.py +12 -2
- dataflow/models/role_zone.py +17 -0
- dataflow/models/user.py +2 -2
- dataflow/schemas/__init__.py +0 -0
- dataflow/schemas/connection.py +84 -0
- dataflow/schemas/git_ssh.py +50 -0
- dataflow/schemas/secret.py +44 -0
- dataflow/secrets_manager/__init__.py +13 -0
- dataflow/secrets_manager/factory.py +59 -0
- dataflow/secrets_manager/interface.py +22 -0
- dataflow/secrets_manager/providers/__init__.py +0 -0
- dataflow/secrets_manager/providers/aws_manager.py +164 -0
- dataflow/secrets_manager/providers/azure_manager.py +185 -0
- dataflow/secrets_manager/service.py +156 -0
- dataflow/utils/exceptions.py +112 -0
- dataflow/utils/get_current_user.py +2 -0
- {dataflow_core-2.1.6.dist-info → dataflow_core-2.1.8.dist-info}/METADATA +3 -1
- {dataflow_core-2.1.6.dist-info → dataflow_core-2.1.8.dist-info}/RECORD +25 -14
- dataflow/models/runtime.py +0 -11
- dataflow/utils/aws_secrets_manager.py +0 -57
- dataflow/utils/json_handler.py +0 -33
- {dataflow_core-2.1.6.dist-info → dataflow_core-2.1.8.dist-info}/WHEEL +0 -0
- {dataflow_core-2.1.6.dist-info → dataflow_core-2.1.8.dist-info}/entry_points.txt +0 -0
- {dataflow_core-2.1.6.dist-info → dataflow_core-2.1.8.dist-info}/top_level.txt +0 -0
|
@@ -109,7 +109,7 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
109
109
|
return None
|
|
110
110
|
|
|
111
111
|
username = self.extract_username_from_email(email)
|
|
112
|
-
username = re.sub(r'[^
|
|
112
|
+
username = re.sub(r'[^a-z0-9]', '', username.lower())
|
|
113
113
|
if not username:
|
|
114
114
|
self.log.error("Cannot create user: Username is empty")
|
|
115
115
|
return None
|
|
@@ -136,7 +136,6 @@ class DataflowBaseAuthenticator(Authenticator):
|
|
|
136
136
|
last_name=last_name or "",
|
|
137
137
|
email=email,
|
|
138
138
|
role_id=role_id,
|
|
139
|
-
active='Y',
|
|
140
139
|
password='user@123',
|
|
141
140
|
)
|
|
142
141
|
|
|
@@ -248,7 +247,11 @@ class DataflowAzureAuthenticator(DataflowBaseAuthenticator, AzureAdOAuthenticato
|
|
|
248
247
|
azure_client_secret = Unicode(config=True, help="Azure AD OAuth client secret")
|
|
249
248
|
azure_tenant_id = Unicode(config=True, help="Azure AD tenant ID")
|
|
250
249
|
azure_scope = Unicode("openid profile email", config=True, help="Azure AD OAuth scopes")
|
|
251
|
-
|
|
250
|
+
dataflow_oauth_type = Unicode(
|
|
251
|
+
default_value="google",
|
|
252
|
+
config=True,
|
|
253
|
+
help="The OAuth provider type for DataflowHub (e.g., github, google)"
|
|
254
|
+
)
|
|
252
255
|
def __init__(self, **kwargs):
|
|
253
256
|
super().__init__(**kwargs)
|
|
254
257
|
self.client_id = self.azure_client_id
|
|
@@ -270,48 +273,47 @@ class DataflowAzureAuthenticator(DataflowBaseAuthenticator, AzureAdOAuthenticato
|
|
|
270
273
|
if not user:
|
|
271
274
|
self.log.warning("Azure AD OAuth authentication failed: No user data returned")
|
|
272
275
|
return None
|
|
273
|
-
|
|
274
|
-
|
|
276
|
+
|
|
277
|
+
auth_state = user.get("auth_state", {})
|
|
278
|
+
user_info = auth_state.get("user", {}) if auth_state else {}
|
|
279
|
+
email = user_info.get("upn")
|
|
275
280
|
if not email:
|
|
276
|
-
self.log.warning("Azure AD OAuth authentication failed: No
|
|
281
|
+
self.log.warning("Azure AD OAuth authentication failed: No upn in user data")
|
|
277
282
|
return None
|
|
278
|
-
|
|
283
|
+
|
|
279
284
|
db_user = (
|
|
280
285
|
self.db.query(m_user.User)
|
|
281
286
|
.filter(m_user.User.email == email)
|
|
282
287
|
.first()
|
|
283
288
|
)
|
|
284
|
-
|
|
289
|
+
|
|
285
290
|
if not db_user:
|
|
286
291
|
self.log.info(f"User with email {email} not found in Dataflow database, creating new user")
|
|
287
|
-
# Extract additional info from user data if available
|
|
288
|
-
auth_state = user.get("auth_state", {})
|
|
289
|
-
user_info = auth_state.get("user", {}) if auth_state else {}
|
|
290
292
|
|
|
291
|
-
first_name = user_info.get("
|
|
292
|
-
last_name = user_info.get("family_name") or user.get("family_name")
|
|
293
|
+
first_name = user_info.get("name") or user.get("name")
|
|
293
294
|
|
|
294
|
-
db_user = self.create_new_user(email, first_name, last_name)
|
|
295
|
+
db_user = self.create_new_user(email, first_name, last_name=None)
|
|
295
296
|
if not db_user:
|
|
296
297
|
self.log.error(f"Failed to create new user for email: {email}")
|
|
297
298
|
return None
|
|
298
|
-
|
|
299
|
+
|
|
299
300
|
username = db_user.user_name
|
|
300
301
|
session_id = self.get_or_create_session(db_user.user_id)
|
|
301
302
|
self.set_session_cookie(handler, session_id)
|
|
302
303
|
self.log.info(f"Azure AD OAuth completed for user: {username}, session_id={session_id}")
|
|
303
304
|
return {
|
|
304
|
-
"name":
|
|
305
|
+
"name": db_user.first_name,
|
|
305
306
|
"session_id": session_id,
|
|
306
307
|
"auth_state": user.get("auth_state", {})
|
|
307
308
|
}
|
|
309
|
+
|
|
308
310
|
except Exception as e:
|
|
309
311
|
self.log.error(f"Azure AD OAuth authentication error: {str(e)}", exc_info=True)
|
|
310
312
|
return None
|
|
311
313
|
finally:
|
|
312
314
|
self.db.close()
|
|
313
315
|
|
|
314
|
-
auth_type = os.environ.get("
|
|
316
|
+
auth_type = os.environ.get("DATAFLOW_OAUTH_TYPE", "google")
|
|
315
317
|
|
|
316
318
|
if auth_type == "google":
|
|
317
319
|
BaseAuthenticator = DataflowGoogleAuthenticator
|
dataflow/dataflow.py
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import os, requests
|
|
2
2
|
from .database_manager import DatabaseManager
|
|
3
|
-
from .utils.aws_secrets_manager import SecretsManagerClient
|
|
4
3
|
import json
|
|
5
4
|
from .configuration import ConfigurationManager
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class Dataflow:
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
"""
|
|
9
|
+
Dataflow class to interact with Dataflow services.
|
|
10
|
+
"""
|
|
12
11
|
def auth(self, session_id: str):
|
|
13
12
|
"""
|
|
14
13
|
Retrieve and return user information using their session ID.
|
|
@@ -55,19 +54,26 @@ class Dataflow:
|
|
|
55
54
|
"""
|
|
56
55
|
try:
|
|
57
56
|
host_name = os.environ.get("HOSTNAME", "")
|
|
58
|
-
user_name = host_name.replace("jupyter-", "") if host_name.startswith("jupyter-") else host_name
|
|
59
57
|
runtime = os.environ.get("RUNTIME")
|
|
60
58
|
slug = os.environ.get("SLUG")
|
|
61
59
|
|
|
62
60
|
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
63
|
-
|
|
61
|
+
|
|
62
|
+
variable_api = None
|
|
63
|
+
if runtime and slug:
|
|
64
|
+
variable_api = dataflow_config.get_config_value("auth", "variable_ui_api")
|
|
65
|
+
elif host_name:
|
|
66
|
+
variable_api = dataflow_config.get_config_value("auth", "variable_manager_api")
|
|
67
|
+
else:
|
|
68
|
+
raise Exception("Cannot run dataflow methods here!")
|
|
69
|
+
|
|
64
70
|
if not variable_api:
|
|
65
71
|
print("[Dataflow.variable] Variable Unreachable")
|
|
66
72
|
return None
|
|
67
73
|
|
|
68
74
|
if runtime:
|
|
69
75
|
query_params = {
|
|
70
|
-
"
|
|
76
|
+
"key": variable_name,
|
|
71
77
|
"runtime": runtime,
|
|
72
78
|
"slug": slug
|
|
73
79
|
}
|
|
@@ -85,17 +91,27 @@ class Dataflow:
|
|
|
85
91
|
return None
|
|
86
92
|
|
|
87
93
|
query_params = {
|
|
88
|
-
"
|
|
89
|
-
"runtime": None,
|
|
90
|
-
"slug": None,
|
|
91
|
-
"created_by": user_name
|
|
94
|
+
"key": variable_name,
|
|
92
95
|
}
|
|
93
96
|
response = requests.get(variable_api, params=query_params)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
97
|
+
|
|
98
|
+
# Handle different HTTP status codes gracefully
|
|
99
|
+
if response.status_code == 404:
|
|
100
|
+
return None # Variable not found
|
|
101
|
+
elif response.status_code >= 500:
|
|
102
|
+
response.raise_for_status() # Let server errors propagate
|
|
103
|
+
elif response.status_code >= 400:
|
|
104
|
+
print(f"[Dataflow.variable] Client error {response.status_code} for variable '{variable_name}'")
|
|
98
105
|
return None
|
|
106
|
+
elif response.status_code != 200:
|
|
107
|
+
print(f"[Dataflow.variable] Unexpected status {response.status_code} for variable '{variable_name}'")
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
return response.text.strip().strip('"')
|
|
111
|
+
|
|
112
|
+
except requests.exceptions.RequestException as e:
|
|
113
|
+
raise RuntimeError(f"[Dataflow.variable] Failed to fetch variable '{variable_name}'") from e
|
|
114
|
+
|
|
99
115
|
except Exception as e:
|
|
100
116
|
print(f"[Dataflow.variable] Exception occurred: {e}")
|
|
101
117
|
return None
|
|
@@ -112,19 +128,20 @@ class Dataflow:
|
|
|
112
128
|
"""
|
|
113
129
|
try:
|
|
114
130
|
host_name = os.environ.get("HOSTNAME", "")
|
|
115
|
-
user_name = host_name.replace("jupyter-", "") if host_name.startswith("jupyter-") else host_name
|
|
116
131
|
runtime = os.environ.get("RUNTIME")
|
|
117
132
|
slug = os.environ.get("SLUG")
|
|
118
133
|
|
|
119
134
|
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
120
|
-
|
|
135
|
+
if runtime:
|
|
136
|
+
secret_api = dataflow_config.get_config_value("auth", "secret_ui_api")
|
|
137
|
+
else:
|
|
138
|
+
secret_api = dataflow_config.get_config_value("auth", "secret_manager_api")
|
|
121
139
|
if not secret_api:
|
|
122
140
|
print("[Dataflow.secret] Secret API Unreachable")
|
|
123
141
|
return None
|
|
124
142
|
|
|
125
143
|
query_params = {
|
|
126
|
-
"
|
|
127
|
-
"created_by": user_name
|
|
144
|
+
"key": secret_name
|
|
128
145
|
}
|
|
129
146
|
|
|
130
147
|
if runtime:
|
|
@@ -134,11 +151,22 @@ class Dataflow:
|
|
|
134
151
|
|
|
135
152
|
response = requests.get(secret_api, params=query_params)
|
|
136
153
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
return
|
|
140
|
-
|
|
154
|
+
# Handle different HTTP status codes gracefully
|
|
155
|
+
if response.status_code == 404:
|
|
156
|
+
return None # Secret not found
|
|
157
|
+
elif response.status_code >= 500:
|
|
158
|
+
response.raise_for_status() # Let server errors propagate
|
|
159
|
+
elif response.status_code >= 400:
|
|
160
|
+
print(f"[Dataflow.secret] Client error {response.status_code} for secret '{secret_name}'")
|
|
161
|
+
return None
|
|
162
|
+
elif response.status_code != 200:
|
|
163
|
+
print(f"[Dataflow.secret] Unexpected status {response.status_code} for secret '{secret_name}'")
|
|
141
164
|
return None
|
|
165
|
+
|
|
166
|
+
return response.text.strip().strip('"')
|
|
167
|
+
|
|
168
|
+
except requests.exceptions.RequestException as e:
|
|
169
|
+
raise RuntimeError(f"[Dataflow.secret] Failed to fetch secret '{secret_name}'") from e
|
|
142
170
|
except Exception as e:
|
|
143
171
|
print(f"[Dataflow.secret] Exception occurred: {e}")
|
|
144
172
|
return None
|
|
@@ -156,26 +184,62 @@ class Dataflow:
|
|
|
156
184
|
"""
|
|
157
185
|
try:
|
|
158
186
|
host_name = os.environ["HOSTNAME"]
|
|
159
|
-
user_name=host_name.replace("jupyter-","")
|
|
160
187
|
runtime = os.environ.get("RUNTIME")
|
|
161
188
|
slug = os.environ.get("SLUG")
|
|
162
189
|
|
|
163
|
-
|
|
164
|
-
|
|
190
|
+
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
191
|
+
if runtime:
|
|
192
|
+
connection_api = dataflow_config.get_config_value("auth", "connection_ui_api")
|
|
193
|
+
elif host_name:
|
|
194
|
+
connection_api = dataflow_config.get_config_value("auth", "connection_manager_api")
|
|
195
|
+
else:
|
|
196
|
+
raise Exception("Cannot run dataflow methods here! HOSTNAME or RUNTIME env variable not set.")
|
|
197
|
+
|
|
198
|
+
query_params = {
|
|
199
|
+
"conn_id": conn_id
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if runtime:
|
|
203
|
+
query_params["runtime"] = runtime
|
|
204
|
+
if slug:
|
|
205
|
+
query_params["slug"] = slug
|
|
206
|
+
|
|
207
|
+
response = requests.get(connection_api, params=query_params)
|
|
208
|
+
|
|
209
|
+
# Handle different HTTP status codes gracefully
|
|
210
|
+
if response.status_code == 404:
|
|
211
|
+
raise RuntimeError(f"[Dataflow.connection] Connection '{conn_id}' not found!")
|
|
212
|
+
elif response.status_code >= 500:
|
|
213
|
+
response.raise_for_status() # Let server errors propagate
|
|
214
|
+
elif response.status_code >= 400:
|
|
215
|
+
raise RuntimeError(f"[Dataflow.connection] Client error {response.status_code} for connection '{conn_id}'")
|
|
216
|
+
elif response.status_code != 200:
|
|
217
|
+
raise RuntimeError(f"[Dataflow.connection] Unexpected status {response.status_code} for connection '{conn_id}'")
|
|
218
|
+
|
|
219
|
+
connection_details = response.json()
|
|
220
|
+
|
|
221
|
+
if not connection_details:
|
|
222
|
+
raise RuntimeError(f"[Dataflow.connection] Connection '{conn_id}' not found!")
|
|
223
|
+
|
|
224
|
+
if mode == "dict":
|
|
225
|
+
with open('/home/jovyan/log.txt', 'w') as log_file:
|
|
226
|
+
log_file.write(f"Connection details for {conn_id}: {connection_details}\n")
|
|
227
|
+
print(f"connection_details: {connection_details}")
|
|
228
|
+
return dict(connection_details)
|
|
165
229
|
|
|
166
|
-
conn_type =
|
|
167
|
-
username =
|
|
168
|
-
password =
|
|
169
|
-
host =
|
|
170
|
-
port =
|
|
171
|
-
database =
|
|
230
|
+
conn_type = connection_details['conn_type'].lower()
|
|
231
|
+
username = connection_details['login']
|
|
232
|
+
password = connection_details.get('password', '')
|
|
233
|
+
host = connection_details['host']
|
|
234
|
+
port = connection_details['port']
|
|
235
|
+
database = connection_details.get('schemas', '')
|
|
172
236
|
|
|
173
237
|
user_info = f"{username}:{password}@" if password else f"{username}@"
|
|
174
238
|
db_info = f"/{database}" if database else ""
|
|
175
239
|
|
|
176
240
|
connection_string = f"{conn_type}://{user_info}{host}:{port}{db_info}"
|
|
177
241
|
|
|
178
|
-
extra =
|
|
242
|
+
extra = connection_details.get('extra', '')
|
|
179
243
|
if extra:
|
|
180
244
|
try:
|
|
181
245
|
extra_params = json.loads(extra)
|
|
@@ -194,6 +258,66 @@ class Dataflow:
|
|
|
194
258
|
return connection_instance.get_engine()
|
|
195
259
|
elif mode == "session":
|
|
196
260
|
return next(connection_instance.get_session())
|
|
261
|
+
else:
|
|
262
|
+
raise ValueError(f"Unsupported mode: {mode}. Use 'session', 'engine', 'url'.")
|
|
197
263
|
|
|
264
|
+
except requests.exceptions.RequestException as e:
|
|
265
|
+
raise RuntimeError(f"[Dataflow.connection] Failed to fetch connection '{conn_id}'") from e
|
|
266
|
+
|
|
198
267
|
except Exception as e:
|
|
199
|
-
|
|
268
|
+
raise RuntimeError(f"[Dataflow.connection] Error connecting to '{conn_id}': {str(e)}") from e
|
|
269
|
+
|
|
270
|
+
def variable_or_secret(self, key: str):
|
|
271
|
+
"""
|
|
272
|
+
Retrieve a variable or secret by key.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
key (str): Key of the variable or secret
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
str or None: Value if found, None otherwise
|
|
279
|
+
"""
|
|
280
|
+
try:
|
|
281
|
+
host_name = os.environ.get("HOSTNAME", "")
|
|
282
|
+
runtime = os.environ.get("RUNTIME")
|
|
283
|
+
slug = os.environ.get("SLUG")
|
|
284
|
+
|
|
285
|
+
dataflow_config = ConfigurationManager('/dataflow/app/auth_config/dataflow_auth.cfg')
|
|
286
|
+
if runtime and slug:
|
|
287
|
+
variableorsecret_api = dataflow_config.get_config_value("auth", "variableorsecret_ui_api")
|
|
288
|
+
query_params = {
|
|
289
|
+
"key": key,
|
|
290
|
+
"runtime": runtime,
|
|
291
|
+
"slug": slug
|
|
292
|
+
}
|
|
293
|
+
elif host_name:
|
|
294
|
+
variableorsecret_api = dataflow_config.get_config_value("auth", "variableorsecret_manager_api")
|
|
295
|
+
query_params = {
|
|
296
|
+
"key": key
|
|
297
|
+
}
|
|
298
|
+
else:
|
|
299
|
+
raise Exception("Cannot run dataflow methods here!")
|
|
300
|
+
|
|
301
|
+
if not variableorsecret_api:
|
|
302
|
+
print("[Dataflow.variable_or_secret] Variable/Secret Unreachable")
|
|
303
|
+
return None
|
|
304
|
+
|
|
305
|
+
response = requests.get(variableorsecret_api, params=query_params)
|
|
306
|
+
|
|
307
|
+
# Handle different HTTP status codes gracefully
|
|
308
|
+
if response.status_code == 404:
|
|
309
|
+
return None # Variable/secret not found
|
|
310
|
+
elif response.status_code >= 500:
|
|
311
|
+
response.raise_for_status() # Let server errors propagate
|
|
312
|
+
elif response.status_code >= 400:
|
|
313
|
+
print(f"[Dataflow.variable_or_secret] Client error {response.status_code} for key '{key}'")
|
|
314
|
+
return None
|
|
315
|
+
elif response.status_code != 200:
|
|
316
|
+
print(f"[Dataflow.variable_or_secret] Unexpected status {response.status_code} for key '{key}'")
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
response_text = response.text.strip().strip('"')
|
|
320
|
+
return response_text
|
|
321
|
+
|
|
322
|
+
except requests.exceptions.RequestException as e:
|
|
323
|
+
raise RuntimeError(f"[Dataflow.variable_or_secret] Failed to fetch '{key}'") from e
|
dataflow/models/__init__.py
CHANGED
|
@@ -12,7 +12,8 @@ from .blacklist_library import BlacklistedLibrary
|
|
|
12
12
|
from .environment_status import EnvironmentStatus
|
|
13
13
|
from .session import Session
|
|
14
14
|
from .server_config import ServerConfig, CustomServerConfig
|
|
15
|
-
from .
|
|
15
|
+
from .dataflow_zone import DataflowZone
|
|
16
|
+
from .role_zone import RoleZone
|
|
16
17
|
from .environment_status import EnvironmentStatus
|
|
17
18
|
from .user_team import UserTeam
|
|
18
19
|
from .role_server import RoleServer
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from sqlalchemy import Column, Integer, String, Boolean
|
|
2
|
+
from sqlalchemy.orm import relationship
|
|
3
|
+
from dataflow.db import Base
|
|
4
|
+
|
|
5
|
+
class DataflowZone(Base):
|
|
6
|
+
__tablename__ = "DATAFLOW_ZONE"
|
|
7
|
+
|
|
8
|
+
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
9
|
+
slug = Column(String, unique=True, nullable=False)
|
|
10
|
+
display_name = Column(String, nullable=False)
|
|
11
|
+
is_runtime = Column(Boolean, default=False)
|
|
12
|
+
subdomain = Column(String)
|
|
13
|
+
spark_enabled = Column(Boolean, default=False)
|
|
14
|
+
display_order = Column(Integer, default=0)
|
|
15
|
+
|
|
16
|
+
role_zone_assocs = relationship("RoleZone", back_populates="zone")
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return f"<DataflowZone(id={self.id}, slug='{self.slug}', display_name='{self.display_name}', display_order={self.display_order})>"
|
dataflow/models/role.py
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
from sqlalchemy import Column, Integer, String, Enum
|
|
3
3
|
from sqlalchemy.orm import relationship
|
|
4
4
|
from dataflow.db import Base
|
|
5
|
+
import enum
|
|
6
|
+
|
|
7
|
+
class BaseRoleField(enum.Enum):
|
|
8
|
+
admin = "admin"
|
|
9
|
+
user = "user"
|
|
10
|
+
applicant = "applicant"
|
|
5
11
|
|
|
6
12
|
class Role(Base):
|
|
7
13
|
"""
|
|
@@ -13,7 +19,11 @@ class Role(Base):
|
|
|
13
19
|
id = Column(Integer, primary_key=True, index=True, autoincrement=True, nullable=False)
|
|
14
20
|
name = Column(String, unique=True, nullable=False)
|
|
15
21
|
description = Column(String, nullable=True)
|
|
16
|
-
base_role = Column(Enum(
|
|
22
|
+
base_role = Column(Enum(BaseRoleField), nullable=False, default=BaseRoleField.user)
|
|
17
23
|
|
|
18
24
|
users = relationship("User", back_populates="role_details", cascade="all, delete-orphan")
|
|
19
|
-
role_server_assocs = relationship("RoleServer", back_populates="role")
|
|
25
|
+
role_server_assocs = relationship("RoleServer", back_populates="role")
|
|
26
|
+
role_zone_assocs = relationship("RoleZone", back_populates="role")
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
return f"<Role(id={self.id}, name='{self.name}', base_role='{self.base_role}')>"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import Dict, List, Optional
|
|
2
|
+
from sqlalchemy import Column, Integer, ForeignKey, UniqueConstraint, Boolean
|
|
3
|
+
from sqlalchemy.orm import relationship
|
|
4
|
+
from dataflow.db import Base
|
|
5
|
+
|
|
6
|
+
class RoleZone(Base):
|
|
7
|
+
__tablename__ = 'ROLE_ZONE'
|
|
8
|
+
|
|
9
|
+
role_id = Column(Integer, ForeignKey('ROLE.id', ondelete="CASCADE"), primary_key=True)
|
|
10
|
+
zone_id = Column(Integer, ForeignKey('DATAFLOW_ZONE.id', ondelete="CASCADE"), primary_key=True)
|
|
11
|
+
is_default = Column(Boolean, default=False, nullable=False)
|
|
12
|
+
|
|
13
|
+
role = relationship("Role", back_populates="role_zone_assocs")
|
|
14
|
+
zone = relationship("DataflowZone", back_populates="role_zone_assocs")
|
|
15
|
+
|
|
16
|
+
def __repr__(self):
|
|
17
|
+
return f"<RoleZone(role_id={self.role_id}, zone_id={self.zone_id}, is_default={self.is_default})>"
|
dataflow/models/user.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""models.py"""
|
|
2
|
-
from sqlalchemy import Column, Integer, String, Boolean, LargeBinary,
|
|
2
|
+
from sqlalchemy import Column, Integer, String, Boolean, LargeBinary, ForeignKey
|
|
3
3
|
from sqlalchemy.orm import relationship
|
|
4
4
|
from dataflow.db import Base
|
|
5
5
|
|
|
@@ -18,7 +18,7 @@ class User(Base):
|
|
|
18
18
|
role_id = Column(Integer, ForeignKey('ROLE.id'), nullable=False)
|
|
19
19
|
image = Column(LargeBinary)
|
|
20
20
|
image_url = Column(String, nullable=True)
|
|
21
|
-
active = Column(
|
|
21
|
+
active = Column(Boolean, nullable=False, default=True)
|
|
22
22
|
password = Column(String, nullable=False)
|
|
23
23
|
active_env = Column(String)
|
|
24
24
|
active_env_type = Column(String, nullable=True)
|
|
File without changes
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""schemas/connection.py"""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, field_validator
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from enum import Enum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ConnectionType(str, Enum):
|
|
10
|
+
"""Enum for supported connection types."""
|
|
11
|
+
POSTGRESQL = "PostgreSQL"
|
|
12
|
+
MYSQL = "MySQL"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ConnectionBase(BaseModel):
|
|
16
|
+
"""Base connection model with common fields."""
|
|
17
|
+
conn_id: str
|
|
18
|
+
conn_type: ConnectionType
|
|
19
|
+
description: Optional[str] = None
|
|
20
|
+
host: str
|
|
21
|
+
schemas: Optional[str] = None
|
|
22
|
+
password: str
|
|
23
|
+
login: str
|
|
24
|
+
port: int
|
|
25
|
+
extra: Optional[str] = None
|
|
26
|
+
|
|
27
|
+
@field_validator("conn_id")
|
|
28
|
+
def validate_conn_id(cls, v) -> str:
|
|
29
|
+
import re
|
|
30
|
+
if not isinstance(v, str):
|
|
31
|
+
raise ValueError("Connection ID must be a string.")
|
|
32
|
+
if len(v) > 20:
|
|
33
|
+
raise ValueError("Connection ID must be at most 20 characters long.")
|
|
34
|
+
if not re.fullmatch(r"[A-Za-z0-9-]+", v):
|
|
35
|
+
raise ValueError(
|
|
36
|
+
"Connection ID can only contain letters, numbers, and hyphens (-)!"
|
|
37
|
+
)
|
|
38
|
+
return v
|
|
39
|
+
|
|
40
|
+
@field_validator("conn_type")
|
|
41
|
+
def validate_conn_type(cls, v) -> ConnectionType:
|
|
42
|
+
if isinstance(v, str):
|
|
43
|
+
try:
|
|
44
|
+
return ConnectionType(v)
|
|
45
|
+
except ValueError:
|
|
46
|
+
raise ValueError(f'conn_type must be one of {[e.value for e in ConnectionType]}')
|
|
47
|
+
return v
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class ConnectionSave(ConnectionBase):
|
|
51
|
+
"""Model for creating a new connection."""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ConnectionUpdate(BaseModel):
|
|
56
|
+
"""Model for updating an existing connection."""
|
|
57
|
+
conn_type: Optional[ConnectionType] = None
|
|
58
|
+
description: Optional[str] = None
|
|
59
|
+
host: Optional[str] = None
|
|
60
|
+
schemas: Optional[str] = None
|
|
61
|
+
login: Optional[str] = None
|
|
62
|
+
password: Optional[str] = None
|
|
63
|
+
port: Optional[int] = None
|
|
64
|
+
extra: Optional[str] = None
|
|
65
|
+
|
|
66
|
+
@field_validator("conn_type")
|
|
67
|
+
def validate_conn_type(cls, v) -> Optional[ConnectionType]:
|
|
68
|
+
if v is None:
|
|
69
|
+
return v
|
|
70
|
+
if isinstance(v, str):
|
|
71
|
+
# Convert string to enum if needed
|
|
72
|
+
try:
|
|
73
|
+
return ConnectionType(v)
|
|
74
|
+
except ValueError:
|
|
75
|
+
raise ValueError(f'conn_type must be one of {[e.value for e in ConnectionType]}')
|
|
76
|
+
return v
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ConnectionRead(ConnectionBase):
|
|
80
|
+
"""Model for reading/displaying connection data."""
|
|
81
|
+
pass
|
|
82
|
+
|
|
83
|
+
class Config:
|
|
84
|
+
from_attributes = True
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""schemas/git_ssh.py"""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, field_validator
|
|
4
|
+
from typing import Optional, Literal
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SSHBase(BaseModel):
|
|
9
|
+
"""Base SSH key model with common fields."""
|
|
10
|
+
key_name: str
|
|
11
|
+
description: Optional[str] = None
|
|
12
|
+
|
|
13
|
+
@field_validator("key_name")
|
|
14
|
+
def validate_key_name(cls, v) -> str:
|
|
15
|
+
import re
|
|
16
|
+
if not isinstance(v, str):
|
|
17
|
+
raise ValueError("SSH key name must be a string.")
|
|
18
|
+
if len(v) > 20:
|
|
19
|
+
raise ValueError("SSH key name must be at most 20 characters long.")
|
|
20
|
+
if not re.fullmatch(r"[A-Za-z0-9-]+", v):
|
|
21
|
+
raise ValueError(
|
|
22
|
+
"SSH key name can only contain letters, numbers, and hyphens (-)!"
|
|
23
|
+
)
|
|
24
|
+
return v
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SSHSave(SSHBase):
|
|
28
|
+
"""Model for creating a new SSH key."""
|
|
29
|
+
public_key: str
|
|
30
|
+
private_key: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SSHUpdate(BaseModel):
|
|
34
|
+
"""Model for updating an existing SSH key."""
|
|
35
|
+
description: Optional[str] = None
|
|
36
|
+
public_key: Optional[str] = None
|
|
37
|
+
private_key: Optional[str] = None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SSHRead(SSHBase):
|
|
41
|
+
"""Model for reading/displaying SSH key data."""
|
|
42
|
+
public_key: str
|
|
43
|
+
private_key: str
|
|
44
|
+
created_date: Optional[datetime] = None
|
|
45
|
+
|
|
46
|
+
class Config:
|
|
47
|
+
from_attributes = True
|
|
48
|
+
|
|
49
|
+
class Config:
|
|
50
|
+
from_attributes = True
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""schemas/secret.py"""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, field_validator
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SecretBase(BaseModel):
|
|
9
|
+
"""Base secret model with common fields."""
|
|
10
|
+
key: str
|
|
11
|
+
value: str
|
|
12
|
+
description: Optional[str] = None
|
|
13
|
+
|
|
14
|
+
@field_validator("key")
|
|
15
|
+
def validate_key(cls, v) -> str:
|
|
16
|
+
import re
|
|
17
|
+
if not isinstance(v, str):
|
|
18
|
+
raise ValueError("Secret key must be a string.")
|
|
19
|
+
if len(v) > 20:
|
|
20
|
+
raise ValueError("Secret key must be at most 20 characters long.")
|
|
21
|
+
if not re.fullmatch(r"[A-Za-z0-9-]+", v):
|
|
22
|
+
raise ValueError(
|
|
23
|
+
"Secret key can only contain letters, numbers, and hyphens (-)!"
|
|
24
|
+
)
|
|
25
|
+
return v
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SecretSave(SecretBase):
|
|
29
|
+
"""Model for creating a new secret."""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SecretUpdate(BaseModel):
|
|
34
|
+
"""Model for updating an existing secret."""
|
|
35
|
+
value: Optional[str] = None
|
|
36
|
+
description: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SecretRead(SecretBase):
|
|
40
|
+
"""Model for reading/displaying secret data."""
|
|
41
|
+
created_date: Optional[datetime] = None
|
|
42
|
+
|
|
43
|
+
class Config:
|
|
44
|
+
from_attributes = True
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# secrets_manager/__init__.py
|
|
2
|
+
|
|
3
|
+
from .factory import get_secret_manager
|
|
4
|
+
from .service import SecretsService
|
|
5
|
+
|
|
6
|
+
# 1. Call the factory to get the configured low-level secret manager
|
|
7
|
+
# (e.g., an instance of AWSSecretsManager or AzureKeyVault).
|
|
8
|
+
# This happens only once when the package is first imported.
|
|
9
|
+
secret_manager_instance = get_secret_manager()
|
|
10
|
+
|
|
11
|
+
# 2. Create the single, high-level service instance that the rest of
|
|
12
|
+
# your application will use. It wraps the low-level instance.
|
|
13
|
+
secrets_service = SecretsService(secret_manager=secret_manager_instance)
|