From 2fad79b4fecbeec0617bc759acabd7c2098108ed Mon Sep 17 00:00:00 2001 From: Yassin Kortam Date: Thu, 11 Jun 2026 09:51:14 -0700 Subject: [PATCH] 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. --- tests/test_litellm/proxy/auth_v2/test_rbac.py | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/test_litellm/proxy/auth_v2/test_rbac.py b/tests/test_litellm/proxy/auth_v2/test_rbac.py index 7dc9738bf57..69f557762c4 100644 --- a/tests/test_litellm/proxy/auth_v2/test_rbac.py +++ b/tests/test_litellm/proxy/auth_v2/test_rbac.py @@ -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")