mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-17 23:51:30 +00:00
Move the admin surface toward explicit, backend-owned routers instead of trimming the proxy app by path allowlist. This adds a backend/routers package whose teams router is the source of truth for /admin/teams CRUD plus membership; proxy_server imports and mounts it (guarded by the backend package being importable, since the pip wheel ships only litellm), and backend/main keeps those explicit routes regardless of the allowlist. Every route authenticates through the auth_v2 AuthSecurity stored on app.state.auth_v2 (a require_roles gate over the Principal) rather than the legacy user_api_key_auth dependency; app.state.auth_v2 is wired in the proxy startup once the DB is connected. Two resolver fixes were needed to make the DB-backed path actually work, since it was previously only exercised against an in-memory store: API-key principals now resolve their platform role from the owning user (get_key_object does not join user_role onto the token), and team group writes wrap members_with_roles in prisma Json so upsert_group persists. db_team_to_scim now carries members so team membership round-trips on read.
37 lines
1.3 KiB
Python
37 lines
1.3 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Callable, cast
|
|
|
|
from fastapi import Request
|
|
from fastapi.security import SecurityScopes
|
|
|
|
from litellm.proxy.auth_v2 import Principal, Role
|
|
from litellm.proxy.auth_v2.errors import forbidden_role
|
|
from litellm.proxy.auth_v2.resolvers import ProvisioningStore
|
|
from litellm.proxy.auth_v2.security import AuthSecurity
|
|
|
|
|
|
def get_auth(request: Request) -> AuthSecurity:
|
|
return cast(AuthSecurity, request.app.state.auth_v2)
|
|
|
|
|
|
def team_store(request: Request) -> ProvisioningStore:
|
|
return cast(ProvisioningStore, get_auth(request).resolver)
|
|
|
|
|
|
def require_roles(*allowed: Role) -> Callable[[Request], object]:
|
|
"""Request-scoped role gate built on the ``auth_v2`` Security layer.
|
|
|
|
Mirrors ``AuthSecurity.require_roles`` but reaches the per-app ``AuthSecurity``
|
|
via ``request.app.state`` at request time, since these routers are wired into
|
|
the app after import rather than closing over an instance.
|
|
"""
|
|
|
|
async def dependency(request: Request) -> Principal:
|
|
auth = get_auth(request)
|
|
principal = await auth.principal(SecurityScopes(scopes=[]), request)
|
|
if not auth.authorizer.has_any_role(principal, allowed):
|
|
raise forbidden_role()
|
|
return principal
|
|
|
|
return dependency
|