Flowfile 0.5.3__py3-none-any.whl → 0.5.6__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.
- flowfile/__init__.py +16 -0
- flowfile/__main__.py +94 -1
- flowfile/web/static/assets/{AdminView-49392a9a.js → AdminView-c2c7942b.js} +1 -1
- flowfile/web/static/assets/{CloudConnectionView-f13f202b.js → CloudConnectionView-7a3042c6.js} +4 -4
- flowfile/web/static/assets/{CloudConnectionView-36bcd6df.css → CloudConnectionView-cf85f943.css} +17 -17
- flowfile/web/static/assets/{CloudStorageReader-0023d4a5.js → CloudStorageReader-709c4037.js} +8 -8
- flowfile/web/static/assets/{CloudStorageWriter-8e781e11.js → CloudStorageWriter-604c51a8.js} +8 -8
- flowfile/web/static/assets/ColumnActionInput-c44b7aee.css +159 -0
- flowfile/web/static/assets/ColumnActionInput-d63d6746.js +330 -0
- flowfile/web/static/assets/{ColumnSelector-8ad68ea9.js → ColumnSelector-0c8cd1cd.js} +1 -1
- flowfile/web/static/assets/ContextMenu-366bf1b4.js +9 -0
- flowfile/web/static/assets/ContextMenu-85cf5b44.js +9 -0
- flowfile/web/static/assets/ContextMenu-9d28ae6d.js +9 -0
- flowfile/web/static/assets/ContextMenu.vue_vue_type_script_setup_true_lang-774c517c.js +59 -0
- flowfile/web/static/assets/{CrossJoin-03df6938.js → CrossJoin-38e5b99a.js} +9 -9
- flowfile/web/static/assets/{CustomNode-8479239b.js → CustomNode-76e8f3f5.js} +27 -20
- flowfile/web/static/assets/CustomNode-edb9b939.css +42 -0
- flowfile/web/static/assets/{DatabaseConnectionSettings-869e3efd.js → DatabaseConnectionSettings-38155669.js} +4 -4
- flowfile/web/static/assets/{DatabaseConnectionSettings-e91df89a.css → DatabaseConnectionSettings-c20a1e16.css} +22 -20
- flowfile/web/static/assets/{DatabaseReader-c58b9552.js → DatabaseReader-2e549c8f.js} +13 -13
- flowfile/web/static/assets/{DatabaseReader-36898a00.css → DatabaseReader-5bf8c75b.css} +39 -44
- flowfile/web/static/assets/{DatabaseView-d26a9140.js → DatabaseView-dc877c29.js} +2 -2
- flowfile/web/static/assets/{DatabaseWriter-217a99f1.css → DatabaseWriter-bdcf2c8b.css} +27 -25
- flowfile/web/static/assets/{DatabaseWriter-4d05ddc7.js → DatabaseWriter-ffb91864.js} +12 -12
- flowfile/web/static/assets/{DesignerView-a6d0ee84.css → DesignerView-71d4e9a1.css} +429 -376
- flowfile/web/static/assets/{DesignerView-e6f5c0e8.js → DesignerView-a4466dab.js} +338 -183
- flowfile/web/static/assets/{DocumentationView-2e78ef1b.js → DocumentationView-979afc84.js} +3 -3
- flowfile/web/static/assets/{DocumentationView-fd46c656.css → DocumentationView-9ea6e871.css} +9 -9
- flowfile/web/static/assets/{ExploreData-7b54caca.js → ExploreData-e4b92aaf.js} +7 -7
- flowfile/web/static/assets/{ExternalSource-47ab05a3.css → ExternalSource-7ac7373f.css} +17 -17
- flowfile/web/static/assets/{ExternalSource-3fa399b2.js → ExternalSource-d08e7227.js} +9 -9
- flowfile/web/static/assets/{Filter-8cbbdbf3.js → Filter-7add806d.js} +9 -9
- flowfile/web/static/assets/{Formula-aac42b1e.js → Formula-36ab24d2.js} +9 -9
- flowfile/web/static/assets/{FuzzyMatch-cd9bbfca.js → FuzzyMatch-cc01bb04.js} +10 -10
- flowfile/web/static/assets/{GraphSolver-c24dec17.css → GraphSolver-4b4d7db9.css} +4 -4
- flowfile/web/static/assets/{GraphSolver-c7e6780e.js → GraphSolver-4fb98f3b.js} +11 -11
- flowfile/web/static/assets/GroupBy-5792782d.css +9 -0
- flowfile/web/static/assets/{GroupBy-93c5d22b.js → GroupBy-b3c8f429.js} +9 -9
- flowfile/web/static/assets/{Join-a19b2de2.js → Join-096b7b26.js} +10 -10
- flowfile/web/static/assets/{LoginView-0df4ed0a.js → LoginView-c33a246a.js} +1 -1
- flowfile/web/static/assets/{ManualInput-3702e677.css → ManualInput-39111f19.css} +48 -48
- flowfile/web/static/assets/{ManualInput-8d3374b2.js → ManualInput-7307e9b1.js} +55 -13
- flowfile/web/static/assets/{MultiSelect-ad1b6243.js → MultiSelect-14822c48.js} +2 -2
- flowfile/web/static/assets/{MultiSelect.vue_vue_type_script_setup_true_lang-e278950d.js → MultiSelect.vue_vue_type_script_setup_true_lang-90c4d340.js} +1 -1
- flowfile/web/static/assets/{NodeDesigner-40b647c9.js → NodeDesigner-5036c392.js} +171 -69
- flowfile/web/static/assets/{NodeDesigner-5f53be3f.css → NodeDesigner-94cd4dd3.css} +190 -190
- flowfile/web/static/assets/{NumericInput-7100234c.js → NumericInput-15cf3b72.js} +2 -2
- flowfile/web/static/assets/{NumericInput.vue_vue_type_script_setup_true_lang-5130219f.js → NumericInput.vue_vue_type_script_setup_true_lang-91e679d7.js} +1 -1
- flowfile/web/static/assets/{Output-f5efd2aa.js → Output-1f8ed42c.js} +13 -12
- flowfile/web/static/assets/{Output-35e97000.css → Output-692dd25d.css} +10 -10
- flowfile/web/static/assets/{Pivot-d981d23c.js → Pivot-0e153f4e.js} +10 -10
- flowfile/web/static/assets/{PivotValidation-63de1f73.js → PivotValidation-5a4f7c79.js} +1 -1
- flowfile/web/static/assets/{PivotValidation-39386e95.js → PivotValidation-81ec2a33.js} +1 -1
- flowfile/web/static/assets/{PolarsCode-f9d69217.js → PolarsCode-a39f15ac.js} +7 -7
- flowfile/web/static/assets/PopOver-ddcfe4f6.js +138 -0
- flowfile/web/static/assets/{Read-aec2e377.js → Read-39b63932.js} +15 -14
- flowfile/web/static/assets/{Read-36e7bd51.css → Read-90f366bc.css} +13 -13
- flowfile/web/static/assets/{RecordCount-78ed6845.js → RecordCount-e9048ccd.js} +6 -6
- flowfile/web/static/assets/{RecordId-2156e890.js → RecordId-ad02521d.js} +9 -9
- flowfile/web/static/assets/{SQLQueryComponent-48c72f5b.js → SQLQueryComponent-2eeecf0b.js} +3 -3
- flowfile/web/static/assets/SQLQueryComponent-edb90b98.css +29 -0
- flowfile/web/static/assets/{Sample-1352ca74.js → Sample-9a68c23d.js} +6 -6
- flowfile/web/static/assets/{SecretSelector-22b5ff89.js → SecretSelector-2429f35a.js} +2 -2
- flowfile/web/static/assets/{SecretsView-17df66ee.js → SecretsView-c6afc915.js} +2 -2
- flowfile/web/static/assets/{Select-0aee4c54.js → Select-fcd002b6.js} +9 -9
- flowfile/web/static/assets/{SettingsSection-cd341bb6.js → SettingsSection-5ce15962.js} +1 -1
- flowfile/web/static/assets/{SettingsSection-0784e157.js → SettingsSection-c6b1362c.js} +1 -1
- flowfile/web/static/assets/{SettingsSection-f2002a6d.js → SettingsSection-cebb91d5.js} +1 -1
- flowfile/web/static/assets/SetupView-2d12e01f.js +160 -0
- flowfile/web/static/assets/SetupView-ec26f76a.css +230 -0
- flowfile/web/static/assets/{SingleSelect-460cc0ea.js → SingleSelect-b67de4eb.js} +2 -2
- flowfile/web/static/assets/{SingleSelect.vue_vue_type_script_setup_true_lang-30741bb2.js → SingleSelect.vue_vue_type_script_setup_true_lang-eedb70eb.js} +1 -1
- flowfile/web/static/assets/{SliderInput-5d926864.js → SliderInput-fd8134ac.js} +1 -1
- flowfile/web/static/assets/Sort-4abb7fae.css +9 -0
- flowfile/web/static/assets/{Sort-3cdc971b.js → Sort-c005a573.js} +9 -9
- flowfile/web/static/assets/{TextInput-a2d0bfbd.js → TextInput-1bb31dab.js} +2 -2
- flowfile/web/static/assets/{TextInput.vue_vue_type_script_setup_true_lang-abad1ca2.js → TextInput.vue_vue_type_script_setup_true_lang-a51fe730.js} +1 -1
- flowfile/web/static/assets/{TextToRows-918945f7.js → TextToRows-4f363753.js} +9 -9
- flowfile/web/static/assets/{ToggleSwitch-f0ef5196.js → ToggleSwitch-ca0f2e5e.js} +2 -2
- flowfile/web/static/assets/{ToggleSwitch.vue_vue_type_script_setup_true_lang-5605c793.js → ToggleSwitch.vue_vue_type_script_setup_true_lang-49aa41d8.js} +1 -1
- flowfile/web/static/assets/{UnavailableFields-54d2f518.css → UnavailableFields-394a1f78.css} +13 -13
- flowfile/web/static/assets/{UnavailableFields-bdad6144.js → UnavailableFields-f6147968.js} +4 -4
- flowfile/web/static/assets/{Union-e8ab8c86.js → Union-c65f17b7.js} +6 -6
- flowfile/web/static/assets/Unique-2b705521.css +3 -0
- flowfile/web/static/assets/{Unique-8cd4f976.js → Unique-a1d96fb2.js} +12 -12
- flowfile/web/static/assets/{Unpivot-710a2948.css → Unpivot-b6ad6427.css} +6 -6
- flowfile/web/static/assets/{Unpivot-8da14095.js → Unpivot-c2657ff3.js} +11 -11
- flowfile/web/static/assets/{UnpivotValidation-6f7d89ff.js → UnpivotValidation-28e29a3b.js} +1 -1
- flowfile/web/static/assets/{VueGraphicWalker-3fb312e1.js → VueGraphicWalker-2fc3ddd4.js} +1 -1
- flowfile/web/static/assets/{api-24483f0d.js → api-df48ec50.js} +1 -1
- flowfile/web/static/assets/{api-8b81fa73.js → api-ee542cf7.js} +1 -1
- flowfile/web/static/assets/{dropDown-3d8dc5fa.css → dropDown-1d6acbd9.css} +26 -26
- flowfile/web/static/assets/{dropDown-ac0fda9d.js → dropDown-7576a76a.js} +3 -3
- flowfile/web/static/assets/{fullEditor-5497a84a.js → fullEditor-7583bef5.js} +3 -3
- flowfile/web/static/assets/{fullEditor-a0be62b3.css → fullEditor-fe9f7e18.css} +3 -3
- flowfile/web/static/assets/{genericNodeSettings-99014e1d.js → genericNodeSettings-0155288b.js} +2 -3
- flowfile/web/static/assets/{index-3ba44389.js → index-057d770d.js} +2 -2
- flowfile/web/static/assets/{index-07dda503.js → index-aeec439d.js} +1 -1
- flowfile/web/static/assets/{index-fb6493ae.js → index-ca6799de.js} +2293 -196
- flowfile/web/static/assets/{index-e6289dd0.css → index-d60c9dd4.css} +560 -10
- flowfile/web/static/assets/nodeInput-d478b9ac.js +2 -0
- flowfile/web/static/assets/{outputCsv-8f8ba42d.js → outputCsv-c492b15e.js} +3 -3
- flowfile/web/static/assets/outputCsv-cc84e09f.css +2499 -0
- flowfile/web/static/assets/{outputExcel-393f4fef.js → outputExcel-13bfa10f.js} +1 -1
- flowfile/web/static/assets/{outputParquet-07c81f65.js → outputParquet-9be1523a.js} +1 -1
- flowfile/web/static/assets/{readCsv-07f6d9ad.js → readCsv-5a49a8c9.js} +1 -1
- flowfile/web/static/assets/{readExcel-ed69bc8f.js → readExcel-27c30ad8.js} +3 -3
- flowfile/web/static/assets/{readParquet-e3ed4528.js → readParquet-446bde68.js} +1 -1
- flowfile/web/static/assets/{secrets.api-002e7d7e.js → secrets.api-34431884.js} +1 -1
- flowfile/web/static/assets/{selectDynamic-80b92899.js → selectDynamic-5754a2b1.js} +2 -3
- flowfile/web/static/assets/{vue-codemirror.esm-0965f39f.js → vue-codemirror.esm-8f46fb36.js} +1 -1
- flowfile/web/static/assets/{vue-content-loader.es-c506ad97.js → vue-content-loader.es-808fe33a.js} +1 -1
- flowfile/web/static/index.html +2 -2
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/METADATA +2 -2
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/RECORD +139 -134
- flowfile_core/auth/secrets.py +56 -13
- flowfile_core/fileExplorer/funcs.py +26 -4
- flowfile_core/flowfile/code_generator/__init__.py +11 -0
- flowfile_core/flowfile/code_generator/code_generator.py +347 -2
- flowfile_core/flowfile/flow_data_engine/flow_data_engine.py +13 -1
- flowfile_core/flowfile/flow_data_engine/subprocess_operations/subprocess_operations.py +12 -0
- flowfile_core/flowfile/flow_graph.py +2 -0
- flowfile_core/flowfile/flow_node/flow_node.py +52 -28
- flowfile_core/flowfile/node_designer/__init__.py +4 -0
- flowfile_core/flowfile/node_designer/ui_components.py +144 -1
- flowfile_core/main.py +2 -4
- flowfile_core/routes/public.py +43 -1
- flowfile_core/schemas/cloud_storage_schemas.py +39 -15
- flowfile_core/secret_manager/secret_manager.py +107 -6
- flowfile_frame/__init__.py +11 -0
- flowfile_frame/database/__init__.py +36 -0
- flowfile_frame/database/connection_manager.py +205 -0
- flowfile_frame/database/frame_helpers.py +249 -0
- flowfile_worker/configs.py +31 -15
- flowfile_worker/secrets.py +105 -15
- flowfile_worker/spawner.py +10 -6
- flowfile/web/static/assets/ContextMenu-26d4dd27.css +0 -26
- flowfile/web/static/assets/ContextMenu-31ee57f0.js +0 -41
- flowfile/web/static/assets/ContextMenu-69a74055.js +0 -41
- flowfile/web/static/assets/ContextMenu-8e2051c6.js +0 -41
- flowfile/web/static/assets/ContextMenu-8ec1729e.css +0 -26
- flowfile/web/static/assets/ContextMenu-9b310c60.css +0 -26
- flowfile/web/static/assets/CustomNode-59e99a86.css +0 -32
- flowfile/web/static/assets/GroupBy-be7ac0bf.css +0 -51
- flowfile/web/static/assets/PopOver-b22f049e.js +0 -939
- flowfile/web/static/assets/SQLQueryComponent-1c2f26b4.css +0 -27
- flowfile/web/static/assets/Sort-8a871341.css +0 -51
- flowfile/web/static/assets/Unique-9fb2f567.css +0 -51
- flowfile/web/static/assets/nodeInput-0eb13f1a.js +0 -2
- flowfile/web/static/assets/outputCsv-b9a072af.css +0 -2499
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/WHEEL +0 -0
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/entry_points.txt +0 -0
- {flowfile-0.5.3.dist-info → flowfile-0.5.6.dist-info}/licenses/LICENSE +0 -0
flowfile_worker/secrets.py
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
Simplified secure storage module for FlowFile worker to read credentials and secrets.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import base64
|
|
5
6
|
import json
|
|
6
7
|
import logging
|
|
7
8
|
import os
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
|
|
10
11
|
from cryptography.fernet import Fernet
|
|
12
|
+
from cryptography.hazmat.primitives import hashes
|
|
13
|
+
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
|
11
14
|
from pydantic import SecretStr
|
|
12
15
|
|
|
13
16
|
from flowfile_worker.configs import TEST_MODE
|
|
@@ -15,6 +18,12 @@ from flowfile_worker.configs import TEST_MODE
|
|
|
15
18
|
# Set up logging
|
|
16
19
|
logger = logging.getLogger(__name__)
|
|
17
20
|
|
|
21
|
+
# Version identifier for key derivation scheme (must match flowfile_core)
|
|
22
|
+
KEY_DERIVATION_VERSION = b"flowfile-secrets-v1"
|
|
23
|
+
|
|
24
|
+
# Encrypted secret format: $ffsec$1${user_id}${fernet_token}
|
|
25
|
+
SECRET_FORMAT_PREFIX = "$ffsec$1$"
|
|
26
|
+
|
|
18
27
|
|
|
19
28
|
class SecureStorage:
|
|
20
29
|
"""A secure local storage mechanism for reading secrets using Fernet encryption."""
|
|
@@ -69,27 +78,42 @@ def get_password(service_name, username):
|
|
|
69
78
|
return _storage.get_password(service_name, username)
|
|
70
79
|
|
|
71
80
|
|
|
72
|
-
def get_docker_secret_key():
|
|
81
|
+
def get_docker_secret_key() -> str | None:
|
|
73
82
|
"""
|
|
74
|
-
Get the master key from Docker secret.
|
|
83
|
+
Get the master key from Docker secret or environment variable.
|
|
75
84
|
|
|
76
85
|
Returns:
|
|
77
|
-
str: The master key if successfully read
|
|
86
|
+
str: The master key if successfully read, None if not configured.
|
|
78
87
|
|
|
79
88
|
Raises:
|
|
80
|
-
RuntimeError: If
|
|
89
|
+
RuntimeError: If the secret file exists but cannot be read, or key is invalid.
|
|
81
90
|
"""
|
|
91
|
+
# First, check for environment variable (allows runtime configuration)
|
|
92
|
+
env_key = os.environ.get("FLOWFILE_MASTER_KEY")
|
|
93
|
+
if env_key:
|
|
94
|
+
# Validate it's a proper Fernet key
|
|
95
|
+
try:
|
|
96
|
+
Fernet(env_key.encode())
|
|
97
|
+
return env_key
|
|
98
|
+
except Exception:
|
|
99
|
+
logger.error("FLOWFILE_MASTER_KEY environment variable is not a valid Fernet key")
|
|
100
|
+
raise RuntimeError("FLOWFILE_MASTER_KEY is not a valid Fernet key")
|
|
101
|
+
|
|
102
|
+
# Then, check for Docker secret file
|
|
82
103
|
secret_path = "/run/secrets/flowfile_master_key"
|
|
83
104
|
if os.path.exists(secret_path):
|
|
84
105
|
try:
|
|
85
106
|
with open(secret_path) as f:
|
|
86
|
-
|
|
107
|
+
key = f.read().strip()
|
|
108
|
+
# Validate the key
|
|
109
|
+
Fernet(key.encode())
|
|
110
|
+
return key
|
|
87
111
|
except Exception as e:
|
|
88
|
-
logger.error(f"Failed to read master key from Docker secret: {e}")
|
|
112
|
+
logger.error(f"Failed to read or validate master key from Docker secret: {e}")
|
|
89
113
|
raise RuntimeError("Failed to read master key from Docker secret")
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
114
|
+
|
|
115
|
+
# No key configured
|
|
116
|
+
return None
|
|
93
117
|
|
|
94
118
|
|
|
95
119
|
def get_master_key() -> str:
|
|
@@ -97,13 +121,14 @@ def get_master_key() -> str:
|
|
|
97
121
|
Get the master encryption key.
|
|
98
122
|
|
|
99
123
|
If in TEST_MODE, returns a test key.
|
|
100
|
-
If running in Docker, retrieves the key from Docker secrets.
|
|
124
|
+
If running in Docker, retrieves the key from Docker secrets or environment.
|
|
101
125
|
Otherwise, retrieves the key from secure storage.
|
|
102
126
|
|
|
103
127
|
Returns:
|
|
104
128
|
str: The master encryption key
|
|
105
129
|
|
|
106
130
|
Raises:
|
|
131
|
+
RuntimeError: If in Docker mode and no key is configured.
|
|
107
132
|
ValueError: If the master key is not found in storage.
|
|
108
133
|
"""
|
|
109
134
|
# First check for test mode
|
|
@@ -112,7 +137,13 @@ def get_master_key() -> str:
|
|
|
112
137
|
|
|
113
138
|
# Next check if running in Docker
|
|
114
139
|
if os.environ.get("FLOWFILE_MODE") == "docker":
|
|
115
|
-
|
|
140
|
+
key = get_docker_secret_key()
|
|
141
|
+
if key is None:
|
|
142
|
+
raise RuntimeError(
|
|
143
|
+
"Master key not configured. Set FLOWFILE_MASTER_KEY environment variable "
|
|
144
|
+
"or mount the flowfile_master_key Docker secret."
|
|
145
|
+
)
|
|
146
|
+
return key
|
|
116
147
|
|
|
117
148
|
# Otherwise read from local storage
|
|
118
149
|
key = get_password("flowfile", "master_key")
|
|
@@ -121,9 +152,41 @@ def get_master_key() -> str:
|
|
|
121
152
|
return key
|
|
122
153
|
|
|
123
154
|
|
|
124
|
-
def
|
|
155
|
+
def derive_user_key(user_id: int) -> bytes:
|
|
125
156
|
"""
|
|
126
|
-
|
|
157
|
+
Derive a user-specific encryption key from the master key using HKDF.
|
|
158
|
+
|
|
159
|
+
This provides cryptographic isolation between users - each user's secrets
|
|
160
|
+
are encrypted with a unique key derived from the master key.
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
user_id: The unique identifier for the user
|
|
164
|
+
|
|
165
|
+
Returns:
|
|
166
|
+
bytes: A 32-byte URL-safe base64-encoded key suitable for Fernet
|
|
167
|
+
"""
|
|
168
|
+
master_key = get_master_key().encode()
|
|
169
|
+
|
|
170
|
+
# Use HKDF to derive a user-specific key
|
|
171
|
+
hkdf = HKDF(
|
|
172
|
+
algorithm=hashes.SHA256(),
|
|
173
|
+
length=32, # Fernet requires 32 bytes
|
|
174
|
+
salt=KEY_DERIVATION_VERSION, # Static salt is fine for key derivation
|
|
175
|
+
info=f"user-{user_id}".encode(), # User-specific context
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Derive raw key material and encode for Fernet
|
|
179
|
+
derived_key = hkdf.derive(master_key)
|
|
180
|
+
return base64.urlsafe_b64encode(derived_key)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def decrypt_secret(encrypted_value: str) -> SecretStr:
|
|
184
|
+
"""
|
|
185
|
+
Decrypt an encrypted value.
|
|
186
|
+
|
|
187
|
+
Supports both new format (with embedded user_id) and legacy format.
|
|
188
|
+
- New format: $ffsec$1${user_id}${fernet_token} - user_id extracted automatically
|
|
189
|
+
- Legacy format: raw Fernet token - uses master key directly
|
|
127
190
|
|
|
128
191
|
Args:
|
|
129
192
|
encrypted_value: The encrypted value as a string
|
|
@@ -131,21 +194,48 @@ def decrypt_secret(encrypted_value) -> SecretStr:
|
|
|
131
194
|
Returns:
|
|
132
195
|
SecretStr: The decrypted value as a SecretStr
|
|
133
196
|
"""
|
|
197
|
+
# Check for new versioned format with embedded user_id
|
|
198
|
+
if encrypted_value.startswith(SECRET_FORMAT_PREFIX):
|
|
199
|
+
# Parse: $ffsec$1${user_id}${fernet_token}
|
|
200
|
+
remainder = encrypted_value[len(SECRET_FORMAT_PREFIX):]
|
|
201
|
+
parts = remainder.split("$", 1)
|
|
202
|
+
if len(parts) != 2:
|
|
203
|
+
raise ValueError("Invalid encrypted secret format")
|
|
204
|
+
|
|
205
|
+
embedded_user_id = int(parts[0])
|
|
206
|
+
fernet_token = parts[1]
|
|
207
|
+
|
|
208
|
+
key = derive_user_key(embedded_user_id)
|
|
209
|
+
f = Fernet(key)
|
|
210
|
+
return SecretStr(f.decrypt(fernet_token.encode()).decode())
|
|
211
|
+
|
|
212
|
+
# Legacy format - use master key directly
|
|
134
213
|
key = get_master_key().encode()
|
|
135
214
|
f = Fernet(key)
|
|
136
215
|
return SecretStr(f.decrypt(encrypted_value.encode()).decode())
|
|
137
216
|
|
|
138
217
|
|
|
139
|
-
def encrypt_secret(secret_value):
|
|
218
|
+
def encrypt_secret(secret_value: str, user_id: int | None = None) -> str:
|
|
140
219
|
"""
|
|
141
|
-
Encrypt a secret value
|
|
220
|
+
Encrypt a secret value.
|
|
221
|
+
|
|
222
|
+
If user_id is provided, uses per-user key derivation with embedded user_id format.
|
|
223
|
+
Otherwise, uses legacy master key encryption (for backward compatibility in tests).
|
|
142
224
|
|
|
143
225
|
Args:
|
|
144
226
|
secret_value: The secret value to encrypt
|
|
227
|
+
user_id: Optional user ID for per-user key derivation
|
|
145
228
|
|
|
146
229
|
Returns:
|
|
147
230
|
str: The encrypted value as a string
|
|
148
231
|
"""
|
|
232
|
+
if user_id is not None:
|
|
233
|
+
key = derive_user_key(user_id)
|
|
234
|
+
f = Fernet(key)
|
|
235
|
+
fernet_token = f.encrypt(secret_value.encode()).decode()
|
|
236
|
+
return f"{SECRET_FORMAT_PREFIX}{user_id}${fernet_token}"
|
|
237
|
+
|
|
238
|
+
# Legacy format for backward compatibility
|
|
149
239
|
key = get_master_key().encode()
|
|
150
240
|
f = Fernet(key)
|
|
151
241
|
return f.encrypt(secret_value.encode()).decode()
|
flowfile_worker/spawner.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import gc
|
|
2
|
-
from multiprocessing import Process
|
|
2
|
+
from multiprocessing import Process
|
|
3
|
+
from multiprocessing.queues import Queue
|
|
3
4
|
from time import sleep
|
|
4
5
|
|
|
5
6
|
from flowfile_worker import funcs, models, mp_context, status_dict, status_dict_lock
|
|
@@ -55,11 +56,14 @@ def handle_task(task_id: str, p: Process, progress: mp_context.Value, error_mess
|
|
|
55
56
|
with status_dict_lock:
|
|
56
57
|
status = status_dict[task_id]
|
|
57
58
|
if status.status != "Cancelled":
|
|
58
|
-
|
|
59
|
+
# Read progress value with lock to ensure consistency
|
|
60
|
+
with progress.get_lock():
|
|
61
|
+
final_progress = progress.value
|
|
62
|
+
if final_progress == 100:
|
|
59
63
|
status.status = "Completed"
|
|
60
64
|
if not q.empty():
|
|
61
65
|
status.results = q.get()
|
|
62
|
-
elif
|
|
66
|
+
elif final_progress != -1:
|
|
63
67
|
status_dict[task_id].status = "Unknown Error"
|
|
64
68
|
|
|
65
69
|
finally:
|
|
@@ -103,7 +107,7 @@ def start_process(
|
|
|
103
107
|
kwargs["polars_serializable_object"] = polars_serializable_object
|
|
104
108
|
kwargs["progress"] = mp_context.Value("i", 0)
|
|
105
109
|
kwargs["error_message"] = mp_context.Array("c", 1024)
|
|
106
|
-
kwargs["queue"] = Queue(maxsize=1)
|
|
110
|
+
kwargs["queue"] = mp_context.Queue(maxsize=1)
|
|
107
111
|
kwargs["file_path"] = file_ref
|
|
108
112
|
kwargs["flowfile_flow_id"] = flowfile_flow_id
|
|
109
113
|
kwargs["flowfile_node_id"] = flowfile_node_id
|
|
@@ -145,7 +149,7 @@ def start_generic_process(
|
|
|
145
149
|
kwargs["func"] = func_ref
|
|
146
150
|
kwargs["progress"] = mp_context.Value("i", 0)
|
|
147
151
|
kwargs["error_message"] = mp_context.Array("c", 1024)
|
|
148
|
-
kwargs["queue"] = Queue(maxsize=1)
|
|
152
|
+
kwargs["queue"] = mp_context.Queue(maxsize=1)
|
|
149
153
|
kwargs["file_path"] = file_ref
|
|
150
154
|
kwargs["flowfile_flow_id"] = flowfile_flow_id
|
|
151
155
|
kwargs["flowfile_node_id"] = flowfile_node_id
|
|
@@ -187,7 +191,7 @@ def start_fuzzy_process(
|
|
|
187
191
|
"""
|
|
188
192
|
progress = mp_context.Value("i", 0)
|
|
189
193
|
error_message = mp_context.Array("c", 1024)
|
|
190
|
-
q = Queue(maxsize=1)
|
|
194
|
+
q = mp_context.Queue(maxsize=1)
|
|
191
195
|
|
|
192
196
|
args: tuple[
|
|
193
197
|
bytes,
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
.context-menu[data-v-f6c94f99] {
|
|
3
|
-
position: fixed;
|
|
4
|
-
z-index: 1000;
|
|
5
|
-
border: 1px solid #ccc;
|
|
6
|
-
background-color: var(--color-background-primary);
|
|
7
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
8
|
-
border-radius: 4px;
|
|
9
|
-
user-select: none;
|
|
10
|
-
}
|
|
11
|
-
.context-menu ul[data-v-f6c94f99] {
|
|
12
|
-
list-style: none;
|
|
13
|
-
padding: 0;
|
|
14
|
-
margin: 0;
|
|
15
|
-
}
|
|
16
|
-
.context-menu li[data-v-f6c94f99] {
|
|
17
|
-
padding: 8px 16px;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
}
|
|
20
|
-
.context-menu li.disabled[data-v-f6c94f99] {
|
|
21
|
-
color: #ccc;
|
|
22
|
-
cursor: not-allowed;
|
|
23
|
-
}
|
|
24
|
-
.context-menu li[data-v-f6c94f99]:hover:not(.disabled) {
|
|
25
|
-
background-color: #f0f0f0;
|
|
26
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { d as defineComponent, o as openBlock, c as createElementBlock, b as createBaseVNode, F as Fragment, D as renderList, n as normalizeClass, t as toDisplayString, Z as normalizeStyle, i as _export_sfc } from "./index-fb6493ae.js";
|
|
2
|
-
const _hoisted_1 = ["onClick"];
|
|
3
|
-
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
4
|
-
__name: "ContextMenu",
|
|
5
|
-
props: {
|
|
6
|
-
position: { type: Object, required: true },
|
|
7
|
-
options: {
|
|
8
|
-
type: Array,
|
|
9
|
-
required: true
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
emits: ["select", "close"],
|
|
13
|
-
setup(__props, { emit: __emit }) {
|
|
14
|
-
const emit = __emit;
|
|
15
|
-
const selectOption = (action) => {
|
|
16
|
-
emit("select", action);
|
|
17
|
-
emit("close");
|
|
18
|
-
};
|
|
19
|
-
return (_ctx, _cache) => {
|
|
20
|
-
return openBlock(), createElementBlock("div", {
|
|
21
|
-
class: "context-menu",
|
|
22
|
-
style: normalizeStyle({ top: __props.position.y + "px", left: __props.position.x + "px" })
|
|
23
|
-
}, [
|
|
24
|
-
createBaseVNode("ul", null, [
|
|
25
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.options, (option) => {
|
|
26
|
-
return openBlock(), createElementBlock("li", {
|
|
27
|
-
key: option.action,
|
|
28
|
-
class: normalizeClass({ disabled: option.disabled }),
|
|
29
|
-
onClick: ($event) => !option.disabled && selectOption(option.action)
|
|
30
|
-
}, toDisplayString(option.label), 11, _hoisted_1);
|
|
31
|
-
}), 128))
|
|
32
|
-
])
|
|
33
|
-
], 4);
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
const ContextMenu_vue_vue_type_style_index_0_scoped_f6c94f99_lang = "";
|
|
38
|
-
const ContextMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-f6c94f99"]]);
|
|
39
|
-
export {
|
|
40
|
-
ContextMenu as default
|
|
41
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { d as defineComponent, o as openBlock, c as createElementBlock, b as createBaseVNode, F as Fragment, D as renderList, n as normalizeClass, t as toDisplayString, Z as normalizeStyle, i as _export_sfc } from "./index-fb6493ae.js";
|
|
2
|
-
const _hoisted_1 = ["onClick"];
|
|
3
|
-
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
4
|
-
__name: "ContextMenu",
|
|
5
|
-
props: {
|
|
6
|
-
position: { type: Object, required: true },
|
|
7
|
-
options: {
|
|
8
|
-
type: Array,
|
|
9
|
-
required: true
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
emits: ["select", "close"],
|
|
13
|
-
setup(__props, { emit: __emit }) {
|
|
14
|
-
const emit = __emit;
|
|
15
|
-
const selectOption = (action) => {
|
|
16
|
-
emit("select", action);
|
|
17
|
-
emit("close");
|
|
18
|
-
};
|
|
19
|
-
return (_ctx, _cache) => {
|
|
20
|
-
return openBlock(), createElementBlock("div", {
|
|
21
|
-
class: "context-menu",
|
|
22
|
-
style: normalizeStyle({ top: __props.position.y + "px", left: __props.position.x + "px" })
|
|
23
|
-
}, [
|
|
24
|
-
createBaseVNode("ul", null, [
|
|
25
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.options, (option) => {
|
|
26
|
-
return openBlock(), createElementBlock("li", {
|
|
27
|
-
key: option.action,
|
|
28
|
-
class: normalizeClass({ disabled: option.disabled }),
|
|
29
|
-
onClick: ($event) => !option.disabled && selectOption(option.action)
|
|
30
|
-
}, toDisplayString(option.label), 11, _hoisted_1);
|
|
31
|
-
}), 128))
|
|
32
|
-
])
|
|
33
|
-
], 4);
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
const ContextMenu_vue_vue_type_style_index_0_scoped_6b361c01_lang = "";
|
|
38
|
-
const ContextMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6b361c01"]]);
|
|
39
|
-
export {
|
|
40
|
-
ContextMenu as default
|
|
41
|
-
};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { d as defineComponent, o as openBlock, c as createElementBlock, b as createBaseVNode, F as Fragment, D as renderList, n as normalizeClass, t as toDisplayString, Z as normalizeStyle, i as _export_sfc } from "./index-fb6493ae.js";
|
|
2
|
-
const _hoisted_1 = ["onClick"];
|
|
3
|
-
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
4
|
-
__name: "ContextMenu",
|
|
5
|
-
props: {
|
|
6
|
-
position: { type: Object, required: true },
|
|
7
|
-
options: {
|
|
8
|
-
type: Array,
|
|
9
|
-
required: true
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
emits: ["select", "close"],
|
|
13
|
-
setup(__props, { emit: __emit }) {
|
|
14
|
-
const emit = __emit;
|
|
15
|
-
const selectOption = (action) => {
|
|
16
|
-
emit("select", action);
|
|
17
|
-
emit("close");
|
|
18
|
-
};
|
|
19
|
-
return (_ctx, _cache) => {
|
|
20
|
-
return openBlock(), createElementBlock("div", {
|
|
21
|
-
class: "context-menu",
|
|
22
|
-
style: normalizeStyle({ top: __props.position.y + "px", left: __props.position.x + "px" })
|
|
23
|
-
}, [
|
|
24
|
-
createBaseVNode("ul", null, [
|
|
25
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList(__props.options, (option) => {
|
|
26
|
-
return openBlock(), createElementBlock("li", {
|
|
27
|
-
key: option.action,
|
|
28
|
-
class: normalizeClass({ disabled: option.disabled }),
|
|
29
|
-
onClick: ($event) => !option.disabled && selectOption(option.action)
|
|
30
|
-
}, toDisplayString(option.label), 11, _hoisted_1);
|
|
31
|
-
}), 128))
|
|
32
|
-
])
|
|
33
|
-
], 4);
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
const ContextMenu_vue_vue_type_style_index_0_scoped_5437ad46_lang = "";
|
|
38
|
-
const ContextMenu = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-5437ad46"]]);
|
|
39
|
-
export {
|
|
40
|
-
ContextMenu as default
|
|
41
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
.context-menu[data-v-6b361c01] {
|
|
3
|
-
position: fixed;
|
|
4
|
-
z-index: 1000;
|
|
5
|
-
border: 1px solid #ccc;
|
|
6
|
-
background-color: var(--color-background-primary);
|
|
7
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
8
|
-
border-radius: 4px;
|
|
9
|
-
user-select: none;
|
|
10
|
-
}
|
|
11
|
-
.context-menu ul[data-v-6b361c01] {
|
|
12
|
-
list-style: none;
|
|
13
|
-
padding: 0;
|
|
14
|
-
margin: 0;
|
|
15
|
-
}
|
|
16
|
-
.context-menu li[data-v-6b361c01] {
|
|
17
|
-
padding: 8px 16px;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
}
|
|
20
|
-
.context-menu li.disabled[data-v-6b361c01] {
|
|
21
|
-
color: #ccc;
|
|
22
|
-
cursor: not-allowed;
|
|
23
|
-
}
|
|
24
|
-
.context-menu li[data-v-6b361c01]:hover:not(.disabled) {
|
|
25
|
-
background-color: #f0f0f0;
|
|
26
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
.context-menu[data-v-5437ad46] {
|
|
3
|
-
position: fixed;
|
|
4
|
-
z-index: 1000;
|
|
5
|
-
border: 1px solid #ccc;
|
|
6
|
-
background-color: var(--color-background-primary);
|
|
7
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
8
|
-
border-radius: 4px;
|
|
9
|
-
user-select: none;
|
|
10
|
-
}
|
|
11
|
-
.context-menu ul[data-v-5437ad46] {
|
|
12
|
-
list-style: none;
|
|
13
|
-
padding: 0;
|
|
14
|
-
margin: 0;
|
|
15
|
-
}
|
|
16
|
-
.context-menu li[data-v-5437ad46] {
|
|
17
|
-
padding: 8px 16px;
|
|
18
|
-
cursor: pointer;
|
|
19
|
-
}
|
|
20
|
-
.context-menu li.disabled[data-v-5437ad46] {
|
|
21
|
-
color: #ccc;
|
|
22
|
-
cursor: not-allowed;
|
|
23
|
-
}
|
|
24
|
-
.context-menu li[data-v-5437ad46]:hover:not(.disabled) {
|
|
25
|
-
background-color: #f0f0f0;
|
|
26
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
.custom-node-wrapper[data-v-04422d4f] {
|
|
3
|
-
padding: 1.5rem;
|
|
4
|
-
background-color: var(--color-background-primary);
|
|
5
|
-
}
|
|
6
|
-
.node-header[data-v-04422d4f] {
|
|
7
|
-
padding-bottom: 1rem;
|
|
8
|
-
border-bottom: 1px solid var(--color-border-primary);
|
|
9
|
-
margin-bottom: 1.5rem;
|
|
10
|
-
}
|
|
11
|
-
.node-title[data-v-04422d4f] {
|
|
12
|
-
font-size: 1.25rem;
|
|
13
|
-
font-weight: 700;
|
|
14
|
-
color: var(--color-text-primary);
|
|
15
|
-
}
|
|
16
|
-
.node-category[data-v-04422d4f] {
|
|
17
|
-
font-size: 0.875rem;
|
|
18
|
-
color: var(--color-text-secondary);
|
|
19
|
-
margin-top: 0.25rem;
|
|
20
|
-
}
|
|
21
|
-
.section-description[data-v-04422d4f] {
|
|
22
|
-
font-size: 0.875rem;
|
|
23
|
-
color: var(--color-text-secondary);
|
|
24
|
-
margin-top: 0.25rem;
|
|
25
|
-
margin-bottom: 1.25rem;
|
|
26
|
-
padding-left: 0.5rem;
|
|
27
|
-
}
|
|
28
|
-
.components-container[data-v-04422d4f] {
|
|
29
|
-
display: flex;
|
|
30
|
-
flex-direction: column;
|
|
31
|
-
gap: 1.25rem;
|
|
32
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
.context-menu[data-v-9d79e03b] {
|
|
3
|
-
position: fixed;
|
|
4
|
-
z-index: 1000;
|
|
5
|
-
border: 1px solid #ccc;
|
|
6
|
-
background-color: var(--color-background-primary);
|
|
7
|
-
padding: 8px;
|
|
8
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
9
|
-
border-radius: 4px;
|
|
10
|
-
user-select: none;
|
|
11
|
-
}
|
|
12
|
-
.context-menu button[data-v-9d79e03b] {
|
|
13
|
-
display: block;
|
|
14
|
-
background: none;
|
|
15
|
-
border: none;
|
|
16
|
-
padding: 4px 8px;
|
|
17
|
-
text-align: left;
|
|
18
|
-
width: 100%;
|
|
19
|
-
cursor: pointer;
|
|
20
|
-
z-index: 100;
|
|
21
|
-
}
|
|
22
|
-
.context-menu button[data-v-9d79e03b]:hover {
|
|
23
|
-
background-color: #f0f0f0;
|
|
24
|
-
}
|
|
25
|
-
.table-wrapper[data-v-9d79e03b] {
|
|
26
|
-
max-height: 300px; /* Adjust this value as needed */
|
|
27
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* subtle shadow for depth */
|
|
28
|
-
border-radius: 8px; /* rounded corners */
|
|
29
|
-
overflow: auto; /* ensures the rounded corners are applied to the child elements */
|
|
30
|
-
margin: 5px; /* adds a small margin around the table */
|
|
31
|
-
}
|
|
32
|
-
.context-menu[data-v-9d79e03b] {
|
|
33
|
-
position: fixed;
|
|
34
|
-
z-index: 1000;
|
|
35
|
-
border: 1px solid #ccc;
|
|
36
|
-
background-color: var(--color-background-primary);
|
|
37
|
-
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
|
38
|
-
border-radius: 4px;
|
|
39
|
-
}
|
|
40
|
-
.context-menu ul[data-v-9d79e03b] {
|
|
41
|
-
list-style: none;
|
|
42
|
-
padding: 0;
|
|
43
|
-
margin: 0;
|
|
44
|
-
}
|
|
45
|
-
.context-menu li[data-v-9d79e03b] {
|
|
46
|
-
padding: 8px 16px;
|
|
47
|
-
cursor: pointer;
|
|
48
|
-
}
|
|
49
|
-
.context-menu li[data-v-9d79e03b]:hover {
|
|
50
|
-
background-color: #f0f0f0;
|
|
51
|
-
}
|