Harden session endpoint owner scope (#1308)

This commit is contained in:
Vykos
2026-06-02 19:40:22 +02:00
committed by GitHub
parent 80de69ebb0
commit 4771d80eb2
6 changed files with 261 additions and 71 deletions

View File

@@ -234,7 +234,7 @@ def resolve_endpoint(
ep_id = _stg(f"{setting_prefix}_endpoint_id")
model = _stg(f"{setting_prefix}_model")
# If the specific endpoint is not configured, but the caller provided a
# If the specific endpoint is not configured, but the caller provided a
# valid fallback (e.g. the active session model), use that immediately.
# This prevents background tasks from jumping to the global default_model
# when the user is mid-conversation with a different model.
@@ -295,7 +295,7 @@ def resolve_endpoint(
def resolve_endpoint_by_id(
ep_id: str, model: Optional[str] = None
ep_id: str, model: Optional[str] = None, owner: Optional[str] = None
) -> Optional[Tuple[str, str, Dict]]:
"""Resolve a specific endpoint id (+ optional model) to (chat_url, model, headers).
@@ -306,10 +306,14 @@ def resolve_endpoint_by_id(
return None
db = SessionLocal()
try:
ep = db.query(ModelEndpoint).filter(
q = db.query(ModelEndpoint).filter(
ModelEndpoint.id == ep_id,
ModelEndpoint.is_enabled == True,
).first()
)
if owner:
from src.auth_helpers import owner_filter
q = owner_filter(q, ModelEndpoint, owner)
ep = q.first()
if not ep:
return None
base = normalize_base(ep.base_url)
@@ -332,14 +336,14 @@ def resolve_endpoint_by_id(
db.close()
def resolve_chat_fallback_candidates() -> list:
def resolve_chat_fallback_candidates(owner: Optional[str] = None) -> list:
"""Build the configured default-chat fallback chain as a list of
(chat_url, model, headers) tuples, skipping any that can't resolve.
The primary model is NOT included — callers prepend their session's
current (url, model, headers) so per-session model overrides are honored.
"""
return _resolve_fallback_candidates("default_model_fallbacks")
return _resolve_fallback_candidates("default_model_fallbacks", owner=owner)
def resolve_utility_fallback_candidates(owner: Optional[str] = None) -> list:
@@ -355,9 +359,9 @@ def resolve_utility_fallback_candidates(owner: Optional[str] = None) -> list:
return _resolve_fallback_candidates("utility_model_fallbacks", owner=owner)
def resolve_vision_fallback_candidates() -> list:
def resolve_vision_fallback_candidates(owner: Optional[str] = None) -> list:
"""Configured fallback chain for the Vision model (`vision_model_fallbacks`)."""
return _resolve_fallback_candidates("vision_model_fallbacks")
return _resolve_fallback_candidates("vision_model_fallbacks", owner=owner)
def _resolve_fallback_candidates(setting_key: str, owner: Optional[str] = None) -> list:
@@ -371,7 +375,7 @@ def _resolve_fallback_candidates(setting_key: str, owner: Optional[str] = None)
for entry in chain:
if not isinstance(entry, dict):
continue
resolved = resolve_endpoint_by_id(entry.get("endpoint_id", ""), entry.get("model", ""))
resolved = resolve_endpoint_by_id(entry.get("endpoint_id", ""), entry.get("model", ""), owner=owner)
if resolved:
out.append(resolved)
return out

View File

@@ -3,11 +3,11 @@
from src.endpoint_resolver import resolve_endpoint
def resolve_task_endpoint(fallback_url=None, fallback_model=None, fallback_headers=None):
def resolve_task_endpoint(fallback_url=None, fallback_model=None, fallback_headers=None, owner=None):
"""Return (endpoint_url, model, headers) for background tasks.
Reads task_endpoint_id / task_model from admin settings.
Falls back to the provided values when the setting is empty or the
endpoint cannot be resolved.
"""
return resolve_endpoint("task", fallback_url, fallback_model, fallback_headers)
return resolve_endpoint("task", fallback_url, fallback_model, fallback_headers, owner=owner)