mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-30 01:52:18 +00:00
test(mcp): align JWT registry ids with the MCP grammar and add a cold inactive-user case
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Some checks failed
LiteLLM Rust / rust-lint (push) Has been cancelled
LiteLLM Rust / rust-test (push) Has been cancelled
LiteLLM Rust / rust-wheel (push) Has been cancelled
Terraform Modules / fmt, validate, test (aws) (push) Has been cancelled
Terraform Modules / fmt, validate, test (gcp) (push) Has been cancelled
Terraform Provider / gofmt, vet, build, test (push) Has been cancelled
Terraform Provider / Provider endpoints vs proxy OpenAPI schema (push) Has been cancelled
Co-Authored-By: bot_apk <apk@cognition.ai>
This commit is contained in:
parent
e8e4b09fae
commit
1c92361284
3 changed files with 60 additions and 37 deletions
|
|
@ -182,6 +182,8 @@ mcp.<operation>.<auth_family>.<assertion>
|
|||
operation : list_tools | call_tool | list_resources | read_resource | list_prompts | get_prompt
|
||||
auth_family : none | api_key | bearer | oauth
|
||||
assertion : succeeds | denied_without_permission | persists_across_processes
|
||||
| access_group_scoped | toolset_scoped | scoped | denied_invalid_signature
|
||||
| denied_expired | denied_inactive_user | explicit_header_precedence
|
||||
e.g. mcp.call_tool.oauth.succeeds
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -136,92 +136,92 @@
|
|||
source: "user_api_key_auth_mcp.py:2137"
|
||||
rationale: "A key granted a toolset lists exactly the toolset's tools: the rest of the server's catalog stays hidden and every stored name resolves"
|
||||
|
||||
- id: mcp.list_tools.bearer.jwt_valid_scoped
|
||||
- id: mcp.list_tools.bearer.scoped
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: list_tools
|
||||
auth_family: bearer
|
||||
assertions: [valid_scoped]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [scoped]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.call_tool.bearer.jwt_valid_scoped
|
||||
- id: mcp.call_tool.bearer.scoped
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: call_tool
|
||||
auth_family: bearer
|
||||
assertions: [valid_scoped]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [scoped]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.list_tools.bearer.jwt_invalid_signature_denied
|
||||
- id: mcp.list_tools.bearer.denied_invalid_signature
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: list_tools
|
||||
auth_family: bearer
|
||||
assertions: [invalid_signature_denied]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [denied_invalid_signature]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.call_tool.bearer.jwt_invalid_signature_denied
|
||||
- id: mcp.call_tool.bearer.denied_invalid_signature
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: call_tool
|
||||
auth_family: bearer
|
||||
assertions: [invalid_signature_denied]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [denied_invalid_signature]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.list_tools.bearer.jwt_expired_denied
|
||||
- id: mcp.list_tools.bearer.denied_expired
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: list_tools
|
||||
auth_family: bearer
|
||||
assertions: [expired_denied]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [denied_expired]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.call_tool.bearer.jwt_expired_denied
|
||||
- id: mcp.call_tool.bearer.denied_expired
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: call_tool
|
||||
auth_family: bearer
|
||||
assertions: [expired_denied]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [denied_expired]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.list_tools.bearer.jwt_inactive_user_denied
|
||||
- id: mcp.list_tools.bearer.denied_inactive_user
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: list_tools
|
||||
auth_family: bearer
|
||||
assertions: [inactive_user_denied]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [denied_inactive_user]
|
||||
source: "user_api_key_auth.py:1722"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.call_tool.bearer.jwt_inactive_user_denied
|
||||
- id: mcp.call_tool.bearer.denied_inactive_user
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: call_tool
|
||||
auth_family: bearer
|
||||
assertions: [inactive_user_denied]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [denied_inactive_user]
|
||||
source: "user_api_key_auth.py:1722"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.list_tools.bearer.jwt_header_precedence
|
||||
- id: mcp.list_tools.bearer.explicit_header_precedence
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: list_tools
|
||||
auth_family: bearer
|
||||
assertions: [header_precedence]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [explicit_header_precedence]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
||||
- id: mcp.call_tool.bearer.jwt_header_precedence
|
||||
- id: mcp.call_tool.bearer.explicit_header_precedence
|
||||
module: mcp
|
||||
tier: P0
|
||||
operation: call_tool
|
||||
auth_family: bearer
|
||||
assertions: [header_precedence]
|
||||
source: "test_mcp_jwt_auth_e2e.py"
|
||||
assertions: [explicit_header_precedence]
|
||||
source: "user_api_key_auth.py"
|
||||
rationale: "LIT-4506 preserves scoped gateway JWT admission independently of upstream credentials"
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ def granted_mcp(client: McpClient, resources: ResourceManager, jwt_identity: Ide
|
|||
|
||||
|
||||
class TestMcpGatewayJwt:
|
||||
@pytest.mark.covers("mcp.list_tools.bearer.jwt_valid_scoped", "mcp.call_tool.bearer.jwt_valid_scoped")
|
||||
@pytest.mark.covers("mcp.list_tools.bearer.scoped", "mcp.call_tool.bearer.scoped")
|
||||
@pytest.mark.parametrize("header", ("authorization", "x_litellm_api_key"))
|
||||
def test_valid_scoped_jwt_lists_and_calls(
|
||||
self, client: McpClient, granted_mcp: GrantedMcp, header: Literal["authorization", "x_litellm_api_key"]
|
||||
|
|
@ -99,7 +99,7 @@ class TestMcpGatewayJwt:
|
|||
_allowed(client, granted_mcp, headers)
|
||||
|
||||
@pytest.mark.covers(
|
||||
"mcp.list_tools.bearer.jwt_invalid_signature_denied", "mcp.call_tool.bearer.jwt_invalid_signature_denied"
|
||||
"mcp.list_tools.bearer.denied_invalid_signature", "mcp.call_tool.bearer.denied_invalid_signature"
|
||||
)
|
||||
def test_tampered_signature_denies_both_operations(self, client: McpClient, granted_mcp: GrantedMcp) -> None:
|
||||
valid: Final = AuthHeaders(authorization=f"Bearer {granted_mcp.token}")
|
||||
|
|
@ -110,7 +110,7 @@ class TestMcpGatewayJwt:
|
|||
_denied(client, granted_mcp, AuthHeaders(authorization=f"Bearer {tampered}"), "signature")
|
||||
_allowed(client, granted_mcp, valid)
|
||||
|
||||
@pytest.mark.covers("mcp.list_tools.bearer.jwt_expired_denied", "mcp.call_tool.bearer.jwt_expired_denied")
|
||||
@pytest.mark.covers("mcp.list_tools.bearer.denied_expired", "mcp.call_tool.bearer.denied_expired")
|
||||
def test_expired_signed_jwt_denies_both_operations(
|
||||
self, client: McpClient, granted_mcp: GrantedMcp, jwt_identity: Identity, idp: Keycloak
|
||||
) -> None:
|
||||
|
|
@ -125,9 +125,7 @@ class TestMcpGatewayJwt:
|
|||
_denied(client, granted_mcp, AuthHeaders(authorization=f"Bearer {expiring}"), "expired")
|
||||
_allowed(client, granted_mcp, valid)
|
||||
|
||||
@pytest.mark.covers(
|
||||
"mcp.list_tools.bearer.jwt_inactive_user_denied", "mcp.call_tool.bearer.jwt_inactive_user_denied"
|
||||
)
|
||||
@pytest.mark.covers("mcp.list_tools.bearer.denied_inactive_user", "mcp.call_tool.bearer.denied_inactive_user")
|
||||
def test_deactivated_user_cannot_reuse_warm_jwt(
|
||||
self, client: McpClient, granted_mcp: GrantedMcp, jwt_identity: Identity
|
||||
) -> None:
|
||||
|
|
@ -149,7 +147,30 @@ class TestMcpGatewayJwt:
|
|||
)
|
||||
_allowed(client, granted_mcp, headers)
|
||||
|
||||
@pytest.mark.covers("mcp.list_tools.bearer.jwt_header_precedence", "mcp.call_tool.bearer.jwt_header_precedence")
|
||||
@pytest.mark.covers("mcp.list_tools.bearer.denied_inactive_user", "mcp.call_tool.bearer.denied_inactive_user")
|
||||
def test_deactivated_user_is_denied_on_a_cold_jwt(
|
||||
self, client: McpClient, granted_mcp: GrantedMcp, jwt_identity: Identity, idp: Keycloak
|
||||
) -> None:
|
||||
management: Final = build_management_client(client.proxy)
|
||||
management.update_user(
|
||||
UserUpdateBody(
|
||||
user_id=jwt_identity.user_id, user_role="internal_user", metadata=UserScimMetadata(scim_active=False)
|
||||
)
|
||||
)
|
||||
cold: Final = idp.access_token(jwt_identity)
|
||||
assert cold != granted_mcp.token
|
||||
access: Final = GrantedMcp(granted_mcp.server_id, granted_mcp.other_server_id, granted_mcp.tool, cold)
|
||||
_denied(client, access, AuthHeaders(authorization=f"Bearer {cold}"), "deactivated")
|
||||
management.update_user(
|
||||
UserUpdateBody(
|
||||
user_id=jwt_identity.user_id, user_role="internal_user", metadata=UserScimMetadata(scim_active=True)
|
||||
)
|
||||
)
|
||||
_allowed(client, access, AuthHeaders(authorization=f"Bearer {cold}"))
|
||||
|
||||
@pytest.mark.covers(
|
||||
"mcp.list_tools.bearer.explicit_header_precedence", "mcp.call_tool.bearer.explicit_header_precedence"
|
||||
)
|
||||
def test_explicit_gateway_header_wins_without_fallback(
|
||||
self, client: McpClient, granted_mcp: GrantedMcp, resources: ResourceManager
|
||||
) -> None:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue