Rename IdentityResolver -> Resolver and DbIdentityStore -> DbResolver so the
names stop colliding with authentication; the package these move to is
resolvers/, not identity/. Drop the IdentityStore union protocol, which was only
used as DbResolver's base; DbResolver now inherits Resolver and ProvisioningStore
directly. Drop the @runtime_checkable decorators on these protocols, which had no
isinstance callers. ProvisioningStore keeps its name.
Principal carried user, organization and teams but omitted the project
and end-user attribution axes that the existing key path tracks. Add
ProjectIdentity and EndUserIdentity sub-models and the matching optional
fields, and project them off the key object in _principal_from_key:
project_id/project_alias map to ProjectIdentity, end_user_id to
EndUserIdentity. Both stay None when absent.
project_id is a column on the verification token, so it resolves from
the combined-view key object directly. end_user_id is request-scoped and
will be stamped at the seam, the same way network context is; the
resolver maps it whenever the key carries it.
There was only ever one resolver, so the resolvers/ package (base, db,
utils) collapses into a single resolvers.py holding the protocols and
DbIdentityStore, plus a utils.py for the pure SCIM/role-mapping helpers.
Drops the unused roles_from_claims/public_claims helpers.
Scope checking is identity state, so it belongs on the Principal rather
than a standalone authorization/scopes.py helper. Callers now use
principal.has_required_scopes(security_scopes).
Add ABACEngine alongside RBACEngine for attribute-based decisions over subject
attributes (roles, teams, org, scopes, claims) and resource attributes
(endpoint, method, model, mcp_server, mcp_tool). Policies are operator-supplied
YAML loaded via add_policy.
The Casbin CSV FileAdapter is avoided on purpose: it retains the quotes around a
comma-bearing expression, turning an eval'd rule into a truthy string literal and
silently allowing inputs that should deny. Claims access yields None for missing
keys so a single policy row referencing an absent claim cannot poison the whole
decision, and rule-evaluation errors fail closed.
Engine only; not yet wired into the live request path
Add a platform_admin POST /scim/v2/Groups assertion alongside the existing
/scim/v2/Users DELETE so the multi-segment grant is pinned on a second deep
path, and correct the stale keyMatch2 comment to keyMatch.
The Casbin object matcher spans path separators now, so a "/*" or "/api/*"
policy covers nested routes:
- a granted role is allowed on a multi-level path (platform_viewer GET
/api/v1/models, platform_admin POST /api/v1/x/y)
- an ungranted role/verb is still denied across segments (org_viewer and
GET-only viewers on writes), and the anchored act matcher still rejects a
superstring verb (GETX)
- an operator CSV object pattern spans segments the same way
Full auth_v2 suite: 178 passing.
The forwarded subject-DN trust gate keys on the raw socket peer and prefers a
verified TLS-layer cert:
- an untrusted peer cannot smuggle a forged DN by claiming a trusted address via
X-Forwarded-For (the gate ignores XFF)
- a verified client cert from the ASGI TLS extension wins over a proxy-forwarded
DN header
Full auth_v2 suite: 175 passing.
The veria review-response fix gates IdP-asserted roles through the same
per-provider allowlist on every role-bearing path, not just JWT bearer tokens.
- rbac: filter_claim_roles (the shared gate) denies a self-asserted role by
default, filters to the allowlist, and only admits platform roles behind the
explicit allow_platform_roles flag
- saml: a signed SSO assertion asserting platform_admin yields a session whose
Principal has no roles by default, and is filtered to the allowlist when set
- oidc: the login callback's identity build (map userinfo -> gate roles ->
session) denies platform_admin by default and filters to the allowlist
Full auth_v2 suite: 173 passing.
Regression coverage for the security review fixes (H1/M1/M3/S7):
- resolver: a deactivated SCIM user (active=False) is rejected 403; claims
whose keys start with "_" never surface on the Principal
- authenticators: H1 privilege escalation - a self-asserted token role grants
nothing without a per-provider allowlist, the allowlist filters roles, and
platform-level roles need an explicit allow_platform_roles gate
- rbac: the Casbin act matcher is anchored, so a "GET" policy does not grant
"GETX"
- scim: PATCH that targets the read-only id (replace, remove, no-path replace)
is rejected 400 with the record's id unchanged; unauthenticated/under-scoped
requests render a SCIM Error body (401/403); /Schemas is a ListResponse
envelope
- saml: a replayed signed assertion is rejected 401 (single-use), and an
unsolicited IdP-initiated response is rejected 401 when allow_unsolicited is
off (default secure)
Full auth_v2 suite: 165 passing.
Follow the sub-package split (oidc/saml/scim/*) and the token-claim hardening:
- import config models (OIDCProviderConfig, SAMLConfig) from the top-level
package and the moved helpers from their concrete sub-modules
(saml.router, saml.config, oidc.router) so tests are stable against
__init__ re-export churn
- resolver: a token group claim is no longer authoritative on its own; it
becomes a TeamIdentity only when it resolves to a provisioned SCIM Group in
the store (split into provisioned vs not-provisioned cases)
Full auth_v2 suite green (153) and stable across repeated runs.
Repoint the whole test surface off install_auth/app.state onto the AuthSecurity
composition root: build AuthSecurity(config, store, ...) and declare routes with
Security(auth.principal[, scopes]), auth.require_roles, auth.require_permission;
mount routers via build_*_router(auth). Apply the PEP8 renames (JWTVerifier,
APIKeyAuthenticator, OIDCAuthenticator, MutualTLSAuthenticator, OIDCProviderConfig,
SAMLConfig, MutualTLSConfig, RBACEngine.has_any_role). SAML moves to the shared
SessionStore + "litellm_session" cookie and session.safe_relay_state; the
build_authenticators tests assert concrete types now that the scheme attribute is
gone. No coverage lost; 151 tests pass.
Note: routes use the default-value Security() style instead of Annotated[...]
because this module runs under future annotations, where an Annotated marker is
stringified and FastAPI re-evaluates it in module globals, which cannot see the
closure-local auth instance.
Cover the security fixes landed in 71a189b, ca896ac, 6f3fc5e and 4503499:
- HTTP basic now verifies the password via an injected BasicAuthVerifier:
correct creds 200, wrong password / unknown user / no verifier wired all 401
(fail closed), and the password is never carried on the credential; plus a
unit test that hash_basic_password is salted and InMemoryBasicAuthStore
verifies it
- mTLS only trusts the forwarded subject-DN header from a peer inside the
trusted-proxy CIDRs; a forged header from an untrusted peer is ignored
- SCIM discovery endpoints (ServiceProviderConfig, ResourceTypes, Schemas) are
public, Users/Groups stay guarded, DELETE on a missing resource is a SCIM 404
Error, and PATCH honors nested dotted paths while rejecting filter paths 400
- SAML ACS sets a Secure session cookie, binds the redirect target server-side
so a client-supplied form RelayState is never trusted (falls back to the
default path), and the session store enforces TTL expiry and size eviction
Mutation-checked: removing the basic-auth password check or the mTLS
trusted-peer gate fails these.
The module moved from litellm/auth_v2 to litellm/proxy/auth_v2 (commit 104a5e1),
so the mirrored tests move from tests/test_litellm/auth_v2 to
tests/test_litellm/proxy/auth_v2 and their imports switch to
litellm.proxy.auth_v2. No behavior change; 134 tests still pass at the new path.