test(auth_v2): pin multi-segment path authorization (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.
This commit is contained in:
Yassin Kortam 2026-06-11 09:51:14 -07:00
parent e989016c2d
commit 2fad79b4fe

View file

@ -159,3 +159,41 @@ def test_filter_claim_roles(roles, allowed, allow_platform, expected):
from litellm.proxy.auth_v2.rbac import filter_claim_roles
assert filter_claim_roles(roles, allowed, allow_platform) == expected
# --------------------------------------------------------------------------- #
# Object matcher spans path separators (keyMatch): multi-segment authorization
# --------------------------------------------------------------------------- #
def test_enforce_matches_multi_segment_paths(engine):
# "/*" now spans separators, so nested routes are covered by the default policy
assert engine.enforce(
_principal(roles=[Role.PLATFORM_VIEWER]), "/api/v1/models", "GET"
)
assert engine.enforce(
_principal(roles=[Role.PLATFORM_ADMIN]), "/api/v1/x/y", "POST"
)
def test_enforce_denies_multi_segment_when_unauthorized(engine):
# viewer is GET-only and org_viewer has no write grant, even on nested paths;
# the act anchor still rejects a superstring verb
assert not engine.enforce(
_principal(roles=[Role.ORG_VIEWER]), "/api/v1/models", "POST"
)
assert not engine.enforce(
_principal(roles=[Role.PLATFORM_VIEWER]), "/api/v1/models", "POST"
)
assert not engine.enforce(
_principal(roles=[Role.PLATFORM_VIEWER]), "/api/v1/models", "GETX"
)
def test_operator_csv_object_pattern_spans_segments(tmp_path):
policy = tmp_path / "policy.csv"
policy.write_text("p, org_viewer, /api/*, GET\n")
engine = RBACEngine(policy_path=str(policy))
viewer = _principal(roles=[Role.ORG_VIEWER])
assert engine.enforce(viewer, "/api/v1/models", "GET")
assert not engine.enforce(viewer, "/api/v1/models", "GETX")