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,412 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Casbin policy engine integration for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module provides integration with Casbin, an authorization library
|
|
5
|
+
that supports various access control models including RBAC, ABAC, and ACL.
|
|
6
|
+
|
|
7
|
+
Casbin is an optional dependency. If not installed, attempting to use
|
|
8
|
+
this engine will raise an informative error.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import TYPE_CHECKING, Any
|
|
16
|
+
|
|
17
|
+
from proxilion.engines.base import (
|
|
18
|
+
BasePolicyEngine,
|
|
19
|
+
EngineCapabilities,
|
|
20
|
+
EngineNotAvailableError,
|
|
21
|
+
PolicyEvaluationError,
|
|
22
|
+
PolicyLoadError,
|
|
23
|
+
)
|
|
24
|
+
from proxilion.types import AuthorizationResult
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from proxilion.types import UserContext
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# Check if casbin is available
|
|
32
|
+
try:
|
|
33
|
+
import casbin
|
|
34
|
+
HAS_CASBIN = True
|
|
35
|
+
except ImportError:
|
|
36
|
+
casbin = None # type: ignore
|
|
37
|
+
HAS_CASBIN = False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class CasbinPolicyEngine(BasePolicyEngine):
|
|
41
|
+
"""
|
|
42
|
+
Policy engine using Casbin for authorization.
|
|
43
|
+
|
|
44
|
+
Casbin is a powerful authorization library that supports:
|
|
45
|
+
- RBAC (Role-Based Access Control)
|
|
46
|
+
- ABAC (Attribute-Based Access Control)
|
|
47
|
+
- ACL (Access Control List)
|
|
48
|
+
- And more through its flexible model system
|
|
49
|
+
|
|
50
|
+
This engine wraps Casbin's enforcer and maps Proxilion's
|
|
51
|
+
user context and authorization requests to Casbin's format.
|
|
52
|
+
|
|
53
|
+
Requirements:
|
|
54
|
+
Install with: pip install proxilion[casbin]
|
|
55
|
+
|
|
56
|
+
Configuration:
|
|
57
|
+
- model_path: Path to the Casbin model file (model.conf)
|
|
58
|
+
- policy_path: Path to the policy file (policy.csv)
|
|
59
|
+
- adapter: Optional Casbin adapter for database storage
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
>>> engine = CasbinPolicyEngine({
|
|
63
|
+
... "model_path": "model.conf",
|
|
64
|
+
... "policy_path": "policy.csv",
|
|
65
|
+
... })
|
|
66
|
+
>>> result = engine.evaluate(user, "read", "document")
|
|
67
|
+
|
|
68
|
+
Model Example (RBAC):
|
|
69
|
+
[request_definition]
|
|
70
|
+
r = sub, obj, act
|
|
71
|
+
|
|
72
|
+
[policy_definition]
|
|
73
|
+
p = sub, obj, act
|
|
74
|
+
|
|
75
|
+
[role_definition]
|
|
76
|
+
g = _, _
|
|
77
|
+
|
|
78
|
+
[policy_effect]
|
|
79
|
+
e = some(where (p.eft == allow))
|
|
80
|
+
|
|
81
|
+
[matchers]
|
|
82
|
+
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
|
|
83
|
+
|
|
84
|
+
Policy Example:
|
|
85
|
+
p, admin, document, read
|
|
86
|
+
p, admin, document, write
|
|
87
|
+
p, user, document, read
|
|
88
|
+
g, alice, admin
|
|
89
|
+
g, bob, user
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
name = "casbin"
|
|
93
|
+
|
|
94
|
+
def __init__(self, config: dict[str, Any] | None = None) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Initialize the Casbin policy engine.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
config: Configuration with model_path and policy_path.
|
|
100
|
+
|
|
101
|
+
Raises:
|
|
102
|
+
EngineNotAvailableError: If casbin is not installed.
|
|
103
|
+
"""
|
|
104
|
+
if not HAS_CASBIN:
|
|
105
|
+
raise EngineNotAvailableError(
|
|
106
|
+
"Casbin is not installed. Install with: pip install proxilion[casbin]",
|
|
107
|
+
engine_name=self.name,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
super().__init__(config)
|
|
111
|
+
|
|
112
|
+
self._enforcer: Any = None
|
|
113
|
+
self._model_path: Path | None = None
|
|
114
|
+
self._policy_path: Path | None = None
|
|
115
|
+
|
|
116
|
+
# Initialize from config if paths provided
|
|
117
|
+
model_path = self.get_config("model_path")
|
|
118
|
+
policy_path = self.get_config("policy_path")
|
|
119
|
+
|
|
120
|
+
if model_path and policy_path:
|
|
121
|
+
self.load_policies_from_files(model_path, policy_path)
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def capabilities(self) -> EngineCapabilities:
|
|
125
|
+
"""Get engine capabilities."""
|
|
126
|
+
return EngineCapabilities(
|
|
127
|
+
supports_async=False, # Casbin is synchronous
|
|
128
|
+
supports_caching=True,
|
|
129
|
+
supports_explain=False,
|
|
130
|
+
supports_partial_eval=False,
|
|
131
|
+
supports_hot_reload=True,
|
|
132
|
+
max_batch_size=100,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def enforcer(self) -> Any:
|
|
137
|
+
"""Get the Casbin enforcer instance."""
|
|
138
|
+
if self._enforcer is None:
|
|
139
|
+
raise PolicyLoadError(
|
|
140
|
+
"Casbin enforcer not initialized. Call load_policies() first.",
|
|
141
|
+
engine_name=self.name,
|
|
142
|
+
)
|
|
143
|
+
return self._enforcer
|
|
144
|
+
|
|
145
|
+
def load_policies(self, source: str | Path) -> None:
|
|
146
|
+
"""
|
|
147
|
+
Load policies from a source.
|
|
148
|
+
|
|
149
|
+
For Casbin, this expects a directory containing model.conf
|
|
150
|
+
and policy.csv files, or a path to the model file with
|
|
151
|
+
the policy file inferred.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
source: Path to model file or directory with model and policy.
|
|
155
|
+
"""
|
|
156
|
+
path = Path(source)
|
|
157
|
+
|
|
158
|
+
if path.is_dir():
|
|
159
|
+
model_path = path / "model.conf"
|
|
160
|
+
policy_path = path / "policy.csv"
|
|
161
|
+
elif path.suffix == ".conf":
|
|
162
|
+
model_path = path
|
|
163
|
+
policy_path = path.with_suffix(".csv")
|
|
164
|
+
else:
|
|
165
|
+
raise PolicyLoadError(
|
|
166
|
+
f"Invalid source for Casbin: {source}. "
|
|
167
|
+
"Provide a directory with model.conf and policy.csv, "
|
|
168
|
+
"or a path to model.conf file.",
|
|
169
|
+
engine_name=self.name,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
self.load_policies_from_files(model_path, policy_path)
|
|
173
|
+
|
|
174
|
+
def load_policies_from_files(
|
|
175
|
+
self,
|
|
176
|
+
model_path: str | Path,
|
|
177
|
+
policy_path: str | Path,
|
|
178
|
+
) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Load Casbin model and policy from specific files.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
model_path: Path to the model configuration file.
|
|
184
|
+
policy_path: Path to the policy file.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
PolicyLoadError: If files cannot be loaded.
|
|
188
|
+
"""
|
|
189
|
+
self._model_path = Path(model_path)
|
|
190
|
+
self._policy_path = Path(policy_path)
|
|
191
|
+
|
|
192
|
+
if not self._model_path.exists():
|
|
193
|
+
raise PolicyLoadError(
|
|
194
|
+
f"Model file not found: {self._model_path}",
|
|
195
|
+
engine_name=self.name,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
if not self._policy_path.exists():
|
|
199
|
+
raise PolicyLoadError(
|
|
200
|
+
f"Policy file not found: {self._policy_path}",
|
|
201
|
+
engine_name=self.name,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
try:
|
|
205
|
+
self._enforcer = casbin.Enforcer(
|
|
206
|
+
str(self._model_path),
|
|
207
|
+
str(self._policy_path),
|
|
208
|
+
)
|
|
209
|
+
self._initialized = True
|
|
210
|
+
logger.info(
|
|
211
|
+
f"Casbin enforcer initialized with model={self._model_path}, "
|
|
212
|
+
f"policy={self._policy_path}"
|
|
213
|
+
)
|
|
214
|
+
except Exception as e:
|
|
215
|
+
raise PolicyLoadError(
|
|
216
|
+
f"Failed to initialize Casbin enforcer: {e}",
|
|
217
|
+
engine_name=self.name,
|
|
218
|
+
) from e
|
|
219
|
+
|
|
220
|
+
def load_policies_from_adapter(self, adapter: Any) -> None:
|
|
221
|
+
"""
|
|
222
|
+
Load policies using a Casbin adapter.
|
|
223
|
+
|
|
224
|
+
Adapters allow storing policies in databases like
|
|
225
|
+
PostgreSQL, MySQL, Redis, etc.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
adapter: A Casbin adapter instance.
|
|
229
|
+
"""
|
|
230
|
+
model_path = self.get_config("model_path")
|
|
231
|
+
if not model_path:
|
|
232
|
+
raise PolicyLoadError(
|
|
233
|
+
"model_path is required when using an adapter",
|
|
234
|
+
engine_name=self.name,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
self._enforcer = casbin.Enforcer(str(model_path), adapter)
|
|
239
|
+
self._initialized = True
|
|
240
|
+
logger.info(f"Casbin enforcer initialized with adapter: {type(adapter)}")
|
|
241
|
+
except Exception as e:
|
|
242
|
+
raise PolicyLoadError(
|
|
243
|
+
f"Failed to initialize Casbin with adapter: {e}",
|
|
244
|
+
engine_name=self.name,
|
|
245
|
+
) from e
|
|
246
|
+
|
|
247
|
+
def evaluate(
|
|
248
|
+
self,
|
|
249
|
+
user: UserContext,
|
|
250
|
+
action: str,
|
|
251
|
+
resource: str,
|
|
252
|
+
context: dict[str, Any] | None = None,
|
|
253
|
+
) -> AuthorizationResult:
|
|
254
|
+
"""
|
|
255
|
+
Evaluate an authorization request using Casbin.
|
|
256
|
+
|
|
257
|
+
The user_id is passed as the subject (sub), the resource
|
|
258
|
+
as the object (obj), and the action as the action (act).
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
user: The user context.
|
|
262
|
+
action: The action being attempted.
|
|
263
|
+
resource: The resource being accessed.
|
|
264
|
+
context: Additional context (not used by basic Casbin models).
|
|
265
|
+
|
|
266
|
+
Returns:
|
|
267
|
+
AuthorizationResult with the decision.
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
# Basic enforcement: (sub, obj, act)
|
|
271
|
+
allowed = self.enforcer.enforce(user.user_id, resource, action)
|
|
272
|
+
|
|
273
|
+
if allowed:
|
|
274
|
+
reason = f"Casbin allowed {user.user_id} to {action} on {resource}"
|
|
275
|
+
else:
|
|
276
|
+
reason = f"Casbin denied {user.user_id} to {action} on {resource}"
|
|
277
|
+
|
|
278
|
+
logger.debug(f"Casbin evaluation: {reason}")
|
|
279
|
+
|
|
280
|
+
return AuthorizationResult(
|
|
281
|
+
allowed=allowed,
|
|
282
|
+
reason=reason,
|
|
283
|
+
policies_evaluated=["casbin"],
|
|
284
|
+
)
|
|
285
|
+
except Exception as e:
|
|
286
|
+
raise PolicyEvaluationError(
|
|
287
|
+
f"Casbin evaluation failed: {e}",
|
|
288
|
+
engine_name=self.name,
|
|
289
|
+
) from e
|
|
290
|
+
|
|
291
|
+
def evaluate_with_roles(
|
|
292
|
+
self,
|
|
293
|
+
user: UserContext,
|
|
294
|
+
action: str,
|
|
295
|
+
resource: str,
|
|
296
|
+
context: dict[str, Any] | None = None,
|
|
297
|
+
) -> AuthorizationResult:
|
|
298
|
+
"""
|
|
299
|
+
Evaluate using user's roles in addition to user_id.
|
|
300
|
+
|
|
301
|
+
This method checks if any of the user's roles would allow
|
|
302
|
+
the action, which is useful for RBAC models.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
user: The user context.
|
|
306
|
+
action: The action being attempted.
|
|
307
|
+
resource: The resource being accessed.
|
|
308
|
+
context: Additional context.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
AuthorizationResult with the decision.
|
|
312
|
+
"""
|
|
313
|
+
# First check user_id directly
|
|
314
|
+
result = self.evaluate(user, action, resource, context)
|
|
315
|
+
if result.allowed:
|
|
316
|
+
return result
|
|
317
|
+
|
|
318
|
+
# Then check each role
|
|
319
|
+
for role in user.roles:
|
|
320
|
+
try:
|
|
321
|
+
if self.enforcer.enforce(role, resource, action):
|
|
322
|
+
return AuthorizationResult(
|
|
323
|
+
allowed=True,
|
|
324
|
+
reason=f"Casbin allowed role '{role}' to {action} on {resource}",
|
|
325
|
+
policies_evaluated=["casbin"],
|
|
326
|
+
)
|
|
327
|
+
except Exception:
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
return AuthorizationResult(
|
|
331
|
+
allowed=False,
|
|
332
|
+
reason=f"Casbin denied {user.user_id} (and all roles) to {action} on {resource}",
|
|
333
|
+
policies_evaluated=["casbin"],
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
def add_policy(self, subject: str, resource: str, action: str) -> bool:
|
|
337
|
+
"""
|
|
338
|
+
Add a policy rule dynamically.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
subject: The subject (user or role).
|
|
342
|
+
resource: The resource.
|
|
343
|
+
action: The action.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
True if the policy was added, False if it already exists.
|
|
347
|
+
"""
|
|
348
|
+
return self.enforcer.add_policy(subject, resource, action)
|
|
349
|
+
|
|
350
|
+
def remove_policy(self, subject: str, resource: str, action: str) -> bool:
|
|
351
|
+
"""
|
|
352
|
+
Remove a policy rule.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
subject: The subject (user or role).
|
|
356
|
+
resource: The resource.
|
|
357
|
+
action: The action.
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
True if the policy was removed, False if it didn't exist.
|
|
361
|
+
"""
|
|
362
|
+
return self.enforcer.remove_policy(subject, resource, action)
|
|
363
|
+
|
|
364
|
+
def add_role_for_user(self, user: str, role: str) -> bool:
|
|
365
|
+
"""
|
|
366
|
+
Add a role for a user.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
user: The user identifier.
|
|
370
|
+
role: The role to add.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
True if the role was added.
|
|
374
|
+
"""
|
|
375
|
+
return self.enforcer.add_role_for_user(user, role)
|
|
376
|
+
|
|
377
|
+
def remove_role_for_user(self, user: str, role: str) -> bool:
|
|
378
|
+
"""
|
|
379
|
+
Remove a role from a user.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
user: The user identifier.
|
|
383
|
+
role: The role to remove.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
True if the role was removed.
|
|
387
|
+
"""
|
|
388
|
+
return self.enforcer.delete_role_for_user(user, role)
|
|
389
|
+
|
|
390
|
+
def get_roles_for_user(self, user: str) -> list[str]:
|
|
391
|
+
"""
|
|
392
|
+
Get all roles for a user.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
user: The user identifier.
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
List of role names.
|
|
399
|
+
"""
|
|
400
|
+
return self.enforcer.get_roles_for_user(user)
|
|
401
|
+
|
|
402
|
+
def reload_policies(self) -> None:
|
|
403
|
+
"""Reload policies from the policy file."""
|
|
404
|
+
if self._enforcer is not None:
|
|
405
|
+
self._enforcer.load_policy()
|
|
406
|
+
logger.info("Casbin policies reloaded")
|
|
407
|
+
|
|
408
|
+
def save_policies(self) -> None:
|
|
409
|
+
"""Save current policies to the policy file."""
|
|
410
|
+
if self._enforcer is not None:
|
|
411
|
+
self._enforcer.save_policy()
|
|
412
|
+
logger.info("Casbin policies saved")
|