refactor(proxy): group auth_v2 into authn/authz/stages subpackages

Pure move (git mv, history preserved): the flat 17-file package hid the
architecture behind an alphabetical wall. Group by request phase so the module
is self-documenting:

  authn/   authenticators, jwt_claims, jwt_verifier, oauth2_introspection
  authz/   enforcer + model.conf, authorizer, route_map, policy_store, policy_admin
  stages/  enrichment, end_user, budgets
  (top)    entry, context, principal, management_endpoints, __init__

No content changes beyond rewired relative imports; tests moved to mirror the
layout. Behavior is identical — the same 129 tests pass, mypy/black/ruff clean,
and the package still imports with casbin/authlib absent.
This commit is contained in:
ryan-crabbe-berri 2026-06-05 12:25:11 -07:00
parent c7f1215bad
commit 764ffd834e
33 changed files with 33 additions and 33 deletions

View file

@ -45,13 +45,13 @@ independent of v1/v2.
JWT, and OAuth authenticators return a thin identity (no budget/limit fields), so
the hooks read `None` and enforce nothing for those logins.
**Built.** `enrichment.py` (`enrich_identity`) copies the user/team limit fields
**Built.** `stages/enrichment.py` (`enrich_identity`) copies the user/team limit fields
1:1 from the rows into the identity's distinct `user_*` / `team_*` slots, filling
only unset fields so it never overrides an already-resolved value. It is wired
into the inference path in `entry.py` for non-virtual-key logins
(`_enrich_for_limits`), with the loaders (`get_user_object` auth_checks.py:1650,
`get_team_object` auth_checks.py:1982) injected. Unit-tested in
`test_enrichment.py`.
`stages/test_enrichment.py`.
**What remains (live).** Only the verification below. The mapping is additive
(these logins enforce nothing today, so it cannot regress existing behavior), but
@ -74,12 +74,12 @@ limit silently over- or under-enforces a customer's spend.
**Done.** Team, organization, and global proxy-spend caps live in v1's
`common_checks`, which v2 does not run. Rather than move them (which would risk
v1's path), `budgets.py` (`enforce_hierarchy_budgets`) calls the *same* functions
v1's path), `stages/budgets.py` (`enforce_hierarchy_budgets`) calls the *same* functions
v1 uses — `_team_max_budget_check`, `_organization_max_budget_check`, and
`get_global_proxy_spend` + `_global_proxy_budget_check` — from v2's inference
path. One implementation, two callers: single authority, correct counter keys, no
edit to v1. A breach surfaces as the same 429 `ProxyException` v1 raises.
Unit-tested in `test_budgets.py` (team over/under budget, no-team no-op).
Unit-tested in `stages/test_budgets.py` (team over/under budget, no-team no-op).
**What remains (live).** Cross-pod spend-counter accuracy. Set a team `max_budget`
(and `litellm.max_budget` for the global cap) below current spend and confirm

View file

@ -6,9 +6,9 @@ from .context import (
set_auth_context,
try_get_auth_context,
)
from .end_user import resolve_end_user
from .enrichment import enrich_identity
from .entry import user_api_key_auth_v2
from .stages.end_user import resolve_end_user
from .stages.enrichment import enrich_identity
__all__ = [
"user_api_key_auth_v2",

View file

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, List, Optional, Protocol, runtime_checkab
from fastapi import HTTPException, status
from .context import AuthMethod
from ..context import AuthMethod
if TYPE_CHECKING:
from litellm.proxy._types import UserAPIKeyAuth

View file

View file

@ -1,7 +1,7 @@
import logging
from typing import Any, Dict, Optional
from .principal import Principal
from ..principal import Principal
from .route_map import GovernedRoute, match_route
logger = logging.getLogger("litellm.proxy.auth.v2")

View file

@ -4,16 +4,16 @@ from fastapi import HTTPException, Request, status
from litellm.integrations.otel.runtime import seed_request_identity
from .authenticators import AuthContext, AuthResult, authenticate
from .authorizer import AuthorizationDenied, authorize
from .budgets import enforce_hierarchy_budgets
from .authn.authenticators import AuthContext, AuthResult, authenticate
from .authz.authorizer import AuthorizationDenied, authorize
from .authz.enforcer import CasbinEnforcer
from .authz.policy_store import load_policy_snapshot
from .authz.route_map import is_inference_route, match_route
from .context import AuthMethod, RequestAuthContext, set_auth_context
from .end_user import resolve_end_user
from .enforcer import CasbinEnforcer
from .enrichment import enrich_identity
from .policy_store import load_policy_snapshot
from .principal import Principal, build_principal
from .route_map import is_inference_route, match_route
from .stages.budgets import enforce_hierarchy_budgets
from .stages.end_user import resolve_end_user
from .stages.enrichment import enrich_identity
async def _anonymous_identity(api_key: Optional[str]) -> Any:

View file

@ -6,12 +6,12 @@ from pydantic import BaseModel
from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from .policy_admin import (
from .authz.policy_admin import (
PolicyValidationError,
make_assignment_rule,
make_permission_rule,
)
from .policy_store import reset_cache
from .authz.policy_store import reset_cache
router = APIRouter(tags=["auth_v2"])

View file

View file

@ -2,7 +2,7 @@ from typing import Awaitable, Callable, Optional
from starlette.requests import Request
from .context import attach_end_user
from ..context import attach_end_user
# Extraction reuses the existing request-body/header logic; validation (the
# customer-table lookup) is injected so this stage is testable without a DB and

View file

@ -1,6 +1,6 @@
import pytest
from litellm.proxy.auth.v2.authenticators import (
from litellm.proxy.auth.v2.authn.authenticators import (
OAuth2IntrospectionAuthenticator,
JWTAuthenticator,
MasterKeyAuthenticator,

View file

@ -1,6 +1,6 @@
import pytest
from litellm.proxy.auth.v2.jwt_claims import (
from litellm.proxy.auth.v2.authn.jwt_claims import (
JWTClaimError,
JWTSettings,
extract_identity,

View file

@ -3,7 +3,7 @@ import time
import pytest
from authlib.jose import JsonWebKey, jwt
from litellm.proxy.auth.v2.jwt_verifier import JWTVerificationError, verify
from litellm.proxy.auth.v2.authn.jwt_verifier import JWTVerificationError, verify
ISSUER = "https://idp.example"
AUDIENCE = "litellm"

View file

@ -1,6 +1,6 @@
import pytest
from litellm.proxy.auth.v2.oauth2_introspection import (
from litellm.proxy.auth.v2.authn.oauth2_introspection import (
IntrospectionSettings,
OAuth2IntrospectionError,
parse_introspection_response,

View file

@ -2,7 +2,7 @@ import logging
import pytest
from litellm.proxy.auth.v2.authorizer import AuthorizationDenied, authorize
from litellm.proxy.auth.v2.authz.authorizer import AuthorizationDenied, authorize
from litellm.proxy.auth.v2.principal import Principal
PRINCIPAL = Principal(subject="user:u1", domain="*", groupings=[])

View file

@ -1,4 +1,4 @@
from litellm.proxy.auth.v2.enforcer import CasbinEnforcer
from litellm.proxy.auth.v2.authz.enforcer import CasbinEnforcer
READER_POLICY = ["role:model_reader", "*", "model:*", "read", "allow"]
ADMIN_POLICY = ["role:proxy_admin", "*", "*", "*", "allow"]

View file

@ -1,6 +1,6 @@
import pytest
from litellm.proxy.auth.v2.policy_admin import (
from litellm.proxy.auth.v2.authz.policy_admin import (
PolicyValidationError,
make_assignment_rule,
make_permission_rule,

View file

@ -1,7 +1,7 @@
import pytest
from litellm.proxy.auth.v2 import policy_store
from litellm.proxy.auth.v2.policy_store import (
from litellm.proxy.auth.v2.authz import policy_store
from litellm.proxy.auth.v2.authz.policy_store import (
DEFAULT_POLICIES,
load_policy_snapshot,
reset_cache,

View file

@ -1,4 +1,4 @@
from litellm.proxy.auth.v2.route_map import is_inference_route, match_route
from litellm.proxy.auth.v2.authz.route_map import is_inference_route, match_route
def test_model_routes_map_to_resource_and_action():

View file

@ -12,7 +12,7 @@ import pytest
import litellm
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.auth.v2.budgets import enforce_hierarchy_budgets
from litellm.proxy.auth.v2.stages.budgets import enforce_hierarchy_budgets
class _Logging:

View file

@ -8,7 +8,7 @@ from litellm.proxy.auth.v2.context import (
get_auth_context,
set_auth_context,
)
from litellm.proxy.auth.v2.end_user import resolve_end_user
from litellm.proxy.auth.v2.stages.end_user import resolve_end_user
from litellm.proxy.auth.v2.principal import Principal

View file

@ -2,7 +2,7 @@ from types import SimpleNamespace
import pytest
from litellm.proxy.auth.v2.enrichment import enrich_identity
from litellm.proxy.auth.v2.stages.enrichment import enrich_identity
def _identity(**overrides):

View file

@ -13,7 +13,7 @@ from fastapi.testclient import TestClient
import litellm.proxy.proxy_server as ps
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
from litellm.proxy.auth.v2 import policy_store
from litellm.proxy.auth.v2.authz import policy_store
from litellm.proxy.auth.v2.management_endpoints import router as auth_v2_router
MASTER_KEY = "sk-master-1234"