proxilion 0.0.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.
- proxilion/__init__.py +136 -0
- proxilion/audit/__init__.py +133 -0
- proxilion/audit/base_exporters.py +527 -0
- proxilion/audit/compliance/__init__.py +130 -0
- proxilion/audit/compliance/base.py +457 -0
- proxilion/audit/compliance/eu_ai_act.py +603 -0
- proxilion/audit/compliance/iso27001.py +544 -0
- proxilion/audit/compliance/soc2.py +491 -0
- proxilion/audit/events.py +493 -0
- proxilion/audit/explainability.py +1173 -0
- proxilion/audit/exporters/__init__.py +58 -0
- proxilion/audit/exporters/aws_s3.py +636 -0
- proxilion/audit/exporters/azure_storage.py +608 -0
- proxilion/audit/exporters/cloud_base.py +468 -0
- proxilion/audit/exporters/gcp_storage.py +570 -0
- proxilion/audit/exporters/multi_exporter.py +498 -0
- proxilion/audit/hash_chain.py +652 -0
- proxilion/audit/logger.py +543 -0
- proxilion/caching/__init__.py +49 -0
- proxilion/caching/tool_cache.py +633 -0
- proxilion/context/__init__.py +73 -0
- proxilion/context/context_window.py +556 -0
- proxilion/context/message_history.py +505 -0
- proxilion/context/session.py +735 -0
- proxilion/contrib/__init__.py +51 -0
- proxilion/contrib/anthropic.py +609 -0
- proxilion/contrib/google.py +1012 -0
- proxilion/contrib/langchain.py +641 -0
- proxilion/contrib/mcp.py +893 -0
- proxilion/contrib/openai.py +646 -0
- proxilion/core.py +3058 -0
- proxilion/decorators.py +966 -0
- proxilion/engines/__init__.py +287 -0
- proxilion/engines/base.py +266 -0
- proxilion/engines/casbin_engine.py +412 -0
- proxilion/engines/opa_engine.py +493 -0
- proxilion/engines/simple.py +437 -0
- proxilion/exceptions.py +887 -0
- proxilion/guards/__init__.py +54 -0
- proxilion/guards/input_guard.py +522 -0
- proxilion/guards/output_guard.py +634 -0
- proxilion/observability/__init__.py +198 -0
- proxilion/observability/cost_tracker.py +866 -0
- proxilion/observability/hooks.py +683 -0
- proxilion/observability/metrics.py +798 -0
- proxilion/observability/session_cost_tracker.py +1063 -0
- proxilion/policies/__init__.py +67 -0
- proxilion/policies/base.py +304 -0
- proxilion/policies/builtin.py +486 -0
- proxilion/policies/registry.py +376 -0
- proxilion/providers/__init__.py +201 -0
- proxilion/providers/adapter.py +468 -0
- proxilion/providers/anthropic_adapter.py +330 -0
- proxilion/providers/gemini_adapter.py +391 -0
- proxilion/providers/openai_adapter.py +294 -0
- proxilion/py.typed +0 -0
- proxilion/resilience/__init__.py +81 -0
- proxilion/resilience/degradation.py +615 -0
- proxilion/resilience/fallback.py +555 -0
- proxilion/resilience/retry.py +554 -0
- proxilion/scheduling/__init__.py +57 -0
- proxilion/scheduling/priority_queue.py +419 -0
- proxilion/scheduling/scheduler.py +459 -0
- proxilion/security/__init__.py +244 -0
- proxilion/security/agent_trust.py +968 -0
- proxilion/security/behavioral_drift.py +794 -0
- proxilion/security/cascade_protection.py +869 -0
- proxilion/security/circuit_breaker.py +428 -0
- proxilion/security/cost_limiter.py +690 -0
- proxilion/security/idor_protection.py +460 -0
- proxilion/security/intent_capsule.py +849 -0
- proxilion/security/intent_validator.py +495 -0
- proxilion/security/memory_integrity.py +767 -0
- proxilion/security/rate_limiter.py +509 -0
- proxilion/security/scope_enforcer.py +680 -0
- proxilion/security/sequence_validator.py +636 -0
- proxilion/security/trust_boundaries.py +784 -0
- proxilion/streaming/__init__.py +70 -0
- proxilion/streaming/detector.py +761 -0
- proxilion/streaming/transformer.py +674 -0
- proxilion/timeouts/__init__.py +55 -0
- proxilion/timeouts/decorators.py +477 -0
- proxilion/timeouts/manager.py +545 -0
- proxilion/tools/__init__.py +69 -0
- proxilion/tools/decorators.py +493 -0
- proxilion/tools/registry.py +732 -0
- proxilion/types.py +339 -0
- proxilion/validation/__init__.py +93 -0
- proxilion/validation/pydantic_schema.py +351 -0
- proxilion/validation/schema.py +651 -0
- proxilion-0.0.1.dist-info/METADATA +872 -0
- proxilion-0.0.1.dist-info/RECORD +94 -0
- proxilion-0.0.1.dist-info/WHEEL +4 -0
- proxilion-0.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Simple policy engine for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module provides the SimplePolicyEngine, a zero-dependency
|
|
5
|
+
policy engine that uses the Pundit-style Policy classes for
|
|
6
|
+
authorization decisions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import TYPE_CHECKING, Any
|
|
14
|
+
|
|
15
|
+
from proxilion.engines.base import BasePolicyEngine, EngineCapabilities
|
|
16
|
+
from proxilion.policies.base import Policy
|
|
17
|
+
from proxilion.policies.builtin import DenyAllPolicy
|
|
18
|
+
from proxilion.policies.registry import PolicyRegistry
|
|
19
|
+
from proxilion.types import AuthorizationResult
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from proxilion.types import UserContext
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SimplePolicyEngine(BasePolicyEngine):
|
|
28
|
+
"""
|
|
29
|
+
Simple policy engine using Pundit-style Policy classes.
|
|
30
|
+
|
|
31
|
+
This is the default policy engine for Proxilion. It uses the
|
|
32
|
+
PolicyRegistry to look up policy classes and evaluates them
|
|
33
|
+
against user context and resources.
|
|
34
|
+
|
|
35
|
+
Features:
|
|
36
|
+
- Class-based policies (Pundit-style)
|
|
37
|
+
- Dictionary-based rules for simple cases
|
|
38
|
+
- Default deny behavior
|
|
39
|
+
- Integration with PolicyRegistry
|
|
40
|
+
- No external dependencies
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> engine = SimplePolicyEngine()
|
|
44
|
+
>>>
|
|
45
|
+
>>> # Register a policy
|
|
46
|
+
>>> @engine.registry.policy("calculator")
|
|
47
|
+
>>> class CalculatorPolicy(Policy):
|
|
48
|
+
... def can_execute(self, context: dict) -> bool:
|
|
49
|
+
... return "calculator_user" in self.user.roles
|
|
50
|
+
>>>
|
|
51
|
+
>>> # Evaluate
|
|
52
|
+
>>> result = engine.evaluate(user, "execute", "calculator")
|
|
53
|
+
>>> print(result.allowed) # True or False
|
|
54
|
+
|
|
55
|
+
Configuration:
|
|
56
|
+
- default_policy: Policy class to use when no policy is registered.
|
|
57
|
+
Defaults to DenyAllPolicy.
|
|
58
|
+
- allow_missing_policies: If True, missing policies return deny
|
|
59
|
+
instead of raising an error. Defaults to True.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
name = "simple"
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
config: dict[str, Any] | None = None,
|
|
67
|
+
registry: PolicyRegistry | None = None,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Initialize the SimplePolicyEngine.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
config: Engine configuration options.
|
|
74
|
+
registry: Optional PolicyRegistry to use. If not provided,
|
|
75
|
+
a new registry is created.
|
|
76
|
+
"""
|
|
77
|
+
super().__init__(config)
|
|
78
|
+
|
|
79
|
+
# Get default policy from config or use DenyAllPolicy
|
|
80
|
+
default_policy = self.get_config("default_policy", DenyAllPolicy)
|
|
81
|
+
self.allow_missing_policies = self.get_config("allow_missing_policies", True)
|
|
82
|
+
|
|
83
|
+
# Use provided registry or create new one
|
|
84
|
+
self.registry = registry or PolicyRegistry(default_policy=default_policy)
|
|
85
|
+
|
|
86
|
+
# Dictionary-based rules for simple authorization
|
|
87
|
+
self._dict_rules: dict[str, dict[str, list[str]]] = {}
|
|
88
|
+
|
|
89
|
+
self._initialized = True
|
|
90
|
+
logger.debug(f"SimplePolicyEngine initialized with registry: {self.registry}")
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def capabilities(self) -> EngineCapabilities:
|
|
94
|
+
"""Get engine capabilities."""
|
|
95
|
+
return EngineCapabilities(
|
|
96
|
+
supports_async=True,
|
|
97
|
+
supports_caching=False,
|
|
98
|
+
supports_explain=True,
|
|
99
|
+
supports_partial_eval=False,
|
|
100
|
+
supports_hot_reload=True,
|
|
101
|
+
max_batch_size=100,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def evaluate(
|
|
105
|
+
self,
|
|
106
|
+
user: UserContext,
|
|
107
|
+
action: str,
|
|
108
|
+
resource: str,
|
|
109
|
+
context: dict[str, Any] | None = None,
|
|
110
|
+
) -> AuthorizationResult:
|
|
111
|
+
"""
|
|
112
|
+
Evaluate an authorization request.
|
|
113
|
+
|
|
114
|
+
The evaluation process:
|
|
115
|
+
1. Check dictionary-based rules first (if configured)
|
|
116
|
+
2. Look up policy class from registry
|
|
117
|
+
3. Instantiate policy with user and resource
|
|
118
|
+
4. Call the can_<action> method
|
|
119
|
+
5. Return AuthorizationResult
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
user: The user context.
|
|
123
|
+
action: The action being attempted.
|
|
124
|
+
resource: The resource being accessed.
|
|
125
|
+
context: Additional context for the decision.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
AuthorizationResult with the decision.
|
|
129
|
+
"""
|
|
130
|
+
ctx = context or {}
|
|
131
|
+
policies_evaluated: list[str] = []
|
|
132
|
+
|
|
133
|
+
logger.debug(
|
|
134
|
+
f"Evaluating: user={user.user_id}, action={action}, "
|
|
135
|
+
f"resource={resource}"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# Check dictionary rules first
|
|
139
|
+
if resource in self._dict_rules:
|
|
140
|
+
dict_result = self._evaluate_dict_rules(user, action, resource)
|
|
141
|
+
if dict_result is not None:
|
|
142
|
+
policies_evaluated.append(f"dict_rules:{resource}")
|
|
143
|
+
return AuthorizationResult(
|
|
144
|
+
allowed=dict_result,
|
|
145
|
+
reason=f"Dictionary rule for {resource}:{action}",
|
|
146
|
+
policies_evaluated=policies_evaluated,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Get policy class from registry
|
|
150
|
+
try:
|
|
151
|
+
policy_class = self.registry.get_policy(resource)
|
|
152
|
+
policies_evaluated.append(policy_class.__name__)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
if self.allow_missing_policies:
|
|
155
|
+
logger.debug(f"No policy for '{resource}', denying: {e}")
|
|
156
|
+
return AuthorizationResult(
|
|
157
|
+
allowed=False,
|
|
158
|
+
reason=f"No policy registered for resource '{resource}'",
|
|
159
|
+
policies_evaluated=policies_evaluated,
|
|
160
|
+
)
|
|
161
|
+
raise
|
|
162
|
+
|
|
163
|
+
# Get the resource object from context if provided
|
|
164
|
+
resource_obj = ctx.get("resource_object")
|
|
165
|
+
|
|
166
|
+
# Instantiate policy
|
|
167
|
+
policy = policy_class(user, resource_obj)
|
|
168
|
+
|
|
169
|
+
# Evaluate the action
|
|
170
|
+
allowed = policy.authorize(action, ctx)
|
|
171
|
+
|
|
172
|
+
# Mark as authorized in registry for tracking
|
|
173
|
+
self.registry.mark_authorized(resource)
|
|
174
|
+
|
|
175
|
+
# Build result
|
|
176
|
+
if allowed:
|
|
177
|
+
reason = f"Policy {policy_class.__name__} allowed action '{action}'"
|
|
178
|
+
else:
|
|
179
|
+
reason = f"Policy {policy_class.__name__} denied action '{action}'"
|
|
180
|
+
|
|
181
|
+
logger.debug(f"Result: allowed={allowed}, reason={reason}")
|
|
182
|
+
|
|
183
|
+
return AuthorizationResult(
|
|
184
|
+
allowed=allowed,
|
|
185
|
+
reason=reason,
|
|
186
|
+
policies_evaluated=policies_evaluated,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def _evaluate_dict_rules(
|
|
190
|
+
self,
|
|
191
|
+
user: UserContext,
|
|
192
|
+
action: str,
|
|
193
|
+
resource: str,
|
|
194
|
+
) -> bool | None:
|
|
195
|
+
"""
|
|
196
|
+
Evaluate dictionary-based rules.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
user: The user context.
|
|
200
|
+
action: The action being attempted.
|
|
201
|
+
resource: The resource being accessed.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
True if allowed, False if denied, None if no rule matches.
|
|
205
|
+
"""
|
|
206
|
+
rules = self._dict_rules.get(resource, {})
|
|
207
|
+
required_roles = rules.get(action)
|
|
208
|
+
|
|
209
|
+
if required_roles is None:
|
|
210
|
+
return None # No rule for this action
|
|
211
|
+
|
|
212
|
+
user_roles = set(user.roles)
|
|
213
|
+
allowed_roles = set(required_roles)
|
|
214
|
+
|
|
215
|
+
return bool(user_roles & allowed_roles)
|
|
216
|
+
|
|
217
|
+
def add_rule(
|
|
218
|
+
self,
|
|
219
|
+
resource: str,
|
|
220
|
+
action: str,
|
|
221
|
+
allowed_roles: list[str],
|
|
222
|
+
) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Add a dictionary-based authorization rule.
|
|
225
|
+
|
|
226
|
+
This is a simpler alternative to defining a full Policy class.
|
|
227
|
+
Dictionary rules are checked before policy classes.
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
resource: The resource name.
|
|
231
|
+
action: The action name.
|
|
232
|
+
allowed_roles: List of roles that are allowed.
|
|
233
|
+
|
|
234
|
+
Example:
|
|
235
|
+
>>> engine.add_rule("calculator", "execute", ["user", "admin"])
|
|
236
|
+
>>> engine.add_rule("calculator", "configure", ["admin"])
|
|
237
|
+
"""
|
|
238
|
+
if resource not in self._dict_rules:
|
|
239
|
+
self._dict_rules[resource] = {}
|
|
240
|
+
|
|
241
|
+
self._dict_rules[resource][action] = allowed_roles
|
|
242
|
+
logger.debug(
|
|
243
|
+
f"Added rule: {resource}:{action} -> {allowed_roles}"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def add_rules(self, rules: dict[str, dict[str, list[str]]]) -> None:
|
|
247
|
+
"""
|
|
248
|
+
Add multiple dictionary-based rules at once.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
rules: Nested dict of resource -> action -> roles.
|
|
252
|
+
|
|
253
|
+
Example:
|
|
254
|
+
>>> engine.add_rules({
|
|
255
|
+
... "calculator": {
|
|
256
|
+
... "execute": ["user", "admin"],
|
|
257
|
+
... "configure": ["admin"],
|
|
258
|
+
... },
|
|
259
|
+
... "database": {
|
|
260
|
+
... "query": ["analyst", "admin"],
|
|
261
|
+
... "write": ["admin"],
|
|
262
|
+
... },
|
|
263
|
+
... })
|
|
264
|
+
"""
|
|
265
|
+
for resource, actions in rules.items():
|
|
266
|
+
for action, roles in actions.items():
|
|
267
|
+
self.add_rule(resource, action, roles)
|
|
268
|
+
|
|
269
|
+
def remove_rule(self, resource: str, action: str | None = None) -> bool:
|
|
270
|
+
"""
|
|
271
|
+
Remove a dictionary-based rule.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
resource: The resource name.
|
|
275
|
+
action: The action to remove, or None to remove all actions.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
True if a rule was removed, False otherwise.
|
|
279
|
+
"""
|
|
280
|
+
if resource not in self._dict_rules:
|
|
281
|
+
return False
|
|
282
|
+
|
|
283
|
+
if action is None:
|
|
284
|
+
del self._dict_rules[resource]
|
|
285
|
+
return True
|
|
286
|
+
|
|
287
|
+
if action in self._dict_rules[resource]:
|
|
288
|
+
del self._dict_rules[resource][action]
|
|
289
|
+
if not self._dict_rules[resource]:
|
|
290
|
+
del self._dict_rules[resource]
|
|
291
|
+
return True
|
|
292
|
+
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
def clear_rules(self) -> None:
|
|
296
|
+
"""Clear all dictionary-based rules."""
|
|
297
|
+
self._dict_rules.clear()
|
|
298
|
+
logger.debug("Cleared all dictionary rules")
|
|
299
|
+
|
|
300
|
+
def load_policies(self, source: str | Path) -> None:
|
|
301
|
+
"""
|
|
302
|
+
Load policies from a Python module or directory.
|
|
303
|
+
|
|
304
|
+
This method imports Policy classes from the specified source
|
|
305
|
+
and registers them with the registry.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
source: Path to a Python module or directory containing
|
|
309
|
+
Policy class definitions.
|
|
310
|
+
|
|
311
|
+
Note:
|
|
312
|
+
Policy classes must use the naming convention *Policy
|
|
313
|
+
(e.g., DatabasePolicy) to be auto-discovered.
|
|
314
|
+
"""
|
|
315
|
+
path = Path(source)
|
|
316
|
+
|
|
317
|
+
if path.is_file() and path.suffix == ".py":
|
|
318
|
+
self._load_from_file(path)
|
|
319
|
+
elif path.is_dir():
|
|
320
|
+
self._load_from_directory(path)
|
|
321
|
+
else:
|
|
322
|
+
# Assume it's a module path
|
|
323
|
+
self._load_from_module(str(source))
|
|
324
|
+
|
|
325
|
+
def _load_from_file(self, path: Path) -> None:
|
|
326
|
+
"""Load policies from a Python file."""
|
|
327
|
+
import importlib.util
|
|
328
|
+
|
|
329
|
+
spec = importlib.util.spec_from_file_location("policies", path)
|
|
330
|
+
if spec is None or spec.loader is None:
|
|
331
|
+
logger.warning(f"Could not load policies from {path}")
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
module = importlib.util.module_from_spec(spec)
|
|
335
|
+
spec.loader.exec_module(module)
|
|
336
|
+
|
|
337
|
+
self._register_policies_from_module(module)
|
|
338
|
+
|
|
339
|
+
def _load_from_directory(self, path: Path) -> None:
|
|
340
|
+
"""Load policies from all Python files in a directory."""
|
|
341
|
+
for py_file in path.glob("*.py"):
|
|
342
|
+
if py_file.name.startswith("_"):
|
|
343
|
+
continue
|
|
344
|
+
self._load_from_file(py_file)
|
|
345
|
+
|
|
346
|
+
def _load_from_module(self, module_path: str) -> None:
|
|
347
|
+
"""Load policies from a module path."""
|
|
348
|
+
import importlib
|
|
349
|
+
|
|
350
|
+
try:
|
|
351
|
+
module = importlib.import_module(module_path)
|
|
352
|
+
self._register_policies_from_module(module)
|
|
353
|
+
except ImportError as e:
|
|
354
|
+
logger.warning(f"Could not import module {module_path}: {e}")
|
|
355
|
+
|
|
356
|
+
def _register_policies_from_module(self, module: Any) -> None:
|
|
357
|
+
"""Register all Policy subclasses from a module."""
|
|
358
|
+
for name in dir(module):
|
|
359
|
+
obj = getattr(module, name)
|
|
360
|
+
if (
|
|
361
|
+
isinstance(obj, type)
|
|
362
|
+
and issubclass(obj, Policy)
|
|
363
|
+
and obj is not Policy
|
|
364
|
+
and name.endswith("Policy")
|
|
365
|
+
):
|
|
366
|
+
self.registry.register_by_convention(obj)
|
|
367
|
+
logger.debug(f"Registered policy: {name}")
|
|
368
|
+
|
|
369
|
+
def policy(self, resource_name: str) -> Any:
|
|
370
|
+
"""
|
|
371
|
+
Decorator for registering a policy class.
|
|
372
|
+
|
|
373
|
+
Convenience method that delegates to the registry.
|
|
374
|
+
|
|
375
|
+
Args:
|
|
376
|
+
resource_name: The resource this policy handles.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
A decorator function.
|
|
380
|
+
|
|
381
|
+
Example:
|
|
382
|
+
>>> @engine.policy("database")
|
|
383
|
+
>>> class DatabasePolicy(Policy):
|
|
384
|
+
... def can_query(self, context: dict) -> bool:
|
|
385
|
+
... return True
|
|
386
|
+
"""
|
|
387
|
+
return self.registry.policy(resource_name)
|
|
388
|
+
|
|
389
|
+
def explain(
|
|
390
|
+
self,
|
|
391
|
+
user: UserContext,
|
|
392
|
+
action: str,
|
|
393
|
+
resource: str,
|
|
394
|
+
context: dict[str, Any] | None = None,
|
|
395
|
+
) -> dict[str, Any]:
|
|
396
|
+
"""
|
|
397
|
+
Explain an authorization decision.
|
|
398
|
+
|
|
399
|
+
Provides detailed information about how a decision was made,
|
|
400
|
+
useful for debugging and auditing.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
user: The user context.
|
|
404
|
+
action: The action being attempted.
|
|
405
|
+
resource: The resource being accessed.
|
|
406
|
+
context: Additional context.
|
|
407
|
+
|
|
408
|
+
Returns:
|
|
409
|
+
Dictionary containing explanation details.
|
|
410
|
+
"""
|
|
411
|
+
result = self.evaluate(user, action, resource, context)
|
|
412
|
+
|
|
413
|
+
explanation = {
|
|
414
|
+
"decision": "ALLOW" if result.allowed else "DENY",
|
|
415
|
+
"reason": result.reason,
|
|
416
|
+
"policies_evaluated": result.policies_evaluated,
|
|
417
|
+
"user": {
|
|
418
|
+
"user_id": user.user_id,
|
|
419
|
+
"roles": user.roles,
|
|
420
|
+
},
|
|
421
|
+
"request": {
|
|
422
|
+
"action": action,
|
|
423
|
+
"resource": resource,
|
|
424
|
+
},
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
# Check for dict rules
|
|
428
|
+
if resource in self._dict_rules:
|
|
429
|
+
explanation["dict_rules"] = self._dict_rules[resource]
|
|
430
|
+
|
|
431
|
+
# Check for policy class
|
|
432
|
+
if self.registry.has_policy(resource):
|
|
433
|
+
policy_class = self.registry.get_policy(resource)
|
|
434
|
+
explanation["policy_class"] = policy_class.__name__
|
|
435
|
+
explanation["available_actions"] = policy_class.get_available_actions()
|
|
436
|
+
|
|
437
|
+
return explanation
|