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,864 @@
1
+ """Enterprise permission checking node with real-time RBAC and ABAC evaluation.
2
+
3
+ This node provides high-performance permission checking capabilities that integrate
4
+ both Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC)
5
+ from Session 065. Designed for real-time permission evaluation in enterprise
6
+ applications with comprehensive caching and audit logging.
7
+
8
+ Features:
9
+ - Real-time RBAC and ABAC permission evaluation
10
+ - Multi-level permission caching for performance
11
+ - Batch permission checking for efficiency
12
+ - Permission explanation and debugging
13
+ - Conditional permission evaluation
14
+ - Integration with user and role management
15
+ - Comprehensive audit logging
16
+ - Multi-tenant permission isolation
17
+ """
18
+
19
+ import hashlib
20
+ import json
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, Set, Tuple
25
+
26
+ from kailash.access_control import (
27
+ AccessControlManager,
28
+ AccessDecision,
29
+ NodePermission,
30
+ PermissionEffect,
31
+ PermissionRule,
32
+ UserContext,
33
+ WorkflowPermission,
34
+ )
35
+ from kailash.access_control_abac import (
36
+ AttributeCondition,
37
+ AttributeEvaluator,
38
+ AttributeExpression,
39
+ AttributeOperator,
40
+ LogicalOperator,
41
+ )
42
+ from kailash.nodes.base import Node, NodeParameter, register_node
43
+ from kailash.nodes.data import AsyncSQLDatabaseNode
44
+ from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
45
+
46
+
47
+ class PermissionCheckOperation(Enum):
48
+ """Supported permission check operations."""
49
+
50
+ CHECK_PERMISSION = "check_permission"
51
+ BATCH_CHECK = "batch_check"
52
+ CHECK_NODE_ACCESS = "check_node_access"
53
+ CHECK_WORKFLOW_ACCESS = "check_workflow_access"
54
+ GET_USER_PERMISSIONS = "get_user_permissions"
55
+ EXPLAIN_PERMISSION = "explain_permission"
56
+ VALIDATE_CONDITIONS = "validate_conditions"
57
+ CHECK_HIERARCHICAL = "check_hierarchical"
58
+ BULK_USER_CHECK = "bulk_user_check"
59
+ CLEAR_CACHE = "clear_cache"
60
+
61
+
62
+ class CacheLevel(Enum):
63
+ """Permission cache levels."""
64
+
65
+ NONE = "none" # No caching
66
+ USER = "user" # Cache per user
67
+ ROLE = "role" # Cache per role
68
+ PERMISSION = "permission" # Cache per permission
69
+ FULL = "full" # Full caching
70
+
71
+
72
+ @dataclass
73
+ class PermissionCheckResult:
74
+ """Result of a permission check operation."""
75
+
76
+ allowed: bool
77
+ reason: str
78
+ applied_rules: List[str]
79
+ user_id: str
80
+ resource_id: str
81
+ permission: str
82
+ evaluation_time_ms: float
83
+ cached: bool
84
+ cache_hit: bool = False
85
+
86
+ def to_dict(self) -> Dict[str, Any]:
87
+ """Convert to dictionary for JSON serialization."""
88
+ return {
89
+ "allowed": self.allowed,
90
+ "reason": self.reason,
91
+ "applied_rules": self.applied_rules,
92
+ "user_id": self.user_id,
93
+ "resource_id": self.resource_id,
94
+ "permission": self.permission,
95
+ "evaluation_time_ms": self.evaluation_time_ms,
96
+ "cached": self.cached,
97
+ "cache_hit": self.cache_hit,
98
+ }
99
+
100
+
101
+ @dataclass
102
+ class PermissionExplanation:
103
+ """Detailed explanation of permission evaluation."""
104
+
105
+ permission_granted: bool
106
+ rbac_result: bool
107
+ abac_result: bool
108
+ role_permissions: List[str]
109
+ inherited_permissions: List[str]
110
+ attribute_conditions: List[Dict[str, Any]]
111
+ failed_conditions: List[Dict[str, Any]]
112
+ decision_path: List[str]
113
+
114
+ def to_dict(self) -> Dict[str, Any]:
115
+ """Convert to dictionary for JSON serialization."""
116
+ return {
117
+ "permission_granted": self.permission_granted,
118
+ "rbac_result": self.rbac_result,
119
+ "abac_result": self.abac_result,
120
+ "role_permissions": self.role_permissions,
121
+ "inherited_permissions": self.inherited_permissions,
122
+ "attribute_conditions": self.attribute_conditions,
123
+ "failed_conditions": self.failed_conditions,
124
+ "decision_path": self.decision_path,
125
+ }
126
+
127
+
128
+ @register_node()
129
+ class PermissionCheckNode(Node):
130
+ """Enterprise permission checking node with RBAC/ABAC integration.
131
+
132
+ This node provides comprehensive permission checking capabilities including:
133
+ - Real-time RBAC and ABAC evaluation
134
+ - Multi-level caching for performance
135
+ - Batch permission checking
136
+ - Permission explanation and debugging
137
+ - Integration with user and role management
138
+ - Multi-tenant permission isolation
139
+
140
+ Parameters:
141
+ operation: Type of permission check operation
142
+ user_id: User ID for permission check
143
+ user_ids: List of user IDs for bulk operations
144
+ resource_id: Resource being accessed
145
+ resource_ids: List of resources for batch checking
146
+ permission: Permission being checked
147
+ permissions: List of permissions for batch checking
148
+ context: Additional context for ABAC evaluation
149
+ cache_level: Level of caching to use
150
+ cache_ttl: Cache time-to-live in seconds
151
+ explain: Whether to provide detailed explanation
152
+ tenant_id: Tenant isolation
153
+
154
+ Example:
155
+ >>> # Single permission check with caching
156
+ >>> node = PermissionCheckNode(
157
+ ... operation="check_permission",
158
+ ... user_id="user123",
159
+ ... resource_id="sensitive_data",
160
+ ... permission="read",
161
+ ... cache_level="user",
162
+ ... cache_ttl=300,
163
+ ... explain=True
164
+ ... )
165
+ >>> result = node.run()
166
+ >>> allowed = result["check"]["allowed"]
167
+ >>> explanation = result["explanation"]
168
+
169
+ >>> # Batch permission checking
170
+ >>> node = PermissionCheckNode(
171
+ ... operation="batch_check",
172
+ ... user_id="user123",
173
+ ... resource_ids=["data1", "data2", "data3"],
174
+ ... permissions=["read", "write", "delete"],
175
+ ... cache_level="full"
176
+ ... )
177
+ >>> result = node.run()
178
+ >>> results = result["batch_results"]
179
+
180
+ >>> # Bulk user permission check
181
+ >>> node = PermissionCheckNode(
182
+ ... operation="bulk_user_check",
183
+ ... user_ids=["user1", "user2", "user3"],
184
+ ... resource_id="workflow_execute",
185
+ ... permission="execute"
186
+ ... )
187
+ >>> result = node.run()
188
+ >>> access_matrix = result["access_matrix"]
189
+ """
190
+
191
+ def __init__(self, **config):
192
+ super().__init__(**config)
193
+ self._db_node = None
194
+ self._access_manager = None
195
+ self._attribute_evaluator = None
196
+ self._permission_cache = {}
197
+ self._cache_timestamps = {}
198
+
199
+ def get_parameters(self) -> Dict[str, NodeParameter]:
200
+ """Define parameters for permission checking operations."""
201
+ return {
202
+ param.name: param
203
+ for param in [
204
+ # Operation type
205
+ NodeParameter(
206
+ name="operation",
207
+ type=str,
208
+ required=True,
209
+ description="Permission check operation to perform",
210
+ choices=[op.value for op in PermissionCheckOperation],
211
+ ),
212
+ # User identification
213
+ NodeParameter(
214
+ name="user_id",
215
+ type=str,
216
+ required=False,
217
+ description="User ID for permission check",
218
+ ),
219
+ NodeParameter(
220
+ name="user_ids",
221
+ type=list,
222
+ required=False,
223
+ description="List of user IDs for bulk operations",
224
+ ),
225
+ # Resource identification
226
+ NodeParameter(
227
+ name="resource_id",
228
+ type=str,
229
+ required=False,
230
+ description="Resource being accessed",
231
+ ),
232
+ NodeParameter(
233
+ name="resource_ids",
234
+ type=list,
235
+ required=False,
236
+ description="List of resources for batch checking",
237
+ ),
238
+ # Permission identification
239
+ NodeParameter(
240
+ name="permission",
241
+ type=str,
242
+ required=False,
243
+ description="Permission being checked",
244
+ ),
245
+ NodeParameter(
246
+ name="permissions",
247
+ type=list,
248
+ required=False,
249
+ description="List of permissions for batch checking",
250
+ ),
251
+ # Context for ABAC
252
+ NodeParameter(
253
+ name="context",
254
+ type=dict,
255
+ required=False,
256
+ description="Additional context for ABAC evaluation",
257
+ ),
258
+ # Caching configuration
259
+ NodeParameter(
260
+ name="cache_level",
261
+ type=str,
262
+ required=False,
263
+ default="user",
264
+ choices=[level.value for level in CacheLevel],
265
+ description="Level of caching to use",
266
+ ),
267
+ NodeParameter(
268
+ name="cache_ttl",
269
+ type=int,
270
+ required=False,
271
+ default=300,
272
+ description="Cache time-to-live in seconds",
273
+ ),
274
+ # Output options
275
+ NodeParameter(
276
+ name="explain",
277
+ type=bool,
278
+ required=False,
279
+ default=False,
280
+ description="Whether to provide detailed explanation",
281
+ ),
282
+ NodeParameter(
283
+ name="include_timing",
284
+ type=bool,
285
+ required=False,
286
+ default=True,
287
+ description="Include timing information in results",
288
+ ),
289
+ # Multi-tenancy
290
+ NodeParameter(
291
+ name="tenant_id",
292
+ type=str,
293
+ required=False,
294
+ description="Tenant ID for multi-tenant isolation",
295
+ ),
296
+ # Database configuration
297
+ NodeParameter(
298
+ name="database_config",
299
+ type=dict,
300
+ required=False,
301
+ description="Database connection configuration",
302
+ ),
303
+ # Validation options
304
+ NodeParameter(
305
+ name="strict_validation",
306
+ type=bool,
307
+ required=False,
308
+ default=True,
309
+ description="Enable strict validation of inputs",
310
+ ),
311
+ ]
312
+ }
313
+
314
+ def run(self, **inputs) -> Dict[str, Any]:
315
+ """Execute permission check operation."""
316
+ try:
317
+ operation = PermissionCheckOperation(inputs["operation"])
318
+
319
+ # Initialize dependencies
320
+ self._init_dependencies(inputs)
321
+
322
+ # Route to appropriate operation
323
+ if operation == PermissionCheckOperation.CHECK_PERMISSION:
324
+ return self._check_permission(inputs)
325
+ elif operation == PermissionCheckOperation.BATCH_CHECK:
326
+ return self._batch_check(inputs)
327
+ elif operation == PermissionCheckOperation.CHECK_NODE_ACCESS:
328
+ return self._check_node_access(inputs)
329
+ elif operation == PermissionCheckOperation.CHECK_WORKFLOW_ACCESS:
330
+ return self._check_workflow_access(inputs)
331
+ elif operation == PermissionCheckOperation.GET_USER_PERMISSIONS:
332
+ return self._get_user_permissions(inputs)
333
+ elif operation == PermissionCheckOperation.EXPLAIN_PERMISSION:
334
+ return self._explain_permission(inputs)
335
+ elif operation == PermissionCheckOperation.VALIDATE_CONDITIONS:
336
+ return self._validate_conditions(inputs)
337
+ elif operation == PermissionCheckOperation.CHECK_HIERARCHICAL:
338
+ return self._check_hierarchical(inputs)
339
+ elif operation == PermissionCheckOperation.BULK_USER_CHECK:
340
+ return self._bulk_user_check(inputs)
341
+ elif operation == PermissionCheckOperation.CLEAR_CACHE:
342
+ return self._clear_cache(inputs)
343
+ else:
344
+ raise NodeExecutionError(f"Unknown operation: {operation}")
345
+
346
+ except Exception as e:
347
+ raise NodeExecutionError(f"Permission check operation failed: {str(e)}")
348
+
349
+ def _init_dependencies(self, inputs: Dict[str, Any]):
350
+ """Initialize database and access manager dependencies."""
351
+ # Get database config
352
+ db_config = inputs.get(
353
+ "database_config",
354
+ {
355
+ "database_type": "postgresql",
356
+ "host": "localhost",
357
+ "port": 5432,
358
+ "database": "kailash_admin",
359
+ "user": "admin",
360
+ "password": "admin",
361
+ },
362
+ )
363
+
364
+ # Initialize async database node
365
+ self._db_node = AsyncSQLDatabaseNode(name="permission_check_db", **db_config)
366
+
367
+ # Initialize enhanced access manager
368
+ self._access_manager = AccessControlManager(strategy="abac")
369
+ self._attribute_evaluator = AttributeEvaluator()
370
+
371
+ def _check_permission(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
372
+ """Check a single permission with caching and explanation."""
373
+ user_id = inputs["user_id"]
374
+ resource_id = inputs["resource_id"]
375
+ permission = inputs["permission"]
376
+ tenant_id = inputs.get("tenant_id", "default")
377
+ context = inputs.get("context", {})
378
+ cache_level = CacheLevel(inputs.get("cache_level", "user"))
379
+ cache_ttl = inputs.get("cache_ttl", 300)
380
+ explain = inputs.get("explain", False)
381
+ include_timing = inputs.get("include_timing", True)
382
+
383
+ start_time = datetime.now(UTC) if include_timing else None
384
+
385
+ # Check cache first
386
+ cache_key = self._generate_cache_key(user_id, resource_id, permission, context)
387
+ cached_result = (
388
+ self._get_from_cache(cache_key, cache_ttl)
389
+ if cache_level != CacheLevel.NONE
390
+ else None
391
+ )
392
+
393
+ if cached_result:
394
+ cached_result["cache_hit"] = True
395
+ if include_timing:
396
+ cached_result["evaluation_time_ms"] = 0.1 # Minimal cache lookup time
397
+
398
+ result = {
399
+ "result": {
400
+ "check": cached_result,
401
+ "operation": "check_permission",
402
+ "timestamp": datetime.now(UTC).isoformat(),
403
+ }
404
+ }
405
+
406
+ if explain:
407
+ result["result"]["explanation"] = cached_result.get("explanation", {})
408
+
409
+ return result
410
+
411
+ # Get user context
412
+ user_context = self._get_user_context(user_id, tenant_id)
413
+ if not user_context:
414
+ raise NodeValidationError(f"User not found: {user_id}")
415
+
416
+ # Perform RBAC check
417
+ rbac_result = self._check_rbac_permission(user_context, resource_id, permission)
418
+
419
+ # Perform ABAC check if needed
420
+ abac_result = True # Default to allow if no ABAC rules
421
+ if context:
422
+ abac_result = self._check_abac_permission(
423
+ user_context, resource_id, permission, context
424
+ )
425
+
426
+ # Combine results
427
+ allowed = rbac_result and abac_result
428
+
429
+ # Build explanation if requested
430
+ explanation = None
431
+ if explain:
432
+ explanation = self._build_permission_explanation(
433
+ user_context, resource_id, permission, context, rbac_result, abac_result
434
+ )
435
+
436
+ # Calculate timing
437
+ evaluation_time_ms = 0.0
438
+ if include_timing and start_time:
439
+ evaluation_time_ms = (datetime.now(UTC) - start_time).total_seconds() * 1000
440
+
441
+ # Build result
442
+ check_result = PermissionCheckResult(
443
+ allowed=allowed,
444
+ reason="Permission granted" if allowed else "Permission denied",
445
+ applied_rules=[], # Would be populated with actual rule IDs
446
+ user_id=user_id,
447
+ resource_id=resource_id,
448
+ permission=permission,
449
+ evaluation_time_ms=evaluation_time_ms,
450
+ cached=False,
451
+ cache_hit=False,
452
+ )
453
+
454
+ # Cache result if caching enabled
455
+ if cache_level != CacheLevel.NONE:
456
+ cache_data = check_result.to_dict()
457
+ if explanation:
458
+ cache_data["explanation"] = explanation.to_dict()
459
+ self._set_cache(cache_key, cache_data, cache_ttl)
460
+
461
+ result = {
462
+ "result": {
463
+ "check": check_result.to_dict(),
464
+ "operation": "check_permission",
465
+ "timestamp": datetime.now(UTC).isoformat(),
466
+ }
467
+ }
468
+
469
+ if explain and explanation:
470
+ result["result"]["explanation"] = explanation.to_dict()
471
+
472
+ return result
473
+
474
+ def _batch_check(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
475
+ """Check multiple permissions for a user efficiently."""
476
+ user_id = inputs["user_id"]
477
+ resource_ids = inputs.get("resource_ids", [inputs.get("resource_id")])
478
+ permissions = inputs.get("permissions", [inputs.get("permission")])
479
+ tenant_id = inputs.get("tenant_id", "default")
480
+ context = inputs.get("context", {})
481
+
482
+ if not resource_ids or not permissions:
483
+ raise NodeValidationError(
484
+ "resource_ids and permissions must be provided for batch check"
485
+ )
486
+
487
+ # Get user context once for efficiency
488
+ user_context = self._get_user_context(user_id, tenant_id)
489
+ if not user_context:
490
+ raise NodeValidationError(f"User not found: {user_id}")
491
+
492
+ batch_results = []
493
+ stats = {"allowed": 0, "denied": 0, "total": 0}
494
+
495
+ # Check each resource-permission combination
496
+ for resource_id in resource_ids:
497
+ for permission in permissions:
498
+ try:
499
+ # Use single check method for consistency
500
+ check_inputs = {
501
+ "operation": "check_permission",
502
+ "user_id": user_id,
503
+ "resource_id": resource_id,
504
+ "permission": permission,
505
+ "context": context,
506
+ "tenant_id": tenant_id,
507
+ "cache_level": inputs.get("cache_level", "user"),
508
+ "cache_ttl": inputs.get("cache_ttl", 300),
509
+ "explain": False, # Skip explanation for batch operations
510
+ "include_timing": False,
511
+ }
512
+
513
+ result = self._check_permission(check_inputs)
514
+ check_data = result["result"]["check"]
515
+
516
+ batch_results.append(
517
+ {
518
+ "resource_id": resource_id,
519
+ "permission": permission,
520
+ "allowed": check_data["allowed"],
521
+ "reason": check_data["reason"],
522
+ }
523
+ )
524
+
525
+ if check_data["allowed"]:
526
+ stats["allowed"] += 1
527
+ else:
528
+ stats["denied"] += 1
529
+ stats["total"] += 1
530
+
531
+ except Exception as e:
532
+ batch_results.append(
533
+ {
534
+ "resource_id": resource_id,
535
+ "permission": permission,
536
+ "allowed": False,
537
+ "reason": f"Error: {str(e)}",
538
+ }
539
+ )
540
+ stats["denied"] += 1
541
+ stats["total"] += 1
542
+
543
+ return {
544
+ "result": {
545
+ "batch_results": batch_results,
546
+ "stats": stats,
547
+ "user_id": user_id,
548
+ "operation": "batch_check",
549
+ "timestamp": datetime.now(UTC).isoformat(),
550
+ }
551
+ }
552
+
553
+ def _bulk_user_check(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
554
+ """Check permission for multiple users against a resource."""
555
+ user_ids = inputs["user_ids"]
556
+ resource_id = inputs["resource_id"]
557
+ permission = inputs["permission"]
558
+ tenant_id = inputs.get("tenant_id", "default")
559
+ context = inputs.get("context", {})
560
+
561
+ if not isinstance(user_ids, list):
562
+ raise NodeValidationError("user_ids must be a list for bulk operations")
563
+
564
+ access_matrix = []
565
+ stats = {"allowed": 0, "denied": 0, "total": 0}
566
+
567
+ for user_id in user_ids:
568
+ try:
569
+ # Use single check method for consistency
570
+ check_inputs = {
571
+ "operation": "check_permission",
572
+ "user_id": user_id,
573
+ "resource_id": resource_id,
574
+ "permission": permission,
575
+ "context": context,
576
+ "tenant_id": tenant_id,
577
+ "cache_level": inputs.get("cache_level", "user"),
578
+ "cache_ttl": inputs.get("cache_ttl", 300),
579
+ "explain": False,
580
+ "include_timing": False,
581
+ }
582
+
583
+ result = self._check_permission(check_inputs)
584
+ check_data = result["result"]["check"]
585
+
586
+ access_matrix.append(
587
+ {
588
+ "user_id": user_id,
589
+ "allowed": check_data["allowed"],
590
+ "reason": check_data["reason"],
591
+ "cache_hit": check_data.get("cache_hit", False),
592
+ }
593
+ )
594
+
595
+ if check_data["allowed"]:
596
+ stats["allowed"] += 1
597
+ else:
598
+ stats["denied"] += 1
599
+ stats["total"] += 1
600
+
601
+ except Exception as e:
602
+ access_matrix.append(
603
+ {
604
+ "user_id": user_id,
605
+ "allowed": False,
606
+ "reason": f"Error: {str(e)}",
607
+ "cache_hit": False,
608
+ }
609
+ )
610
+ stats["denied"] += 1
611
+ stats["total"] += 1
612
+
613
+ return {
614
+ "result": {
615
+ "access_matrix": access_matrix,
616
+ "stats": stats,
617
+ "resource_id": resource_id,
618
+ "permission": permission,
619
+ "operation": "bulk_user_check",
620
+ "timestamp": datetime.now(UTC).isoformat(),
621
+ }
622
+ }
623
+
624
+ def _get_user_context(self, user_id: str, tenant_id: str) -> Optional[UserContext]:
625
+ """Get user context for permission evaluation."""
626
+ # Query user data from database
627
+ query = """
628
+ SELECT user_id, email, roles, attributes, status, tenant_id
629
+ FROM users
630
+ WHERE user_id = $1 AND tenant_id = $2 AND status = 'active'
631
+ """
632
+
633
+ self._db_node.config.update(
634
+ {"query": query, "params": [user_id, tenant_id], "fetch_mode": "one"}
635
+ )
636
+
637
+ result = self._db_node.run()
638
+ user_data = result.get("result", {}).get("data")
639
+
640
+ if not user_data:
641
+ return None
642
+
643
+ return UserContext(
644
+ user_id=user_data["user_id"],
645
+ tenant_id=user_data["tenant_id"],
646
+ email=user_data["email"],
647
+ roles=user_data.get("roles", []),
648
+ attributes=user_data.get("attributes", {}),
649
+ )
650
+
651
+ def _check_rbac_permission(
652
+ self, user_context: UserContext, resource_id: str, permission: str
653
+ ) -> bool:
654
+ """Check RBAC permission using role-based access."""
655
+ # Get user's effective permissions from roles
656
+ user_permissions = self._get_user_effective_permissions(user_context)
657
+
658
+ # Check if user has the required permission
659
+ required_permission = f"{resource_id}:{permission}"
660
+
661
+ # Check for exact match or wildcard permissions
662
+ if required_permission in user_permissions:
663
+ return True
664
+
665
+ # Check for wildcard permissions
666
+ wildcard_permission = f"{resource_id}:*"
667
+ if wildcard_permission in user_permissions:
668
+ return True
669
+
670
+ # Check for global permissions
671
+ global_permission = f"*:{permission}"
672
+ if global_permission in user_permissions:
673
+ return True
674
+
675
+ return False
676
+
677
+ def _check_abac_permission(
678
+ self,
679
+ user_context: UserContext,
680
+ resource_id: str,
681
+ permission: str,
682
+ context: Dict[str, Any],
683
+ ) -> bool:
684
+ """Check ABAC permission using attribute-based access."""
685
+ # Use the enhanced access control manager for ABAC evaluation
686
+ try:
687
+ # Convert permission string to NodePermission if possible
688
+ node_permission = NodePermission.EXECUTE # Default
689
+ if permission.lower() in ["read", "view"]:
690
+ node_permission = NodePermission.VIEW
691
+ elif permission.lower() in ["write", "edit", "update"]:
692
+ node_permission = NodePermission.EDIT
693
+ elif permission.lower() in ["execute", "run"]:
694
+ node_permission = NodePermission.EXECUTE
695
+ elif permission.lower() in ["delete", "remove"]:
696
+ node_permission = NodePermission.DELETE
697
+
698
+ # Check access using the enhanced access control manager
699
+ decision = self._access_manager.check_node_access(
700
+ user=user_context,
701
+ resource_id=resource_id,
702
+ permission=node_permission,
703
+ context=context,
704
+ )
705
+
706
+ return decision.allowed
707
+
708
+ except Exception as e:
709
+ # If ABAC evaluation fails, default to deny
710
+ return False
711
+
712
+ def _get_user_effective_permissions(self, user_context: UserContext) -> Set[str]:
713
+ """Get all effective permissions for a user including inherited permissions."""
714
+ permissions = set()
715
+
716
+ # Get permissions from each role
717
+ for role in user_context.roles:
718
+ role_permissions = self._get_role_permissions(role, user_context.tenant_id)
719
+ permissions.update(role_permissions)
720
+
721
+ return permissions
722
+
723
+ def _get_role_permissions(self, role_id: str, tenant_id: str) -> Set[str]:
724
+ """Get permissions for a specific role including inherited permissions."""
725
+ # Query role and its hierarchy
726
+ query = """
727
+ WITH RECURSIVE role_hierarchy AS (
728
+ SELECT role_id, permissions, parent_roles
729
+ FROM roles
730
+ WHERE role_id = $1 AND tenant_id = $2 AND is_active = true
731
+
732
+ UNION ALL
733
+
734
+ SELECT r.role_id, r.permissions, r.parent_roles
735
+ FROM roles r
736
+ JOIN role_hierarchy rh ON r.role_id = ANY(rh.parent_roles)
737
+ WHERE r.tenant_id = $2 AND r.is_active = true
738
+ )
739
+ SELECT DISTINCT unnest(permissions) as permission
740
+ FROM role_hierarchy
741
+ """
742
+
743
+ self._db_node.config.update(
744
+ {"query": query, "params": [role_id, tenant_id], "fetch_mode": "all"}
745
+ )
746
+
747
+ result = self._db_node.run()
748
+ permission_rows = result.get("result", {}).get("data", [])
749
+
750
+ return {row["permission"] for row in permission_rows}
751
+
752
+ def _build_permission_explanation(
753
+ self,
754
+ user_context: UserContext,
755
+ resource_id: str,
756
+ permission: str,
757
+ context: Dict[str, Any],
758
+ rbac_result: bool,
759
+ abac_result: bool,
760
+ ) -> PermissionExplanation:
761
+ """Build detailed explanation of permission evaluation."""
762
+ # Get role permissions
763
+ role_permissions = []
764
+ inherited_permissions = []
765
+
766
+ for role in user_context.roles:
767
+ perms = self._get_role_permissions(role, user_context.tenant_id)
768
+ role_permissions.extend(list(perms))
769
+
770
+ # Build decision path
771
+ decision_path = []
772
+ decision_path.append(f"User: {user_context.user_id}")
773
+ decision_path.append(f"Roles: {', '.join(user_context.roles)}")
774
+ decision_path.append(f"RBAC Result: {'ALLOW' if rbac_result else 'DENY'}")
775
+ decision_path.append(f"ABAC Result: {'ALLOW' if abac_result else 'DENY'}")
776
+ decision_path.append(
777
+ f"Final Decision: {'ALLOW' if rbac_result and abac_result else 'DENY'}"
778
+ )
779
+
780
+ return PermissionExplanation(
781
+ permission_granted=rbac_result and abac_result,
782
+ rbac_result=rbac_result,
783
+ abac_result=abac_result,
784
+ role_permissions=role_permissions,
785
+ inherited_permissions=inherited_permissions,
786
+ attribute_conditions=[], # Would be populated with actual conditions
787
+ failed_conditions=[], # Would be populated with failed conditions
788
+ decision_path=decision_path,
789
+ )
790
+
791
+ # Caching utilities
792
+ def _generate_cache_key(
793
+ self, user_id: str, resource_id: str, permission: str, context: Dict[str, Any]
794
+ ) -> str:
795
+ """Generate cache key for permission check."""
796
+ # Create a hash of the context for consistent caching
797
+ context_str = json.dumps(context, sort_keys=True) if context else ""
798
+ cache_data = f"{user_id}:{resource_id}:{permission}:{context_str}"
799
+ return hashlib.sha256(cache_data.encode()).hexdigest()
800
+
801
+ def _get_from_cache(
802
+ self, cache_key: str, cache_ttl: int
803
+ ) -> Optional[Dict[str, Any]]:
804
+ """Get cached permission result if still valid."""
805
+ if cache_key not in self._permission_cache:
806
+ return None
807
+
808
+ # Check if cache entry is still valid
809
+ cache_time = self._cache_timestamps.get(cache_key)
810
+ if not cache_time:
811
+ return None
812
+
813
+ if (datetime.now(UTC) - cache_time).total_seconds() > cache_ttl:
814
+ # Cache expired, remove entry
815
+ self._permission_cache.pop(cache_key, None)
816
+ self._cache_timestamps.pop(cache_key, None)
817
+ return None
818
+
819
+ return self._permission_cache[cache_key]
820
+
821
+ def _set_cache(self, cache_key: str, result: Dict[str, Any], cache_ttl: int):
822
+ """Set permission result in cache."""
823
+ self._permission_cache[cache_key] = result
824
+ self._cache_timestamps[cache_key] = datetime.now(UTC)
825
+
826
+ def _clear_cache(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
827
+ """Clear permission cache."""
828
+ cache_size_before = len(self._permission_cache)
829
+ self._permission_cache.clear()
830
+ self._cache_timestamps.clear()
831
+
832
+ return {
833
+ "result": {
834
+ "cache_cleared": True,
835
+ "entries_removed": cache_size_before,
836
+ "operation": "clear_cache",
837
+ "timestamp": datetime.now(UTC).isoformat(),
838
+ }
839
+ }
840
+
841
+ # Additional operations would follow similar patterns
842
+ def _check_node_access(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
843
+ """Check access to a specific node type."""
844
+ raise NotImplementedError("Check node access operation will be implemented")
845
+
846
+ def _check_workflow_access(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
847
+ """Check access to workflow operations."""
848
+ raise NotImplementedError("Check workflow access operation will be implemented")
849
+
850
+ def _get_user_permissions(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
851
+ """Get all permissions for a user."""
852
+ raise NotImplementedError("Get user permissions operation will be implemented")
853
+
854
+ def _explain_permission(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
855
+ """Provide detailed explanation of permission logic."""
856
+ raise NotImplementedError("Explain permission operation will be implemented")
857
+
858
+ def _validate_conditions(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
859
+ """Validate ABAC conditions and rules."""
860
+ raise NotImplementedError("Validate conditions operation will be implemented")
861
+
862
+ def _check_hierarchical(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
863
+ """Check permissions with hierarchical resource access."""
864
+ raise NotImplementedError("Check hierarchical operation will be implemented")