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:
Ishaan Jaff 2024-04-20 12:40:43 -07:00 committed by GitHub
commit f89f8a4157
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 179 additions and 57 deletions

View file

@ -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",

View file

@ -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

View file

@ -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"],

View file

@ -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=",

View file

@ -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"

View file

@ -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>

View file

@ -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}`;