mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-25 02:34:29 +00:00
Internal User Endpoint - vulnerability fix + response type fix (#8228)
* fix(key_management_endpoints.py): fix vulnerability where a user could update another user's keys Resolves https://github.com/BerriAI/litellm/issues/8031 * test(key_management_endpoints.py): return consistent 403 forbidden error when modifying key that doesn't belong to user * fix(internal_user_endpoints.py): return model max budget in internal user create response Fixes https://github.com/BerriAI/litellm/issues/7047 * test: fix test * test: update test to handle gemini token counter change * fix(factory.py): fix bedrock http:// handling * docs: fix typo in lm_studio.md (#8222) * test: fix testing * test: fix test --------- Co-authored-by: foreign-sub <51928805+foreign-sub@users.noreply.github.com>
This commit is contained in:
parent
f6bd48a1c5
commit
df93debbc7
7 changed files with 240 additions and 28 deletions
|
@ -315,3 +315,142 @@ async def test_user_model_access():
|
|||
key=key,
|
||||
model="groq/claude-3-5-haiku-20241022",
|
||||
)
|
||||
|
||||
|
||||
import json
|
||||
import uuid
|
||||
import pytest
|
||||
import aiohttp
|
||||
from typing import Dict, Tuple
|
||||
|
||||
|
||||
async def setup_test_users(session: aiohttp.ClientSession) -> Tuple[Dict, Dict]:
|
||||
"""
|
||||
Create two test users and an additional key for the first user.
|
||||
Returns tuple of (user1_data, user2_data) where each contains user info and keys.
|
||||
"""
|
||||
# Create two test users
|
||||
user1 = await new_user(
|
||||
session=session,
|
||||
i=0,
|
||||
budget=100,
|
||||
budget_duration="30d",
|
||||
models=["anthropic.claude-3-5-sonnet-20240620-v1:0"],
|
||||
)
|
||||
|
||||
user2 = await new_user(
|
||||
session=session,
|
||||
i=1,
|
||||
budget=100,
|
||||
budget_duration="30d",
|
||||
models=["anthropic.claude-3-5-sonnet-20240620-v1:0"],
|
||||
)
|
||||
|
||||
print("\nCreated two test users:")
|
||||
print(f"User 1 ID: {user1['user_id']}")
|
||||
print(f"User 2 ID: {user2['user_id']}")
|
||||
|
||||
# Create an additional key for user1
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {user1['key']}",
|
||||
}
|
||||
|
||||
key_payload = {
|
||||
"user_id": user1["user_id"],
|
||||
"duration": "7d",
|
||||
"key_alias": f"test_key_{uuid.uuid4()}",
|
||||
"models": ["anthropic.claude-3-5-sonnet-20240620-v1:0"],
|
||||
}
|
||||
|
||||
print("\nGenerating additional key for user1...")
|
||||
key_response = await session.post(
|
||||
f"http://0.0.0.0:4000/key/generate", headers=headers, json=key_payload
|
||||
)
|
||||
|
||||
assert key_response.status == 200, "Failed to generate additional key for user1"
|
||||
user1_additional_key = await key_response.json()
|
||||
|
||||
print(f"\nGenerated key details:")
|
||||
print(json.dumps(user1_additional_key, indent=2))
|
||||
|
||||
# Return both users' data including the additional key
|
||||
return {
|
||||
"user_data": user1,
|
||||
"additional_key": user1_additional_key,
|
||||
"headers": headers,
|
||||
}, {
|
||||
"user_data": user2,
|
||||
"headers": {
|
||||
"Content-Type": "application/json",
|
||||
"Authorization": f"Bearer {user2['key']}",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
async def print_response_details(response: aiohttp.ClientResponse) -> None:
|
||||
"""Helper function to print response details"""
|
||||
print("\nResponse Details:")
|
||||
print(f"Status Code: {response.status}")
|
||||
print("\nResponse Content:")
|
||||
try:
|
||||
formatted_json = json.dumps(await response.json(), indent=2)
|
||||
print(formatted_json)
|
||||
except json.JSONDecodeError:
|
||||
print(await response.text())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_key_update_user_isolation():
|
||||
"""Test that a user cannot update a key that belongs to another user"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
user1_data, user2_data = await setup_test_users(session)
|
||||
|
||||
# Try to update the key to belong to user2
|
||||
update_payload = {
|
||||
"key": user1_data["additional_key"]["key"],
|
||||
"user_id": user2_data["user_data"][
|
||||
"user_id"
|
||||
], # Attempting to change ownership
|
||||
"metadata": {"purpose": "testing_user_isolation", "environment": "test"},
|
||||
}
|
||||
|
||||
print("\nAttempting to update key ownership to user2...")
|
||||
update_response = await session.post(
|
||||
f"http://0.0.0.0:4000/key/update",
|
||||
headers=user1_data["headers"], # Using user1's headers
|
||||
json=update_payload,
|
||||
)
|
||||
|
||||
await print_response_details(update_response)
|
||||
|
||||
# Verify update attempt was rejected
|
||||
assert (
|
||||
update_response.status == 403
|
||||
), "Request should have been rejected with 403 status code"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_key_delete_user_isolation():
|
||||
"""Test that a user cannot delete a key that belongs to another user"""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
user1_data, user2_data = await setup_test_users(session)
|
||||
|
||||
# Try to delete user1's additional key using user2's credentials
|
||||
delete_payload = {
|
||||
"keys": [user1_data["additional_key"]["key"]],
|
||||
}
|
||||
|
||||
print("\nAttempting to delete user1's key using user2's credentials...")
|
||||
delete_response = await session.post(
|
||||
f"http://0.0.0.0:4000/key/delete",
|
||||
headers=user2_data["headers"],
|
||||
json=delete_payload,
|
||||
)
|
||||
|
||||
await print_response_details(delete_response)
|
||||
|
||||
# Verify delete attempt was rejected
|
||||
assert (
|
||||
delete_response.status == 403
|
||||
), "Request should have been rejected with 403 status code"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue