mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Introduce a single, typed caller identity that is resolved once at the auth boundary and read by reference downstream, instead of being re-derived from a 50-field key object or rebuilt from request metadata. What this adds (litellm/proxy/auth/resolvers/), organized by responsibility: - Principal: a small, frozen, identity-only value type (user / organization / teams / project / end-user / roles / scopes / network), with its sub-models and the role mapping. No budget or policy state; those stay on the key object. - DbIdentityStore: the auth flow's resolver, owning both halves of resolving a caller. resolve_key does the one combined_view lookup (cache, then DB via the shared lower-level helpers, then write-back) and returns the key object, which still flows for budget / rate-limit / policy unchanged. principal_from_key projects the identity slice of that key object into a Principal, issuing no lookup. user_api_key_auth resolves every key through the store rather than calling get_key_object directly; auth_checks.get_key_object stays as the legacy entrypoint for its other callers until they migrate. - network: the X-Forwarded-For / trusted-proxy CIDR primitives live here in one place. trusted_proxy_utils now imports them rather than keeping a second copy. At the seam, user_api_key_auth projects one per-request Principal off the resolved key object and stamps the request network context onto it once (X-Forwarded-For is trusted only when trusted_proxy_ranges is configured). It is attached to request.state.principal for the downstream consumers later phases add. The projection is additive and defensive: a failure never rejects an already-authenticated request, and a missing principal must be treated as deny by any future reader. The Principal is always identifiable (credential_ref and a stable subject off the token), never anonymous. This is additive and changes no behavior today; it is the identity foundation the spend-attribution and authorization phases build on.
162 lines
5.2 KiB
Python
162 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Total-count gate for the strict ruff rules in ruff-strict.toml.
|
|
|
|
Each rule has a hard ceiling (baseline + slack) in ruff-strict-budget.json. The
|
|
gate counts each rule across the whole tree and fails when a rule is both over
|
|
its ceiling and higher than the base it merges into, so a change is blamed for
|
|
the violations it adds, never for drift that already exists in the base.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from collections import Counter
|
|
from pathlib import Path
|
|
from typing import NamedTuple
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
STRICT_CONFIG = REPO_ROOT / "ruff-strict.toml"
|
|
BUDGET_PATH = REPO_ROOT / "ruff-strict-budget.json"
|
|
TARGET = "litellm"
|
|
DEFAULT_BASE = "origin/litellm_internal_staging"
|
|
|
|
_HUNK = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
|
|
|
|
|
|
class Violation(NamedTuple):
|
|
file: str
|
|
line: int
|
|
code: str
|
|
|
|
|
|
class Breach(NamedTuple):
|
|
rule: str
|
|
total: int
|
|
cap: int
|
|
added: int
|
|
|
|
|
|
def _run(cmd: list, cwd: Path = REPO_ROOT) -> str:
|
|
proc = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
|
|
if proc.returncode not in (0, 1):
|
|
sys.stderr.write(proc.stderr)
|
|
raise SystemExit(f"{cmd[0]} exited {proc.returncode}")
|
|
return proc.stdout
|
|
|
|
|
|
def _ruff_json(cwd: Path, config: Path) -> list:
|
|
raw = _run(
|
|
["ruff", "check", TARGET, "--config", str(config), "--output-format", "json"],
|
|
cwd=cwd,
|
|
)
|
|
return json.loads(raw or "[]")
|
|
|
|
|
|
def head_violations() -> list:
|
|
out = []
|
|
for item in _ruff_json(REPO_ROOT, STRICT_CONFIG):
|
|
name = Path(item["filename"])
|
|
rel = (
|
|
(name if name.is_absolute() else REPO_ROOT / name)
|
|
.resolve()
|
|
.relative_to(REPO_ROOT)
|
|
.as_posix()
|
|
)
|
|
out.append(Violation(rel, item["location"]["row"], item["code"]))
|
|
return out
|
|
|
|
|
|
def count_by_rule(violations: list) -> dict:
|
|
return dict(Counter(v.code for v in violations))
|
|
|
|
|
|
def base_counts(ref: str) -> dict:
|
|
parent = Path(tempfile.mkdtemp(prefix="ruff_base_"))
|
|
worktree = parent / "wt"
|
|
try:
|
|
_run(["git", "worktree", "add", "--detach", str(worktree), ref])
|
|
shutil.copy(STRICT_CONFIG, worktree / "ruff-strict.toml")
|
|
items = _ruff_json(worktree, worktree / "ruff-strict.toml")
|
|
return dict(Counter(item["code"] for item in items))
|
|
finally:
|
|
_run(["git", "worktree", "remove", "--force", str(worktree)])
|
|
shutil.rmtree(parent, ignore_errors=True)
|
|
|
|
|
|
def evaluate(head: dict, base: dict, budget: dict) -> list:
|
|
breaches = []
|
|
for rule, spec in budget.items():
|
|
cap = spec["baseline"] + spec["slack"]
|
|
total = head.get(rule, 0)
|
|
if total > cap and total > base.get(rule, 0):
|
|
breaches.append(Breach(rule, total, cap, total - base.get(rule, 0)))
|
|
return sorted(breaches)
|
|
|
|
|
|
def parse_changed_lines(diff_text: str) -> dict:
|
|
changed: dict = {}
|
|
path = None
|
|
for line in diff_text.splitlines():
|
|
if line.startswith("+++ b/"):
|
|
path = line[6:]
|
|
elif path and (match := _HUNK.match(line)):
|
|
start = int(match.group(1))
|
|
count = int(match.group(2)) if match.group(2) is not None else 1
|
|
changed.setdefault(path, set()).update(range(start, start + count))
|
|
return changed
|
|
|
|
|
|
def introduced(violations: list, changed: dict) -> list:
|
|
return [v for v in violations if v.line in changed.get(v.file, set())]
|
|
|
|
|
|
def cmd_check(base: str) -> None:
|
|
budget = json.loads(BUDGET_PATH.read_text())
|
|
head = head_violations()
|
|
base_point = _run(["git", "merge-base", base, "HEAD"]).strip() or base
|
|
breaches = evaluate(count_by_rule(head), base_counts(base_point), budget)
|
|
if not breaches:
|
|
print(f"OK: every strict rule is within its codebase ceiling (base {base})")
|
|
return
|
|
new = introduced(
|
|
head,
|
|
parse_changed_lines(
|
|
_run(["git", "diff", base_point, "--unified=0", "--no-color", "--", TARGET])
|
|
),
|
|
)
|
|
print(f"FAIL: strict-rule totals exceed their ceiling (base {base}):")
|
|
for breach in breaches:
|
|
print(
|
|
f" {breach.rule}: total {breach.total} over cap {breach.cap} (this change added {breach.added})"
|
|
)
|
|
for violation in sorted(v for v in new if v.code == breach.rule):
|
|
print(f" {violation.file}:{violation.line}")
|
|
print(
|
|
"Reduce the new violations or remove an equal number elsewhere; the ceiling is baseline + slack in ruff-strict-budget.json."
|
|
)
|
|
raise SystemExit(1)
|
|
|
|
|
|
def cmd_update() -> None:
|
|
budget = json.loads(BUDGET_PATH.read_text())
|
|
head = count_by_rule(head_violations())
|
|
for rule in budget:
|
|
budget[rule]["baseline"] = head.get(rule, 0)
|
|
BUDGET_PATH.write_text(json.dumps(budget, indent=2, sort_keys=True) + "\n")
|
|
print("Re-captured per-rule baselines from the current tree")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--base", default=DEFAULT_BASE)
|
|
parser.add_argument("--update", action="store_true")
|
|
args = parser.parse_args()
|
|
cmd_update() if args.update else cmd_check(args.base)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|