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.
- kailash/__init__.py +33 -1
- kailash/access_control/__init__.py +129 -0
- kailash/access_control/managers.py +461 -0
- kailash/access_control/rule_evaluators.py +467 -0
- kailash/access_control_abac.py +825 -0
- kailash/config/__init__.py +27 -0
- kailash/config/database_config.py +359 -0
- kailash/database/__init__.py +28 -0
- kailash/database/execution_pipeline.py +499 -0
- kailash/middleware/__init__.py +306 -0
- kailash/middleware/auth/__init__.py +33 -0
- kailash/middleware/auth/access_control.py +436 -0
- kailash/middleware/auth/auth_manager.py +422 -0
- kailash/middleware/auth/jwt_auth.py +477 -0
- kailash/middleware/auth/kailash_jwt_auth.py +616 -0
- kailash/middleware/communication/__init__.py +37 -0
- kailash/middleware/communication/ai_chat.py +989 -0
- kailash/middleware/communication/api_gateway.py +802 -0
- kailash/middleware/communication/events.py +470 -0
- kailash/middleware/communication/realtime.py +710 -0
- kailash/middleware/core/__init__.py +21 -0
- kailash/middleware/core/agent_ui.py +890 -0
- kailash/middleware/core/schema.py +643 -0
- kailash/middleware/core/workflows.py +396 -0
- kailash/middleware/database/__init__.py +63 -0
- kailash/middleware/database/base.py +113 -0
- kailash/middleware/database/base_models.py +525 -0
- kailash/middleware/database/enums.py +106 -0
- kailash/middleware/database/migrations.py +12 -0
- kailash/{api/database.py → middleware/database/models.py} +183 -291
- kailash/middleware/database/repositories.py +685 -0
- kailash/middleware/database/session_manager.py +19 -0
- kailash/middleware/mcp/__init__.py +38 -0
- kailash/middleware/mcp/client_integration.py +585 -0
- kailash/middleware/mcp/enhanced_server.py +576 -0
- kailash/nodes/__init__.py +25 -3
- kailash/nodes/admin/__init__.py +35 -0
- kailash/nodes/admin/audit_log.py +794 -0
- kailash/nodes/admin/permission_check.py +864 -0
- kailash/nodes/admin/role_management.py +823 -0
- kailash/nodes/admin/security_event.py +1519 -0
- kailash/nodes/admin/user_management.py +944 -0
- kailash/nodes/ai/a2a.py +24 -7
- kailash/nodes/ai/ai_providers.py +1 -0
- kailash/nodes/ai/embedding_generator.py +11 -11
- kailash/nodes/ai/intelligent_agent_orchestrator.py +99 -11
- kailash/nodes/ai/llm_agent.py +407 -2
- kailash/nodes/ai/self_organizing.py +85 -10
- kailash/nodes/api/auth.py +287 -6
- kailash/nodes/api/rest.py +151 -0
- kailash/nodes/auth/__init__.py +17 -0
- kailash/nodes/auth/directory_integration.py +1228 -0
- kailash/nodes/auth/enterprise_auth_provider.py +1328 -0
- kailash/nodes/auth/mfa.py +2338 -0
- kailash/nodes/auth/risk_assessment.py +872 -0
- kailash/nodes/auth/session_management.py +1093 -0
- kailash/nodes/auth/sso.py +1040 -0
- kailash/nodes/base.py +344 -13
- kailash/nodes/base_cycle_aware.py +4 -2
- kailash/nodes/base_with_acl.py +1 -1
- kailash/nodes/code/python.py +293 -12
- kailash/nodes/compliance/__init__.py +9 -0
- kailash/nodes/compliance/data_retention.py +1888 -0
- kailash/nodes/compliance/gdpr.py +2004 -0
- kailash/nodes/data/__init__.py +22 -2
- kailash/nodes/data/async_connection.py +469 -0
- kailash/nodes/data/async_sql.py +757 -0
- kailash/nodes/data/async_vector.py +598 -0
- kailash/nodes/data/readers.py +767 -0
- kailash/nodes/data/retrieval.py +360 -1
- kailash/nodes/data/sharepoint_graph.py +397 -21
- kailash/nodes/data/sql.py +94 -5
- kailash/nodes/data/streaming.py +68 -8
- kailash/nodes/data/vector_db.py +54 -4
- kailash/nodes/enterprise/__init__.py +13 -0
- kailash/nodes/enterprise/batch_processor.py +741 -0
- kailash/nodes/enterprise/data_lineage.py +497 -0
- kailash/nodes/logic/convergence.py +31 -9
- kailash/nodes/logic/operations.py +14 -3
- kailash/nodes/mixins/__init__.py +8 -0
- kailash/nodes/mixins/event_emitter.py +201 -0
- kailash/nodes/mixins/mcp.py +9 -4
- kailash/nodes/mixins/security.py +165 -0
- kailash/nodes/monitoring/__init__.py +7 -0
- kailash/nodes/monitoring/performance_benchmark.py +2497 -0
- kailash/nodes/rag/__init__.py +284 -0
- kailash/nodes/rag/advanced.py +1615 -0
- kailash/nodes/rag/agentic.py +773 -0
- kailash/nodes/rag/conversational.py +999 -0
- kailash/nodes/rag/evaluation.py +875 -0
- kailash/nodes/rag/federated.py +1188 -0
- kailash/nodes/rag/graph.py +721 -0
- kailash/nodes/rag/multimodal.py +671 -0
- kailash/nodes/rag/optimized.py +933 -0
- kailash/nodes/rag/privacy.py +1059 -0
- kailash/nodes/rag/query_processing.py +1335 -0
- kailash/nodes/rag/realtime.py +764 -0
- kailash/nodes/rag/registry.py +547 -0
- kailash/nodes/rag/router.py +837 -0
- kailash/nodes/rag/similarity.py +1854 -0
- kailash/nodes/rag/strategies.py +566 -0
- kailash/nodes/rag/workflows.py +575 -0
- kailash/nodes/security/__init__.py +19 -0
- kailash/nodes/security/abac_evaluator.py +1411 -0
- kailash/nodes/security/audit_log.py +91 -0
- kailash/nodes/security/behavior_analysis.py +1893 -0
- kailash/nodes/security/credential_manager.py +401 -0
- kailash/nodes/security/rotating_credentials.py +760 -0
- kailash/nodes/security/security_event.py +132 -0
- kailash/nodes/security/threat_detection.py +1103 -0
- kailash/nodes/testing/__init__.py +9 -0
- kailash/nodes/testing/credential_testing.py +499 -0
- kailash/nodes/transform/__init__.py +10 -2
- kailash/nodes/transform/chunkers.py +592 -1
- kailash/nodes/transform/processors.py +484 -14
- kailash/nodes/validation.py +321 -0
- kailash/runtime/access_controlled.py +1 -1
- kailash/runtime/async_local.py +41 -7
- kailash/runtime/docker.py +1 -1
- kailash/runtime/local.py +474 -55
- kailash/runtime/parallel.py +1 -1
- kailash/runtime/parallel_cyclic.py +1 -1
- kailash/runtime/testing.py +210 -2
- kailash/utils/migrations/__init__.py +25 -0
- kailash/utils/migrations/generator.py +433 -0
- kailash/utils/migrations/models.py +231 -0
- kailash/utils/migrations/runner.py +489 -0
- kailash/utils/secure_logging.py +342 -0
- kailash/workflow/__init__.py +16 -0
- kailash/workflow/cyclic_runner.py +3 -4
- kailash/workflow/graph.py +70 -2
- kailash/workflow/resilience.py +249 -0
- kailash/workflow/templates.py +726 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/METADATA +253 -20
- kailash-0.4.0.dist-info/RECORD +223 -0
- kailash/api/__init__.py +0 -17
- kailash/api/__main__.py +0 -6
- kailash/api/studio_secure.py +0 -893
- kailash/mcp/__main__.py +0 -13
- kailash/mcp/server_new.py +0 -336
- kailash/mcp/servers/__init__.py +0 -12
- kailash-0.3.1.dist-info/RECORD +0 -136
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.1.dist-info → kailash-0.4.0.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")
|