kailash 0.3.2__py3-none-any.whl → 0.4.1__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.
- kailash/__init__.py +33 -1
- kailash/access_control/__init__.py +129 -0
- kailash/access_control/managers.py +461 -0
- kailash/access_control/rule_evaluators.py +467 -0
- kailash/access_control_abac.py +825 -0
- kailash/config/__init__.py +27 -0
- kailash/config/database_config.py +359 -0
- kailash/database/__init__.py +28 -0
- kailash/database/execution_pipeline.py +499 -0
- kailash/middleware/__init__.py +306 -0
- kailash/middleware/auth/__init__.py +33 -0
- kailash/middleware/auth/access_control.py +436 -0
- kailash/middleware/auth/auth_manager.py +422 -0
- kailash/middleware/auth/jwt_auth.py +477 -0
- kailash/middleware/auth/kailash_jwt_auth.py +616 -0
- kailash/middleware/communication/__init__.py +37 -0
- kailash/middleware/communication/ai_chat.py +989 -0
- kailash/middleware/communication/api_gateway.py +802 -0
- kailash/middleware/communication/events.py +470 -0
- kailash/middleware/communication/realtime.py +710 -0
- kailash/middleware/core/__init__.py +21 -0
- kailash/middleware/core/agent_ui.py +890 -0
- kailash/middleware/core/schema.py +643 -0
- kailash/middleware/core/workflows.py +396 -0
- kailash/middleware/database/__init__.py +63 -0
- kailash/middleware/database/base.py +113 -0
- kailash/middleware/database/base_models.py +525 -0
- kailash/middleware/database/enums.py +106 -0
- kailash/middleware/database/migrations.py +12 -0
- kailash/{api/database.py → middleware/database/models.py} +183 -291
- kailash/middleware/database/repositories.py +685 -0
- kailash/middleware/database/session_manager.py +19 -0
- kailash/middleware/mcp/__init__.py +38 -0
- kailash/middleware/mcp/client_integration.py +585 -0
- kailash/middleware/mcp/enhanced_server.py +576 -0
- kailash/nodes/__init__.py +27 -3
- kailash/nodes/admin/__init__.py +42 -0
- kailash/nodes/admin/audit_log.py +794 -0
- kailash/nodes/admin/permission_check.py +864 -0
- kailash/nodes/admin/role_management.py +823 -0
- kailash/nodes/admin/security_event.py +1523 -0
- kailash/nodes/admin/user_management.py +944 -0
- kailash/nodes/ai/a2a.py +24 -7
- kailash/nodes/ai/ai_providers.py +248 -40
- kailash/nodes/ai/embedding_generator.py +11 -11
- kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
- kailash/nodes/ai/llm_agent.py +436 -5
- kailash/nodes/ai/self_organizing.py +85 -10
- kailash/nodes/ai/vision_utils.py +148 -0
- kailash/nodes/alerts/__init__.py +26 -0
- kailash/nodes/alerts/base.py +234 -0
- kailash/nodes/alerts/discord.py +499 -0
- kailash/nodes/api/auth.py +287 -6
- kailash/nodes/api/rest.py +151 -0
- kailash/nodes/auth/__init__.py +17 -0
- kailash/nodes/auth/directory_integration.py +1228 -0
- kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
- kailash/nodes/auth/mfa.py +2338 -0
- kailash/nodes/auth/risk_assessment.py +872 -0
- kailash/nodes/auth/session_management.py +1093 -0
- kailash/nodes/auth/sso.py +1040 -0
- kailash/nodes/base.py +344 -13
- kailash/nodes/base_cycle_aware.py +4 -2
- kailash/nodes/base_with_acl.py +1 -1
- kailash/nodes/code/python.py +283 -10
- kailash/nodes/compliance/__init__.py +9 -0
- kailash/nodes/compliance/data_retention.py +1888 -0
- kailash/nodes/compliance/gdpr.py +2004 -0
- kailash/nodes/data/__init__.py +22 -2
- kailash/nodes/data/async_connection.py +469 -0
- kailash/nodes/data/async_sql.py +757 -0
- kailash/nodes/data/async_vector.py +598 -0
- kailash/nodes/data/readers.py +767 -0
- kailash/nodes/data/retrieval.py +360 -1
- kailash/nodes/data/sharepoint_graph.py +397 -21
- kailash/nodes/data/sql.py +94 -5
- kailash/nodes/data/streaming.py +68 -8
- kailash/nodes/data/vector_db.py +54 -4
- kailash/nodes/enterprise/__init__.py +13 -0
- kailash/nodes/enterprise/batch_processor.py +741 -0
- kailash/nodes/enterprise/data_lineage.py +497 -0
- kailash/nodes/logic/convergence.py +31 -9
- kailash/nodes/logic/operations.py +14 -3
- kailash/nodes/mixins/__init__.py +8 -0
- kailash/nodes/mixins/event_emitter.py +201 -0
- kailash/nodes/mixins/mcp.py +9 -4
- kailash/nodes/mixins/security.py +165 -0
- kailash/nodes/monitoring/__init__.py +7 -0
- kailash/nodes/monitoring/performance_benchmark.py +2497 -0
- kailash/nodes/rag/__init__.py +284 -0
- kailash/nodes/rag/advanced.py +1615 -0
- kailash/nodes/rag/agentic.py +773 -0
- kailash/nodes/rag/conversational.py +999 -0
- kailash/nodes/rag/evaluation.py +875 -0
- kailash/nodes/rag/federated.py +1188 -0
- kailash/nodes/rag/graph.py +721 -0
- kailash/nodes/rag/multimodal.py +671 -0
- kailash/nodes/rag/optimized.py +933 -0
- kailash/nodes/rag/privacy.py +1059 -0
- kailash/nodes/rag/query_processing.py +1335 -0
- kailash/nodes/rag/realtime.py +764 -0
- kailash/nodes/rag/registry.py +547 -0
- kailash/nodes/rag/router.py +837 -0
- kailash/nodes/rag/similarity.py +1854 -0
- kailash/nodes/rag/strategies.py +566 -0
- kailash/nodes/rag/workflows.py +575 -0
- kailash/nodes/security/__init__.py +19 -0
- kailash/nodes/security/abac_evaluator.py +1411 -0
- kailash/nodes/security/audit_log.py +103 -0
- kailash/nodes/security/behavior_analysis.py +1893 -0
- kailash/nodes/security/credential_manager.py +401 -0
- kailash/nodes/security/rotating_credentials.py +760 -0
- kailash/nodes/security/security_event.py +133 -0
- kailash/nodes/security/threat_detection.py +1103 -0
- kailash/nodes/testing/__init__.py +9 -0
- kailash/nodes/testing/credential_testing.py +499 -0
- kailash/nodes/transform/__init__.py +10 -2
- kailash/nodes/transform/chunkers.py +592 -1
- kailash/nodes/transform/processors.py +484 -14
- kailash/nodes/validation.py +321 -0
- kailash/runtime/access_controlled.py +1 -1
- kailash/runtime/async_local.py +41 -7
- kailash/runtime/docker.py +1 -1
- kailash/runtime/local.py +474 -55
- kailash/runtime/parallel.py +1 -1
- kailash/runtime/parallel_cyclic.py +1 -1
- kailash/runtime/testing.py +210 -2
- kailash/security.py +1 -1
- kailash/utils/migrations/__init__.py +25 -0
- kailash/utils/migrations/generator.py +433 -0
- kailash/utils/migrations/models.py +231 -0
- kailash/utils/migrations/runner.py +489 -0
- kailash/utils/secure_logging.py +342 -0
- kailash/workflow/__init__.py +16 -0
- kailash/workflow/cyclic_runner.py +3 -4
- kailash/workflow/graph.py +70 -2
- kailash/workflow/resilience.py +249 -0
- kailash/workflow/templates.py +726 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/METADATA +256 -20
- kailash-0.4.1.dist-info/RECORD +227 -0
- kailash/api/__init__.py +0 -17
- kailash/api/__main__.py +0 -6
- kailash/api/studio_secure.py +0 -893
- kailash/mcp/__main__.py +0 -13
- kailash/mcp/server_new.py +0 -336
- kailash/mcp/servers/__init__.py +0 -12
- kailash-0.3.2.dist-info/RECORD +0 -136
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/WHEEL +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,401 @@
|
|
1
|
+
"""
|
2
|
+
Credential Manager Node for centralized credential handling.
|
3
|
+
|
4
|
+
This node provides enterprise-grade credential management with support for
|
5
|
+
multiple credential sources, validation, and secure handling.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import os
|
10
|
+
import re
|
11
|
+
from datetime import datetime, timezone
|
12
|
+
from typing import Any, Dict, List, Literal, Optional
|
13
|
+
|
14
|
+
from kailash.nodes.base import Node, NodeParameter
|
15
|
+
|
16
|
+
|
17
|
+
class CredentialManagerNode(Node):
|
18
|
+
"""
|
19
|
+
Node for centralized credential management with validation and masking.
|
20
|
+
|
21
|
+
Supports multiple credential sources:
|
22
|
+
- Environment variables
|
23
|
+
- JSON files
|
24
|
+
- AWS Secrets Manager (simulated)
|
25
|
+
- Azure Key Vault (simulated)
|
26
|
+
- HashiCorp Vault (simulated)
|
27
|
+
|
28
|
+
Example:
|
29
|
+
```python
|
30
|
+
# Basic usage with environment variables
|
31
|
+
cred_node = CredentialManagerNode(
|
32
|
+
name="get_api_creds",
|
33
|
+
credential_name="openai_api",
|
34
|
+
credential_type="api_key"
|
35
|
+
)
|
36
|
+
|
37
|
+
# Advanced usage with multiple sources
|
38
|
+
cred_node = CredentialManagerNode(
|
39
|
+
name="get_db_creds",
|
40
|
+
credential_name="postgres_prod",
|
41
|
+
credential_type="database",
|
42
|
+
credential_sources=["vault", "env", "file"],
|
43
|
+
validate_on_fetch=True
|
44
|
+
)
|
45
|
+
```
|
46
|
+
"""
|
47
|
+
|
48
|
+
def __init__(
|
49
|
+
self,
|
50
|
+
credential_name: str,
|
51
|
+
credential_type: Literal[
|
52
|
+
"api_key", "oauth2", "database", "certificate", "basic_auth", "custom"
|
53
|
+
] = "custom",
|
54
|
+
credential_sources: Optional[
|
55
|
+
List[Literal["env", "file", "vault", "aws_secrets", "azure_keyvault"]]
|
56
|
+
] = None,
|
57
|
+
validate_on_fetch: bool = True,
|
58
|
+
mask_in_logs: bool = True,
|
59
|
+
cache_duration_seconds: Optional[int] = 300,
|
60
|
+
**kwargs,
|
61
|
+
):
|
62
|
+
"""
|
63
|
+
Initialize the CredentialManagerNode.
|
64
|
+
|
65
|
+
Args:
|
66
|
+
credential_name: Name/identifier of the credential to fetch
|
67
|
+
credential_type: Type of credential for validation
|
68
|
+
credential_sources: List of sources to check (default: ["env", "file"])
|
69
|
+
validate_on_fetch: Whether to validate credentials when fetched
|
70
|
+
mask_in_logs: Whether to mask credential values in logs
|
71
|
+
cache_duration_seconds: How long to cache credentials (None = no cache)
|
72
|
+
"""
|
73
|
+
super().__init__(**kwargs)
|
74
|
+
|
75
|
+
self.credential_name = credential_name
|
76
|
+
self.credential_type = credential_type
|
77
|
+
self.credential_sources = credential_sources or ["env", "file"]
|
78
|
+
self.validate_on_fetch = validate_on_fetch
|
79
|
+
self.mask_in_logs = mask_in_logs
|
80
|
+
self.cache_duration_seconds = cache_duration_seconds
|
81
|
+
|
82
|
+
self._cache = {}
|
83
|
+
self._cache_timestamps = {}
|
84
|
+
|
85
|
+
# Define credential validation patterns
|
86
|
+
self._validation_patterns = {
|
87
|
+
"api_key": r"^[A-Za-z0-9\-_]{20,}$",
|
88
|
+
"oauth2": {
|
89
|
+
"client_id": r"^[A-Za-z0-9\-_]{10,}$",
|
90
|
+
"client_secret": r"^[A-Za-z0-9\-_]{20,}$",
|
91
|
+
},
|
92
|
+
"database": {
|
93
|
+
"host": r"^[A-Za-z0-9\-\._]+$",
|
94
|
+
"port": r"^\d{1,5}$",
|
95
|
+
"username": r"^[A-Za-z0-9\-_]+$",
|
96
|
+
"password": r".{8,}",
|
97
|
+
},
|
98
|
+
"basic_auth": {"username": r"^[A-Za-z0-9\-_@\.]+$", "password": r".{6,}"},
|
99
|
+
}
|
100
|
+
|
101
|
+
def _mask_value(self, value: str, mask_type: str = "partial") -> str:
|
102
|
+
"""Mask sensitive credential values."""
|
103
|
+
if not value or not self.mask_in_logs:
|
104
|
+
return value
|
105
|
+
|
106
|
+
if mask_type == "full":
|
107
|
+
return "*" * len(value)
|
108
|
+
elif mask_type == "partial":
|
109
|
+
if len(value) <= 8:
|
110
|
+
return value[:2] + "*" * (len(value) - 2)
|
111
|
+
else:
|
112
|
+
return value[:4] + "*" * (len(value) - 8) + value[-4:]
|
113
|
+
return value
|
114
|
+
|
115
|
+
def _fetch_from_env(self, credential_name: str) -> Optional[Dict[str, Any]]:
|
116
|
+
"""Fetch credentials from environment variables."""
|
117
|
+
# Try common patterns
|
118
|
+
prefixes = [credential_name.upper(), f"{credential_name.upper()}_", ""]
|
119
|
+
|
120
|
+
if self.credential_type == "api_key":
|
121
|
+
for prefix in prefixes:
|
122
|
+
key_names = [f"{prefix}API_KEY", f"{prefix}KEY", f"{prefix}TOKEN"]
|
123
|
+
for key_name in key_names:
|
124
|
+
if key_name in os.environ:
|
125
|
+
return {"api_key": os.environ[key_name]}
|
126
|
+
|
127
|
+
elif self.credential_type == "oauth2":
|
128
|
+
for prefix in prefixes:
|
129
|
+
client_id = os.environ.get(f"{prefix}CLIENT_ID")
|
130
|
+
client_secret = os.environ.get(f"{prefix}CLIENT_SECRET")
|
131
|
+
if client_id and client_secret:
|
132
|
+
return {
|
133
|
+
"client_id": client_id,
|
134
|
+
"client_secret": client_secret,
|
135
|
+
"token_url": os.environ.get(f"{prefix}TOKEN_URL", ""),
|
136
|
+
}
|
137
|
+
|
138
|
+
elif self.credential_type == "database":
|
139
|
+
for prefix in prefixes:
|
140
|
+
host = os.environ.get(f"{prefix}DB_HOST") or os.environ.get(
|
141
|
+
f"{prefix}HOST"
|
142
|
+
)
|
143
|
+
if host:
|
144
|
+
return {
|
145
|
+
"host": host,
|
146
|
+
"port": os.environ.get(f"{prefix}DB_PORT", "5432"),
|
147
|
+
"username": os.environ.get(f"{prefix}DB_USER")
|
148
|
+
or os.environ.get(f"{prefix}USER"),
|
149
|
+
"password": os.environ.get(f"{prefix}DB_PASSWORD")
|
150
|
+
or os.environ.get(f"{prefix}PASSWORD"),
|
151
|
+
"database": os.environ.get(f"{prefix}DB_NAME")
|
152
|
+
or os.environ.get(f"{prefix}DATABASE"),
|
153
|
+
}
|
154
|
+
|
155
|
+
elif self.credential_type == "basic_auth":
|
156
|
+
for prefix in prefixes:
|
157
|
+
username = os.environ.get(f"{prefix}USERNAME") or os.environ.get(
|
158
|
+
f"{prefix}USER"
|
159
|
+
)
|
160
|
+
password = os.environ.get(f"{prefix}PASSWORD") or os.environ.get(
|
161
|
+
f"{prefix}PASS"
|
162
|
+
)
|
163
|
+
if username and password:
|
164
|
+
return {"username": username, "password": password}
|
165
|
+
|
166
|
+
# Generic fetch for custom type
|
167
|
+
result = {}
|
168
|
+
prefix = credential_name.upper() + "_"
|
169
|
+
for key, value in os.environ.items():
|
170
|
+
if key.startswith(prefix):
|
171
|
+
result[key[len(prefix) :].lower()] = value
|
172
|
+
|
173
|
+
return result if result else None
|
174
|
+
|
175
|
+
def _fetch_from_file(self, credential_name: str) -> Optional[Dict[str, Any]]:
|
176
|
+
"""Fetch credentials from JSON file."""
|
177
|
+
# Look in common credential file locations
|
178
|
+
search_paths = [
|
179
|
+
f".credentials/{credential_name}.json",
|
180
|
+
f"credentials/{credential_name}.json",
|
181
|
+
f".secrets/{credential_name}.json",
|
182
|
+
f"config/credentials/{credential_name}.json",
|
183
|
+
".env.json",
|
184
|
+
]
|
185
|
+
|
186
|
+
for path in search_paths:
|
187
|
+
if os.path.exists(path):
|
188
|
+
try:
|
189
|
+
with open(path, "r") as f:
|
190
|
+
data = json.load(f)
|
191
|
+
# If .env.json, look for the specific credential
|
192
|
+
if path == ".env.json" and credential_name in data:
|
193
|
+
return data[credential_name]
|
194
|
+
elif path != ".env.json":
|
195
|
+
return data
|
196
|
+
except Exception:
|
197
|
+
continue
|
198
|
+
|
199
|
+
return None
|
200
|
+
|
201
|
+
def _fetch_from_vault(self, credential_name: str) -> Optional[Dict[str, Any]]:
|
202
|
+
"""Simulate fetching from HashiCorp Vault."""
|
203
|
+
# In production, this would use hvac client
|
204
|
+
# For now, return simulated data for testing
|
205
|
+
if credential_name == "test_vault_creds":
|
206
|
+
return {
|
207
|
+
"api_key": "vault_simulated_key_123456",
|
208
|
+
"metadata": {
|
209
|
+
"version": 1,
|
210
|
+
"created_at": datetime.now(timezone.utc).isoformat(),
|
211
|
+
},
|
212
|
+
}
|
213
|
+
return None
|
214
|
+
|
215
|
+
def _fetch_from_aws_secrets(self, credential_name: str) -> Optional[Dict[str, Any]]:
|
216
|
+
"""Simulate fetching from AWS Secrets Manager."""
|
217
|
+
# In production, this would use boto3 client
|
218
|
+
# For now, return simulated data for testing
|
219
|
+
if credential_name == "test_aws_creds":
|
220
|
+
return {
|
221
|
+
"username": "aws_user",
|
222
|
+
"password": "aws_simulated_password_123",
|
223
|
+
"engine": "postgres",
|
224
|
+
"host": "test.rds.amazonaws.com",
|
225
|
+
}
|
226
|
+
return None
|
227
|
+
|
228
|
+
def _fetch_from_azure_keyvault(
|
229
|
+
self, credential_name: str
|
230
|
+
) -> Optional[Dict[str, Any]]:
|
231
|
+
"""Simulate fetching from Azure Key Vault."""
|
232
|
+
# In production, this would use azure-keyvault-secrets client
|
233
|
+
# For now, return simulated data for testing
|
234
|
+
if credential_name == "test_azure_creds":
|
235
|
+
return {
|
236
|
+
"client_id": "azure_client_123",
|
237
|
+
"client_secret": "azure_secret_456",
|
238
|
+
"tenant_id": "azure_tenant_789",
|
239
|
+
}
|
240
|
+
return None
|
241
|
+
|
242
|
+
def _validate_credential(self, credential: Dict[str, Any]) -> bool:
|
243
|
+
"""Validate credential format based on type."""
|
244
|
+
if not self.validate_on_fetch:
|
245
|
+
return True
|
246
|
+
|
247
|
+
patterns = self._validation_patterns.get(self.credential_type)
|
248
|
+
if not patterns:
|
249
|
+
return True # No validation for custom type
|
250
|
+
|
251
|
+
if isinstance(patterns, str):
|
252
|
+
# Single pattern (e.g., api_key)
|
253
|
+
value = credential.get(self.credential_type) or credential.get("value")
|
254
|
+
if value and re.match(patterns, value):
|
255
|
+
return True
|
256
|
+
elif isinstance(patterns, dict):
|
257
|
+
# Multiple patterns (e.g., oauth2, database)
|
258
|
+
for field, pattern in patterns.items():
|
259
|
+
value = credential.get(field)
|
260
|
+
if value and not re.match(pattern, str(value)):
|
261
|
+
return False
|
262
|
+
return True
|
263
|
+
|
264
|
+
return False
|
265
|
+
|
266
|
+
def _is_cache_valid(self, credential_name: str) -> bool:
|
267
|
+
"""Check if cached credential is still valid."""
|
268
|
+
if not self.cache_duration_seconds:
|
269
|
+
return False
|
270
|
+
|
271
|
+
if credential_name not in self._cache_timestamps:
|
272
|
+
return False
|
273
|
+
|
274
|
+
elapsed = (
|
275
|
+
datetime.now(timezone.utc) - self._cache_timestamps[credential_name]
|
276
|
+
).total_seconds()
|
277
|
+
return elapsed < self.cache_duration_seconds
|
278
|
+
|
279
|
+
def run(self, **inputs) -> Dict[str, Any]:
|
280
|
+
"""
|
281
|
+
Fetch and validate credentials from configured sources.
|
282
|
+
|
283
|
+
Returns:
|
284
|
+
Dict containing:
|
285
|
+
- credentials: The fetched credential data
|
286
|
+
- source: Which source provided the credentials
|
287
|
+
- validated: Whether credentials passed validation
|
288
|
+
- masked_display: Masked version for logging
|
289
|
+
- metadata: Additional information about the credentials
|
290
|
+
"""
|
291
|
+
# Check cache first
|
292
|
+
if self._is_cache_valid(self.credential_name):
|
293
|
+
cached = self._cache[self.credential_name]
|
294
|
+
return {
|
295
|
+
"credentials": cached["credentials"],
|
296
|
+
"source": cached["source"],
|
297
|
+
"validated": cached["validated"],
|
298
|
+
"masked_display": cached["masked_display"],
|
299
|
+
"metadata": {**cached.get("metadata", {}), "from_cache": True},
|
300
|
+
}
|
301
|
+
|
302
|
+
# Try each source in order
|
303
|
+
credentials = None
|
304
|
+
source = None
|
305
|
+
|
306
|
+
source_methods = {
|
307
|
+
"env": self._fetch_from_env,
|
308
|
+
"file": self._fetch_from_file,
|
309
|
+
"vault": self._fetch_from_vault,
|
310
|
+
"aws_secrets": self._fetch_from_aws_secrets,
|
311
|
+
"azure_keyvault": self._fetch_from_azure_keyvault,
|
312
|
+
}
|
313
|
+
|
314
|
+
for src in self.credential_sources:
|
315
|
+
if src in source_methods:
|
316
|
+
try:
|
317
|
+
result = source_methods[src](self.credential_name)
|
318
|
+
if result:
|
319
|
+
credentials = result
|
320
|
+
source = src
|
321
|
+
break
|
322
|
+
except Exception as e:
|
323
|
+
# Log error but continue to next source
|
324
|
+
continue
|
325
|
+
|
326
|
+
if not credentials:
|
327
|
+
raise ValueError(
|
328
|
+
f"Credential '{self.credential_name}' not found in any configured source"
|
329
|
+
)
|
330
|
+
|
331
|
+
# Validate credentials
|
332
|
+
validated = self._validate_credential(credentials)
|
333
|
+
|
334
|
+
# Create masked display version
|
335
|
+
masked_display = {}
|
336
|
+
for key, value in credentials.items():
|
337
|
+
if isinstance(value, str) and key.lower() in [
|
338
|
+
"password",
|
339
|
+
"secret",
|
340
|
+
"key",
|
341
|
+
"token",
|
342
|
+
"api_key",
|
343
|
+
"client_secret",
|
344
|
+
]:
|
345
|
+
masked_display[key] = self._mask_value(value)
|
346
|
+
else:
|
347
|
+
masked_display[key] = value
|
348
|
+
|
349
|
+
# Prepare result
|
350
|
+
result = {
|
351
|
+
"credentials": credentials,
|
352
|
+
"source": source,
|
353
|
+
"validated": validated,
|
354
|
+
"masked_display": masked_display,
|
355
|
+
"metadata": {
|
356
|
+
"credential_type": self.credential_type,
|
357
|
+
"fetched_at": datetime.now(timezone.utc).isoformat(),
|
358
|
+
"rotation_detected": False, # Placeholder for rotation detection
|
359
|
+
},
|
360
|
+
}
|
361
|
+
|
362
|
+
# Cache if enabled
|
363
|
+
if self.cache_duration_seconds:
|
364
|
+
self._cache[self.credential_name] = result
|
365
|
+
self._cache_timestamps[self.credential_name] = datetime.now(timezone.utc)
|
366
|
+
|
367
|
+
return result
|
368
|
+
|
369
|
+
def get_parameters(self) -> Dict[str, NodeParameter]:
|
370
|
+
"""No input parameters required."""
|
371
|
+
return {}
|
372
|
+
|
373
|
+
def get_output_schema(self) -> Dict[str, NodeParameter]:
|
374
|
+
"""Define output parameters."""
|
375
|
+
return {
|
376
|
+
"credentials": NodeParameter(
|
377
|
+
name="credentials", type=dict, description="The fetched credential data"
|
378
|
+
),
|
379
|
+
"source": NodeParameter(
|
380
|
+
name="source",
|
381
|
+
type=str,
|
382
|
+
description="Which source provided the credentials",
|
383
|
+
),
|
384
|
+
"validated": NodeParameter(
|
385
|
+
name="validated",
|
386
|
+
type=bool,
|
387
|
+
description="Whether credentials passed validation",
|
388
|
+
),
|
389
|
+
"masked_display": NodeParameter(
|
390
|
+
name="masked_display",
|
391
|
+
type=dict,
|
392
|
+
description="Masked version for safe logging",
|
393
|
+
),
|
394
|
+
"metadata": NodeParameter(
|
395
|
+
name="metadata", type=dict, description="Additional credential metadata"
|
396
|
+
),
|
397
|
+
}
|
398
|
+
|
399
|
+
async def async_run(self, **kwargs) -> Dict[str, Any]:
|
400
|
+
"""Async execution method for enterprise integration."""
|
401
|
+
return self.run(**kwargs)
|