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.
Files changed (94) hide show
  1. proxilion/__init__.py +136 -0
  2. proxilion/audit/__init__.py +133 -0
  3. proxilion/audit/base_exporters.py +527 -0
  4. proxilion/audit/compliance/__init__.py +130 -0
  5. proxilion/audit/compliance/base.py +457 -0
  6. proxilion/audit/compliance/eu_ai_act.py +603 -0
  7. proxilion/audit/compliance/iso27001.py +544 -0
  8. proxilion/audit/compliance/soc2.py +491 -0
  9. proxilion/audit/events.py +493 -0
  10. proxilion/audit/explainability.py +1173 -0
  11. proxilion/audit/exporters/__init__.py +58 -0
  12. proxilion/audit/exporters/aws_s3.py +636 -0
  13. proxilion/audit/exporters/azure_storage.py +608 -0
  14. proxilion/audit/exporters/cloud_base.py +468 -0
  15. proxilion/audit/exporters/gcp_storage.py +570 -0
  16. proxilion/audit/exporters/multi_exporter.py +498 -0
  17. proxilion/audit/hash_chain.py +652 -0
  18. proxilion/audit/logger.py +543 -0
  19. proxilion/caching/__init__.py +49 -0
  20. proxilion/caching/tool_cache.py +633 -0
  21. proxilion/context/__init__.py +73 -0
  22. proxilion/context/context_window.py +556 -0
  23. proxilion/context/message_history.py +505 -0
  24. proxilion/context/session.py +735 -0
  25. proxilion/contrib/__init__.py +51 -0
  26. proxilion/contrib/anthropic.py +609 -0
  27. proxilion/contrib/google.py +1012 -0
  28. proxilion/contrib/langchain.py +641 -0
  29. proxilion/contrib/mcp.py +893 -0
  30. proxilion/contrib/openai.py +646 -0
  31. proxilion/core.py +3058 -0
  32. proxilion/decorators.py +966 -0
  33. proxilion/engines/__init__.py +287 -0
  34. proxilion/engines/base.py +266 -0
  35. proxilion/engines/casbin_engine.py +412 -0
  36. proxilion/engines/opa_engine.py +493 -0
  37. proxilion/engines/simple.py +437 -0
  38. proxilion/exceptions.py +887 -0
  39. proxilion/guards/__init__.py +54 -0
  40. proxilion/guards/input_guard.py +522 -0
  41. proxilion/guards/output_guard.py +634 -0
  42. proxilion/observability/__init__.py +198 -0
  43. proxilion/observability/cost_tracker.py +866 -0
  44. proxilion/observability/hooks.py +683 -0
  45. proxilion/observability/metrics.py +798 -0
  46. proxilion/observability/session_cost_tracker.py +1063 -0
  47. proxilion/policies/__init__.py +67 -0
  48. proxilion/policies/base.py +304 -0
  49. proxilion/policies/builtin.py +486 -0
  50. proxilion/policies/registry.py +376 -0
  51. proxilion/providers/__init__.py +201 -0
  52. proxilion/providers/adapter.py +468 -0
  53. proxilion/providers/anthropic_adapter.py +330 -0
  54. proxilion/providers/gemini_adapter.py +391 -0
  55. proxilion/providers/openai_adapter.py +294 -0
  56. proxilion/py.typed +0 -0
  57. proxilion/resilience/__init__.py +81 -0
  58. proxilion/resilience/degradation.py +615 -0
  59. proxilion/resilience/fallback.py +555 -0
  60. proxilion/resilience/retry.py +554 -0
  61. proxilion/scheduling/__init__.py +57 -0
  62. proxilion/scheduling/priority_queue.py +419 -0
  63. proxilion/scheduling/scheduler.py +459 -0
  64. proxilion/security/__init__.py +244 -0
  65. proxilion/security/agent_trust.py +968 -0
  66. proxilion/security/behavioral_drift.py +794 -0
  67. proxilion/security/cascade_protection.py +869 -0
  68. proxilion/security/circuit_breaker.py +428 -0
  69. proxilion/security/cost_limiter.py +690 -0
  70. proxilion/security/idor_protection.py +460 -0
  71. proxilion/security/intent_capsule.py +849 -0
  72. proxilion/security/intent_validator.py +495 -0
  73. proxilion/security/memory_integrity.py +767 -0
  74. proxilion/security/rate_limiter.py +509 -0
  75. proxilion/security/scope_enforcer.py +680 -0
  76. proxilion/security/sequence_validator.py +636 -0
  77. proxilion/security/trust_boundaries.py +784 -0
  78. proxilion/streaming/__init__.py +70 -0
  79. proxilion/streaming/detector.py +761 -0
  80. proxilion/streaming/transformer.py +674 -0
  81. proxilion/timeouts/__init__.py +55 -0
  82. proxilion/timeouts/decorators.py +477 -0
  83. proxilion/timeouts/manager.py +545 -0
  84. proxilion/tools/__init__.py +69 -0
  85. proxilion/tools/decorators.py +493 -0
  86. proxilion/tools/registry.py +732 -0
  87. proxilion/types.py +339 -0
  88. proxilion/validation/__init__.py +93 -0
  89. proxilion/validation/pydantic_schema.py +351 -0
  90. proxilion/validation/schema.py +651 -0
  91. proxilion-0.0.1.dist-info/METADATA +872 -0
  92. proxilion-0.0.1.dist-info/RECORD +94 -0
  93. proxilion-0.0.1.dist-info/WHEEL +4 -0
  94. 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")