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.
Files changed (151) hide show
  1. kailash/__init__.py +33 -1
  2. kailash/access_control/__init__.py +129 -0
  3. kailash/access_control/managers.py +461 -0
  4. kailash/access_control/rule_evaluators.py +467 -0
  5. kailash/access_control_abac.py +825 -0
  6. kailash/config/__init__.py +27 -0
  7. kailash/config/database_config.py +359 -0
  8. kailash/database/__init__.py +28 -0
  9. kailash/database/execution_pipeline.py +499 -0
  10. kailash/middleware/__init__.py +306 -0
  11. kailash/middleware/auth/__init__.py +33 -0
  12. kailash/middleware/auth/access_control.py +436 -0
  13. kailash/middleware/auth/auth_manager.py +422 -0
  14. kailash/middleware/auth/jwt_auth.py +477 -0
  15. kailash/middleware/auth/kailash_jwt_auth.py +616 -0
  16. kailash/middleware/communication/__init__.py +37 -0
  17. kailash/middleware/communication/ai_chat.py +989 -0
  18. kailash/middleware/communication/api_gateway.py +802 -0
  19. kailash/middleware/communication/events.py +470 -0
  20. kailash/middleware/communication/realtime.py +710 -0
  21. kailash/middleware/core/__init__.py +21 -0
  22. kailash/middleware/core/agent_ui.py +890 -0
  23. kailash/middleware/core/schema.py +643 -0
  24. kailash/middleware/core/workflows.py +396 -0
  25. kailash/middleware/database/__init__.py +63 -0
  26. kailash/middleware/database/base.py +113 -0
  27. kailash/middleware/database/base_models.py +525 -0
  28. kailash/middleware/database/enums.py +106 -0
  29. kailash/middleware/database/migrations.py +12 -0
  30. kailash/{api/database.py → middleware/database/models.py} +183 -291
  31. kailash/middleware/database/repositories.py +685 -0
  32. kailash/middleware/database/session_manager.py +19 -0
  33. kailash/middleware/mcp/__init__.py +38 -0
  34. kailash/middleware/mcp/client_integration.py +585 -0
  35. kailash/middleware/mcp/enhanced_server.py +576 -0
  36. kailash/nodes/__init__.py +27 -3
  37. kailash/nodes/admin/__init__.py +42 -0
  38. kailash/nodes/admin/audit_log.py +794 -0
  39. kailash/nodes/admin/permission_check.py +864 -0
  40. kailash/nodes/admin/role_management.py +823 -0
  41. kailash/nodes/admin/security_event.py +1523 -0
  42. kailash/nodes/admin/user_management.py +944 -0
  43. kailash/nodes/ai/a2a.py +24 -7
  44. kailash/nodes/ai/ai_providers.py +248 -40
  45. kailash/nodes/ai/embedding_generator.py +11 -11
  46. kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
  47. kailash/nodes/ai/llm_agent.py +436 -5
  48. kailash/nodes/ai/self_organizing.py +85 -10
  49. kailash/nodes/ai/vision_utils.py +148 -0
  50. kailash/nodes/alerts/__init__.py +26 -0
  51. kailash/nodes/alerts/base.py +234 -0
  52. kailash/nodes/alerts/discord.py +499 -0
  53. kailash/nodes/api/auth.py +287 -6
  54. kailash/nodes/api/rest.py +151 -0
  55. kailash/nodes/auth/__init__.py +17 -0
  56. kailash/nodes/auth/directory_integration.py +1228 -0
  57. kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
  58. kailash/nodes/auth/mfa.py +2338 -0
  59. kailash/nodes/auth/risk_assessment.py +872 -0
  60. kailash/nodes/auth/session_management.py +1093 -0
  61. kailash/nodes/auth/sso.py +1040 -0
  62. kailash/nodes/base.py +344 -13
  63. kailash/nodes/base_cycle_aware.py +4 -2
  64. kailash/nodes/base_with_acl.py +1 -1
  65. kailash/nodes/code/python.py +283 -10
  66. kailash/nodes/compliance/__init__.py +9 -0
  67. kailash/nodes/compliance/data_retention.py +1888 -0
  68. kailash/nodes/compliance/gdpr.py +2004 -0
  69. kailash/nodes/data/__init__.py +22 -2
  70. kailash/nodes/data/async_connection.py +469 -0
  71. kailash/nodes/data/async_sql.py +757 -0
  72. kailash/nodes/data/async_vector.py +598 -0
  73. kailash/nodes/data/readers.py +767 -0
  74. kailash/nodes/data/retrieval.py +360 -1
  75. kailash/nodes/data/sharepoint_graph.py +397 -21
  76. kailash/nodes/data/sql.py +94 -5
  77. kailash/nodes/data/streaming.py +68 -8
  78. kailash/nodes/data/vector_db.py +54 -4
  79. kailash/nodes/enterprise/__init__.py +13 -0
  80. kailash/nodes/enterprise/batch_processor.py +741 -0
  81. kailash/nodes/enterprise/data_lineage.py +497 -0
  82. kailash/nodes/logic/convergence.py +31 -9
  83. kailash/nodes/logic/operations.py +14 -3
  84. kailash/nodes/mixins/__init__.py +8 -0
  85. kailash/nodes/mixins/event_emitter.py +201 -0
  86. kailash/nodes/mixins/mcp.py +9 -4
  87. kailash/nodes/mixins/security.py +165 -0
  88. kailash/nodes/monitoring/__init__.py +7 -0
  89. kailash/nodes/monitoring/performance_benchmark.py +2497 -0
  90. kailash/nodes/rag/__init__.py +284 -0
  91. kailash/nodes/rag/advanced.py +1615 -0
  92. kailash/nodes/rag/agentic.py +773 -0
  93. kailash/nodes/rag/conversational.py +999 -0
  94. kailash/nodes/rag/evaluation.py +875 -0
  95. kailash/nodes/rag/federated.py +1188 -0
  96. kailash/nodes/rag/graph.py +721 -0
  97. kailash/nodes/rag/multimodal.py +671 -0
  98. kailash/nodes/rag/optimized.py +933 -0
  99. kailash/nodes/rag/privacy.py +1059 -0
  100. kailash/nodes/rag/query_processing.py +1335 -0
  101. kailash/nodes/rag/realtime.py +764 -0
  102. kailash/nodes/rag/registry.py +547 -0
  103. kailash/nodes/rag/router.py +837 -0
  104. kailash/nodes/rag/similarity.py +1854 -0
  105. kailash/nodes/rag/strategies.py +566 -0
  106. kailash/nodes/rag/workflows.py +575 -0
  107. kailash/nodes/security/__init__.py +19 -0
  108. kailash/nodes/security/abac_evaluator.py +1411 -0
  109. kailash/nodes/security/audit_log.py +103 -0
  110. kailash/nodes/security/behavior_analysis.py +1893 -0
  111. kailash/nodes/security/credential_manager.py +401 -0
  112. kailash/nodes/security/rotating_credentials.py +760 -0
  113. kailash/nodes/security/security_event.py +133 -0
  114. kailash/nodes/security/threat_detection.py +1103 -0
  115. kailash/nodes/testing/__init__.py +9 -0
  116. kailash/nodes/testing/credential_testing.py +499 -0
  117. kailash/nodes/transform/__init__.py +10 -2
  118. kailash/nodes/transform/chunkers.py +592 -1
  119. kailash/nodes/transform/processors.py +484 -14
  120. kailash/nodes/validation.py +321 -0
  121. kailash/runtime/access_controlled.py +1 -1
  122. kailash/runtime/async_local.py +41 -7
  123. kailash/runtime/docker.py +1 -1
  124. kailash/runtime/local.py +474 -55
  125. kailash/runtime/parallel.py +1 -1
  126. kailash/runtime/parallel_cyclic.py +1 -1
  127. kailash/runtime/testing.py +210 -2
  128. kailash/security.py +1 -1
  129. kailash/utils/migrations/__init__.py +25 -0
  130. kailash/utils/migrations/generator.py +433 -0
  131. kailash/utils/migrations/models.py +231 -0
  132. kailash/utils/migrations/runner.py +489 -0
  133. kailash/utils/secure_logging.py +342 -0
  134. kailash/workflow/__init__.py +16 -0
  135. kailash/workflow/cyclic_runner.py +3 -4
  136. kailash/workflow/graph.py +70 -2
  137. kailash/workflow/resilience.py +249 -0
  138. kailash/workflow/templates.py +726 -0
  139. {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/METADATA +256 -20
  140. kailash-0.4.1.dist-info/RECORD +227 -0
  141. kailash/api/__init__.py +0 -17
  142. kailash/api/__main__.py +0 -6
  143. kailash/api/studio_secure.py +0 -893
  144. kailash/mcp/__main__.py +0 -13
  145. kailash/mcp/server_new.py +0 -336
  146. kailash/mcp/servers/__init__.py +0 -12
  147. kailash-0.3.2.dist-info/RECORD +0 -136
  148. {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/WHEEL +0 -0
  149. {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/entry_points.txt +0 -0
  150. {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/licenses/LICENSE +0 -0
  151. {kailash-0.3.2.dist-info → kailash-0.4.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,944 @@
1
+ """Enterprise user management node with comprehensive CRUD operations.
2
+
3
+ This node provides Django Admin-level user management capabilities with enhanced
4
+ features for enterprise environments. Built on Session 065's async database
5
+ infrastructure for high-performance operations.
6
+
7
+ Features:
8
+ - Complete user lifecycle (create, read, update, delete, restore)
9
+ - Bulk operations with validation and rollback
10
+ - Password management with security policies
11
+ - User attribute management for ABAC
12
+ - Multi-tenant user isolation
13
+ - Comprehensive audit logging
14
+ - Integration with external identity providers
15
+ - User search, filtering, and pagination
16
+ """
17
+
18
+ import hashlib
19
+ import json
20
+ import secrets
21
+ from dataclasses import dataclass
22
+ from datetime import UTC, datetime, timedelta
23
+ from enum import Enum
24
+ from typing import Any, Dict, List, Optional, Union
25
+
26
+ from kailash.access_control import AccessControlManager, UserContext
27
+ from kailash.nodes.base import Node, NodeParameter, register_node
28
+ from kailash.nodes.data import AsyncSQLDatabaseNode
29
+ from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
30
+
31
+
32
+ @dataclass
33
+ class UserConfig:
34
+ """Configuration for user management node."""
35
+
36
+ abac_enabled: bool = True
37
+ audit_enabled: bool = True
38
+ multi_tenant: bool = True
39
+ password_policy: Dict[str, Any] = None
40
+
41
+ def __post_init__(self):
42
+ if self.password_policy is None:
43
+ self.password_policy = {
44
+ "min_length": 8,
45
+ "require_uppercase": True,
46
+ "require_lowercase": True,
47
+ "require_numbers": True,
48
+ "require_special": False,
49
+ "history_count": 3,
50
+ }
51
+
52
+
53
+ class UserOperation(Enum):
54
+ """Supported user management operations."""
55
+
56
+ CREATE = "create"
57
+ READ = "read"
58
+ UPDATE = "update"
59
+ DELETE = "delete"
60
+ RESTORE = "restore"
61
+ LIST = "list"
62
+ SEARCH = "search"
63
+ BULK_CREATE = "bulk_create"
64
+ BULK_UPDATE = "bulk_update"
65
+ BULK_DELETE = "bulk_delete"
66
+ CHANGE_PASSWORD = "change_password"
67
+ RESET_PASSWORD = "reset_password"
68
+ DEACTIVATE = "deactivate"
69
+ ACTIVATE = "activate"
70
+
71
+
72
+ class UserStatus(Enum):
73
+ """User account status."""
74
+
75
+ ACTIVE = "active"
76
+ INACTIVE = "inactive"
77
+ SUSPENDED = "suspended"
78
+ PENDING = "pending"
79
+ DELETED = "deleted"
80
+
81
+
82
+ @dataclass
83
+ class UserProfile:
84
+ """Enhanced user profile with ABAC attributes."""
85
+
86
+ user_id: str
87
+ email: str
88
+ username: str
89
+ first_name: str
90
+ last_name: str
91
+ status: UserStatus
92
+ roles: List[str]
93
+ attributes: Dict[str, Any]
94
+ created_at: datetime
95
+ updated_at: datetime
96
+ last_login: Optional[datetime] = None
97
+ password_changed_at: Optional[datetime] = None
98
+ tenant_id: Optional[str] = None
99
+
100
+ def to_user_context(self) -> UserContext:
101
+ """Convert to UserContext for permission checks."""
102
+ return UserContext(
103
+ user_id=self.user_id,
104
+ tenant_id=self.tenant_id or "default",
105
+ email=self.email,
106
+ roles=self.roles,
107
+ attributes=self.attributes,
108
+ )
109
+
110
+
111
+ @register_node()
112
+ class UserManagementNode(Node):
113
+ """Enterprise user management node with Django Admin-inspired features.
114
+
115
+ This node provides comprehensive user management capabilities including:
116
+ - User CRUD operations with validation
117
+ - Bulk operations with transaction support
118
+ - Password management with security policies
119
+ - User search and filtering
120
+ - Attribute management for ABAC
121
+ - Audit logging integration
122
+ - Multi-tenant support
123
+
124
+ Parameters:
125
+ operation: Type of operation to perform
126
+ user_data: User data for create/update operations
127
+ user_id: User ID for single-user operations
128
+ user_ids: List of user IDs for bulk operations
129
+ search_query: Search query for user lookup
130
+ filters: Filters for user listing
131
+ pagination: Pagination parameters
132
+ tenant_id: Tenant isolation
133
+ include_deleted: Whether to include soft-deleted users
134
+
135
+ Example:
136
+ >>> # Create new user
137
+ >>> node = UserManagementNode(
138
+ ... operation="create",
139
+ ... user_data={
140
+ ... "email": "john@company.com",
141
+ ... "username": "john.smith",
142
+ ... "first_name": "John",
143
+ ... "last_name": "Smith",
144
+ ... "roles": ["analyst"],
145
+ ... "attributes": {
146
+ ... "department": "finance",
147
+ ... "clearance": "confidential"
148
+ ... }
149
+ ... }
150
+ ... )
151
+ >>> result = node.run()
152
+ >>> user_id = result["user"]["user_id"]
153
+
154
+ >>> # Bulk user operations
155
+ >>> node = UserManagementNode(
156
+ ... operation="bulk_create",
157
+ ... user_data=[
158
+ ... {"email": "user1@company.com", ...},
159
+ ... {"email": "user2@company.com", ...}
160
+ ... ]
161
+ ... )
162
+ >>> result = node.run()
163
+ >>> created_count = result["stats"]["created"]
164
+ """
165
+
166
+ def __init__(self, **config):
167
+ super().__init__(**config)
168
+ self._db_node = None
169
+ self._access_manager = None
170
+ self._config = UserConfig(
171
+ abac_enabled=config.get("abac_enabled", True),
172
+ audit_enabled=config.get("audit_enabled", True),
173
+ multi_tenant=config.get("multi_tenant", True),
174
+ password_policy=config.get(
175
+ "password_policy",
176
+ {
177
+ "min_length": 8,
178
+ "require_uppercase": True,
179
+ "require_lowercase": True,
180
+ "require_numbers": True,
181
+ "require_special": False,
182
+ "history_count": 3,
183
+ },
184
+ ),
185
+ )
186
+ self._audit_logger = None
187
+
188
+ def get_parameters(self) -> Dict[str, NodeParameter]:
189
+ """Define parameters for user management operations."""
190
+ return {
191
+ param.name: param
192
+ for param in [
193
+ # Operation type
194
+ NodeParameter(
195
+ name="operation",
196
+ type=str,
197
+ required=True,
198
+ description="User management operation to perform",
199
+ choices=[op.value for op in UserOperation],
200
+ ),
201
+ # User data for create/update
202
+ NodeParameter(
203
+ name="user_data",
204
+ type=dict,
205
+ required=False,
206
+ description="User data for create/update operations",
207
+ ),
208
+ # Single user operations
209
+ NodeParameter(
210
+ name="user_id",
211
+ type=str,
212
+ required=False,
213
+ description="User ID for single-user operations",
214
+ ),
215
+ # Bulk operations
216
+ NodeParameter(
217
+ name="user_ids",
218
+ type=list,
219
+ required=False,
220
+ description="List of user IDs for bulk operations",
221
+ ),
222
+ # Search and filtering
223
+ NodeParameter(
224
+ name="search_query",
225
+ type=str,
226
+ required=False,
227
+ description="Search query for user lookup",
228
+ ),
229
+ NodeParameter(
230
+ name="filters",
231
+ type=dict,
232
+ required=False,
233
+ description="Filters for user listing",
234
+ ),
235
+ NodeParameter(
236
+ name="pagination",
237
+ type=dict,
238
+ required=False,
239
+ description="Pagination parameters (page, size, sort)",
240
+ ),
241
+ # Multi-tenancy
242
+ NodeParameter(
243
+ name="tenant_id",
244
+ type=str,
245
+ required=False,
246
+ description="Tenant ID for multi-tenant isolation",
247
+ ),
248
+ # Options
249
+ NodeParameter(
250
+ name="include_deleted",
251
+ type=bool,
252
+ required=False,
253
+ default=False,
254
+ description="Include soft-deleted users in results",
255
+ ),
256
+ # Database configuration
257
+ NodeParameter(
258
+ name="database_config",
259
+ type=dict,
260
+ required=False,
261
+ description="Database connection configuration",
262
+ ),
263
+ # Password options
264
+ NodeParameter(
265
+ name="password",
266
+ type=str,
267
+ required=False,
268
+ description="Password for create/change operations",
269
+ ),
270
+ NodeParameter(
271
+ name="force_password_change",
272
+ type=bool,
273
+ required=False,
274
+ default=False,
275
+ description="Force password change on next login",
276
+ ),
277
+ # Validation options
278
+ NodeParameter(
279
+ name="validate_email",
280
+ type=bool,
281
+ required=False,
282
+ default=True,
283
+ description="Validate email format and uniqueness",
284
+ ),
285
+ NodeParameter(
286
+ name="validate_username",
287
+ type=bool,
288
+ required=False,
289
+ default=True,
290
+ description="Validate username format and uniqueness",
291
+ ),
292
+ ]
293
+ }
294
+
295
+ def run(self, **inputs) -> Dict[str, Any]:
296
+ """Execute user management operation (sync wrapper for async_run)."""
297
+ import asyncio
298
+
299
+ try:
300
+ # Check if we're already in an event loop
301
+ loop = asyncio.get_running_loop()
302
+ # If we are, we need to handle this differently
303
+ import concurrent.futures
304
+
305
+ # Run in a thread pool to avoid blocking the event loop
306
+ with concurrent.futures.ThreadPoolExecutor() as executor:
307
+ future = executor.submit(asyncio.run, self.async_run(**inputs))
308
+ return future.result()
309
+ except RuntimeError:
310
+ # No event loop running, we can use asyncio.run()
311
+ return asyncio.run(self.async_run(**inputs))
312
+
313
+ async def async_run(self, **inputs) -> Dict[str, Any]:
314
+ """Execute user management operation asynchronously."""
315
+ try:
316
+ operation = UserOperation(inputs["operation"])
317
+
318
+ # Initialize database and access manager
319
+ self._init_dependencies(inputs)
320
+
321
+ # Route to appropriate operation
322
+ if operation == UserOperation.CREATE:
323
+ return await self._create_user_async(inputs)
324
+ elif operation == UserOperation.READ:
325
+ return await self._read_user_async(inputs)
326
+ elif operation == UserOperation.UPDATE:
327
+ return await self._update_user_async(inputs)
328
+ elif operation == UserOperation.DELETE:
329
+ return await self._delete_user_async(inputs)
330
+ elif operation == UserOperation.RESTORE:
331
+ return await self._restore_user_async(inputs)
332
+ elif operation == UserOperation.LIST:
333
+ return await self._list_users_async(inputs)
334
+ elif operation == UserOperation.SEARCH:
335
+ return await self._search_users_async(inputs)
336
+ elif operation == UserOperation.BULK_CREATE:
337
+ return await self._bulk_create_users_async(inputs)
338
+ elif operation == UserOperation.BULK_UPDATE:
339
+ return await self._bulk_update_users_async(inputs)
340
+ elif operation == UserOperation.BULK_DELETE:
341
+ return await self._bulk_delete_users_async(inputs)
342
+ elif operation == UserOperation.CHANGE_PASSWORD:
343
+ return await self._change_password_async(inputs)
344
+ elif operation == UserOperation.RESET_PASSWORD:
345
+ return await self._reset_password_async(inputs)
346
+ elif operation == UserOperation.DEACTIVATE:
347
+ return await self._deactivate_user_async(inputs)
348
+ elif operation == UserOperation.ACTIVATE:
349
+ return await self._activate_user_async(inputs)
350
+ else:
351
+ raise NodeExecutionError(f"Unknown operation: {operation}")
352
+
353
+ except Exception as e:
354
+ raise NodeExecutionError(f"User management operation failed: {str(e)}")
355
+
356
+ def _init_dependencies(self, inputs: Dict[str, Any]):
357
+ """Initialize database and access manager dependencies."""
358
+ # Get database config
359
+ db_config = inputs.get(
360
+ "database_config",
361
+ {
362
+ "database_type": "postgresql",
363
+ "host": "localhost",
364
+ "port": 5432,
365
+ "database": "kailash_admin",
366
+ "user": "admin",
367
+ "password": "admin",
368
+ },
369
+ )
370
+
371
+ # Initialize async database node
372
+ self._db_node = AsyncSQLDatabaseNode(name="user_management_db", **db_config)
373
+
374
+ # Initialize enhanced access manager
375
+ self._access_manager = AccessControlManager(strategy="abac")
376
+
377
+ async def _create_user_async(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
378
+ """Create a new user with validation and audit logging (async version)."""
379
+ user_data = inputs["user_data"]
380
+ tenant_id = inputs.get("tenant_id", "default")
381
+
382
+ # Validate required fields
383
+ required_fields = ["email", "username", "first_name", "last_name"]
384
+ for field in required_fields:
385
+ if field not in user_data:
386
+ raise NodeValidationError(f"Missing required field: {field}")
387
+
388
+ # Validate email format
389
+ if inputs.get("validate_email", True):
390
+ if not self._validate_email(user_data["email"]):
391
+ raise NodeValidationError(f"Invalid email format: {user_data['email']}")
392
+
393
+ # Validate username format
394
+ if inputs.get("validate_username", True):
395
+ if not self._validate_username(user_data["username"]):
396
+ raise NodeValidationError(
397
+ "Invalid username format. Username must be 3-50 characters, "
398
+ "alphanumeric with underscores/dashes allowed"
399
+ )
400
+
401
+ # Generate user ID
402
+ user_id = self._generate_user_id()
403
+
404
+ # Hash password if provided
405
+ password_hash = None
406
+ if "password" in user_data:
407
+ # Validate password against policy
408
+ policy = self._config.password_policy
409
+ password = user_data["password"]
410
+
411
+ if len(password) < policy["min_length"]:
412
+ raise NodeValidationError(
413
+ f"Password must be at least {policy['min_length']} characters"
414
+ )
415
+
416
+ if policy.get("require_uppercase") and not any(
417
+ c.isupper() for c in password
418
+ ):
419
+ raise NodeValidationError("Password must contain uppercase letters")
420
+
421
+ if policy.get("require_lowercase") and not any(
422
+ c.islower() for c in password
423
+ ):
424
+ raise NodeValidationError("Password must contain lowercase letters")
425
+
426
+ if policy.get("require_numbers") and not any(c.isdigit() for c in password):
427
+ raise NodeValidationError("Password must contain numbers")
428
+
429
+ if policy.get("require_special") and not any(
430
+ c in "!@#$%^&*()_+-=[]{}|;:,.<>?" for c in password
431
+ ):
432
+ raise NodeValidationError("Password must contain special characters")
433
+
434
+ password_hash = self._hash_password(password)
435
+
436
+ # Create user record
437
+ user_record = {
438
+ "user_id": user_id,
439
+ "tenant_id": tenant_id,
440
+ "email": user_data["email"],
441
+ "username": user_data["username"],
442
+ "first_name": user_data["first_name"],
443
+ "last_name": user_data["last_name"],
444
+ "status": user_data.get("status", UserStatus.ACTIVE.value),
445
+ "roles": json.dumps(user_data.get("roles", ["user"])),
446
+ "attributes": json.dumps(user_data.get("attributes", {})),
447
+ "password_hash": password_hash,
448
+ "force_password_change": user_data.get("force_password_change", False),
449
+ "created_at": datetime.now(UTC),
450
+ "updated_at": datetime.now(UTC),
451
+ "created_by": inputs.get("metadata", {}).get("created_by", "system"),
452
+ }
453
+
454
+ # Insert into database
455
+ insert_query = """
456
+ INSERT INTO users (
457
+ user_id, tenant_id, email, username, first_name, last_name,
458
+ status, roles, attributes, password_hash, force_password_change,
459
+ created_at, updated_at, created_by
460
+ ) VALUES (
461
+ $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14
462
+ )
463
+ """
464
+
465
+ # Execute database insert using async method
466
+ query = {
467
+ "query": insert_query,
468
+ "params": [
469
+ user_record["user_id"],
470
+ user_record["tenant_id"],
471
+ user_record["email"],
472
+ user_record["username"],
473
+ user_record["first_name"],
474
+ user_record["last_name"],
475
+ user_record["status"],
476
+ user_record["roles"],
477
+ user_record["attributes"],
478
+ user_record["password_hash"],
479
+ user_record["force_password_change"],
480
+ user_record["created_at"],
481
+ user_record["updated_at"],
482
+ user_record["created_by"],
483
+ ],
484
+ }
485
+
486
+ db_result = await self._db_node.async_run(**query)
487
+
488
+ # Create user profile response
489
+ user_profile = UserProfile(
490
+ user_id=user_id,
491
+ email=user_record["email"],
492
+ username=user_record["username"],
493
+ first_name=user_record["first_name"],
494
+ last_name=user_record["last_name"],
495
+ status=UserStatus(user_record["status"]),
496
+ roles=user_record["roles"],
497
+ attributes=user_record["attributes"],
498
+ created_at=user_record["created_at"],
499
+ updated_at=user_record["updated_at"],
500
+ )
501
+
502
+ # Handle initial role assignments
503
+ if inputs.get("initial_roles"):
504
+ # Role assignment would be handled by RoleManagementNode
505
+ pass
506
+
507
+ # Audit log
508
+ if self._config.audit_enabled:
509
+ # In production, this would use AuditLogNode
510
+ print(f"[AUDIT] user_created: {user_id} ({user_record['username']})")
511
+
512
+ return {
513
+ "success": True,
514
+ "user": user_profile.__dict__,
515
+ "message": f"User {user_record['username']} created successfully",
516
+ }
517
+
518
+ def _create_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
519
+ """Create a new user with validation and audit logging."""
520
+ user_data = inputs["user_data"]
521
+ tenant_id = inputs.get("tenant_id", "default")
522
+
523
+ # Validate required fields
524
+ required_fields = ["email", "username", "first_name", "last_name"]
525
+ for field in required_fields:
526
+ if field not in user_data:
527
+ raise NodeValidationError(f"Missing required field: {field}")
528
+
529
+ # Validate email format
530
+ if inputs.get("validate_email", True):
531
+ if not self._validate_email(user_data["email"]):
532
+ raise NodeValidationError(f"Invalid email format: {user_data['email']}")
533
+
534
+ # Validate username format
535
+ if inputs.get("validate_username", True):
536
+ if not self._validate_username(user_data["username"]):
537
+ raise NodeValidationError(
538
+ f"Invalid username format: {user_data['username']}"
539
+ )
540
+
541
+ # Generate user ID and timestamps
542
+ user_id = self._generate_user_id()
543
+ now = datetime.now(UTC)
544
+
545
+ # Hash password if provided
546
+ password_hash = None
547
+ if "password" in inputs:
548
+ password_hash = self._hash_password(inputs["password"])
549
+ elif "password" in user_data:
550
+ password_hash = self._hash_password(user_data["password"])
551
+
552
+ # Prepare user record
553
+ user_record = {
554
+ "user_id": user_id,
555
+ "tenant_id": tenant_id,
556
+ "email": user_data["email"],
557
+ "username": user_data["username"],
558
+ "first_name": user_data["first_name"],
559
+ "last_name": user_data["last_name"],
560
+ "status": user_data.get("status", UserStatus.ACTIVE.value),
561
+ "roles": user_data.get("roles", []),
562
+ "attributes": user_data.get("attributes", {}),
563
+ "password_hash": password_hash,
564
+ "force_password_change": inputs.get("force_password_change", False),
565
+ "created_at": now,
566
+ "updated_at": now,
567
+ "created_by": inputs.get("created_by", "system"),
568
+ }
569
+
570
+ # Insert user into database
571
+ insert_query = """
572
+ INSERT INTO users (
573
+ user_id, tenant_id, email, username, first_name, last_name,
574
+ status, roles, attributes, password_hash, force_password_change,
575
+ created_at, updated_at, created_by
576
+ ) VALUES (
577
+ $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14
578
+ )
579
+ """
580
+
581
+ # Execute database insert
582
+ query = {
583
+ "query": insert_query,
584
+ "params": [
585
+ user_record["user_id"],
586
+ user_record["tenant_id"],
587
+ user_record["email"],
588
+ user_record["username"],
589
+ user_record["first_name"],
590
+ user_record["last_name"],
591
+ user_record["status"],
592
+ user_record["roles"],
593
+ user_record["attributes"],
594
+ user_record["password_hash"],
595
+ user_record["force_password_change"],
596
+ user_record["created_at"],
597
+ user_record["updated_at"],
598
+ user_record["created_by"],
599
+ ],
600
+ }
601
+
602
+ db_result = self._db_node.run(**query)
603
+
604
+ # Create user profile response
605
+ user_profile = UserProfile(
606
+ user_id=user_id,
607
+ email=user_record["email"],
608
+ username=user_record["username"],
609
+ first_name=user_record["first_name"],
610
+ last_name=user_record["last_name"],
611
+ status=UserStatus(user_record["status"]),
612
+ roles=user_record["roles"],
613
+ attributes=user_record["attributes"],
614
+ created_at=user_record["created_at"],
615
+ updated_at=user_record["updated_at"],
616
+ tenant_id=tenant_id,
617
+ )
618
+
619
+ return {
620
+ "result": {
621
+ "user": {
622
+ "user_id": user_profile.user_id,
623
+ "email": user_profile.email,
624
+ "username": user_profile.username,
625
+ "first_name": user_profile.first_name,
626
+ "last_name": user_profile.last_name,
627
+ "status": user_profile.status.value,
628
+ "roles": user_profile.roles,
629
+ "attributes": user_profile.attributes,
630
+ "created_at": user_profile.created_at.isoformat(),
631
+ "tenant_id": user_profile.tenant_id,
632
+ },
633
+ "operation": "create",
634
+ "success": True,
635
+ "timestamp": datetime.now(UTC).isoformat(),
636
+ }
637
+ }
638
+
639
+ def _read_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
640
+ """Read user information by ID or email."""
641
+ user_id = inputs.get("user_id")
642
+ email = inputs.get("email")
643
+ tenant_id = inputs.get("tenant_id", "default")
644
+ include_deleted = inputs.get("include_deleted", False)
645
+
646
+ if not user_id and not email:
647
+ raise NodeValidationError("Either user_id or email must be provided")
648
+
649
+ # Build query
650
+ where_conditions = ["tenant_id = $1"]
651
+ params = [tenant_id]
652
+ param_count = 1
653
+
654
+ if user_id:
655
+ param_count += 1
656
+ where_conditions.append(f"user_id = ${param_count}")
657
+ params.append(user_id)
658
+
659
+ if email:
660
+ param_count += 1
661
+ where_conditions.append(f"email = ${param_count}")
662
+ params.append(email)
663
+
664
+ if not include_deleted:
665
+ where_conditions.append("status != 'deleted'")
666
+
667
+ query = f"""
668
+ SELECT user_id, tenant_id, email, username, first_name, last_name,
669
+ status, roles, attributes, created_at, updated_at, last_login,
670
+ password_changed_at, force_password_change
671
+ FROM users
672
+ WHERE {' AND '.join(where_conditions)}
673
+ LIMIT 1
674
+ """
675
+
676
+ # Execute query
677
+ self._db_node.config.update(
678
+ {"query": query, "params": params, "fetch_mode": "one"}
679
+ )
680
+
681
+ db_result = self._db_node.run(**query)
682
+
683
+ if not db_result.get("result", {}).get("data"):
684
+ return {
685
+ "result": {
686
+ "user": None,
687
+ "found": False,
688
+ "operation": "read",
689
+ "timestamp": datetime.now(UTC).isoformat(),
690
+ }
691
+ }
692
+
693
+ user_data = db_result["result"]["data"]
694
+
695
+ return {
696
+ "result": {
697
+ "user": {
698
+ "user_id": user_data["user_id"],
699
+ "email": user_data["email"],
700
+ "username": user_data["username"],
701
+ "first_name": user_data["first_name"],
702
+ "last_name": user_data["last_name"],
703
+ "status": user_data["status"],
704
+ "roles": user_data["roles"],
705
+ "attributes": user_data["attributes"],
706
+ "created_at": user_data["created_at"],
707
+ "updated_at": user_data["updated_at"],
708
+ "last_login": user_data["last_login"],
709
+ "tenant_id": user_data["tenant_id"],
710
+ },
711
+ "found": True,
712
+ "operation": "read",
713
+ "timestamp": datetime.now(UTC).isoformat(),
714
+ }
715
+ }
716
+
717
+ def _list_users(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
718
+ """List users with filtering, pagination, and search."""
719
+ tenant_id = inputs.get("tenant_id", "default")
720
+ filters = inputs.get("filters", {})
721
+ pagination = inputs.get(
722
+ "pagination", {"page": 1, "size": 20, "sort": "created_at"}
723
+ )
724
+ include_deleted = inputs.get("include_deleted", False)
725
+
726
+ # Build WHERE clause
727
+ where_conditions = ["tenant_id = $1"]
728
+ params = [tenant_id]
729
+ param_count = 1
730
+
731
+ if not include_deleted:
732
+ where_conditions.append("status != 'deleted'")
733
+
734
+ # Apply filters
735
+ if "status" in filters:
736
+ param_count += 1
737
+ where_conditions.append(f"status = ${param_count}")
738
+ params.append(filters["status"])
739
+
740
+ if "role" in filters:
741
+ param_count += 1
742
+ where_conditions.append(f"${param_count} = ANY(roles)")
743
+ params.append(filters["role"])
744
+
745
+ if "department" in filters:
746
+ param_count += 1
747
+ where_conditions.append(f"attributes->>'department' = ${param_count}")
748
+ params.append(filters["department"])
749
+
750
+ # Search query
751
+ search_query = inputs.get("search_query")
752
+ if search_query:
753
+ param_count += 1
754
+ where_conditions.append(
755
+ f"""
756
+ (email ILIKE ${param_count} OR
757
+ username ILIKE ${param_count} OR
758
+ first_name ILIKE ${param_count} OR
759
+ last_name ILIKE ${param_count})
760
+ """
761
+ )
762
+ params.append(f"%{search_query}%")
763
+
764
+ # Pagination
765
+ page = pagination.get("page", 1)
766
+ size = pagination.get("size", 20)
767
+ sort_field = pagination.get("sort", "created_at")
768
+ sort_direction = pagination.get("direction", "DESC")
769
+
770
+ offset = (page - 1) * size
771
+
772
+ # Count query
773
+ count_query = f"""
774
+ SELECT COUNT(*) as total
775
+ FROM users
776
+ WHERE {' AND '.join(where_conditions)}
777
+ """
778
+
779
+ # Data query
780
+ data_query = f"""
781
+ SELECT user_id, email, username, first_name, last_name,
782
+ status, roles, attributes, created_at, updated_at, last_login
783
+ FROM users
784
+ WHERE {' AND '.join(where_conditions)}
785
+ ORDER BY {sort_field} {sort_direction}
786
+ LIMIT {size} OFFSET {offset}
787
+ """
788
+
789
+ # Execute count query
790
+ self._db_node.config.update(
791
+ {"query": count_query, "params": params, "fetch_mode": "one"}
792
+ )
793
+ count_result = self._db_node.run()
794
+ total_count = count_result["result"]["data"]["total"]
795
+
796
+ # Execute data query
797
+ self._db_node.config.update(
798
+ {"query": data_query, "params": params, "fetch_mode": "all"}
799
+ )
800
+ data_result = self._db_node.run()
801
+ users = data_result["result"]["data"]
802
+
803
+ # Calculate pagination info
804
+ total_pages = (total_count + size - 1) // size
805
+ has_next = page < total_pages
806
+ has_prev = page > 1
807
+
808
+ return {
809
+ "result": {
810
+ "users": users,
811
+ "pagination": {
812
+ "page": page,
813
+ "size": size,
814
+ "total": total_count,
815
+ "total_pages": total_pages,
816
+ "has_next": has_next,
817
+ "has_prev": has_prev,
818
+ },
819
+ "filters_applied": filters,
820
+ "search_query": search_query,
821
+ "operation": "list",
822
+ "timestamp": datetime.now(UTC).isoformat(),
823
+ }
824
+ }
825
+
826
+ def _bulk_create_users(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
827
+ """Create multiple users with transaction support."""
828
+ user_data_list = inputs["user_data"]
829
+ tenant_id = inputs.get("tenant_id", "default")
830
+
831
+ if not isinstance(user_data_list, list):
832
+ raise NodeValidationError("user_data must be a list for bulk operations")
833
+
834
+ results = {"created": [], "failed": [], "stats": {"created": 0, "failed": 0}}
835
+
836
+ for i, user_data in enumerate(user_data_list):
837
+ try:
838
+ # Create individual user
839
+ create_inputs = {
840
+ "operation": "create",
841
+ "user_data": user_data,
842
+ "tenant_id": tenant_id,
843
+ "validate_email": inputs.get("validate_email", True),
844
+ "validate_username": inputs.get("validate_username", True),
845
+ }
846
+
847
+ result = self._create_user(create_inputs)
848
+ results["created"].append(
849
+ {"index": i, "user": result["result"]["user"]}
850
+ )
851
+ results["stats"]["created"] += 1
852
+
853
+ except Exception as e:
854
+ results["failed"].append(
855
+ {"index": i, "user_data": user_data, "error": str(e)}
856
+ )
857
+ results["stats"]["failed"] += 1
858
+
859
+ return {
860
+ "result": {
861
+ "operation": "bulk_create",
862
+ "results": results,
863
+ "timestamp": datetime.now(UTC).isoformat(),
864
+ }
865
+ }
866
+
867
+ # Utility methods
868
+ def _generate_user_id(self) -> str:
869
+ """Generate unique user ID."""
870
+ import uuid
871
+
872
+ return str(uuid.uuid4())
873
+
874
+ def _hash_password(self, password: str) -> str:
875
+ """Hash password using SHA-256 with salt."""
876
+ salt = secrets.token_hex(32)
877
+ password_hash = hashlib.sha256((password + salt).encode("utf-8")).hexdigest()
878
+ return f"{salt}${password_hash}"
879
+
880
+ def _validate_email(self, email: str) -> bool:
881
+ """Validate email format."""
882
+ import re
883
+
884
+ pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
885
+ return bool(re.match(pattern, email))
886
+
887
+ def _validate_username(self, username: str) -> bool:
888
+ """Validate username format."""
889
+ import re
890
+
891
+ # Username: alphanumeric, dots, hyphens, underscores, 3-50 chars
892
+ pattern = r"^[a-zA-Z0-9._-]{3,50}$"
893
+ return bool(re.match(pattern, username))
894
+
895
+ # Additional operations (update, delete, etc.) would follow similar patterns
896
+ def _update_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
897
+ """Update user information."""
898
+ # Implementation similar to create but with UPDATE query
899
+ raise NotImplementedError("Update operation will be implemented")
900
+
901
+ def _delete_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
902
+ """Soft delete user."""
903
+ # Implementation with status change to 'deleted'
904
+ raise NotImplementedError("Delete operation will be implemented")
905
+
906
+ def _change_password(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
907
+ """Change user password."""
908
+ # Implementation with password hashing and audit
909
+ raise NotImplementedError("Change password operation will be implemented")
910
+
911
+ def _reset_password(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
912
+ """Reset user password with token generation."""
913
+ # Implementation with secure token generation
914
+ raise NotImplementedError("Reset password operation will be implemented")
915
+
916
+ def _deactivate_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
917
+ """Deactivate user account."""
918
+ # Implementation with status change to 'inactive'
919
+ raise NotImplementedError("Deactivate operation will be implemented")
920
+
921
+ def _activate_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
922
+ """Activate user account."""
923
+ # Implementation with status change to 'active'
924
+ raise NotImplementedError("Activate operation will be implemented")
925
+
926
+ def _restore_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
927
+ """Restore soft-deleted user."""
928
+ # Implementation with status change from 'deleted'
929
+ raise NotImplementedError("Restore operation will be implemented")
930
+
931
+ def _search_users(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
932
+ """Advanced user search with full-text capabilities."""
933
+ # Implementation with advanced search features
934
+ raise NotImplementedError("Search operation will be implemented")
935
+
936
+ def _bulk_update_users(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
937
+ """Bulk update multiple users."""
938
+ # Implementation with transaction support
939
+ raise NotImplementedError("Bulk update operation will be implemented")
940
+
941
+ def _bulk_delete_users(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
942
+ """Bulk delete multiple users."""
943
+ # Implementation with transaction support
944
+ raise NotImplementedError("Bulk delete operation will be implemented")