forked from phoenix/litellm-mirror
Merge pull request #3184 from BerriAI/litellm_ui_non_admins_flow
[UI] - non admin flow - only Create + Test Key available
This commit is contained in:
commit
f89f8a4157
7 changed files with 179 additions and 57 deletions
|
@ -87,6 +87,14 @@ class LiteLLMRoutes(enum.Enum):
|
||||||
"/v2/key/info",
|
"/v2/key/info",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
sso_only_routes: List = [
|
||||||
|
"/key/generate",
|
||||||
|
"/key/update",
|
||||||
|
"/key/delete",
|
||||||
|
"/global/spend/logs",
|
||||||
|
"/global/predict/spend/logs",
|
||||||
|
]
|
||||||
|
|
||||||
management_routes: List = [ # key
|
management_routes: List = [ # key
|
||||||
"/key/generate",
|
"/key/generate",
|
||||||
"/key/update",
|
"/key/update",
|
||||||
|
|
|
@ -1053,6 +1053,11 @@ async def user_api_key_auth(
|
||||||
status_code=status.HTTP_403_FORBIDDEN,
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
detail="key not allowed to access this team's info",
|
detail="key not allowed to access this team's info",
|
||||||
)
|
)
|
||||||
|
elif (
|
||||||
|
_has_user_setup_sso()
|
||||||
|
and route in LiteLLMRoutes.sso_only_routes.value
|
||||||
|
):
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Only master key can be used to generate, delete, update info for new keys/users/teams. Route={route}"
|
f"Only master key can be used to generate, delete, update info for new keys/users/teams. Route={route}"
|
||||||
|
@ -1102,6 +1107,13 @@ async def user_api_key_auth(
|
||||||
return UserAPIKeyAuth(
|
return UserAPIKeyAuth(
|
||||||
api_key=api_key, user_role="proxy_admin", **valid_token_dict
|
api_key=api_key, user_role="proxy_admin", **valid_token_dict
|
||||||
)
|
)
|
||||||
|
elif (
|
||||||
|
_has_user_setup_sso()
|
||||||
|
and route in LiteLLMRoutes.sso_only_routes.value
|
||||||
|
):
|
||||||
|
return UserAPIKeyAuth(
|
||||||
|
api_key=api_key, user_role="app_owner", **valid_token_dict
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"This key is made for LiteLLM UI, Tried to access route: {route}. Not allowed"
|
f"This key is made for LiteLLM UI, Tried to access route: {route}. Not allowed"
|
||||||
|
@ -5721,6 +5733,20 @@ async def new_user(data: NewUserRequest):
|
||||||
"user" # only create a user, don't create key if 'auto_create_key' set to False
|
"user" # only create a user, don't create key if 'auto_create_key' set to False
|
||||||
)
|
)
|
||||||
response = await generate_key_helper_fn(**data_json)
|
response = await generate_key_helper_fn(**data_json)
|
||||||
|
|
||||||
|
# Admin UI Logic
|
||||||
|
# if team_id passed add this user to the team
|
||||||
|
if data_json.get("team_id", None) is not None:
|
||||||
|
await team_member_add(
|
||||||
|
data=TeamMemberAddRequest(
|
||||||
|
team_id=data_json.get("team_id", None),
|
||||||
|
member=Member(
|
||||||
|
user_id=data_json.get("user_id", None),
|
||||||
|
role="user",
|
||||||
|
user_email=data_json.get("user_email", None),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
return NewUserResponse(
|
return NewUserResponse(
|
||||||
key=response.get("token", ""),
|
key=response.get("token", ""),
|
||||||
expires=response.get("expires", None),
|
expires=response.get("expires", None),
|
||||||
|
@ -6526,13 +6552,20 @@ async def team_member_add(
|
||||||
existing_team_row = await prisma_client.get_data( # type: ignore
|
existing_team_row = await prisma_client.get_data( # type: ignore
|
||||||
team_id=data.team_id, table_name="team", query_type="find_unique"
|
team_id=data.team_id, table_name="team", query_type="find_unique"
|
||||||
)
|
)
|
||||||
|
if existing_team_row is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail={
|
||||||
|
"error": f"Team not found for team_id={getattr(data, 'team_id', None)}"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
new_member = data.member
|
new_member = data.member
|
||||||
|
|
||||||
existing_team_row.members_with_roles.append(new_member)
|
existing_team_row.members_with_roles.append(new_member)
|
||||||
|
|
||||||
complete_team_data = LiteLLM_TeamTable(
|
complete_team_data = LiteLLM_TeamTable(
|
||||||
**existing_team_row.model_dump(),
|
**_get_pydantic_json_dict(existing_team_row),
|
||||||
)
|
)
|
||||||
|
|
||||||
team_row = await prisma_client.update_data(
|
team_row = await prisma_client.update_data(
|
||||||
|
@ -8120,36 +8153,33 @@ async def auth_callback(request: Request):
|
||||||
}
|
}
|
||||||
user_role = getattr(user_info, "user_role", None)
|
user_role = getattr(user_info, "user_role", None)
|
||||||
|
|
||||||
else:
|
## check if user-email in db ##
|
||||||
## check if user-email in db ##
|
user_info = await prisma_client.db.litellm_usertable.find_first(
|
||||||
user_info = await prisma_client.db.litellm_usertable.find_first(
|
where={"user_email": user_email}
|
||||||
where={"user_email": user_email}
|
)
|
||||||
)
|
if user_info is not None:
|
||||||
if user_info is not None:
|
user_defined_values = {
|
||||||
user_defined_values = {
|
"models": getattr(user_info, "models", user_id_models),
|
||||||
"models": getattr(user_info, "models", user_id_models),
|
"user_id": getattr(user_info, "user_id", user_id),
|
||||||
"user_id": getattr(user_info, "user_id", user_id),
|
"user_email": getattr(user_info, "user_id", user_email),
|
||||||
"user_email": getattr(user_info, "user_id", user_email),
|
"user_role": getattr(user_info, "user_role", None),
|
||||||
"user_role": getattr(user_info, "user_role", None),
|
}
|
||||||
}
|
user_role = getattr(user_info, "user_role", None)
|
||||||
user_role = getattr(user_info, "user_role", None)
|
|
||||||
|
|
||||||
# update id
|
# update id
|
||||||
await prisma_client.db.litellm_usertable.update_many(
|
await prisma_client.db.litellm_usertable.update_many(
|
||||||
where={"user_email": user_email}, data={"user_id": user_id} # type: ignore
|
where={"user_email": user_email}, data={"user_id": user_id} # type: ignore
|
||||||
)
|
)
|
||||||
elif litellm.default_user_params is not None and isinstance(
|
elif litellm.default_user_params is not None and isinstance(
|
||||||
litellm.default_user_params, dict
|
litellm.default_user_params, dict
|
||||||
):
|
):
|
||||||
user_defined_values = {
|
user_defined_values = {
|
||||||
"models": litellm.default_user_params.get(
|
"models": litellm.default_user_params.get("models", user_id_models),
|
||||||
"models", user_id_models
|
"user_id": litellm.default_user_params.get("user_id", user_id),
|
||||||
),
|
"user_email": litellm.default_user_params.get(
|
||||||
"user_id": litellm.default_user_params.get("user_id", user_id),
|
"user_email", user_email
|
||||||
"user_email": litellm.default_user_params.get(
|
),
|
||||||
"user_email", user_email
|
}
|
||||||
),
|
|
||||||
}
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,15 @@ async def test_new_user_response(prisma_client):
|
||||||
await litellm.proxy.proxy_server.prisma_client.connect()
|
await litellm.proxy.proxy_server.prisma_client.connect()
|
||||||
from litellm.proxy.proxy_server import user_api_key_cache
|
from litellm.proxy.proxy_server import user_api_key_cache
|
||||||
|
|
||||||
|
await new_team(
|
||||||
|
NewTeamRequest(
|
||||||
|
team_id="ishaan-special-team",
|
||||||
|
),
|
||||||
|
user_api_key_dict=UserAPIKeyAuth(
|
||||||
|
user_role="proxy_admin", api_key="sk-1234", user_id="1234"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
_response = await new_user(
|
_response = await new_user(
|
||||||
data=NewUserRequest(
|
data=NewUserRequest(
|
||||||
models=["azure-gpt-3.5"],
|
models=["azure-gpt-3.5"],
|
||||||
|
@ -999,10 +1008,32 @@ def test_generate_and_update_key(prisma_client):
|
||||||
|
|
||||||
async def test():
|
async def test():
|
||||||
await litellm.proxy.proxy_server.prisma_client.connect()
|
await litellm.proxy.proxy_server.prisma_client.connect()
|
||||||
|
|
||||||
|
# create team "litellm-core-infra@gmail.com""
|
||||||
|
print("creating team litellm-core-infra@gmail.com")
|
||||||
|
await new_team(
|
||||||
|
NewTeamRequest(
|
||||||
|
team_id="litellm-core-infra@gmail.com",
|
||||||
|
),
|
||||||
|
user_api_key_dict=UserAPIKeyAuth(
|
||||||
|
user_role="proxy_admin", api_key="sk-1234", user_id="1234"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
await new_team(
|
||||||
|
NewTeamRequest(
|
||||||
|
team_id="ishaan-special-team",
|
||||||
|
),
|
||||||
|
user_api_key_dict=UserAPIKeyAuth(
|
||||||
|
user_role="proxy_admin", api_key="sk-1234", user_id="1234"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
request = NewUserRequest(
|
request = NewUserRequest(
|
||||||
metadata={"team": "litellm-team3", "project": "litellm-project3"},
|
metadata={"project": "litellm-project3"},
|
||||||
team_id="litellm-core-infra@gmail.com",
|
team_id="litellm-core-infra@gmail.com",
|
||||||
)
|
)
|
||||||
|
|
||||||
key = await new_user(request)
|
key = await new_user(request)
|
||||||
print(key)
|
print(key)
|
||||||
|
|
||||||
|
@ -1015,7 +1046,6 @@ def test_generate_and_update_key(prisma_client):
|
||||||
print("\n info for key=", result["info"])
|
print("\n info for key=", result["info"])
|
||||||
assert result["info"]["max_parallel_requests"] == None
|
assert result["info"]["max_parallel_requests"] == None
|
||||||
assert result["info"]["metadata"] == {
|
assert result["info"]["metadata"] == {
|
||||||
"team": "litellm-team3",
|
|
||||||
"project": "litellm-project3",
|
"project": "litellm-project3",
|
||||||
}
|
}
|
||||||
assert result["info"]["team_id"] == "litellm-core-infra@gmail.com"
|
assert result["info"]["team_id"] == "litellm-core-infra@gmail.com"
|
||||||
|
@ -1037,7 +1067,7 @@ def test_generate_and_update_key(prisma_client):
|
||||||
# update the team id
|
# update the team id
|
||||||
response2 = await update_key_fn(
|
response2 = await update_key_fn(
|
||||||
request=Request,
|
request=Request,
|
||||||
data=UpdateKeyRequest(key=generated_key, team_id="ishaan"),
|
data=UpdateKeyRequest(key=generated_key, team_id="ishaan-special-team"),
|
||||||
)
|
)
|
||||||
print("response2=", response2)
|
print("response2=", response2)
|
||||||
|
|
||||||
|
@ -1048,11 +1078,10 @@ def test_generate_and_update_key(prisma_client):
|
||||||
print("\n info for key=", result["info"])
|
print("\n info for key=", result["info"])
|
||||||
assert result["info"]["max_parallel_requests"] == None
|
assert result["info"]["max_parallel_requests"] == None
|
||||||
assert result["info"]["metadata"] == {
|
assert result["info"]["metadata"] == {
|
||||||
"team": "litellm-team3",
|
|
||||||
"project": "litellm-project3",
|
"project": "litellm-project3",
|
||||||
}
|
}
|
||||||
assert result["info"]["models"] == ["ada", "babbage", "curie", "davinci"]
|
assert result["info"]["models"] == ["ada", "babbage", "curie", "davinci"]
|
||||||
assert result["info"]["team_id"] == "ishaan"
|
assert result["info"]["team_id"] == "ishaan-special-team"
|
||||||
|
|
||||||
# cleanup - delete key
|
# cleanup - delete key
|
||||||
delete_key_request = KeyRequest(keys=[generated_key])
|
delete_key_request = KeyRequest(keys=[generated_key])
|
||||||
|
@ -1941,6 +1970,15 @@ async def test_master_key_hashing(prisma_client):
|
||||||
await litellm.proxy.proxy_server.prisma_client.connect()
|
await litellm.proxy.proxy_server.prisma_client.connect()
|
||||||
from litellm.proxy.proxy_server import user_api_key_cache
|
from litellm.proxy.proxy_server import user_api_key_cache
|
||||||
|
|
||||||
|
await new_team(
|
||||||
|
NewTeamRequest(
|
||||||
|
team_id="ishaan-special-team",
|
||||||
|
),
|
||||||
|
user_api_key_dict=UserAPIKeyAuth(
|
||||||
|
user_role="proxy_admin", api_key="sk-1234", user_id="1234"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
_response = await new_user(
|
_response = await new_user(
|
||||||
data=NewUserRequest(
|
data=NewUserRequest(
|
||||||
models=["azure-gpt-3.5"],
|
models=["azure-gpt-3.5"],
|
||||||
|
|
|
@ -14,6 +14,24 @@ sys.path.insert(
|
||||||
import litellm
|
import litellm
|
||||||
|
|
||||||
|
|
||||||
|
async def generate_team(session):
|
||||||
|
url = "http://0.0.0.0:4000/team/new"
|
||||||
|
headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"}
|
||||||
|
data = {
|
||||||
|
"team_id": "litellm-dashboard",
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
print(f"Response (Status code: {status}):")
|
||||||
|
print(response_text)
|
||||||
|
print()
|
||||||
|
_json_response = await response.json()
|
||||||
|
return _json_response
|
||||||
|
|
||||||
|
|
||||||
async def generate_user(
|
async def generate_user(
|
||||||
session,
|
session,
|
||||||
user_role="app_owner",
|
user_role="app_owner",
|
||||||
|
@ -668,7 +686,7 @@ async def test_key_rate_limit():
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_key_delete():
|
async def test_key_delete_ui():
|
||||||
"""
|
"""
|
||||||
Admin UI flow - DO NOT DELETE
|
Admin UI flow - DO NOT DELETE
|
||||||
-> Create a key with user_id = "ishaan"
|
-> Create a key with user_id = "ishaan"
|
||||||
|
@ -680,6 +698,8 @@ async def test_key_delete():
|
||||||
key = key_gen["key"]
|
key = key_gen["key"]
|
||||||
|
|
||||||
# generate a admin UI key
|
# generate a admin UI key
|
||||||
|
team = await generate_team(session=session)
|
||||||
|
print("generated team: ", team)
|
||||||
admin_ui_key = await generate_user(session=session, user_role="proxy_admin")
|
admin_ui_key = await generate_user(session=session, user_role="proxy_admin")
|
||||||
print(
|
print(
|
||||||
"trying to delete key=",
|
"trying to delete key=",
|
||||||
|
|
|
@ -116,7 +116,7 @@ const CreateKey: React.FC<CreateKeyProps> = ({
|
||||||
wrapperCol={{ span: 16 }}
|
wrapperCol={{ span: 16 }}
|
||||||
labelAlign="left"
|
labelAlign="left"
|
||||||
>
|
>
|
||||||
{userRole === "App Owner" || userRole === "Admin" ? (
|
{userRole === "App Owner" || userRole === "Admin" || userRole === "App User" ? (
|
||||||
<>
|
<>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label="Key Name"
|
label="Key Name"
|
||||||
|
|
|
@ -63,11 +63,16 @@ const Sidebar: React.FC<SidebarProps> = ({
|
||||||
Test Key
|
Test Key
|
||||||
</Text>
|
</Text>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
<Menu.Item key="2" onClick={() => setPage("models")}>
|
{
|
||||||
<Text>
|
userRole == "Admin" ? (
|
||||||
Models
|
<Menu.Item key="2" onClick={() => setPage("models")}>
|
||||||
</Text>
|
<Text>
|
||||||
</Menu.Item>
|
Models
|
||||||
|
</Text>
|
||||||
|
</Menu.Item>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
{userRole == "Admin" ? (
|
{userRole == "Admin" ? (
|
||||||
<Menu.Item key="6" onClick={() => setPage("teams")}>
|
<Menu.Item key="6" onClick={() => setPage("teams")}>
|
||||||
<Text>
|
<Text>
|
||||||
|
@ -75,11 +80,18 @@ const Sidebar: React.FC<SidebarProps> = ({
|
||||||
</Text>
|
</Text>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
) : null}
|
) : null}
|
||||||
<Menu.Item key="4" onClick={() => setPage("usage")}>
|
|
||||||
<Text>
|
{
|
||||||
Usage
|
userRole == "Admin" ? (
|
||||||
</Text>
|
<Menu.Item key="4" onClick={() => setPage("usage")}>
|
||||||
</Menu.Item>
|
<Text>
|
||||||
|
Usage
|
||||||
|
</Text>
|
||||||
|
</Menu.Item>
|
||||||
|
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
{userRole == "Admin" ? (
|
{userRole == "Admin" ? (
|
||||||
<Menu.Item key="5" onClick={() => setPage("users")}>
|
<Menu.Item key="5" onClick={() => setPage("users")}>
|
||||||
<Text>
|
<Text>
|
||||||
|
@ -87,16 +99,27 @@ const Sidebar: React.FC<SidebarProps> = ({
|
||||||
</Text>
|
</Text>
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
) : null}
|
) : null}
|
||||||
<Menu.Item key="8" onClick={() => setPage("settings")}>
|
|
||||||
<Text>
|
{
|
||||||
Integrations
|
userRole == "Admin" ? (
|
||||||
</Text>
|
<Menu.Item key="8" onClick={() => setPage("settings")}>
|
||||||
</Menu.Item>
|
<Text>
|
||||||
<Menu.Item key="9" onClick={() => setPage("general-settings")}>
|
Integrations
|
||||||
<Text>
|
</Text>
|
||||||
Settings
|
</Menu.Item>
|
||||||
</Text>
|
) : null
|
||||||
</Menu.Item>
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
userRole == "Admin" ? (
|
||||||
|
<Menu.Item key="9" onClick={() => setPage("general-settings")}>
|
||||||
|
<Text>
|
||||||
|
Settings
|
||||||
|
</Text>
|
||||||
|
</Menu.Item>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
{userRole == "Admin" ? (
|
{userRole == "Admin" ? (
|
||||||
<Menu.Item key="7" onClick={() => setPage("admin-panel")}>
|
<Menu.Item key="7" onClick={() => setPage("admin-panel")}>
|
||||||
<Text>
|
<Text>
|
||||||
|
|
|
@ -296,6 +296,9 @@ export const userInfoCall = async (
|
||||||
if (userRole == "App Owner" && userID) {
|
if (userRole == "App Owner" && userID) {
|
||||||
url = `${url}?user_id=${userID}`;
|
url = `${url}?user_id=${userID}`;
|
||||||
}
|
}
|
||||||
|
if (userRole == "App User" && userID) {
|
||||||
|
url = `${url}?user_id=${userID}`;
|
||||||
|
}
|
||||||
console.log("in userInfoCall viewAll=", viewAll);
|
console.log("in userInfoCall viewAll=", viewAll);
|
||||||
if (viewAll && page_size && (page != null) && (page != undefined)) {
|
if (viewAll && page_size && (page != null) && (page != undefined)) {
|
||||||
url = `${url}?view_all=true&page=${page}&page_size=${page_size}`;
|
url = `${url}?view_all=true&page=${page}&page_size=${page_size}`;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue