e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
"""Server-side model grant resolution and redacted model views.
|
|
|
|
Grants carry LLM assignments only (grant v2): embedding and search always
|
|
resolve from the deployment's active profiles, so per-user grants for them
|
|
were never enforced and are not stored.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
from deeptutor.services.config.model_catalog import ModelCatalogService
|
|
from deeptutor.services.model_selection import list_llm_options
|
|
|
|
from .context import get_current_user
|
|
from .grants import load_grant
|
|
from .paths import get_admin_path_service
|
|
|
|
|
|
def admin_catalog_service() -> ModelCatalogService:
|
|
return ModelCatalogService(path=get_admin_path_service().get_settings_file("model_catalog"))
|
|
|
|
|
|
def admin_catalog() -> dict[str, Any]:
|
|
return admin_catalog_service().load()
|
|
|
|
|
|
def _profile_by_id(catalog: dict[str, Any], service: str, profile_id: str) -> dict[str, Any] | None:
|
|
for profile in catalog.get("services", {}).get(service, {}).get("profiles", []) or []:
|
|
if str(profile.get("id") or "") == profile_id:
|
|
return profile
|
|
return None
|
|
|
|
|
|
def _model_by_id(profile: dict[str, Any], model_id: str) -> dict[str, Any] | None:
|
|
for model in profile.get("models", []) or []:
|
|
if str(model.get("id") or "") == model_id:
|
|
return model
|
|
return None
|
|
|
|
|
|
def redacted_model_access(user_id: str | None = None) -> dict[str, list[dict[str, Any]]]:
|
|
user = get_current_user()
|
|
if user_id is None:
|
|
user_id = user.id
|
|
grant = load_grant(user_id)
|
|
catalog = admin_catalog()
|
|
result: dict[str, list[dict[str, Any]]] = {"llm": []}
|
|
for item in grant.get("models", {}).get("llm", []) or []:
|
|
profile_id = str(item.get("profile_id") or item.get("id") or "")
|
|
profile = _profile_by_id(catalog, "llm", profile_id)
|
|
if not profile:
|
|
result["llm"].append(
|
|
{
|
|
"profile_id": profile_id,
|
|
"name": item.get("name") or profile_id or "Unavailable profile",
|
|
"source": "admin",
|
|
"available": False,
|
|
}
|
|
)
|
|
continue
|
|
for model_id in item.get("model_ids") or []:
|
|
model = _model_by_id(profile, str(model_id))
|
|
result["llm"].append(
|
|
{
|
|
"profile_id": profile_id,
|
|
"model_id": str(model_id),
|
|
"name": (model or {}).get("name") or str(model_id),
|
|
"model": (model or {}).get("model") or "",
|
|
"source": "admin",
|
|
"available": model is not None,
|
|
}
|
|
)
|
|
return result
|
|
|
|
|
|
def allowed_llm_options() -> dict[str, Any]:
|
|
user = get_current_user()
|
|
if user.is_admin:
|
|
return list_llm_options(admin_catalog())
|
|
options = [
|
|
{
|
|
"profile_id": item.get("profile_id"),
|
|
"model_id": item.get("model_id"),
|
|
"profile_name": item.get("name") or item.get("profile_id") or "LLM",
|
|
"model_name": item.get("name") or item.get("model") or item.get("model_id"),
|
|
"label": item.get("name") or item.get("model") or item.get("model_id"),
|
|
"model": item.get("model") or "",
|
|
"provider": "",
|
|
"source": "admin",
|
|
"is_active_default": False,
|
|
}
|
|
for item in redacted_model_access(user.id).get("llm", [])
|
|
if item.get("available")
|
|
]
|
|
return {"active": None, "options": options}
|
|
|
|
|
|
def has_capability_access(capability: str, user_id: str | None = None) -> bool:
|
|
"""Whether the user has at least one usable model for ``capability``.
|
|
|
|
Admins are never gated — they manage the catalog directly. For ordinary
|
|
users this mirrors exactly what ``redacted_model_access`` exposes to the
|
|
frontend, so the server-side gate and the UI lock always agree.
|
|
"""
|
|
user = get_current_user()
|
|
if user.is_admin:
|
|
return True
|
|
if user_id is None:
|
|
user_id = user.id
|
|
items = redacted_model_access(user_id).get(capability, []) or []
|
|
return any(item.get("available") for item in items)
|
|
|
|
|
|
def apply_allowed_llm_selection(selection: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
"""Allow only admin-granted LLM profile/model selections for ordinary users."""
|
|
user = get_current_user()
|
|
if user.is_admin or not selection:
|
|
return selection
|
|
profile_id = str(selection.get("profile_id") or "")
|
|
model_id = str(selection.get("model_id") or "")
|
|
for item in redacted_model_access(user.id).get("llm", []):
|
|
if item.get("profile_id") == profile_id and item.get("model_id") == model_id:
|
|
return selection
|
|
raise PermissionError("This model is not assigned to your account.")
|