kailash 0.3.2__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 +283 -10
- kailash/nodes/compliance/__init__.py +9 -0
- kailash/nodes/compliance/data_retention.py +1888 -0
- kailash/nodes/compliance/gdpr.py +2004 -0
- kailash/nodes/data/__init__.py +22 -2
- kailash/nodes/data/async_connection.py +469 -0
- kailash/nodes/data/async_sql.py +757 -0
- kailash/nodes/data/async_vector.py +598 -0
- kailash/nodes/data/readers.py +767 -0
- kailash/nodes/data/retrieval.py +360 -1
- kailash/nodes/data/sharepoint_graph.py +397 -21
- kailash/nodes/data/sql.py +94 -5
- kailash/nodes/data/streaming.py +68 -8
- kailash/nodes/data/vector_db.py +54 -4
- kailash/nodes/enterprise/__init__.py +13 -0
- kailash/nodes/enterprise/batch_processor.py +741 -0
- kailash/nodes/enterprise/data_lineage.py +497 -0
- kailash/nodes/logic/convergence.py +31 -9
- kailash/nodes/logic/operations.py +14 -3
- kailash/nodes/mixins/__init__.py +8 -0
- kailash/nodes/mixins/event_emitter.py +201 -0
- kailash/nodes/mixins/mcp.py +9 -4
- kailash/nodes/mixins/security.py +165 -0
- kailash/nodes/monitoring/__init__.py +7 -0
- kailash/nodes/monitoring/performance_benchmark.py +2497 -0
- kailash/nodes/rag/__init__.py +284 -0
- kailash/nodes/rag/advanced.py +1615 -0
- kailash/nodes/rag/agentic.py +773 -0
- kailash/nodes/rag/conversational.py +999 -0
- kailash/nodes/rag/evaluation.py +875 -0
- kailash/nodes/rag/federated.py +1188 -0
- kailash/nodes/rag/graph.py +721 -0
- kailash/nodes/rag/multimodal.py +671 -0
- kailash/nodes/rag/optimized.py +933 -0
- kailash/nodes/rag/privacy.py +1059 -0
- kailash/nodes/rag/query_processing.py +1335 -0
- kailash/nodes/rag/realtime.py +764 -0
- kailash/nodes/rag/registry.py +547 -0
- kailash/nodes/rag/router.py +837 -0
- kailash/nodes/rag/similarity.py +1854 -0
- kailash/nodes/rag/strategies.py +566 -0
- kailash/nodes/rag/workflows.py +575 -0
- kailash/nodes/security/__init__.py +19 -0
- kailash/nodes/security/abac_evaluator.py +1411 -0
- kailash/nodes/security/audit_log.py +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.2.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.2.dist-info/RECORD +0 -136
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/WHEEL +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.2.dist-info → kailash-0.4.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,467 @@
|
|
1
|
+
"""Rule evaluator interfaces and implementations for access control.
|
2
|
+
|
3
|
+
This module provides a composition-based approach to access control rule evaluation,
|
4
|
+
replacing the problematic inheritance pattern with clear, testable interfaces.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
from datetime import UTC, datetime
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
11
|
+
|
12
|
+
# Import base access control components - use absolute imports to avoid circular import
|
13
|
+
try:
|
14
|
+
from kailash.access_control import (
|
15
|
+
AccessDecision,
|
16
|
+
NodePermission,
|
17
|
+
PermissionEffect,
|
18
|
+
PermissionRule,
|
19
|
+
UserContext,
|
20
|
+
WorkflowPermission,
|
21
|
+
)
|
22
|
+
except ImportError:
|
23
|
+
# Local definitions to handle circular import during initial setup
|
24
|
+
from dataclasses import dataclass
|
25
|
+
from datetime import datetime
|
26
|
+
from enum import Enum
|
27
|
+
|
28
|
+
class NodePermission(Enum):
|
29
|
+
EXECUTE = "execute"
|
30
|
+
READ_OUTPUT = "read_output"
|
31
|
+
WRITE_INPUT = "write_input"
|
32
|
+
|
33
|
+
class WorkflowPermission(Enum):
|
34
|
+
VIEW = "view"
|
35
|
+
EXECUTE = "execute"
|
36
|
+
MODIFY = "modify"
|
37
|
+
|
38
|
+
class PermissionEffect(Enum):
|
39
|
+
ALLOW = "allow"
|
40
|
+
DENY = "deny"
|
41
|
+
CONDITIONAL = "conditional"
|
42
|
+
|
43
|
+
@dataclass
|
44
|
+
class UserContext:
|
45
|
+
user_id: str
|
46
|
+
tenant_id: str
|
47
|
+
email: str
|
48
|
+
roles: List[str]
|
49
|
+
attributes: Dict[str, Any]
|
50
|
+
|
51
|
+
@dataclass
|
52
|
+
class AccessDecision:
|
53
|
+
allowed: bool
|
54
|
+
reason: str
|
55
|
+
applied_rules: List[str]
|
56
|
+
conditions_met: Optional[Dict[str, bool]] = None
|
57
|
+
masked_fields: Optional[List[str]] = None
|
58
|
+
|
59
|
+
@dataclass
|
60
|
+
class PermissionRule:
|
61
|
+
id: str
|
62
|
+
resource_type: str
|
63
|
+
resource_id: str
|
64
|
+
permission: Union[NodePermission, WorkflowPermission]
|
65
|
+
effect: PermissionEffect
|
66
|
+
user_id: Optional[str] = None
|
67
|
+
role: Optional[str] = None
|
68
|
+
tenant_id: Optional[str] = None
|
69
|
+
conditions: Optional[Dict[str, Any]] = None
|
70
|
+
priority: int = 0
|
71
|
+
expires_at: Optional[datetime] = None
|
72
|
+
|
73
|
+
|
74
|
+
logger = logging.getLogger(__name__)
|
75
|
+
|
76
|
+
|
77
|
+
class RuleEvaluator(ABC):
|
78
|
+
"""Abstract base class for rule evaluation strategies."""
|
79
|
+
|
80
|
+
@abstractmethod
|
81
|
+
def evaluate_rules(
|
82
|
+
self,
|
83
|
+
rules: List[PermissionRule],
|
84
|
+
user: UserContext,
|
85
|
+
resource_type: str,
|
86
|
+
resource_id: str,
|
87
|
+
permission: Union[NodePermission, WorkflowPermission],
|
88
|
+
runtime_context: Dict[str, Any],
|
89
|
+
) -> AccessDecision:
|
90
|
+
"""Evaluate a set of rules for a user's access request.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
rules: List of applicable permission rules
|
94
|
+
user: User making the request
|
95
|
+
resource_type: Type of resource (node/workflow)
|
96
|
+
resource_id: Specific resource identifier
|
97
|
+
permission: Permission being requested
|
98
|
+
runtime_context: Additional runtime context
|
99
|
+
|
100
|
+
Returns:
|
101
|
+
AccessDecision with allow/deny and reasoning
|
102
|
+
"""
|
103
|
+
pass
|
104
|
+
|
105
|
+
@abstractmethod
|
106
|
+
def supports_conditions(self) -> bool:
|
107
|
+
"""Return whether this evaluator supports conditional rules."""
|
108
|
+
pass
|
109
|
+
|
110
|
+
|
111
|
+
class RBACRuleEvaluator(RuleEvaluator):
|
112
|
+
"""Role-Based Access Control rule evaluator.
|
113
|
+
|
114
|
+
This evaluator handles traditional RBAC rules based on:
|
115
|
+
- User roles
|
116
|
+
- Direct user assignments
|
117
|
+
- Tenant-based permissions
|
118
|
+
"""
|
119
|
+
|
120
|
+
def __init__(self):
|
121
|
+
"""Initialize RBAC evaluator."""
|
122
|
+
self.logger = logging.getLogger(f"{__name__}.RBACRuleEvaluator")
|
123
|
+
|
124
|
+
def evaluate_rules(
|
125
|
+
self,
|
126
|
+
rules: List[PermissionRule],
|
127
|
+
user: UserContext,
|
128
|
+
resource_type: str,
|
129
|
+
resource_id: str,
|
130
|
+
permission: Union[NodePermission, WorkflowPermission],
|
131
|
+
runtime_context: Dict[str, Any],
|
132
|
+
) -> AccessDecision:
|
133
|
+
"""Evaluate RBAC rules."""
|
134
|
+
# Filter applicable rules (only those that apply to this user)
|
135
|
+
applicable_rules = [
|
136
|
+
rule for rule in rules if self._rule_applies_to_user(rule, user)
|
137
|
+
]
|
138
|
+
|
139
|
+
if not applicable_rules:
|
140
|
+
return AccessDecision(
|
141
|
+
allowed=False,
|
142
|
+
reason="No applicable RBAC rules found",
|
143
|
+
applied_rules=[],
|
144
|
+
)
|
145
|
+
|
146
|
+
# Sort by priority (higher first)
|
147
|
+
applicable_rules.sort(key=lambda r: r.priority, reverse=True)
|
148
|
+
|
149
|
+
# Evaluate rules in priority order
|
150
|
+
for rule in applicable_rules:
|
151
|
+
# RBAC doesn't support complex conditions
|
152
|
+
if rule.conditions:
|
153
|
+
self.logger.warning(
|
154
|
+
f"RBAC evaluator ignoring conditions in rule {rule.id}"
|
155
|
+
)
|
156
|
+
|
157
|
+
# Apply effect
|
158
|
+
if rule.effect == PermissionEffect.ALLOW:
|
159
|
+
return AccessDecision(
|
160
|
+
allowed=True,
|
161
|
+
reason=f"RBAC rule {rule.id} granted access",
|
162
|
+
applied_rules=[rule.id],
|
163
|
+
)
|
164
|
+
elif rule.effect == PermissionEffect.DENY:
|
165
|
+
return AccessDecision(
|
166
|
+
allowed=False,
|
167
|
+
reason=f"RBAC rule {rule.id} denied access",
|
168
|
+
applied_rules=[rule.id],
|
169
|
+
)
|
170
|
+
|
171
|
+
# Default deny
|
172
|
+
return AccessDecision(
|
173
|
+
allowed=False,
|
174
|
+
reason="No matching RBAC allow rules",
|
175
|
+
applied_rules=[rule.id for rule in applicable_rules],
|
176
|
+
)
|
177
|
+
|
178
|
+
def supports_conditions(self) -> bool:
|
179
|
+
"""RBAC evaluator does not support complex conditions."""
|
180
|
+
return False
|
181
|
+
|
182
|
+
def _rule_applies_to_user(self, rule: PermissionRule, user: UserContext) -> bool:
|
183
|
+
"""Check if a rule applies to a user based on RBAC criteria."""
|
184
|
+
# Direct user assignment
|
185
|
+
if rule.user_id and rule.user_id == user.user_id:
|
186
|
+
return True
|
187
|
+
|
188
|
+
# Role-based assignment
|
189
|
+
if rule.role and rule.role in user.roles:
|
190
|
+
return True
|
191
|
+
|
192
|
+
# Tenant-based assignment
|
193
|
+
if rule.tenant_id and rule.tenant_id == user.tenant_id:
|
194
|
+
return True
|
195
|
+
|
196
|
+
# No restrictions means applies to all
|
197
|
+
if not rule.user_id and not rule.role and not rule.tenant_id:
|
198
|
+
return True
|
199
|
+
|
200
|
+
return False
|
201
|
+
|
202
|
+
|
203
|
+
class ABACRuleEvaluator(RuleEvaluator):
|
204
|
+
"""Attribute-Based Access Control rule evaluator.
|
205
|
+
|
206
|
+
This evaluator handles advanced ABAC rules with:
|
207
|
+
- Complex attribute expressions
|
208
|
+
- Dynamic condition evaluation
|
209
|
+
- Hierarchical attribute matching
|
210
|
+
"""
|
211
|
+
|
212
|
+
def __init__(self):
|
213
|
+
"""Initialize ABAC evaluator."""
|
214
|
+
self.logger = logging.getLogger(f"{__name__}.ABACRuleEvaluator")
|
215
|
+
# Import here to avoid circular dependencies
|
216
|
+
from kailash.access_control_abac import EnhancedConditionEvaluator
|
217
|
+
|
218
|
+
self.condition_evaluator = EnhancedConditionEvaluator()
|
219
|
+
|
220
|
+
def evaluate_rules(
|
221
|
+
self,
|
222
|
+
rules: List[PermissionRule],
|
223
|
+
user: UserContext,
|
224
|
+
resource_type: str,
|
225
|
+
resource_id: str,
|
226
|
+
permission: Union[NodePermission, WorkflowPermission],
|
227
|
+
runtime_context: Dict[str, Any],
|
228
|
+
) -> AccessDecision:
|
229
|
+
"""Evaluate ABAC rules with complex attribute conditions."""
|
230
|
+
# Build evaluation context
|
231
|
+
context = self._build_context(user, runtime_context)
|
232
|
+
|
233
|
+
# Filter applicable rules (basic RBAC filtering + condition evaluation)
|
234
|
+
applicable_rules = []
|
235
|
+
for rule in rules:
|
236
|
+
# First check basic RBAC criteria
|
237
|
+
if not self._rule_applies_to_user(rule, user):
|
238
|
+
continue
|
239
|
+
|
240
|
+
applicable_rules.append(rule)
|
241
|
+
|
242
|
+
if not applicable_rules:
|
243
|
+
return AccessDecision(
|
244
|
+
allowed=False,
|
245
|
+
reason="No applicable ABAC rules found",
|
246
|
+
applied_rules=[],
|
247
|
+
)
|
248
|
+
|
249
|
+
# Sort by priority (higher first)
|
250
|
+
applicable_rules.sort(key=lambda r: r.priority, reverse=True)
|
251
|
+
|
252
|
+
# Evaluate rules with conditions
|
253
|
+
for rule in applicable_rules:
|
254
|
+
if rule.conditions:
|
255
|
+
try:
|
256
|
+
# Evaluate ABAC conditions
|
257
|
+
cond_type = rule.conditions.get("type", "")
|
258
|
+
cond_value = rule.conditions.get("value", {})
|
259
|
+
|
260
|
+
result = self.condition_evaluator.evaluate(
|
261
|
+
cond_type, cond_value, context
|
262
|
+
)
|
263
|
+
|
264
|
+
if result:
|
265
|
+
# Conditions met - apply rule effect
|
266
|
+
if rule.effect == PermissionEffect.ALLOW:
|
267
|
+
return AccessDecision(
|
268
|
+
allowed=True,
|
269
|
+
reason=f"ABAC rule {rule.id} granted access",
|
270
|
+
applied_rules=[rule.id],
|
271
|
+
)
|
272
|
+
elif rule.effect == PermissionEffect.DENY:
|
273
|
+
return AccessDecision(
|
274
|
+
allowed=False,
|
275
|
+
reason=f"ABAC rule {rule.id} denied access",
|
276
|
+
applied_rules=[rule.id],
|
277
|
+
)
|
278
|
+
except Exception as e:
|
279
|
+
self.logger.warning(f"Error evaluating ABAC rule {rule.id}: {e}")
|
280
|
+
continue
|
281
|
+
else:
|
282
|
+
# No conditions - basic RBAC-style rule
|
283
|
+
if rule.effect == PermissionEffect.ALLOW:
|
284
|
+
return AccessDecision(
|
285
|
+
allowed=True,
|
286
|
+
reason=f"ABAC rule {rule.id} granted access (no conditions)",
|
287
|
+
applied_rules=[rule.id],
|
288
|
+
)
|
289
|
+
elif rule.effect == PermissionEffect.DENY:
|
290
|
+
return AccessDecision(
|
291
|
+
allowed=False,
|
292
|
+
reason=f"ABAC rule {rule.id} denied access (no conditions)",
|
293
|
+
applied_rules=[rule.id],
|
294
|
+
)
|
295
|
+
|
296
|
+
# Default deny
|
297
|
+
return AccessDecision(
|
298
|
+
allowed=False,
|
299
|
+
reason="No matching ABAC allow rules",
|
300
|
+
applied_rules=[rule.id for rule in applicable_rules],
|
301
|
+
)
|
302
|
+
|
303
|
+
def supports_conditions(self) -> bool:
|
304
|
+
"""ABAC evaluator fully supports complex conditions."""
|
305
|
+
return True
|
306
|
+
|
307
|
+
def _rule_applies_to_user(self, rule: PermissionRule, user: UserContext) -> bool:
|
308
|
+
"""Check if a rule applies to a user (same as RBAC for basic filtering)."""
|
309
|
+
# Direct user assignment
|
310
|
+
if rule.user_id and rule.user_id == user.user_id:
|
311
|
+
return True
|
312
|
+
|
313
|
+
# Role-based assignment
|
314
|
+
if rule.role and rule.role in user.roles:
|
315
|
+
return True
|
316
|
+
|
317
|
+
# Tenant-based assignment
|
318
|
+
if rule.tenant_id and rule.tenant_id == user.tenant_id:
|
319
|
+
return True
|
320
|
+
|
321
|
+
# No restrictions means applies to all
|
322
|
+
if not rule.user_id and not rule.role and not rule.tenant_id:
|
323
|
+
return True
|
324
|
+
|
325
|
+
return False
|
326
|
+
|
327
|
+
def _build_context(
|
328
|
+
self, user: UserContext, runtime_context: Dict[str, Any]
|
329
|
+
) -> Dict[str, Any]:
|
330
|
+
"""Build evaluation context for ABAC."""
|
331
|
+
now = datetime.now(UTC)
|
332
|
+
context = {
|
333
|
+
"user": {
|
334
|
+
"user_id": user.user_id,
|
335
|
+
"tenant_id": user.tenant_id,
|
336
|
+
"email": user.email,
|
337
|
+
"roles": user.roles,
|
338
|
+
"attributes": user.attributes,
|
339
|
+
},
|
340
|
+
"runtime": runtime_context,
|
341
|
+
"timestamp": now,
|
342
|
+
}
|
343
|
+
return context
|
344
|
+
|
345
|
+
|
346
|
+
class HybridRuleEvaluator(RuleEvaluator):
|
347
|
+
"""Hybrid evaluator that combines RBAC and ABAC evaluation.
|
348
|
+
|
349
|
+
This evaluator:
|
350
|
+
1. Uses RBAC for basic rules without conditions
|
351
|
+
2. Uses ABAC for complex conditional rules
|
352
|
+
3. Provides seamless transition between evaluation strategies
|
353
|
+
"""
|
354
|
+
|
355
|
+
def __init__(self):
|
356
|
+
"""Initialize hybrid evaluator with both RBAC and ABAC."""
|
357
|
+
self.rbac_evaluator = RBACRuleEvaluator()
|
358
|
+
self.abac_evaluator = ABACRuleEvaluator()
|
359
|
+
self.logger = logging.getLogger(f"{__name__}.HybridRuleEvaluator")
|
360
|
+
|
361
|
+
def evaluate_rules(
|
362
|
+
self,
|
363
|
+
rules: List[PermissionRule],
|
364
|
+
user: UserContext,
|
365
|
+
resource_type: str,
|
366
|
+
resource_id: str,
|
367
|
+
permission: Union[NodePermission, WorkflowPermission],
|
368
|
+
runtime_context: Dict[str, Any],
|
369
|
+
) -> AccessDecision:
|
370
|
+
"""Evaluate rules using appropriate strategy based on rule complexity."""
|
371
|
+
# Separate rules by complexity
|
372
|
+
simple_rules = [rule for rule in rules if not rule.conditions]
|
373
|
+
complex_rules = [rule for rule in rules if rule.conditions]
|
374
|
+
|
375
|
+
# Track all decisions for final reasoning
|
376
|
+
all_decisions = []
|
377
|
+
|
378
|
+
# First evaluate complex ABAC rules (higher precedence)
|
379
|
+
if complex_rules:
|
380
|
+
abac_decision = self.abac_evaluator.evaluate_rules(
|
381
|
+
complex_rules,
|
382
|
+
user,
|
383
|
+
resource_type,
|
384
|
+
resource_id,
|
385
|
+
permission,
|
386
|
+
runtime_context,
|
387
|
+
)
|
388
|
+
all_decisions.append(("ABAC", abac_decision))
|
389
|
+
|
390
|
+
# If ABAC explicitly allows or denies, use that decision
|
391
|
+
if abac_decision.allowed:
|
392
|
+
return abac_decision
|
393
|
+
elif any("denied access" in abac_decision.reason for _ in [abac_decision]):
|
394
|
+
return abac_decision
|
395
|
+
|
396
|
+
# Then evaluate simple RBAC rules
|
397
|
+
if simple_rules:
|
398
|
+
rbac_decision = self.rbac_evaluator.evaluate_rules(
|
399
|
+
simple_rules,
|
400
|
+
user,
|
401
|
+
resource_type,
|
402
|
+
resource_id,
|
403
|
+
permission,
|
404
|
+
runtime_context,
|
405
|
+
)
|
406
|
+
all_decisions.append(("RBAC", rbac_decision))
|
407
|
+
|
408
|
+
if rbac_decision.allowed:
|
409
|
+
return rbac_decision
|
410
|
+
|
411
|
+
# Combine reasoning from all evaluations
|
412
|
+
combined_reason = "; ".join(
|
413
|
+
[f"{eval_type}: {decision.reason}" for eval_type, decision in all_decisions]
|
414
|
+
)
|
415
|
+
|
416
|
+
combined_applied_rules = [
|
417
|
+
rule_id
|
418
|
+
for _, decision in all_decisions
|
419
|
+
for rule_id in decision.applied_rules
|
420
|
+
]
|
421
|
+
|
422
|
+
return AccessDecision(
|
423
|
+
allowed=False,
|
424
|
+
reason=f"Hybrid evaluation - {combined_reason or 'No applicable rules'}",
|
425
|
+
applied_rules=combined_applied_rules,
|
426
|
+
)
|
427
|
+
|
428
|
+
def supports_conditions(self) -> bool:
|
429
|
+
"""Hybrid evaluator supports conditions via ABAC component."""
|
430
|
+
return True
|
431
|
+
|
432
|
+
|
433
|
+
# Factory function for easy evaluator creation
|
434
|
+
def create_rule_evaluator(strategy: str = "hybrid") -> RuleEvaluator:
|
435
|
+
"""Create a rule evaluator based on strategy.
|
436
|
+
|
437
|
+
Args:
|
438
|
+
strategy: One of 'rbac', 'abac', or 'hybrid'
|
439
|
+
|
440
|
+
Returns:
|
441
|
+
Appropriate RuleEvaluator instance
|
442
|
+
|
443
|
+
Raises:
|
444
|
+
ValueError: If strategy is not recognized
|
445
|
+
"""
|
446
|
+
strategy = strategy.lower()
|
447
|
+
|
448
|
+
if strategy == "rbac":
|
449
|
+
return RBACRuleEvaluator()
|
450
|
+
elif strategy == "abac":
|
451
|
+
return ABACRuleEvaluator()
|
452
|
+
elif strategy == "hybrid":
|
453
|
+
return HybridRuleEvaluator()
|
454
|
+
else:
|
455
|
+
raise ValueError(
|
456
|
+
f"Unknown strategy: {strategy}. Use 'rbac', 'abac', or 'hybrid'"
|
457
|
+
)
|
458
|
+
|
459
|
+
|
460
|
+
# Export components
|
461
|
+
__all__ = [
|
462
|
+
"RuleEvaluator",
|
463
|
+
"RBACRuleEvaluator",
|
464
|
+
"ABACRuleEvaluator",
|
465
|
+
"HybridRuleEvaluator",
|
466
|
+
"create_rule_evaluator",
|
467
|
+
]
|