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,460 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IDOR (Insecure Direct Object Reference) protection for Proxilion.
|
|
3
|
+
|
|
4
|
+
This module provides protection against IDOR attacks, where attackers
|
|
5
|
+
attempt to access resources by manipulating object IDs in tool calls.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
import threading
|
|
13
|
+
from collections.abc import Callable
|
|
14
|
+
from dataclasses import dataclass, field
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from proxilion.exceptions import IDORViolationError
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ResourceScope:
|
|
24
|
+
"""Defines the scope of resources a user can access."""
|
|
25
|
+
allowed_ids: set[str] = field(default_factory=set)
|
|
26
|
+
allowed_patterns: list[str] = field(default_factory=list)
|
|
27
|
+
scope_loader: Callable[[str], set[str]] | None = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class IDPattern:
|
|
32
|
+
"""Pattern for identifying object IDs in parameters."""
|
|
33
|
+
parameter_name: str
|
|
34
|
+
resource_type: str
|
|
35
|
+
pattern: str = r".*" # Regex to validate ID format
|
|
36
|
+
extractor: Callable[[Any], list[str]] | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class IDORProtector:
|
|
40
|
+
"""
|
|
41
|
+
Protects against Insecure Direct Object Reference attacks.
|
|
42
|
+
|
|
43
|
+
IDOR attacks occur when a user manipulates object IDs to access
|
|
44
|
+
resources they shouldn't have access to. This class validates
|
|
45
|
+
that object IDs in tool call arguments are within the user's
|
|
46
|
+
authorized scope.
|
|
47
|
+
|
|
48
|
+
Features:
|
|
49
|
+
- Register allowed resource scopes per user
|
|
50
|
+
- Define patterns to extract object IDs from arguments
|
|
51
|
+
- Validate access before tool execution
|
|
52
|
+
- Support for dynamic scope loading
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> protector = IDORProtector()
|
|
56
|
+
>>>
|
|
57
|
+
>>> # Register user's allowed documents
|
|
58
|
+
>>> protector.register_scope(
|
|
59
|
+
... user_id="user_123",
|
|
60
|
+
... resource_type="document",
|
|
61
|
+
... allowed_ids={"doc_1", "doc_2", "doc_3"},
|
|
62
|
+
... )
|
|
63
|
+
>>>
|
|
64
|
+
>>> # Define where IDs appear in arguments
|
|
65
|
+
>>> protector.register_id_pattern(
|
|
66
|
+
... parameter_name="document_id",
|
|
67
|
+
... resource_type="document",
|
|
68
|
+
... )
|
|
69
|
+
>>>
|
|
70
|
+
>>> # Validate access
|
|
71
|
+
>>> protector.validate_access("user_123", "document", "doc_1") # True
|
|
72
|
+
>>> protector.validate_access("user_123", "document", "doc_999") # False
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# Common ID patterns for auto-detection
|
|
76
|
+
UUID_PATTERN = re.compile(
|
|
77
|
+
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
|
|
78
|
+
re.IGNORECASE,
|
|
79
|
+
)
|
|
80
|
+
NUMERIC_PATTERN = re.compile(r"^\d+$")
|
|
81
|
+
ALPHANUMERIC_PATTERN = re.compile(r"^[a-zA-Z0-9_-]+$")
|
|
82
|
+
|
|
83
|
+
def __init__(self) -> None:
|
|
84
|
+
"""Initialize the IDOR protector."""
|
|
85
|
+
self._scopes: dict[str, dict[str, ResourceScope]] = {}
|
|
86
|
+
self._patterns: dict[str, IDPattern] = {}
|
|
87
|
+
self._resource_patterns: dict[str, list[IDPattern]] = {}
|
|
88
|
+
self._lock = threading.RLock()
|
|
89
|
+
|
|
90
|
+
# Global scope loaders by resource type
|
|
91
|
+
self._scope_loaders: dict[str, Callable[[str], set[str]]] = {}
|
|
92
|
+
|
|
93
|
+
def register_scope(
|
|
94
|
+
self,
|
|
95
|
+
user_id: str,
|
|
96
|
+
resource_type: str,
|
|
97
|
+
allowed_ids: set[str] | None = None,
|
|
98
|
+
allowed_patterns: list[str] | None = None,
|
|
99
|
+
scope_loader: Callable[[str], set[str]] | None = None,
|
|
100
|
+
) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Register a resource scope for a user.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
user_id: The user's ID.
|
|
106
|
+
resource_type: Type of resource (e.g., "document", "account").
|
|
107
|
+
allowed_ids: Set of allowed object IDs.
|
|
108
|
+
allowed_patterns: Regex patterns for allowed IDs.
|
|
109
|
+
scope_loader: Function to dynamically load allowed IDs.
|
|
110
|
+
|
|
111
|
+
Example:
|
|
112
|
+
>>> protector.register_scope(
|
|
113
|
+
... user_id="user_123",
|
|
114
|
+
... resource_type="document",
|
|
115
|
+
... allowed_ids={"doc_1", "doc_2"},
|
|
116
|
+
... )
|
|
117
|
+
"""
|
|
118
|
+
with self._lock:
|
|
119
|
+
if user_id not in self._scopes:
|
|
120
|
+
self._scopes[user_id] = {}
|
|
121
|
+
|
|
122
|
+
self._scopes[user_id][resource_type] = ResourceScope(
|
|
123
|
+
allowed_ids=allowed_ids or set(),
|
|
124
|
+
allowed_patterns=allowed_patterns or [],
|
|
125
|
+
scope_loader=scope_loader,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
logger.debug(
|
|
129
|
+
f"Registered scope for user={user_id}, "
|
|
130
|
+
f"resource_type={resource_type}, ids={len(allowed_ids or set())}"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
def register_scope_loader(
|
|
134
|
+
self,
|
|
135
|
+
resource_type: str,
|
|
136
|
+
loader: Callable[[str], set[str]],
|
|
137
|
+
) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Register a global scope loader for a resource type.
|
|
140
|
+
|
|
141
|
+
The loader is called with a user_id and should return
|
|
142
|
+
the set of allowed IDs for that user.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
resource_type: The resource type.
|
|
146
|
+
loader: Function that takes user_id and returns allowed IDs.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
>>> def load_user_documents(user_id: str) -> set[str]:
|
|
150
|
+
... return db.get_user_document_ids(user_id)
|
|
151
|
+
>>>
|
|
152
|
+
>>> protector.register_scope_loader("document", load_user_documents)
|
|
153
|
+
"""
|
|
154
|
+
with self._lock:
|
|
155
|
+
self._scope_loaders[resource_type] = loader
|
|
156
|
+
logger.debug(f"Registered scope loader for resource_type={resource_type}")
|
|
157
|
+
|
|
158
|
+
def register_id_pattern(
|
|
159
|
+
self,
|
|
160
|
+
parameter_name: str,
|
|
161
|
+
resource_type: str,
|
|
162
|
+
pattern: str = r".*",
|
|
163
|
+
extractor: Callable[[Any], list[str]] | None = None,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""
|
|
166
|
+
Register a pattern for extracting object IDs from arguments.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
parameter_name: The parameter name that contains the ID.
|
|
170
|
+
resource_type: The type of resource the ID refers to.
|
|
171
|
+
pattern: Regex pattern to validate the ID format.
|
|
172
|
+
extractor: Custom function to extract IDs from the parameter value.
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
>>> # Simple ID parameter
|
|
176
|
+
>>> protector.register_id_pattern("document_id", "document")
|
|
177
|
+
>>>
|
|
178
|
+
>>> # Parameter with list of IDs
|
|
179
|
+
>>> protector.register_id_pattern(
|
|
180
|
+
... "document_ids",
|
|
181
|
+
... "document",
|
|
182
|
+
... extractor=lambda v: v if isinstance(v, list) else [v],
|
|
183
|
+
... )
|
|
184
|
+
"""
|
|
185
|
+
with self._lock:
|
|
186
|
+
id_pattern = IDPattern(
|
|
187
|
+
parameter_name=parameter_name,
|
|
188
|
+
resource_type=resource_type,
|
|
189
|
+
pattern=pattern,
|
|
190
|
+
extractor=extractor,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
self._patterns[parameter_name] = id_pattern
|
|
194
|
+
|
|
195
|
+
if resource_type not in self._resource_patterns:
|
|
196
|
+
self._resource_patterns[resource_type] = []
|
|
197
|
+
self._resource_patterns[resource_type].append(id_pattern)
|
|
198
|
+
|
|
199
|
+
logger.debug(
|
|
200
|
+
f"Registered ID pattern: {parameter_name} -> {resource_type}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
def validate_access(
|
|
204
|
+
self,
|
|
205
|
+
user_id: str,
|
|
206
|
+
resource_type: str,
|
|
207
|
+
object_id: str,
|
|
208
|
+
) -> bool:
|
|
209
|
+
"""
|
|
210
|
+
Validate that a user can access a specific object.
|
|
211
|
+
|
|
212
|
+
Args:
|
|
213
|
+
user_id: The user's ID.
|
|
214
|
+
resource_type: The type of resource.
|
|
215
|
+
object_id: The object ID being accessed.
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
True if access is allowed, False otherwise.
|
|
219
|
+
"""
|
|
220
|
+
with self._lock:
|
|
221
|
+
# Get user's scope for this resource type
|
|
222
|
+
user_scopes = self._scopes.get(user_id, {})
|
|
223
|
+
scope = user_scopes.get(resource_type)
|
|
224
|
+
|
|
225
|
+
# Try global scope loader if no user-specific scope
|
|
226
|
+
if scope is None and resource_type in self._scope_loaders:
|
|
227
|
+
loader = self._scope_loaders[resource_type]
|
|
228
|
+
try:
|
|
229
|
+
allowed_ids = loader(user_id)
|
|
230
|
+
return object_id in allowed_ids
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.error(f"Scope loader failed: {e}")
|
|
233
|
+
return False
|
|
234
|
+
|
|
235
|
+
if scope is None:
|
|
236
|
+
# No scope defined - default deny
|
|
237
|
+
logger.debug(
|
|
238
|
+
f"No scope for user={user_id}, resource_type={resource_type}"
|
|
239
|
+
)
|
|
240
|
+
return False
|
|
241
|
+
|
|
242
|
+
# Check allowed IDs
|
|
243
|
+
if object_id in scope.allowed_ids:
|
|
244
|
+
return True
|
|
245
|
+
|
|
246
|
+
# Check patterns
|
|
247
|
+
for pattern in scope.allowed_patterns:
|
|
248
|
+
if re.match(pattern, object_id):
|
|
249
|
+
return True
|
|
250
|
+
|
|
251
|
+
# Try scope loader
|
|
252
|
+
if scope.scope_loader:
|
|
253
|
+
try:
|
|
254
|
+
dynamic_ids = scope.scope_loader(user_id)
|
|
255
|
+
if object_id in dynamic_ids:
|
|
256
|
+
return True
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.error(f"Dynamic scope loader failed: {e}")
|
|
259
|
+
|
|
260
|
+
return False
|
|
261
|
+
|
|
262
|
+
def validate_arguments(
|
|
263
|
+
self,
|
|
264
|
+
user_id: str,
|
|
265
|
+
arguments: dict[str, Any],
|
|
266
|
+
) -> list[tuple[str, str, str]]:
|
|
267
|
+
"""
|
|
268
|
+
Validate all object IDs in tool arguments.
|
|
269
|
+
|
|
270
|
+
Scans the arguments for registered ID patterns and validates
|
|
271
|
+
each found ID against the user's scope.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
user_id: The user's ID.
|
|
275
|
+
arguments: The tool call arguments.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
List of (parameter_name, resource_type, object_id) tuples
|
|
279
|
+
for IDs that failed validation.
|
|
280
|
+
"""
|
|
281
|
+
violations: list[tuple[str, str, str]] = []
|
|
282
|
+
|
|
283
|
+
with self._lock:
|
|
284
|
+
for param_name, value in arguments.items():
|
|
285
|
+
pattern = self._patterns.get(param_name)
|
|
286
|
+
if pattern is None:
|
|
287
|
+
continue
|
|
288
|
+
|
|
289
|
+
# Extract IDs from the value
|
|
290
|
+
ids = self._extract_ids(value, pattern)
|
|
291
|
+
|
|
292
|
+
# Validate each ID
|
|
293
|
+
for object_id in ids:
|
|
294
|
+
if not self.validate_access(
|
|
295
|
+
user_id, pattern.resource_type, object_id
|
|
296
|
+
):
|
|
297
|
+
violations.append(
|
|
298
|
+
(param_name, pattern.resource_type, object_id)
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
return violations
|
|
302
|
+
|
|
303
|
+
def check_arguments(
|
|
304
|
+
self,
|
|
305
|
+
user_id: str,
|
|
306
|
+
arguments: dict[str, Any],
|
|
307
|
+
) -> None:
|
|
308
|
+
"""
|
|
309
|
+
Check arguments and raise if any IDOR violations found.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
user_id: The user's ID.
|
|
313
|
+
arguments: The tool call arguments.
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
IDORViolationError: If any object ID is not in user's scope.
|
|
317
|
+
"""
|
|
318
|
+
violations = self.validate_arguments(user_id, arguments)
|
|
319
|
+
|
|
320
|
+
if violations:
|
|
321
|
+
# Report first violation
|
|
322
|
+
param_name, resource_type, object_id = violations[0]
|
|
323
|
+
raise IDORViolationError(
|
|
324
|
+
user_id=user_id,
|
|
325
|
+
resource_type=resource_type,
|
|
326
|
+
object_id=object_id,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def _extract_ids(
|
|
330
|
+
self,
|
|
331
|
+
value: Any,
|
|
332
|
+
pattern: IDPattern,
|
|
333
|
+
) -> list[str]:
|
|
334
|
+
"""Extract object IDs from a parameter value."""
|
|
335
|
+
if pattern.extractor:
|
|
336
|
+
try:
|
|
337
|
+
return pattern.extractor(value)
|
|
338
|
+
except Exception:
|
|
339
|
+
return []
|
|
340
|
+
|
|
341
|
+
# Default extraction logic
|
|
342
|
+
if isinstance(value, str):
|
|
343
|
+
return [value]
|
|
344
|
+
elif isinstance(value, list):
|
|
345
|
+
return [str(v) for v in value if v is not None]
|
|
346
|
+
elif isinstance(value, dict):
|
|
347
|
+
# Look for common ID field names
|
|
348
|
+
for key in ("id", "ID", "_id", "object_id"):
|
|
349
|
+
if key in value:
|
|
350
|
+
return [str(value[key])]
|
|
351
|
+
return []
|
|
352
|
+
else:
|
|
353
|
+
return [str(value)] if value is not None else []
|
|
354
|
+
|
|
355
|
+
def auto_detect_ids(
|
|
356
|
+
self,
|
|
357
|
+
arguments: dict[str, Any],
|
|
358
|
+
) -> dict[str, str]:
|
|
359
|
+
"""
|
|
360
|
+
Auto-detect potential object IDs in arguments.
|
|
361
|
+
|
|
362
|
+
Scans arguments for values that look like object IDs
|
|
363
|
+
(UUIDs, numeric IDs, etc.).
|
|
364
|
+
|
|
365
|
+
Args:
|
|
366
|
+
arguments: The tool call arguments.
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Dictionary mapping parameter names to detected ID types.
|
|
370
|
+
"""
|
|
371
|
+
detected: dict[str, str] = {}
|
|
372
|
+
|
|
373
|
+
for param_name, value in arguments.items():
|
|
374
|
+
if not isinstance(value, str):
|
|
375
|
+
continue
|
|
376
|
+
|
|
377
|
+
# Skip known non-ID parameters
|
|
378
|
+
if param_name in ("query", "content", "message", "text"):
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
# Check for common ID patterns
|
|
382
|
+
if self.UUID_PATTERN.match(value):
|
|
383
|
+
detected[param_name] = "uuid"
|
|
384
|
+
elif self.NUMERIC_PATTERN.match(value) and len(value) <= 20:
|
|
385
|
+
detected[param_name] = "numeric"
|
|
386
|
+
elif (
|
|
387
|
+
self.ALPHANUMERIC_PATTERN.match(value) and
|
|
388
|
+
len(value) <= 50 and
|
|
389
|
+
any(c.isdigit() for c in value)
|
|
390
|
+
):
|
|
391
|
+
detected[param_name] = "alphanumeric"
|
|
392
|
+
|
|
393
|
+
return detected
|
|
394
|
+
|
|
395
|
+
def clear_scope(
|
|
396
|
+
self,
|
|
397
|
+
user_id: str,
|
|
398
|
+
resource_type: str | None = None,
|
|
399
|
+
) -> None:
|
|
400
|
+
"""
|
|
401
|
+
Clear scope for a user.
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
user_id: The user's ID.
|
|
405
|
+
resource_type: Specific resource type to clear, or None for all.
|
|
406
|
+
"""
|
|
407
|
+
with self._lock:
|
|
408
|
+
if user_id not in self._scopes:
|
|
409
|
+
return
|
|
410
|
+
|
|
411
|
+
if resource_type:
|
|
412
|
+
self._scopes[user_id].pop(resource_type, None)
|
|
413
|
+
else:
|
|
414
|
+
del self._scopes[user_id]
|
|
415
|
+
|
|
416
|
+
def add_to_scope(
|
|
417
|
+
self,
|
|
418
|
+
user_id: str,
|
|
419
|
+
resource_type: str,
|
|
420
|
+
object_ids: set[str],
|
|
421
|
+
) -> None:
|
|
422
|
+
"""
|
|
423
|
+
Add object IDs to a user's scope.
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
user_id: The user's ID.
|
|
427
|
+
resource_type: The resource type.
|
|
428
|
+
object_ids: IDs to add.
|
|
429
|
+
"""
|
|
430
|
+
with self._lock:
|
|
431
|
+
if user_id not in self._scopes:
|
|
432
|
+
self._scopes[user_id] = {}
|
|
433
|
+
|
|
434
|
+
if resource_type not in self._scopes[user_id]:
|
|
435
|
+
self._scopes[user_id][resource_type] = ResourceScope()
|
|
436
|
+
|
|
437
|
+
self._scopes[user_id][resource_type].allowed_ids.update(object_ids)
|
|
438
|
+
|
|
439
|
+
def remove_from_scope(
|
|
440
|
+
self,
|
|
441
|
+
user_id: str,
|
|
442
|
+
resource_type: str,
|
|
443
|
+
object_ids: set[str],
|
|
444
|
+
) -> None:
|
|
445
|
+
"""
|
|
446
|
+
Remove object IDs from a user's scope.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
user_id: The user's ID.
|
|
450
|
+
resource_type: The resource type.
|
|
451
|
+
object_ids: IDs to remove.
|
|
452
|
+
"""
|
|
453
|
+
with self._lock:
|
|
454
|
+
if user_id not in self._scopes:
|
|
455
|
+
return
|
|
456
|
+
|
|
457
|
+
if resource_type not in self._scopes[user_id]:
|
|
458
|
+
return
|
|
459
|
+
|
|
460
|
+
self._scopes[user_id][resource_type].allowed_ids -= object_ids
|