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,287 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Policy engines for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module provides the policy engine abstraction layer, enabling
|
|
5
|
+
pluggable authorization backends. Available engines:
|
|
6
|
+
|
|
7
|
+
- SimplePolicyEngine: Built-in engine using Policy classes (no dependencies)
|
|
8
|
+
- CasbinPolicyEngine: Integration with Casbin (requires casbin package)
|
|
9
|
+
- OPAPolicyEngine: Integration with Open Policy Agent (requires OPA server)
|
|
10
|
+
|
|
11
|
+
Quick Start:
|
|
12
|
+
>>> from proxilion.engines import EngineFactory
|
|
13
|
+
>>>
|
|
14
|
+
>>> # Create a simple engine
|
|
15
|
+
>>> engine = EngineFactory.create("simple")
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Or create with configuration
|
|
18
|
+
>>> engine = EngineFactory.create("casbin", {
|
|
19
|
+
... "model_path": "model.conf",
|
|
20
|
+
... "policy_path": "policy.csv",
|
|
21
|
+
... })
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import logging
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from proxilion.engines.base import (
|
|
30
|
+
BasePolicyEngine,
|
|
31
|
+
EngineCapabilities,
|
|
32
|
+
EngineNotAvailableError,
|
|
33
|
+
PolicyEngine,
|
|
34
|
+
PolicyEngineError,
|
|
35
|
+
PolicyEvaluationError,
|
|
36
|
+
PolicyLoadError,
|
|
37
|
+
)
|
|
38
|
+
from proxilion.engines.simple import SimplePolicyEngine
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
# Type alias for engine types
|
|
43
|
+
EngineType = str # "simple", "casbin", "opa"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EngineFactory:
|
|
47
|
+
"""
|
|
48
|
+
Factory for creating policy engine instances.
|
|
49
|
+
|
|
50
|
+
The EngineFactory provides a unified interface for creating
|
|
51
|
+
policy engines based on configuration. It handles dependency
|
|
52
|
+
checking and provides helpful error messages when optional
|
|
53
|
+
engines are not available.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
>>> # Create with default configuration
|
|
57
|
+
>>> engine = EngineFactory.create("simple")
|
|
58
|
+
>>>
|
|
59
|
+
>>> # Create with custom configuration
|
|
60
|
+
>>> engine = EngineFactory.create("casbin", {
|
|
61
|
+
... "model_path": "model.conf",
|
|
62
|
+
... "policy_path": "policy.csv",
|
|
63
|
+
... })
|
|
64
|
+
>>>
|
|
65
|
+
>>> # Create OPA engine
|
|
66
|
+
>>> engine = EngineFactory.create("opa", {
|
|
67
|
+
... "opa_url": "http://localhost:8181",
|
|
68
|
+
... "policy_path": "v1/data/myapp/authz",
|
|
69
|
+
... })
|
|
70
|
+
|
|
71
|
+
Available Engine Types:
|
|
72
|
+
- "simple": Built-in SimplePolicyEngine (always available)
|
|
73
|
+
- "casbin": CasbinPolicyEngine (requires casbin package)
|
|
74
|
+
- "opa": OPAPolicyEngine (requires OPA server)
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
# Registry of available engine types
|
|
78
|
+
_engines: dict[str, type[BasePolicyEngine]] = {
|
|
79
|
+
"simple": SimplePolicyEngine,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@classmethod
|
|
83
|
+
def create(
|
|
84
|
+
cls,
|
|
85
|
+
engine_type: str = "simple",
|
|
86
|
+
config: dict[str, Any] | None = None,
|
|
87
|
+
) -> BasePolicyEngine:
|
|
88
|
+
"""
|
|
89
|
+
Create a policy engine instance.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
engine_type: The type of engine to create.
|
|
93
|
+
config: Engine-specific configuration.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
An initialized policy engine instance.
|
|
97
|
+
|
|
98
|
+
Raises:
|
|
99
|
+
EngineNotAvailableError: If the engine type is not available.
|
|
100
|
+
PolicyEngineError: If engine initialization fails.
|
|
101
|
+
|
|
102
|
+
Example:
|
|
103
|
+
>>> engine = EngineFactory.create("simple")
|
|
104
|
+
>>> engine = EngineFactory.create("casbin", {
|
|
105
|
+
... "model_path": "model.conf",
|
|
106
|
+
... "policy_path": "policy.csv",
|
|
107
|
+
... })
|
|
108
|
+
"""
|
|
109
|
+
engine_type = engine_type.lower()
|
|
110
|
+
|
|
111
|
+
# Handle casbin engine (lazy import due to optional dependency)
|
|
112
|
+
if engine_type == "casbin":
|
|
113
|
+
return cls._create_casbin_engine(config)
|
|
114
|
+
|
|
115
|
+
# Handle OPA engine
|
|
116
|
+
if engine_type == "opa":
|
|
117
|
+
return cls._create_opa_engine(config)
|
|
118
|
+
|
|
119
|
+
# Check built-in engines
|
|
120
|
+
if engine_type not in cls._engines:
|
|
121
|
+
available = cls.get_available_engines()
|
|
122
|
+
raise EngineNotAvailableError(
|
|
123
|
+
f"Unknown engine type: '{engine_type}'. "
|
|
124
|
+
f"Available engines: {', '.join(available)}",
|
|
125
|
+
engine_name=engine_type,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
engine_class = cls._engines[engine_type]
|
|
129
|
+
return engine_class(config)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def _create_casbin_engine(
|
|
133
|
+
cls,
|
|
134
|
+
config: dict[str, Any] | None,
|
|
135
|
+
) -> BasePolicyEngine:
|
|
136
|
+
"""Create a Casbin engine instance."""
|
|
137
|
+
try:
|
|
138
|
+
from proxilion.engines.casbin_engine import CasbinPolicyEngine
|
|
139
|
+
return CasbinPolicyEngine(config)
|
|
140
|
+
except ImportError:
|
|
141
|
+
raise EngineNotAvailableError(
|
|
142
|
+
"Casbin engine requires the 'casbin' package. "
|
|
143
|
+
"Install with: pip install proxilion[casbin]",
|
|
144
|
+
engine_name="casbin",
|
|
145
|
+
) from None
|
|
146
|
+
|
|
147
|
+
@classmethod
|
|
148
|
+
def _create_opa_engine(
|
|
149
|
+
cls,
|
|
150
|
+
config: dict[str, Any] | None,
|
|
151
|
+
) -> BasePolicyEngine:
|
|
152
|
+
"""Create an OPA engine instance."""
|
|
153
|
+
from proxilion.engines.opa_engine import OPAPolicyEngine
|
|
154
|
+
return OPAPolicyEngine(config)
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def register(
|
|
158
|
+
cls,
|
|
159
|
+
engine_type: str,
|
|
160
|
+
engine_class: type[BasePolicyEngine],
|
|
161
|
+
) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Register a custom engine type.
|
|
164
|
+
|
|
165
|
+
This allows extending Proxilion with custom policy engines.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
engine_type: The name for this engine type.
|
|
169
|
+
engine_class: The engine class to register.
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
>>> class MyCustomEngine(BasePolicyEngine):
|
|
173
|
+
... def evaluate(self, user, action, resource, context=None):
|
|
174
|
+
... # Custom logic
|
|
175
|
+
... pass
|
|
176
|
+
>>>
|
|
177
|
+
>>> EngineFactory.register("custom", MyCustomEngine)
|
|
178
|
+
>>> engine = EngineFactory.create("custom")
|
|
179
|
+
"""
|
|
180
|
+
cls._engines[engine_type.lower()] = engine_class
|
|
181
|
+
logger.debug(f"Registered engine type: {engine_type}")
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def unregister(cls, engine_type: str) -> bool:
|
|
185
|
+
"""
|
|
186
|
+
Unregister an engine type.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
engine_type: The engine type to remove.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
True if the engine was unregistered.
|
|
193
|
+
"""
|
|
194
|
+
engine_type = engine_type.lower()
|
|
195
|
+
if engine_type in cls._engines and engine_type != "simple":
|
|
196
|
+
del cls._engines[engine_type]
|
|
197
|
+
return True
|
|
198
|
+
return False
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def get_available_engines(cls) -> list[str]:
|
|
202
|
+
"""
|
|
203
|
+
Get a list of available engine types.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
List of engine type names that can be created.
|
|
207
|
+
"""
|
|
208
|
+
engines = list(cls._engines.keys())
|
|
209
|
+
|
|
210
|
+
# Check if casbin is available
|
|
211
|
+
try:
|
|
212
|
+
import casbin # noqa: F401
|
|
213
|
+
engines.append("casbin")
|
|
214
|
+
except ImportError:
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
# OPA is always "available" (it just needs a server)
|
|
218
|
+
engines.append("opa")
|
|
219
|
+
|
|
220
|
+
return sorted(set(engines))
|
|
221
|
+
|
|
222
|
+
@classmethod
|
|
223
|
+
def is_available(cls, engine_type: str) -> bool:
|
|
224
|
+
"""
|
|
225
|
+
Check if an engine type is available.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
engine_type: The engine type to check.
|
|
229
|
+
|
|
230
|
+
Returns:
|
|
231
|
+
True if the engine can be created.
|
|
232
|
+
"""
|
|
233
|
+
engine_type = engine_type.lower()
|
|
234
|
+
|
|
235
|
+
if engine_type in cls._engines:
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
if engine_type == "casbin":
|
|
239
|
+
try:
|
|
240
|
+
import casbin # noqa: F401
|
|
241
|
+
return True
|
|
242
|
+
except ImportError:
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
return engine_type == "opa"
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
# Convenience function for creating engines
|
|
249
|
+
def create_engine(
|
|
250
|
+
engine_type: str = "simple",
|
|
251
|
+
config: dict[str, Any] | None = None,
|
|
252
|
+
) -> BasePolicyEngine:
|
|
253
|
+
"""
|
|
254
|
+
Create a policy engine instance.
|
|
255
|
+
|
|
256
|
+
Convenience function that delegates to EngineFactory.create().
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
engine_type: The type of engine to create.
|
|
260
|
+
config: Engine-specific configuration.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
An initialized policy engine instance.
|
|
264
|
+
|
|
265
|
+
Example:
|
|
266
|
+
>>> from proxilion.engines import create_engine
|
|
267
|
+
>>> engine = create_engine("simple")
|
|
268
|
+
"""
|
|
269
|
+
return EngineFactory.create(engine_type, config)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
__all__ = [
|
|
273
|
+
# Protocol and base classes
|
|
274
|
+
"PolicyEngine",
|
|
275
|
+
"BasePolicyEngine",
|
|
276
|
+
"EngineCapabilities",
|
|
277
|
+
# Engines
|
|
278
|
+
"SimplePolicyEngine",
|
|
279
|
+
# Factory
|
|
280
|
+
"EngineFactory",
|
|
281
|
+
"create_engine",
|
|
282
|
+
# Exceptions
|
|
283
|
+
"PolicyEngineError",
|
|
284
|
+
"PolicyLoadError",
|
|
285
|
+
"PolicyEvaluationError",
|
|
286
|
+
"EngineNotAvailableError",
|
|
287
|
+
]
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Policy engine base classes and protocols for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module defines the PolicyEngine protocol that all policy engines
|
|
5
|
+
must implement, enabling pluggable authorization backends like
|
|
6
|
+
Casbin, OPA, or custom implementations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import asyncio
|
|
12
|
+
from abc import ABC, abstractmethod
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from proxilion.types import AuthorizationResult, UserContext
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@runtime_checkable
|
|
21
|
+
class PolicyEngine(Protocol):
|
|
22
|
+
"""
|
|
23
|
+
Protocol defining the interface for policy engines.
|
|
24
|
+
|
|
25
|
+
All policy engines must implement this protocol to be compatible
|
|
26
|
+
with Proxilion. This includes the built-in SimplePolicyEngine,
|
|
27
|
+
as well as optional integrations like Casbin and OPA.
|
|
28
|
+
|
|
29
|
+
The protocol requires both synchronous and asynchronous evaluation
|
|
30
|
+
methods to support different application architectures.
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
>>> class CustomEngine:
|
|
34
|
+
... def evaluate(
|
|
35
|
+
... self, user: UserContext, action: str,
|
|
36
|
+
... resource: str, context: dict
|
|
37
|
+
... ) -> AuthorizationResult:
|
|
38
|
+
... # Custom authorization logic
|
|
39
|
+
... return AuthorizationResult(allowed=True, reason="Custom check passed")
|
|
40
|
+
...
|
|
41
|
+
... async def evaluate_async(
|
|
42
|
+
... self, user: UserContext, action: str,
|
|
43
|
+
... resource: str, context: dict
|
|
44
|
+
... ) -> AuthorizationResult:
|
|
45
|
+
... return self.evaluate(user, action, resource, context)
|
|
46
|
+
...
|
|
47
|
+
... def load_policies(self, source: str | Path) -> None:
|
|
48
|
+
... pass
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
def evaluate(
|
|
52
|
+
self,
|
|
53
|
+
user: UserContext,
|
|
54
|
+
action: str,
|
|
55
|
+
resource: str,
|
|
56
|
+
context: dict[str, Any] | None = None,
|
|
57
|
+
) -> AuthorizationResult:
|
|
58
|
+
"""
|
|
59
|
+
Evaluate an authorization request synchronously.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
user: The user context containing identity and roles.
|
|
63
|
+
action: The action being attempted (e.g., "read", "execute").
|
|
64
|
+
resource: The resource being accessed (e.g., "database_query").
|
|
65
|
+
context: Additional context for the authorization decision.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
AuthorizationResult indicating whether the action is allowed.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
async def evaluate_async(
|
|
73
|
+
self,
|
|
74
|
+
user: UserContext,
|
|
75
|
+
action: str,
|
|
76
|
+
resource: str,
|
|
77
|
+
context: dict[str, Any] | None = None,
|
|
78
|
+
) -> AuthorizationResult:
|
|
79
|
+
"""
|
|
80
|
+
Evaluate an authorization request asynchronously.
|
|
81
|
+
|
|
82
|
+
This method is useful for engines that need to make external
|
|
83
|
+
calls (e.g., OPA server) or perform I/O operations.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
user: The user context containing identity and roles.
|
|
87
|
+
action: The action being attempted.
|
|
88
|
+
resource: The resource being accessed.
|
|
89
|
+
context: Additional context for the authorization decision.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
AuthorizationResult indicating whether the action is allowed.
|
|
93
|
+
"""
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
def load_policies(self, source: str | Path) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Load policies from a source.
|
|
99
|
+
|
|
100
|
+
The source format depends on the engine:
|
|
101
|
+
- SimplePolicyEngine: Python module path or directory
|
|
102
|
+
- CasbinPolicyEngine: Path to policy.csv file
|
|
103
|
+
- OPAPolicyEngine: Path to Rego files or OPA bundle
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
source: Path or identifier for the policy source.
|
|
107
|
+
"""
|
|
108
|
+
...
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class BasePolicyEngine(ABC):
|
|
112
|
+
"""
|
|
113
|
+
Abstract base class for policy engines.
|
|
114
|
+
|
|
115
|
+
Provides common functionality and default implementations
|
|
116
|
+
for policy engines. Engines can extend this class instead
|
|
117
|
+
of implementing the Protocol directly.
|
|
118
|
+
|
|
119
|
+
Attributes:
|
|
120
|
+
name: Human-readable name for the engine.
|
|
121
|
+
config: Configuration dictionary passed during initialization.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
name: str = "base"
|
|
125
|
+
|
|
126
|
+
def __init__(self, config: dict[str, Any] | None = None) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Initialize the policy engine.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
config: Engine-specific configuration options.
|
|
132
|
+
"""
|
|
133
|
+
self.config = config or {}
|
|
134
|
+
self._initialized = False
|
|
135
|
+
|
|
136
|
+
@abstractmethod
|
|
137
|
+
def evaluate(
|
|
138
|
+
self,
|
|
139
|
+
user: UserContext,
|
|
140
|
+
action: str,
|
|
141
|
+
resource: str,
|
|
142
|
+
context: dict[str, Any] | None = None,
|
|
143
|
+
) -> AuthorizationResult:
|
|
144
|
+
"""
|
|
145
|
+
Evaluate an authorization request.
|
|
146
|
+
|
|
147
|
+
Subclasses must implement this method.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
user: The user context.
|
|
151
|
+
action: The action being attempted.
|
|
152
|
+
resource: The resource being accessed.
|
|
153
|
+
context: Additional context.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
AuthorizationResult with the decision.
|
|
157
|
+
"""
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
async def evaluate_async(
|
|
161
|
+
self,
|
|
162
|
+
user: UserContext,
|
|
163
|
+
action: str,
|
|
164
|
+
resource: str,
|
|
165
|
+
context: dict[str, Any] | None = None,
|
|
166
|
+
) -> AuthorizationResult:
|
|
167
|
+
"""
|
|
168
|
+
Async version of evaluate.
|
|
169
|
+
|
|
170
|
+
Default implementation wraps the sync version.
|
|
171
|
+
Override for truly async implementations.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
user: The user context.
|
|
175
|
+
action: The action being attempted.
|
|
176
|
+
resource: The resource being accessed.
|
|
177
|
+
context: Additional context.
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
AuthorizationResult with the decision.
|
|
181
|
+
"""
|
|
182
|
+
# Run sync version in thread pool for non-blocking behavior
|
|
183
|
+
loop = asyncio.get_event_loop()
|
|
184
|
+
return await loop.run_in_executor(
|
|
185
|
+
None, self.evaluate, user, action, resource, context
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def load_policies(self, source: str | Path) -> None:
|
|
189
|
+
"""
|
|
190
|
+
Load policies from a source.
|
|
191
|
+
|
|
192
|
+
Default implementation does nothing. Override in subclasses
|
|
193
|
+
that support external policy loading.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
source: Path or identifier for policy source.
|
|
197
|
+
"""
|
|
198
|
+
pass
|
|
199
|
+
|
|
200
|
+
def is_initialized(self) -> bool:
|
|
201
|
+
"""Check if the engine has been initialized."""
|
|
202
|
+
return self._initialized
|
|
203
|
+
|
|
204
|
+
def get_config(self, key: str, default: Any = None) -> Any:
|
|
205
|
+
"""Get a configuration value."""
|
|
206
|
+
return self.config.get(key, default)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class EngineCapabilities:
|
|
210
|
+
"""
|
|
211
|
+
Describes the capabilities of a policy engine.
|
|
212
|
+
|
|
213
|
+
This class helps the Proxilion core understand what features
|
|
214
|
+
an engine supports, enabling graceful degradation when features
|
|
215
|
+
are unavailable.
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
def __init__(
|
|
219
|
+
self,
|
|
220
|
+
supports_async: bool = True,
|
|
221
|
+
supports_caching: bool = False,
|
|
222
|
+
supports_explain: bool = False,
|
|
223
|
+
supports_partial_eval: bool = False,
|
|
224
|
+
supports_hot_reload: bool = False,
|
|
225
|
+
max_batch_size: int = 1,
|
|
226
|
+
) -> None:
|
|
227
|
+
"""
|
|
228
|
+
Initialize engine capabilities.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
supports_async: Whether async evaluation is truly async.
|
|
232
|
+
supports_caching: Whether the engine caches decisions.
|
|
233
|
+
supports_explain: Whether the engine can explain decisions.
|
|
234
|
+
supports_partial_eval: Whether partial evaluation is supported.
|
|
235
|
+
supports_hot_reload: Whether policies can be reloaded at runtime.
|
|
236
|
+
max_batch_size: Maximum number of requests in batch evaluation.
|
|
237
|
+
"""
|
|
238
|
+
self.supports_async = supports_async
|
|
239
|
+
self.supports_caching = supports_caching
|
|
240
|
+
self.supports_explain = supports_explain
|
|
241
|
+
self.supports_partial_eval = supports_partial_eval
|
|
242
|
+
self.supports_hot_reload = supports_hot_reload
|
|
243
|
+
self.max_batch_size = max_batch_size
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class PolicyEngineError(Exception):
|
|
247
|
+
"""Base exception for policy engine errors."""
|
|
248
|
+
|
|
249
|
+
def __init__(self, message: str, engine_name: str | None = None) -> None:
|
|
250
|
+
self.engine_name = engine_name
|
|
251
|
+
super().__init__(f"[{engine_name or 'unknown'}] {message}")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class PolicyLoadError(PolicyEngineError):
|
|
255
|
+
"""Raised when policies fail to load."""
|
|
256
|
+
pass
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class PolicyEvaluationError(PolicyEngineError):
|
|
260
|
+
"""Raised when policy evaluation fails."""
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class EngineNotAvailableError(PolicyEngineError):
|
|
265
|
+
"""Raised when a required engine is not available."""
|
|
266
|
+
pass
|