kailash 0.3.1__py3-none-any.whl → 0.4.0__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 (146) 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 +25 -3
  37. kailash/nodes/admin/__init__.py +35 -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 +1519 -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 +1 -0
  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 +407 -2
  48. kailash/nodes/ai/self_organizing.py +85 -10
  49. kailash/nodes/api/auth.py +287 -6
  50. kailash/nodes/api/rest.py +151 -0
  51. kailash/nodes/auth/__init__.py +17 -0
  52. kailash/nodes/auth/directory_integration.py +1228 -0
  53. kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
  54. kailash/nodes/auth/mfa.py +2338 -0
  55. kailash/nodes/auth/risk_assessment.py +872 -0
  56. kailash/nodes/auth/session_management.py +1093 -0
  57. kailash/nodes/auth/sso.py +1040 -0
  58. kailash/nodes/base.py +344 -13
  59. kailash/nodes/base_cycle_aware.py +4 -2
  60. kailash/nodes/base_with_acl.py +1 -1
  61. kailash/nodes/code/python.py +293 -12
  62. kailash/nodes/compliance/__init__.py +9 -0
  63. kailash/nodes/compliance/data_retention.py +1888 -0
  64. kailash/nodes/compliance/gdpr.py +2004 -0
  65. kailash/nodes/data/__init__.py +22 -2
  66. kailash/nodes/data/async_connection.py +469 -0
  67. kailash/nodes/data/async_sql.py +757 -0
  68. kailash/nodes/data/async_vector.py +598 -0
  69. kailash/nodes/data/readers.py +767 -0
  70. kailash/nodes/data/retrieval.py +360 -1
  71. kailash/nodes/data/sharepoint_graph.py +397 -21
  72. kailash/nodes/data/sql.py +94 -5
  73. kailash/nodes/data/streaming.py +68 -8
  74. kailash/nodes/data/vector_db.py +54 -4
  75. kailash/nodes/enterprise/__init__.py +13 -0
  76. kailash/nodes/enterprise/batch_processor.py +741 -0
  77. kailash/nodes/enterprise/data_lineage.py +497 -0
  78. kailash/nodes/logic/convergence.py +31 -9
  79. kailash/nodes/logic/operations.py +14 -3
  80. kailash/nodes/mixins/__init__.py +8 -0
  81. kailash/nodes/mixins/event_emitter.py +201 -0
  82. kailash/nodes/mixins/mcp.py +9 -4
  83. kailash/nodes/mixins/security.py +165 -0
  84. kailash/nodes/monitoring/__init__.py +7 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +2497 -0
  86. kailash/nodes/rag/__init__.py +284 -0
  87. kailash/nodes/rag/advanced.py +1615 -0
  88. kailash/nodes/rag/agentic.py +773 -0
  89. kailash/nodes/rag/conversational.py +999 -0
  90. kailash/nodes/rag/evaluation.py +875 -0
  91. kailash/nodes/rag/federated.py +1188 -0
  92. kailash/nodes/rag/graph.py +721 -0
  93. kailash/nodes/rag/multimodal.py +671 -0
  94. kailash/nodes/rag/optimized.py +933 -0
  95. kailash/nodes/rag/privacy.py +1059 -0
  96. kailash/nodes/rag/query_processing.py +1335 -0
  97. kailash/nodes/rag/realtime.py +764 -0
  98. kailash/nodes/rag/registry.py +547 -0
  99. kailash/nodes/rag/router.py +837 -0
  100. kailash/nodes/rag/similarity.py +1854 -0
  101. kailash/nodes/rag/strategies.py +566 -0
  102. kailash/nodes/rag/workflows.py +575 -0
  103. kailash/nodes/security/__init__.py +19 -0
  104. kailash/nodes/security/abac_evaluator.py +1411 -0
  105. kailash/nodes/security/audit_log.py +91 -0
  106. kailash/nodes/security/behavior_analysis.py +1893 -0
  107. kailash/nodes/security/credential_manager.py +401 -0
  108. kailash/nodes/security/rotating_credentials.py +760 -0
  109. kailash/nodes/security/security_event.py +132 -0
  110. kailash/nodes/security/threat_detection.py +1103 -0
  111. kailash/nodes/testing/__init__.py +9 -0
  112. kailash/nodes/testing/credential_testing.py +499 -0
  113. kailash/nodes/transform/__init__.py +10 -2
  114. kailash/nodes/transform/chunkers.py +592 -1
  115. kailash/nodes/transform/processors.py +484 -14
  116. kailash/nodes/validation.py +321 -0
  117. kailash/runtime/access_controlled.py +1 -1
  118. kailash/runtime/async_local.py +41 -7
  119. kailash/runtime/docker.py +1 -1
  120. kailash/runtime/local.py +474 -55
  121. kailash/runtime/parallel.py +1 -1
  122. kailash/runtime/parallel_cyclic.py +1 -1
  123. kailash/runtime/testing.py +210 -2
  124. kailash/utils/migrations/__init__.py +25 -0
  125. kailash/utils/migrations/generator.py +433 -0
  126. kailash/utils/migrations/models.py +231 -0
  127. kailash/utils/migrations/runner.py +489 -0
  128. kailash/utils/secure_logging.py +342 -0
  129. kailash/workflow/__init__.py +16 -0
  130. kailash/workflow/cyclic_runner.py +3 -4
  131. kailash/workflow/graph.py +70 -2
  132. kailash/workflow/resilience.py +249 -0
  133. kailash/workflow/templates.py +726 -0
  134. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
  135. kailash-0.4.0.dist-info/RECORD +223 -0
  136. kailash/api/__init__.py +0 -17
  137. kailash/api/__main__.py +0 -6
  138. kailash/api/studio_secure.py +0 -893
  139. kailash/mcp/__main__.py +0 -13
  140. kailash/mcp/server_new.py +0 -336
  141. kailash/mcp/servers/__init__.py +0 -12
  142. kailash-0.3.1.dist-info/RECORD +0 -136
  143. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
  144. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
  145. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
  146. {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,823 @@
1
+ """Enterprise role management node with hierarchical RBAC support.
2
+
3
+ This node provides comprehensive role-based access control (RBAC) management
4
+ with support for role hierarchies, inheritance, and dynamic permission assignment.
5
+ Integrates with Session 065's ABAC system for enhanced access control.
6
+
7
+ Features:
8
+ - Hierarchical role management with inheritance
9
+ - Dynamic permission assignment and revocation
10
+ - Role templates and bulk operations
11
+ - Permission dependency validation
12
+ - Role-based data filtering
13
+ - Multi-tenant role isolation
14
+ - Integration with ABAC attributes
15
+ - Comprehensive audit logging
16
+ """
17
+
18
+ from dataclasses import dataclass
19
+ from datetime import UTC, datetime
20
+ from enum import Enum
21
+ from typing import Any, Dict, List, Optional, Set
22
+
23
+ from kailash.access_control import (
24
+ AccessControlManager,
25
+ NodePermission,
26
+ PermissionEffect,
27
+ PermissionRule,
28
+ WorkflowPermission,
29
+ )
30
+ from kailash.nodes.base import Node, NodeParameter, register_node
31
+ from kailash.nodes.data import AsyncSQLDatabaseNode
32
+ from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
33
+
34
+
35
+ class RoleOperation(Enum):
36
+ """Supported role management operations."""
37
+
38
+ CREATE_ROLE = "create_role"
39
+ UPDATE_ROLE = "update_role"
40
+ DELETE_ROLE = "delete_role"
41
+ LIST_ROLES = "list_roles"
42
+ GET_ROLE = "get_role"
43
+ ASSIGN_USER = "assign_user"
44
+ UNASSIGN_USER = "unassign_user"
45
+ ADD_PERMISSION = "add_permission"
46
+ REMOVE_PERMISSION = "remove_permission"
47
+ BULK_ASSIGN = "bulk_assign"
48
+ BULK_UNASSIGN = "bulk_unassign"
49
+ GET_USER_ROLES = "get_user_roles"
50
+ GET_ROLE_USERS = "get_role_users"
51
+ VALIDATE_HIERARCHY = "validate_hierarchy"
52
+ GET_EFFECTIVE_PERMISSIONS = "get_effective_permissions"
53
+
54
+
55
+ class RoleType(Enum):
56
+ """Types of roles in the system."""
57
+
58
+ SYSTEM = "system" # Built-in system roles
59
+ CUSTOM = "custom" # Custom organization roles
60
+ TEMPLATE = "template" # Role templates for reuse
61
+ TEMPORARY = "temporary" # Time-limited roles
62
+
63
+
64
+ @dataclass
65
+ class Role:
66
+ """Enhanced role definition with hierarchy support."""
67
+
68
+ role_id: str
69
+ name: str
70
+ description: str
71
+ role_type: RoleType
72
+ permissions: Set[str]
73
+ parent_roles: Set[str] # For role hierarchy
74
+ child_roles: Set[str] # Derived roles
75
+ attributes: Dict[str, Any] # ABAC attributes
76
+ is_active: bool
77
+ created_at: datetime
78
+ updated_at: datetime
79
+ tenant_id: str
80
+ created_by: str
81
+
82
+ def get_all_permissions(self, role_hierarchy: Dict[str, "Role"]) -> Set[str]:
83
+ """Get all permissions including inherited from parent roles."""
84
+ all_permissions = self.permissions.copy()
85
+
86
+ # Add permissions from parent roles (recursive)
87
+ for parent_id in self.parent_roles:
88
+ if parent_id in role_hierarchy:
89
+ parent_role = role_hierarchy[parent_id]
90
+ all_permissions.update(parent_role.get_all_permissions(role_hierarchy))
91
+
92
+ return all_permissions
93
+
94
+
95
+ @register_node()
96
+ class RoleManagementNode(Node):
97
+ """Enterprise role management node with hierarchical RBAC.
98
+
99
+ This node provides comprehensive role management capabilities including:
100
+ - Hierarchical role creation and management
101
+ - Permission assignment with inheritance
102
+ - User-role assignment and bulk operations
103
+ - Role validation and dependency checking
104
+ - Integration with ABAC attributes
105
+ - Multi-tenant role isolation
106
+
107
+ Parameters:
108
+ operation: Type of role operation to perform
109
+ role_data: Role configuration data
110
+ role_id: Role ID for single-role operations
111
+ role_ids: List of role IDs for bulk operations
112
+ user_id: User ID for assignment operations
113
+ user_ids: List of user IDs for bulk assignment
114
+ permission: Permission to add/remove
115
+ permissions: List of permissions for bulk operations
116
+ tenant_id: Tenant isolation
117
+ validate_hierarchy: Whether to validate role hierarchy
118
+ include_inherited: Include inherited permissions in results
119
+
120
+ Example:
121
+ >>> # Create hierarchical role structure
122
+ >>> node = RoleManagementNode(
123
+ ... operation="create_role",
124
+ ... role_data={
125
+ ... "name": "Senior Analyst",
126
+ ... "description": "Senior financial analyst with elevated permissions",
127
+ ... "parent_roles": ["analyst"],
128
+ ... "permissions": ["advanced_reports", "data_export"],
129
+ ... "attributes": {
130
+ ... "seniority": "senior",
131
+ ... "clearance_required": "confidential"
132
+ ... }
133
+ ... }
134
+ ... )
135
+ >>> result = node.run()
136
+ >>> role_id = result["role"]["role_id"]
137
+
138
+ >>> # Bulk user assignment
139
+ >>> node = RoleManagementNode(
140
+ ... operation="bulk_assign",
141
+ ... role_id="senior_analyst",
142
+ ... user_ids=["user1", "user2", "user3"],
143
+ ... validate_hierarchy=True
144
+ ... )
145
+ >>> result = node.run()
146
+ >>> assigned_count = result["stats"]["assigned"]
147
+ """
148
+
149
+ def __init__(self, **config):
150
+ super().__init__(**config)
151
+ self._db_node = None
152
+ self._access_manager = None
153
+
154
+ def get_parameters(self) -> Dict[str, NodeParameter]:
155
+ """Define parameters for role management operations."""
156
+ return {
157
+ param.name: param
158
+ for param in [
159
+ # Operation type
160
+ NodeParameter(
161
+ name="operation",
162
+ type=str,
163
+ required=True,
164
+ description="Role management operation to perform",
165
+ choices=[op.value for op in RoleOperation],
166
+ ),
167
+ # Role data
168
+ NodeParameter(
169
+ name="role_data",
170
+ type=dict,
171
+ required=False,
172
+ description="Role configuration data",
173
+ ),
174
+ # Single role operations
175
+ NodeParameter(
176
+ name="role_id",
177
+ type=str,
178
+ required=False,
179
+ description="Role ID for single-role operations",
180
+ ),
181
+ # Bulk operations
182
+ NodeParameter(
183
+ name="role_ids",
184
+ type=list,
185
+ required=False,
186
+ description="List of role IDs for bulk operations",
187
+ ),
188
+ # User assignment
189
+ NodeParameter(
190
+ name="user_id",
191
+ type=str,
192
+ required=False,
193
+ description="User ID for assignment operations",
194
+ ),
195
+ NodeParameter(
196
+ name="user_ids",
197
+ type=list,
198
+ required=False,
199
+ description="List of user IDs for bulk assignment",
200
+ ),
201
+ # Permission management
202
+ NodeParameter(
203
+ name="permission",
204
+ type=str,
205
+ required=False,
206
+ description="Permission to add/remove",
207
+ ),
208
+ NodeParameter(
209
+ name="permissions",
210
+ type=list,
211
+ required=False,
212
+ description="List of permissions for bulk operations",
213
+ ),
214
+ # Multi-tenancy
215
+ NodeParameter(
216
+ name="tenant_id",
217
+ type=str,
218
+ required=False,
219
+ description="Tenant ID for multi-tenant isolation",
220
+ ),
221
+ # Validation options
222
+ NodeParameter(
223
+ name="validate_hierarchy",
224
+ type=bool,
225
+ required=False,
226
+ default=True,
227
+ description="Whether to validate role hierarchy",
228
+ ),
229
+ NodeParameter(
230
+ name="include_inherited",
231
+ type=bool,
232
+ required=False,
233
+ default=True,
234
+ description="Include inherited permissions in results",
235
+ ),
236
+ # Database configuration
237
+ NodeParameter(
238
+ name="database_config",
239
+ type=dict,
240
+ required=False,
241
+ description="Database connection configuration",
242
+ ),
243
+ # Search and filtering
244
+ NodeParameter(
245
+ name="filters",
246
+ type=dict,
247
+ required=False,
248
+ description="Filters for role listing",
249
+ ),
250
+ NodeParameter(
251
+ name="search_query",
252
+ type=str,
253
+ required=False,
254
+ description="Search query for roles",
255
+ ),
256
+ ]
257
+ }
258
+
259
+ def run(self, **inputs) -> Dict[str, Any]:
260
+ """Execute role management operation."""
261
+ try:
262
+ operation = RoleOperation(inputs["operation"])
263
+
264
+ # Initialize dependencies
265
+ self._init_dependencies(inputs)
266
+
267
+ # Route to appropriate operation
268
+ if operation == RoleOperation.CREATE_ROLE:
269
+ return self._create_role(inputs)
270
+ elif operation == RoleOperation.UPDATE_ROLE:
271
+ return self._update_role(inputs)
272
+ elif operation == RoleOperation.DELETE_ROLE:
273
+ return self._delete_role(inputs)
274
+ elif operation == RoleOperation.LIST_ROLES:
275
+ return self._list_roles(inputs)
276
+ elif operation == RoleOperation.GET_ROLE:
277
+ return self._get_role(inputs)
278
+ elif operation == RoleOperation.ASSIGN_USER:
279
+ return self._assign_user(inputs)
280
+ elif operation == RoleOperation.UNASSIGN_USER:
281
+ return self._unassign_user(inputs)
282
+ elif operation == RoleOperation.ADD_PERMISSION:
283
+ return self._add_permission(inputs)
284
+ elif operation == RoleOperation.REMOVE_PERMISSION:
285
+ return self._remove_permission(inputs)
286
+ elif operation == RoleOperation.BULK_ASSIGN:
287
+ return self._bulk_assign(inputs)
288
+ elif operation == RoleOperation.BULK_UNASSIGN:
289
+ return self._bulk_unassign(inputs)
290
+ elif operation == RoleOperation.GET_USER_ROLES:
291
+ return self._get_user_roles(inputs)
292
+ elif operation == RoleOperation.GET_ROLE_USERS:
293
+ return self._get_role_users(inputs)
294
+ elif operation == RoleOperation.VALIDATE_HIERARCHY:
295
+ return self._validate_hierarchy(inputs)
296
+ elif operation == RoleOperation.GET_EFFECTIVE_PERMISSIONS:
297
+ return self._get_effective_permissions(inputs)
298
+ else:
299
+ raise NodeExecutionError(f"Unknown operation: {operation}")
300
+
301
+ except Exception as e:
302
+ raise NodeExecutionError(f"Role management operation failed: {str(e)}")
303
+
304
+ def _init_dependencies(self, inputs: Dict[str, Any]):
305
+ """Initialize database and access manager dependencies."""
306
+ # Get database config
307
+ db_config = inputs.get(
308
+ "database_config",
309
+ {
310
+ "database_type": "postgresql",
311
+ "host": "localhost",
312
+ "port": 5432,
313
+ "database": "kailash_admin",
314
+ "user": "admin",
315
+ "password": "admin",
316
+ },
317
+ )
318
+
319
+ # Initialize async database node
320
+ self._db_node = AsyncSQLDatabaseNode(name="role_management_db", **db_config)
321
+
322
+ # Initialize enhanced access manager
323
+ self._access_manager = AccessControlManager(strategy="abac")
324
+
325
+ def _create_role(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
326
+ """Create a new role with hierarchy validation."""
327
+ role_data = inputs["role_data"]
328
+ tenant_id = inputs.get("tenant_id", "default")
329
+ validate_hierarchy = inputs.get("validate_hierarchy", True)
330
+
331
+ # Validate required fields
332
+ required_fields = ["name", "description"]
333
+ for field in required_fields:
334
+ if field not in role_data:
335
+ raise NodeValidationError(f"Missing required field: {field}")
336
+
337
+ # Generate role ID
338
+ role_id = self._generate_role_id(role_data["name"])
339
+ now = datetime.now(UTC)
340
+
341
+ # Validate parent roles exist if specified
342
+ parent_roles = set(role_data.get("parent_roles", []))
343
+ if parent_roles and validate_hierarchy:
344
+ self._validate_parent_roles_exist(parent_roles, tenant_id)
345
+
346
+ # Validate no circular dependencies
347
+ if parent_roles and validate_hierarchy:
348
+ self._validate_no_circular_dependency(role_id, parent_roles, tenant_id)
349
+
350
+ # Prepare role record
351
+ role_record = {
352
+ "role_id": role_id,
353
+ "name": role_data["name"],
354
+ "description": role_data["description"],
355
+ "role_type": role_data.get("role_type", RoleType.CUSTOM.value),
356
+ "permissions": list(role_data.get("permissions", [])),
357
+ "parent_roles": list(parent_roles),
358
+ "attributes": role_data.get("attributes", {}),
359
+ "is_active": role_data.get("is_active", True),
360
+ "tenant_id": tenant_id,
361
+ "created_at": now,
362
+ "updated_at": now,
363
+ "created_by": inputs.get("created_by", "system"),
364
+ }
365
+
366
+ # Insert role into database
367
+ insert_query = """
368
+ INSERT INTO roles (
369
+ role_id, name, description, role_type, permissions, parent_roles,
370
+ attributes, is_active, tenant_id, created_at, updated_at, created_by
371
+ ) VALUES (
372
+ $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
373
+ )
374
+ """
375
+
376
+ # Execute database insert
377
+ self._db_node.config.update(
378
+ {
379
+ "query": insert_query,
380
+ "params": [
381
+ role_record["role_id"],
382
+ role_record["name"],
383
+ role_record["description"],
384
+ role_record["role_type"],
385
+ role_record["permissions"],
386
+ role_record["parent_roles"],
387
+ role_record["attributes"],
388
+ role_record["is_active"],
389
+ role_record["tenant_id"],
390
+ role_record["created_at"],
391
+ role_record["updated_at"],
392
+ role_record["created_by"],
393
+ ],
394
+ }
395
+ )
396
+
397
+ db_result = self._db_node.run()
398
+
399
+ # Update child_roles for parent roles
400
+ if parent_roles:
401
+ self._update_child_roles(parent_roles, role_id, tenant_id, "add")
402
+
403
+ return {
404
+ "result": {
405
+ "role": {
406
+ "role_id": role_id,
407
+ "name": role_record["name"],
408
+ "description": role_record["description"],
409
+ "role_type": role_record["role_type"],
410
+ "permissions": role_record["permissions"],
411
+ "parent_roles": role_record["parent_roles"],
412
+ "attributes": role_record["attributes"],
413
+ "is_active": role_record["is_active"],
414
+ "tenant_id": role_record["tenant_id"],
415
+ "created_at": role_record["created_at"].isoformat(),
416
+ },
417
+ "operation": "create_role",
418
+ "success": True,
419
+ "timestamp": datetime.now(UTC).isoformat(),
420
+ }
421
+ }
422
+
423
+ def _assign_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
424
+ """Assign role to user with validation."""
425
+ user_id = inputs["user_id"]
426
+ role_id = inputs["role_id"]
427
+ tenant_id = inputs.get("tenant_id", "default")
428
+ validate_hierarchy = inputs.get("validate_hierarchy", True)
429
+
430
+ # Validate role exists and is active
431
+ if validate_hierarchy:
432
+ role = self._get_role_by_id(role_id, tenant_id)
433
+ if not role:
434
+ raise NodeValidationError(f"Role not found: {role_id}")
435
+ if not role["is_active"]:
436
+ raise NodeValidationError(f"Role is not active: {role_id}")
437
+
438
+ # Check if assignment already exists
439
+ existing_query = """
440
+ SELECT 1 FROM user_roles
441
+ WHERE user_id = $1 AND role_id = $2 AND tenant_id = $3
442
+ """
443
+
444
+ self._db_node.config.update(
445
+ {
446
+ "query": existing_query,
447
+ "params": [user_id, role_id, tenant_id],
448
+ "fetch_mode": "one",
449
+ }
450
+ )
451
+
452
+ existing = self._db_node.run()
453
+
454
+ if existing.get("result", {}).get("data"):
455
+ return {
456
+ "result": {
457
+ "assignment": {
458
+ "user_id": user_id,
459
+ "role_id": role_id,
460
+ "already_assigned": True,
461
+ },
462
+ "operation": "assign_user",
463
+ "success": True,
464
+ "timestamp": datetime.now(UTC).isoformat(),
465
+ }
466
+ }
467
+
468
+ # Create assignment
469
+ now = datetime.now(UTC)
470
+ insert_query = """
471
+ INSERT INTO user_roles (user_id, role_id, tenant_id, assigned_at, assigned_by)
472
+ VALUES ($1, $2, $3, $4, $5)
473
+ """
474
+
475
+ self._db_node.config.update(
476
+ {
477
+ "query": insert_query,
478
+ "params": [
479
+ user_id,
480
+ role_id,
481
+ tenant_id,
482
+ now,
483
+ inputs.get("assigned_by", "system"),
484
+ ],
485
+ }
486
+ )
487
+
488
+ db_result = self._db_node.run()
489
+
490
+ return {
491
+ "result": {
492
+ "assignment": {
493
+ "user_id": user_id,
494
+ "role_id": role_id,
495
+ "tenant_id": tenant_id,
496
+ "assigned_at": now.isoformat(),
497
+ "already_assigned": False,
498
+ },
499
+ "operation": "assign_user",
500
+ "success": True,
501
+ "timestamp": datetime.now(UTC).isoformat(),
502
+ }
503
+ }
504
+
505
+ def _bulk_assign(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
506
+ """Bulk assign role to multiple users."""
507
+ role_id = inputs["role_id"]
508
+ user_ids = inputs["user_ids"]
509
+ tenant_id = inputs.get("tenant_id", "default")
510
+
511
+ if not isinstance(user_ids, list):
512
+ raise NodeValidationError("user_ids must be a list for bulk operations")
513
+
514
+ results = {
515
+ "assigned": [],
516
+ "failed": [],
517
+ "stats": {"assigned": 0, "failed": 0, "already_assigned": 0},
518
+ }
519
+
520
+ for user_id in user_ids:
521
+ try:
522
+ assign_inputs = {
523
+ "operation": "assign_user",
524
+ "user_id": user_id,
525
+ "role_id": role_id,
526
+ "tenant_id": tenant_id,
527
+ "validate_hierarchy": inputs.get("validate_hierarchy", True),
528
+ }
529
+
530
+ result = self._assign_user(assign_inputs)
531
+ assignment = result["result"]["assignment"]
532
+
533
+ if assignment["already_assigned"]:
534
+ results["stats"]["already_assigned"] += 1
535
+ else:
536
+ results["stats"]["assigned"] += 1
537
+
538
+ results["assigned"].append(
539
+ {
540
+ "user_id": user_id,
541
+ "already_assigned": assignment["already_assigned"],
542
+ }
543
+ )
544
+
545
+ except Exception as e:
546
+ results["failed"].append({"user_id": user_id, "error": str(e)})
547
+ results["stats"]["failed"] += 1
548
+
549
+ return {
550
+ "result": {
551
+ "operation": "bulk_assign",
552
+ "role_id": role_id,
553
+ "results": results,
554
+ "timestamp": datetime.now(UTC).isoformat(),
555
+ }
556
+ }
557
+
558
+ def _get_effective_permissions(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
559
+ """Get effective permissions for a role including inherited permissions."""
560
+ role_id = inputs["role_id"]
561
+ tenant_id = inputs.get("tenant_id", "default")
562
+ include_inherited = inputs.get("include_inherited", True)
563
+
564
+ # Get role and build hierarchy
565
+ role_hierarchy = self._build_role_hierarchy(tenant_id)
566
+
567
+ if role_id not in role_hierarchy:
568
+ raise NodeValidationError(f"Role not found: {role_id}")
569
+
570
+ role = role_hierarchy[role_id]
571
+
572
+ # Get direct permissions
573
+ direct_permissions = set(role["permissions"])
574
+
575
+ # Get inherited permissions if requested
576
+ inherited_permissions = set()
577
+ all_permissions = direct_permissions.copy()
578
+
579
+ if include_inherited:
580
+ inherited_permissions = self._get_inherited_permissions(
581
+ role_id, role_hierarchy
582
+ )
583
+ all_permissions.update(inherited_permissions)
584
+
585
+ return {
586
+ "result": {
587
+ "role_id": role_id,
588
+ "direct_permissions": list(direct_permissions),
589
+ "inherited_permissions": list(inherited_permissions),
590
+ "all_permissions": list(all_permissions),
591
+ "permission_count": {
592
+ "direct": len(direct_permissions),
593
+ "inherited": len(inherited_permissions),
594
+ "total": len(all_permissions),
595
+ },
596
+ "operation": "get_effective_permissions",
597
+ "timestamp": datetime.now(UTC).isoformat(),
598
+ }
599
+ }
600
+
601
+ # Utility methods
602
+ def _generate_role_id(self, name: str) -> str:
603
+ """Generate role ID from name."""
604
+ import re
605
+
606
+ # Convert to lowercase, replace spaces/special chars with underscores
607
+ role_id = re.sub(r"[^a-zA-Z0-9_]", "_", name.lower())
608
+ role_id = re.sub(r"_+", "_", role_id) # Remove multiple underscores
609
+ role_id = role_id.strip("_") # Remove leading/trailing underscores
610
+ return role_id
611
+
612
+ def _validate_parent_roles_exist(self, parent_roles: Set[str], tenant_id: str):
613
+ """Validate that all parent roles exist."""
614
+ if not parent_roles:
615
+ return
616
+
617
+ placeholders = ",".join(["$" + str(i + 2) for i in range(len(parent_roles))])
618
+ query = f"""
619
+ SELECT role_id FROM roles
620
+ WHERE tenant_id = $1 AND role_id IN ({placeholders}) AND is_active = true
621
+ """
622
+
623
+ params = [tenant_id] + list(parent_roles)
624
+
625
+ self._db_node.config.update(
626
+ {"query": query, "params": params, "fetch_mode": "all"}
627
+ )
628
+
629
+ result = self._db_node.run()
630
+ existing_roles = {
631
+ row["role_id"] for row in result.get("result", {}).get("data", [])
632
+ }
633
+
634
+ missing_roles = parent_roles - existing_roles
635
+ if missing_roles:
636
+ raise NodeValidationError(
637
+ f"Parent roles not found: {', '.join(missing_roles)}"
638
+ )
639
+
640
+ def _validate_no_circular_dependency(
641
+ self, role_id: str, parent_roles: Set[str], tenant_id: str
642
+ ):
643
+ """Validate no circular dependencies in role hierarchy."""
644
+ # Build current hierarchy excluding the new role
645
+ role_hierarchy = self._build_role_hierarchy(tenant_id, exclude_role=role_id)
646
+
647
+ # Check if any parent role has this role as an ancestor
648
+ for parent_id in parent_roles:
649
+ if self._is_ancestor(role_id, parent_id, role_hierarchy):
650
+ raise NodeValidationError(
651
+ f"Circular dependency detected: {role_id} -> {parent_id}"
652
+ )
653
+
654
+ def _is_ancestor(
655
+ self, ancestor_id: str, role_id: str, role_hierarchy: Dict[str, Dict]
656
+ ) -> bool:
657
+ """Check if ancestor_id is an ancestor of role_id."""
658
+ if role_id not in role_hierarchy:
659
+ return False
660
+
661
+ role = role_hierarchy[role_id]
662
+ parent_roles = role.get("parent_roles", [])
663
+
664
+ if ancestor_id in parent_roles:
665
+ return True
666
+
667
+ # Recursively check parent roles
668
+ for parent_id in parent_roles:
669
+ if self._is_ancestor(ancestor_id, parent_id, role_hierarchy):
670
+ return True
671
+
672
+ return False
673
+
674
+ def _build_role_hierarchy(
675
+ self, tenant_id: str, exclude_role: Optional[str] = None
676
+ ) -> Dict[str, Dict]:
677
+ """Build complete role hierarchy for tenant."""
678
+ query = """
679
+ SELECT role_id, name, permissions, parent_roles, child_roles, is_active
680
+ FROM roles
681
+ WHERE tenant_id = $1
682
+ """
683
+ params = [tenant_id]
684
+
685
+ if exclude_role:
686
+ query += " AND role_id != $2"
687
+ params.append(exclude_role)
688
+
689
+ self._db_node.config.update(
690
+ {"query": query, "params": params, "fetch_mode": "all"}
691
+ )
692
+
693
+ result = self._db_node.run()
694
+ roles_data = result.get("result", {}).get("data", [])
695
+
696
+ # Convert to hierarchy dict
697
+ hierarchy = {}
698
+ for role_data in roles_data:
699
+ hierarchy[role_data["role_id"]] = role_data
700
+
701
+ return hierarchy
702
+
703
+ def _get_inherited_permissions(
704
+ self, role_id: str, role_hierarchy: Dict[str, Dict]
705
+ ) -> Set[str]:
706
+ """Get all inherited permissions for a role."""
707
+ inherited = set()
708
+
709
+ if role_id not in role_hierarchy:
710
+ return inherited
711
+
712
+ role = role_hierarchy[role_id]
713
+ parent_roles = role.get("parent_roles", [])
714
+
715
+ for parent_id in parent_roles:
716
+ if parent_id in role_hierarchy:
717
+ parent_role = role_hierarchy[parent_id]
718
+ # Add parent's direct permissions
719
+ inherited.update(parent_role.get("permissions", []))
720
+ # Recursively add inherited permissions
721
+ inherited.update(
722
+ self._get_inherited_permissions(parent_id, role_hierarchy)
723
+ )
724
+
725
+ return inherited
726
+
727
+ def _get_role_by_id(self, role_id: str, tenant_id: str) -> Optional[Dict[str, Any]]:
728
+ """Get role by ID."""
729
+ query = """
730
+ SELECT role_id, name, description, role_type, permissions, parent_roles,
731
+ attributes, is_active, created_at, updated_at
732
+ FROM roles
733
+ WHERE role_id = $1 AND tenant_id = $2
734
+ """
735
+
736
+ self._db_node.config.update(
737
+ {"query": query, "params": [role_id, tenant_id], "fetch_mode": "one"}
738
+ )
739
+
740
+ result = self._db_node.run()
741
+ return result.get("result", {}).get("data")
742
+
743
+ def _update_child_roles(
744
+ self,
745
+ parent_role_ids: Set[str],
746
+ child_role_id: str,
747
+ tenant_id: str,
748
+ operation: str,
749
+ ):
750
+ """Update child_roles arrays for parent roles."""
751
+ if operation == "add":
752
+ query = """
753
+ UPDATE roles
754
+ SET child_roles = array_append(child_roles, $1),
755
+ updated_at = $2
756
+ WHERE role_id = ANY($3) AND tenant_id = $4
757
+ """
758
+ else: # remove
759
+ query = """
760
+ UPDATE roles
761
+ SET child_roles = array_remove(child_roles, $1),
762
+ updated_at = $2
763
+ WHERE role_id = ANY($3) AND tenant_id = $4
764
+ """
765
+
766
+ self._db_node.config.update(
767
+ {
768
+ "query": query,
769
+ "params": [
770
+ child_role_id,
771
+ datetime.now(UTC),
772
+ list(parent_role_ids),
773
+ tenant_id,
774
+ ],
775
+ }
776
+ )
777
+
778
+ self._db_node.run()
779
+
780
+ # Additional operations would follow similar patterns
781
+ def _update_role(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
782
+ """Update role information."""
783
+ raise NotImplementedError("Update role operation will be implemented")
784
+
785
+ def _delete_role(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
786
+ """Delete role with dependency checking."""
787
+ raise NotImplementedError("Delete role operation will be implemented")
788
+
789
+ def _list_roles(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
790
+ """List roles with filtering and pagination."""
791
+ raise NotImplementedError("List roles operation will be implemented")
792
+
793
+ def _get_role(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
794
+ """Get detailed role information."""
795
+ raise NotImplementedError("Get role operation will be implemented")
796
+
797
+ def _unassign_user(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
798
+ """Unassign role from user."""
799
+ raise NotImplementedError("Unassign user operation will be implemented")
800
+
801
+ def _add_permission(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
802
+ """Add permission to role."""
803
+ raise NotImplementedError("Add permission operation will be implemented")
804
+
805
+ def _remove_permission(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
806
+ """Remove permission from role."""
807
+ raise NotImplementedError("Remove permission operation will be implemented")
808
+
809
+ def _bulk_unassign(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
810
+ """Bulk unassign role from multiple users."""
811
+ raise NotImplementedError("Bulk unassign operation will be implemented")
812
+
813
+ def _get_user_roles(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
814
+ """Get all roles for a user."""
815
+ raise NotImplementedError("Get user roles operation will be implemented")
816
+
817
+ def _get_role_users(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
818
+ """Get all users assigned to a role."""
819
+ raise NotImplementedError("Get role users operation will be implemented")
820
+
821
+ def _validate_hierarchy(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
822
+ """Validate entire role hierarchy for consistency."""
823
+ raise NotImplementedError("Validate hierarchy operation will be implemented")