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,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Policy system for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module provides the Pundit-inspired policy pattern for defining
|
|
5
|
+
authorization rules. Policies are classes that define what actions
|
|
6
|
+
a user can perform on a resource.
|
|
7
|
+
|
|
8
|
+
Quick Start:
|
|
9
|
+
>>> from proxilion.policies import Policy, PolicyRegistry
|
|
10
|
+
>>>
|
|
11
|
+
>>> registry = PolicyRegistry()
|
|
12
|
+
>>>
|
|
13
|
+
>>> @registry.policy("document")
|
|
14
|
+
>>> class DocumentPolicy(Policy):
|
|
15
|
+
... def can_read(self, context: dict) -> bool:
|
|
16
|
+
... return True # All users can read
|
|
17
|
+
...
|
|
18
|
+
... def can_write(self, context: dict) -> bool:
|
|
19
|
+
... return "editor" in self.user.roles
|
|
20
|
+
...
|
|
21
|
+
... def can_delete(self, context: dict) -> bool:
|
|
22
|
+
... return "admin" in self.user.roles
|
|
23
|
+
>>>
|
|
24
|
+
>>> # Check permissions
|
|
25
|
+
>>> policy = registry.get_policy_instance("document", user, document)
|
|
26
|
+
>>> if policy.can("write"):
|
|
27
|
+
... document.save()
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from proxilion.policies.base import (
|
|
31
|
+
ActionContext,
|
|
32
|
+
Policy,
|
|
33
|
+
PolicyWithScope,
|
|
34
|
+
Scope,
|
|
35
|
+
)
|
|
36
|
+
from proxilion.policies.builtin import (
|
|
37
|
+
AllowAllPolicy,
|
|
38
|
+
AttributeBasedPolicy,
|
|
39
|
+
CompositePolicy,
|
|
40
|
+
DenyAllPolicy,
|
|
41
|
+
OwnershipPolicy,
|
|
42
|
+
RoleBasedPolicy,
|
|
43
|
+
)
|
|
44
|
+
from proxilion.policies.registry import (
|
|
45
|
+
PolicyRegistry,
|
|
46
|
+
get_global_registry,
|
|
47
|
+
reset_global_registry,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
# Base classes
|
|
52
|
+
"Policy",
|
|
53
|
+
"PolicyWithScope",
|
|
54
|
+
"Scope",
|
|
55
|
+
"ActionContext",
|
|
56
|
+
# Registry
|
|
57
|
+
"PolicyRegistry",
|
|
58
|
+
"get_global_registry",
|
|
59
|
+
"reset_global_registry",
|
|
60
|
+
# Built-in policies
|
|
61
|
+
"DenyAllPolicy",
|
|
62
|
+
"AllowAllPolicy",
|
|
63
|
+
"RoleBasedPolicy",
|
|
64
|
+
"AttributeBasedPolicy",
|
|
65
|
+
"OwnershipPolicy",
|
|
66
|
+
"CompositePolicy",
|
|
67
|
+
]
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Policy base classes for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module implements a Pundit-inspired policy pattern for Python,
|
|
5
|
+
allowing developers to define authorization rules in a clean,
|
|
6
|
+
object-oriented manner.
|
|
7
|
+
|
|
8
|
+
The Policy pattern separates authorization logic from business logic,
|
|
9
|
+
making it easier to understand, test, and maintain security rules.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from abc import ABC
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from proxilion.types import UserContext
|
|
20
|
+
|
|
21
|
+
# Type variable for the resource being authorized
|
|
22
|
+
T = TypeVar("T")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Policy(ABC, Generic[T]):
|
|
26
|
+
"""
|
|
27
|
+
Abstract base class for all Proxilion policies.
|
|
28
|
+
|
|
29
|
+
Policies define what actions a user can perform on a resource.
|
|
30
|
+
Following the Pundit pattern, each policy class corresponds to
|
|
31
|
+
a resource type, and methods named `can_<action>` define
|
|
32
|
+
permissions for specific actions.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
user: The user context for authorization decisions.
|
|
36
|
+
resource: The resource being accessed (can be an instance,
|
|
37
|
+
class, or any object representing the resource).
|
|
38
|
+
|
|
39
|
+
Example:
|
|
40
|
+
>>> class DocumentPolicy(Policy):
|
|
41
|
+
... def can_read(self, context: dict) -> bool:
|
|
42
|
+
... # All authenticated users can read
|
|
43
|
+
... return True
|
|
44
|
+
...
|
|
45
|
+
... def can_write(self, context: dict) -> bool:
|
|
46
|
+
... # Only owners can write
|
|
47
|
+
... return self.resource.owner_id == self.user.user_id
|
|
48
|
+
...
|
|
49
|
+
... def can_delete(self, context: dict) -> bool:
|
|
50
|
+
... # Only admins can delete
|
|
51
|
+
... return "admin" in self.user.roles
|
|
52
|
+
|
|
53
|
+
The `context` parameter allows passing additional runtime
|
|
54
|
+
information for authorization decisions (e.g., IP address,
|
|
55
|
+
time of day, request metadata).
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
# Class-level attribute to store the resource name this policy handles
|
|
59
|
+
# Set automatically by the @registry.policy decorator
|
|
60
|
+
_resource_name: str | None = None
|
|
61
|
+
|
|
62
|
+
def __init__(self, user: UserContext, resource: T | None = None) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Initialize a policy instance.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
user: The authenticated user making the request.
|
|
68
|
+
resource: The resource being accessed. Can be None for
|
|
69
|
+
resource-type level checks (e.g., "can user create documents?").
|
|
70
|
+
"""
|
|
71
|
+
self.user = user
|
|
72
|
+
self.resource = resource
|
|
73
|
+
|
|
74
|
+
def authorize(self, action: str, context: dict[str, Any] | None = None) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
Check if the user is authorized to perform an action.
|
|
77
|
+
|
|
78
|
+
This method looks up the corresponding `can_<action>` method
|
|
79
|
+
and calls it with the provided context.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
action: The action to check (e.g., "read", "write", "execute").
|
|
83
|
+
context: Additional context for the authorization decision.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if authorized, False otherwise.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
AttributeError: If no `can_<action>` method is defined.
|
|
90
|
+
|
|
91
|
+
Example:
|
|
92
|
+
>>> policy = DocumentPolicy(user, document)
|
|
93
|
+
>>> if policy.authorize("write", {"ip": "192.168.1.1"}):
|
|
94
|
+
... document.save()
|
|
95
|
+
"""
|
|
96
|
+
method_name = f"can_{action}"
|
|
97
|
+
method = getattr(self, method_name, None)
|
|
98
|
+
|
|
99
|
+
if method is None:
|
|
100
|
+
# Default deny if action method doesn't exist
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
return method(context or {})
|
|
104
|
+
|
|
105
|
+
def can(self, action: str, context: dict[str, Any] | None = None) -> bool:
|
|
106
|
+
"""
|
|
107
|
+
Alias for authorize() for a more fluent API.
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> if policy.can("delete"):
|
|
111
|
+
... resource.delete()
|
|
112
|
+
"""
|
|
113
|
+
return self.authorize(action, context)
|
|
114
|
+
|
|
115
|
+
@classmethod
|
|
116
|
+
def get_resource_name(cls) -> str:
|
|
117
|
+
"""
|
|
118
|
+
Get the resource name this policy handles.
|
|
119
|
+
|
|
120
|
+
By default, derives from the class name following the convention
|
|
121
|
+
`ResourcePolicy` -> `resource`. Can be overridden by setting
|
|
122
|
+
`_resource_name` or using the @registry.policy decorator.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
The resource name as a lowercase string.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
>>> class DatabaseQueryPolicy(Policy):
|
|
129
|
+
... pass
|
|
130
|
+
>>> DatabaseQueryPolicy.get_resource_name()
|
|
131
|
+
'database_query'
|
|
132
|
+
"""
|
|
133
|
+
if cls._resource_name:
|
|
134
|
+
return cls._resource_name
|
|
135
|
+
|
|
136
|
+
# Convert CamelCase to snake_case and remove 'Policy' suffix
|
|
137
|
+
name = cls.__name__
|
|
138
|
+
if name.endswith("Policy"):
|
|
139
|
+
name = name[:-6] # Remove 'Policy' suffix
|
|
140
|
+
|
|
141
|
+
# Convert CamelCase to snake_case
|
|
142
|
+
# Insert underscore before uppercase letters (except first)
|
|
143
|
+
snake_case = re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
|
|
144
|
+
return snake_case
|
|
145
|
+
|
|
146
|
+
@classmethod
|
|
147
|
+
def get_available_actions(cls) -> list[str]:
|
|
148
|
+
"""
|
|
149
|
+
Get all actions defined by this policy.
|
|
150
|
+
|
|
151
|
+
Scans the class for methods matching the `can_<action>` pattern.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
List of action names (without the `can_` prefix).
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
>>> class MyPolicy(Policy):
|
|
158
|
+
... def can_read(self, ctx): return True
|
|
159
|
+
... def can_write(self, ctx): return False
|
|
160
|
+
>>> MyPolicy.get_available_actions()
|
|
161
|
+
['read', 'write']
|
|
162
|
+
"""
|
|
163
|
+
actions = []
|
|
164
|
+
for name in dir(cls):
|
|
165
|
+
if name.startswith("can_") and callable(getattr(cls, name)):
|
|
166
|
+
action = name[4:] # Remove 'can_' prefix
|
|
167
|
+
actions.append(action)
|
|
168
|
+
return sorted(actions)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class Scope(Generic[T]):
|
|
172
|
+
"""
|
|
173
|
+
Base class for filtering collections based on user permissions.
|
|
174
|
+
|
|
175
|
+
The Scope pattern allows filtering a collection of resources
|
|
176
|
+
to only those the user is authorized to access. This is useful
|
|
177
|
+
for listing operations.
|
|
178
|
+
|
|
179
|
+
Example:
|
|
180
|
+
>>> class DocumentScope(Scope[Document]):
|
|
181
|
+
... def resolve(self) -> list[Document]:
|
|
182
|
+
... if "admin" in self.user.roles:
|
|
183
|
+
... return self.scope # Admins see all
|
|
184
|
+
... return [doc for doc in self.scope
|
|
185
|
+
... if doc.owner_id == self.user.user_id]
|
|
186
|
+
>>>
|
|
187
|
+
>>> # Usage
|
|
188
|
+
>>> visible_docs = DocumentScope(user, all_documents).resolve()
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
def __init__(self, user: UserContext, scope: list[T]) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Initialize a scope instance.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
user: The authenticated user.
|
|
197
|
+
scope: The initial collection to filter.
|
|
198
|
+
"""
|
|
199
|
+
self.user = user
|
|
200
|
+
self.scope = scope
|
|
201
|
+
|
|
202
|
+
def resolve(self) -> list[T]:
|
|
203
|
+
"""
|
|
204
|
+
Filter the scope to authorized items.
|
|
205
|
+
|
|
206
|
+
Subclasses should override this method to implement
|
|
207
|
+
their filtering logic.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Filtered list of items the user can access.
|
|
211
|
+
"""
|
|
212
|
+
# Default implementation returns empty list (safe default)
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class PolicyWithScope(Policy[T]):
|
|
217
|
+
"""
|
|
218
|
+
Policy class that includes a Scope inner class.
|
|
219
|
+
|
|
220
|
+
This combines the Policy and Scope patterns, following
|
|
221
|
+
the Pundit convention where each Policy can have an
|
|
222
|
+
associated Scope class.
|
|
223
|
+
|
|
224
|
+
Example:
|
|
225
|
+
>>> class DocumentPolicy(PolicyWithScope[Document]):
|
|
226
|
+
... def can_read(self, context: dict) -> bool:
|
|
227
|
+
... return True
|
|
228
|
+
...
|
|
229
|
+
... class Scope(Scope[Document]):
|
|
230
|
+
... def resolve(self) -> list[Document]:
|
|
231
|
+
... if "admin" in self.user.roles:
|
|
232
|
+
... return self.scope
|
|
233
|
+
... return [d for d in self.scope
|
|
234
|
+
... if d.owner_id == self.user.user_id]
|
|
235
|
+
>>>
|
|
236
|
+
>>> # Check permission for single resource
|
|
237
|
+
>>> policy = DocumentPolicy(user, document)
|
|
238
|
+
>>> can_read = policy.can("read")
|
|
239
|
+
>>>
|
|
240
|
+
>>> # Filter collection
|
|
241
|
+
>>> visible = DocumentPolicy.Scope(user, all_docs).resolve()
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
# Nested Scope class - subclasses should override
|
|
245
|
+
class Scope(Scope[T]):
|
|
246
|
+
"""Default scope that returns empty list."""
|
|
247
|
+
pass
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class ActionContext:
|
|
251
|
+
"""
|
|
252
|
+
Context object passed to policy methods.
|
|
253
|
+
|
|
254
|
+
Provides a structured way to pass runtime context to
|
|
255
|
+
authorization decisions. Can be extended with additional
|
|
256
|
+
attributes as needed.
|
|
257
|
+
|
|
258
|
+
Attributes:
|
|
259
|
+
request_id: Unique identifier for the request.
|
|
260
|
+
ip_address: Client IP address.
|
|
261
|
+
timestamp: Request timestamp.
|
|
262
|
+
metadata: Additional key-value metadata.
|
|
263
|
+
|
|
264
|
+
Example:
|
|
265
|
+
>>> context = ActionContext(
|
|
266
|
+
... ip_address="192.168.1.1",
|
|
267
|
+
... metadata={"user_agent": "Claude/1.0"}
|
|
268
|
+
... )
|
|
269
|
+
>>> policy.authorize("read", context.to_dict())
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(
|
|
273
|
+
self,
|
|
274
|
+
request_id: str | None = None,
|
|
275
|
+
ip_address: str | None = None,
|
|
276
|
+
timestamp: str | None = None,
|
|
277
|
+
metadata: dict[str, Any] | None = None,
|
|
278
|
+
) -> None:
|
|
279
|
+
self.request_id = request_id
|
|
280
|
+
self.ip_address = ip_address
|
|
281
|
+
self.timestamp = timestamp
|
|
282
|
+
self.metadata = metadata or {}
|
|
283
|
+
|
|
284
|
+
def to_dict(self) -> dict[str, Any]:
|
|
285
|
+
"""Convert to dictionary for passing to policy methods."""
|
|
286
|
+
result: dict[str, Any] = {}
|
|
287
|
+
if self.request_id:
|
|
288
|
+
result["request_id"] = self.request_id
|
|
289
|
+
if self.ip_address:
|
|
290
|
+
result["ip_address"] = self.ip_address
|
|
291
|
+
if self.timestamp:
|
|
292
|
+
result["timestamp"] = self.timestamp
|
|
293
|
+
result.update(self.metadata)
|
|
294
|
+
return result
|
|
295
|
+
|
|
296
|
+
def with_metadata(self, **kwargs: Any) -> ActionContext:
|
|
297
|
+
"""Create a new context with additional metadata."""
|
|
298
|
+
new_metadata = {**self.metadata, **kwargs}
|
|
299
|
+
return ActionContext(
|
|
300
|
+
request_id=self.request_id,
|
|
301
|
+
ip_address=self.ip_address,
|
|
302
|
+
timestamp=self.timestamp,
|
|
303
|
+
metadata=new_metadata,
|
|
304
|
+
)
|