mirror of
https://github.com/BerriAI/litellm.git
synced 2025-04-26 03:04:13 +00:00
[Team Member permissions] - Fixes (#9945)
* only load member permissions for non-admins * run member permission checks on update + regenerate endpoints * run check for /key/generate * working test_default_member_permissions * passing test with permissions on update delete endpoints * test_create_permissions * _team_key_generation_check * fix TeamBase * fix team endpoints * fix api docs check
This commit is contained in:
parent
d2a462fc93
commit
4e81b2cab4
7 changed files with 601 additions and 64 deletions
|
@ -435,13 +435,8 @@ class LiteLLMRoutes(enum.Enum):
|
||||||
"/get/litellm_model_cost_map",
|
"/get/litellm_model_cost_map",
|
||||||
] + info_routes
|
] + info_routes
|
||||||
|
|
||||||
internal_user_routes = [
|
internal_user_routes = (
|
||||||
"/key/generate",
|
[
|
||||||
"/key/{token_id}/regenerate",
|
|
||||||
"/key/update",
|
|
||||||
"/key/delete",
|
|
||||||
"/key/health",
|
|
||||||
"/key/info",
|
|
||||||
"/global/spend/tags",
|
"/global/spend/tags",
|
||||||
"/global/spend/keys",
|
"/global/spend/keys",
|
||||||
"/global/spend/models",
|
"/global/spend/models",
|
||||||
|
@ -449,7 +444,10 @@ class LiteLLMRoutes(enum.Enum):
|
||||||
"/global/spend/end_users",
|
"/global/spend/end_users",
|
||||||
"/global/activity",
|
"/global/activity",
|
||||||
"/global/activity/model",
|
"/global/activity/model",
|
||||||
] + spend_tracking_routes
|
]
|
||||||
|
+ spend_tracking_routes
|
||||||
|
+ key_management_routes
|
||||||
|
)
|
||||||
|
|
||||||
internal_user_view_only_routes = (
|
internal_user_view_only_routes = (
|
||||||
spend_tracking_routes + global_spend_tracking_routes
|
spend_tracking_routes + global_spend_tracking_routes
|
||||||
|
@ -983,6 +981,7 @@ class TeamBase(LiteLLMPydanticObjectBase):
|
||||||
admins: list = []
|
admins: list = []
|
||||||
members: list = []
|
members: list = []
|
||||||
members_with_roles: List[Member] = []
|
members_with_roles: List[Member] = []
|
||||||
|
team_member_permissions: Optional[List[str]] = None
|
||||||
metadata: Optional[dict] = None
|
metadata: Optional[dict] = None
|
||||||
tpm_limit: Optional[int] = None
|
tpm_limit: Optional[int] = None
|
||||||
rpm_limit: Optional[int] = None
|
rpm_limit: Optional[int] = None
|
||||||
|
@ -1138,7 +1137,6 @@ class LiteLLM_TeamTable(TeamBase):
|
||||||
budget_duration: Optional[str] = None
|
budget_duration: Optional[str] = None
|
||||||
budget_reset_at: Optional[datetime] = None
|
budget_reset_at: Optional[datetime] = None
|
||||||
model_id: Optional[int] = None
|
model_id: Optional[int] = None
|
||||||
team_member_permissions: Optional[List[str]] = None
|
|
||||||
litellm_model_table: Optional[LiteLLM_ModelTable] = None
|
litellm_model_table: Optional[LiteLLM_ModelTable] = None
|
||||||
created_at: Optional[datetime] = None
|
created_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,9 @@ from litellm.proxy.management_endpoints.common_utils import (
|
||||||
from litellm.proxy.management_endpoints.model_management_endpoints import (
|
from litellm.proxy.management_endpoints.model_management_endpoints import (
|
||||||
_add_model_to_db,
|
_add_model_to_db,
|
||||||
)
|
)
|
||||||
|
from litellm.proxy.management_helpers.team_member_permission_checks import (
|
||||||
|
TeamMemberPermissionChecks,
|
||||||
|
)
|
||||||
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
|
from litellm.proxy.management_helpers.utils import management_endpoint_wrapper
|
||||||
from litellm.proxy.spend_tracking.spend_tracking_utils import _is_master_key
|
from litellm.proxy.spend_tracking.spend_tracking_utils import _is_master_key
|
||||||
from litellm.proxy.utils import (
|
from litellm.proxy.utils import (
|
||||||
|
@ -104,20 +107,16 @@ def _is_allowed_to_make_key_request(
|
||||||
and user_api_key_dict.team_id == UI_TEAM_ID
|
and user_api_key_dict.team_id == UI_TEAM_ID
|
||||||
):
|
):
|
||||||
return True # handle https://github.com/BerriAI/litellm/issues/7482
|
return True # handle https://github.com/BerriAI/litellm/issues/7482
|
||||||
assert (
|
|
||||||
user_api_key_dict.team_id == team_id
|
|
||||||
), "User can only create keys for their own team. Got={}, Your Team ID={}".format(
|
|
||||||
team_id, user_api_key_dict.team_id
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _team_key_generation_team_member_check(
|
def _team_key_operation_team_member_check(
|
||||||
assigned_user_id: Optional[str],
|
assigned_user_id: Optional[str],
|
||||||
team_table: LiteLLM_TeamTableCachedObj,
|
team_table: LiteLLM_TeamTableCachedObj,
|
||||||
user_api_key_dict: UserAPIKeyAuth,
|
user_api_key_dict: UserAPIKeyAuth,
|
||||||
team_key_generation: TeamUIKeyGenerationConfig,
|
team_key_generation: TeamUIKeyGenerationConfig,
|
||||||
|
route: KeyManagementRoutes,
|
||||||
):
|
):
|
||||||
if assigned_user_id is not None:
|
if assigned_user_id is not None:
|
||||||
key_assigned_user_in_team = _get_user_in_team(
|
key_assigned_user_in_team = _get_user_in_team(
|
||||||
|
@ -130,7 +129,7 @@ def _team_key_generation_team_member_check(
|
||||||
detail=f"User={assigned_user_id} not assigned to team={team_table.team_id}",
|
detail=f"User={assigned_user_id} not assigned to team={team_table.team_id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
key_creating_user_in_team = _get_user_in_team(
|
team_member_object = _get_user_in_team(
|
||||||
team_table=team_table, user_id=user_api_key_dict.user_id
|
team_table=team_table, user_id=user_api_key_dict.user_id
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -141,19 +140,25 @@ def _team_key_generation_team_member_check(
|
||||||
|
|
||||||
if is_admin:
|
if is_admin:
|
||||||
return True
|
return True
|
||||||
elif key_creating_user_in_team is None:
|
elif team_member_object is None:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail=f"User={user_api_key_dict.user_id} not assigned to team={team_table.team_id}",
|
detail=f"User={user_api_key_dict.user_id} not assigned to team={team_table.team_id}",
|
||||||
)
|
)
|
||||||
elif (
|
elif (
|
||||||
"allowed_team_member_roles" in team_key_generation
|
"allowed_team_member_roles" in team_key_generation
|
||||||
and key_creating_user_in_team.role
|
and team_member_object.role
|
||||||
not in team_key_generation["allowed_team_member_roles"]
|
not in team_key_generation["allowed_team_member_roles"]
|
||||||
):
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400,
|
status_code=400,
|
||||||
detail=f"Team member role {key_creating_user_in_team.role} not in allowed_team_member_roles={team_key_generation['allowed_team_member_roles']}",
|
detail=f"Team member role {team_member_object.role} not in allowed_team_member_roles={team_key_generation['allowed_team_member_roles']}",
|
||||||
|
)
|
||||||
|
|
||||||
|
TeamMemberPermissionChecks.does_team_member_have_permissions_for_endpoint(
|
||||||
|
team_member_object=team_member_object,
|
||||||
|
team_table=team_table,
|
||||||
|
route=route,
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -178,6 +183,7 @@ def _team_key_generation_check(
|
||||||
team_table: LiteLLM_TeamTableCachedObj,
|
team_table: LiteLLM_TeamTableCachedObj,
|
||||||
user_api_key_dict: UserAPIKeyAuth,
|
user_api_key_dict: UserAPIKeyAuth,
|
||||||
data: GenerateKeyRequest,
|
data: GenerateKeyRequest,
|
||||||
|
route: KeyManagementRoutes,
|
||||||
):
|
):
|
||||||
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
|
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
|
||||||
return True
|
return True
|
||||||
|
@ -191,11 +197,12 @@ def _team_key_generation_check(
|
||||||
allowed_team_member_roles=["admin", "user"],
|
allowed_team_member_roles=["admin", "user"],
|
||||||
)
|
)
|
||||||
|
|
||||||
_team_key_generation_team_member_check(
|
_team_key_operation_team_member_check(
|
||||||
assigned_user_id=data.user_id,
|
assigned_user_id=data.user_id,
|
||||||
team_table=team_table,
|
team_table=team_table,
|
||||||
user_api_key_dict=user_api_key_dict,
|
user_api_key_dict=user_api_key_dict,
|
||||||
team_key_generation=_team_key_generation,
|
team_key_generation=_team_key_generation,
|
||||||
|
route=route,
|
||||||
)
|
)
|
||||||
_key_generation_required_param_check(
|
_key_generation_required_param_check(
|
||||||
data,
|
data,
|
||||||
|
@ -252,6 +259,7 @@ def key_generation_check(
|
||||||
team_table: Optional[LiteLLM_TeamTableCachedObj],
|
team_table: Optional[LiteLLM_TeamTableCachedObj],
|
||||||
user_api_key_dict: UserAPIKeyAuth,
|
user_api_key_dict: UserAPIKeyAuth,
|
||||||
data: GenerateKeyRequest,
|
data: GenerateKeyRequest,
|
||||||
|
route: KeyManagementRoutes,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if admin has restricted key creation to certain roles for teams or individuals
|
Check if admin has restricted key creation to certain roles for teams or individuals
|
||||||
|
@ -271,6 +279,7 @@ def key_generation_check(
|
||||||
team_table=team_table,
|
team_table=team_table,
|
||||||
user_api_key_dict=user_api_key_dict,
|
user_api_key_dict=user_api_key_dict,
|
||||||
data=data,
|
data=data,
|
||||||
|
route=route,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return _personal_key_generation_check(
|
return _personal_key_generation_check(
|
||||||
|
@ -425,6 +434,7 @@ async def generate_key_fn( # noqa: PLR0915
|
||||||
team_table=team_table,
|
team_table=team_table,
|
||||||
user_api_key_dict=user_api_key_dict,
|
user_api_key_dict=user_api_key_dict,
|
||||||
data=data,
|
data=data,
|
||||||
|
route=KeyManagementRoutes.KEY_GENERATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
common_key_access_checks(
|
common_key_access_checks(
|
||||||
|
@ -567,9 +577,9 @@ async def generate_key_fn( # noqa: PLR0915
|
||||||
request_type="key", **data_json, table_name="key"
|
request_type="key", **data_json, table_name="key"
|
||||||
)
|
)
|
||||||
|
|
||||||
response[
|
response["soft_budget"] = (
|
||||||
"soft_budget"
|
data.soft_budget
|
||||||
] = data.soft_budget # include the user-input soft budget in the response
|
) # include the user-input soft budget in the response
|
||||||
|
|
||||||
response = GenerateKeyResponse(**response)
|
response = GenerateKeyResponse(**response)
|
||||||
|
|
||||||
|
@ -762,6 +772,15 @@ async def update_key_fn(
|
||||||
detail={"error": f"Team not found, passed team_id={data.team_id}"},
|
detail={"error": f"Team not found, passed team_id={data.team_id}"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# check if user has permission to update key
|
||||||
|
await TeamMemberPermissionChecks.can_team_member_execute_key_management_endpoint(
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
route=KeyManagementRoutes.KEY_UPDATE,
|
||||||
|
prisma_client=prisma_client,
|
||||||
|
existing_key_row=existing_key_row,
|
||||||
|
user_api_key_cache=user_api_key_cache,
|
||||||
|
)
|
||||||
|
|
||||||
non_default_values = prepare_key_update_data(
|
non_default_values = prepare_key_update_data(
|
||||||
data=data, existing_key_row=existing_key_row
|
data=data, existing_key_row=existing_key_row
|
||||||
)
|
)
|
||||||
|
@ -1049,7 +1068,7 @@ async def info_key_fn(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
_can_user_query_key_info(
|
await _can_user_query_key_info(
|
||||||
user_api_key_dict=user_api_key_dict,
|
user_api_key_dict=user_api_key_dict,
|
||||||
key=key,
|
key=key,
|
||||||
key_info=key_info,
|
key_info=key_info,
|
||||||
|
@ -1376,11 +1395,12 @@ async def _team_key_deletion_check(
|
||||||
)
|
)
|
||||||
# check if user is team admin
|
# check if user is team admin
|
||||||
if team_table is not None:
|
if team_table is not None:
|
||||||
return _team_key_generation_team_member_check(
|
return _team_key_operation_team_member_check(
|
||||||
assigned_user_id=user_api_key_dict.user_id,
|
assigned_user_id=user_api_key_dict.user_id,
|
||||||
team_table=team_table,
|
team_table=team_table,
|
||||||
user_api_key_dict=user_api_key_dict,
|
user_api_key_dict=user_api_key_dict,
|
||||||
team_key_generation=_team_key_generation,
|
team_key_generation=_team_key_generation,
|
||||||
|
route=KeyManagementRoutes.KEY_DELETE,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
@ -1447,11 +1467,11 @@ async def delete_verification_tokens(
|
||||||
try:
|
try:
|
||||||
if prisma_client:
|
if prisma_client:
|
||||||
tokens = [_hash_token_if_needed(token=key) for key in tokens]
|
tokens = [_hash_token_if_needed(token=key) for key in tokens]
|
||||||
_keys_being_deleted: List[
|
_keys_being_deleted: List[LiteLLM_VerificationToken] = (
|
||||||
LiteLLM_VerificationToken
|
await prisma_client.db.litellm_verificationtoken.find_many(
|
||||||
] = await prisma_client.db.litellm_verificationtoken.find_many(
|
|
||||||
where={"token": {"in": tokens}}
|
where={"token": {"in": tokens}}
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Assuming 'db' is your Prisma Client instance
|
# Assuming 'db' is your Prisma Client instance
|
||||||
# check if admin making request - don't filter by user-id
|
# check if admin making request - don't filter by user-id
|
||||||
|
@ -1552,9 +1572,9 @@ async def _rotate_master_key(
|
||||||
from litellm.proxy.proxy_server import proxy_config
|
from litellm.proxy.proxy_server import proxy_config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
models: Optional[
|
models: Optional[List] = (
|
||||||
List
|
await prisma_client.db.litellm_proxymodeltable.find_many()
|
||||||
] = await prisma_client.db.litellm_proxymodeltable.find_many()
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
models = None
|
models = None
|
||||||
# 2. process model table
|
# 2. process model table
|
||||||
|
@ -1743,6 +1763,15 @@ async def regenerate_key_fn(
|
||||||
detail={"error": f"Key {key} not found."},
|
detail={"error": f"Key {key} not found."},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# check if user has permission to regenerate key
|
||||||
|
await TeamMemberPermissionChecks.can_team_member_execute_key_management_endpoint(
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
route=KeyManagementRoutes.KEY_REGENERATE,
|
||||||
|
prisma_client=prisma_client,
|
||||||
|
existing_key_row=_key_in_db,
|
||||||
|
user_api_key_cache=user_api_key_cache,
|
||||||
|
)
|
||||||
|
|
||||||
verbose_proxy_logger.debug("key_in_db: %s", _key_in_db)
|
verbose_proxy_logger.debug("key_in_db: %s", _key_in_db)
|
||||||
|
|
||||||
new_token = f"sk-{secrets.token_urlsafe(LENGTH_OF_LITELLM_GENERATED_KEY)}"
|
new_token = f"sk-{secrets.token_urlsafe(LENGTH_OF_LITELLM_GENERATED_KEY)}"
|
||||||
|
@ -1832,12 +1861,12 @@ async def validate_key_list_check(
|
||||||
param="user_id",
|
param="user_id",
|
||||||
code=status.HTTP_403_FORBIDDEN,
|
code=status.HTTP_403_FORBIDDEN,
|
||||||
)
|
)
|
||||||
complete_user_info_db_obj: Optional[
|
complete_user_info_db_obj: Optional[BaseModel] = (
|
||||||
BaseModel
|
await prisma_client.db.litellm_usertable.find_unique(
|
||||||
] = await prisma_client.db.litellm_usertable.find_unique(
|
|
||||||
where={"user_id": user_api_key_dict.user_id},
|
where={"user_id": user_api_key_dict.user_id},
|
||||||
include={"organization_memberships": True},
|
include={"organization_memberships": True},
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if complete_user_info_db_obj is None:
|
if complete_user_info_db_obj is None:
|
||||||
raise ProxyException(
|
raise ProxyException(
|
||||||
|
@ -1897,11 +1926,11 @@ async def get_admin_team_ids(
|
||||||
if complete_user_info is None:
|
if complete_user_info is None:
|
||||||
return []
|
return []
|
||||||
# Get all teams that user is an admin of
|
# Get all teams that user is an admin of
|
||||||
teams: Optional[
|
teams: Optional[List[BaseModel]] = (
|
||||||
List[BaseModel]
|
await prisma_client.db.litellm_teamtable.find_many(
|
||||||
] = await prisma_client.db.litellm_teamtable.find_many(
|
|
||||||
where={"team_id": {"in": complete_user_info.teams}}
|
where={"team_id": {"in": complete_user_info.teams}}
|
||||||
)
|
)
|
||||||
|
)
|
||||||
if teams is None:
|
if teams is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -2450,7 +2479,7 @@ async def key_health(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _can_user_query_key_info(
|
async def _can_user_query_key_info(
|
||||||
user_api_key_dict: UserAPIKeyAuth,
|
user_api_key_dict: UserAPIKeyAuth,
|
||||||
key: Optional[str],
|
key: Optional[str],
|
||||||
key_info: LiteLLM_VerificationToken,
|
key_info: LiteLLM_VerificationToken,
|
||||||
|
@ -2468,6 +2497,11 @@ def _can_user_query_key_info(
|
||||||
# user can query their own key info
|
# user can query their own key info
|
||||||
elif key_info.user_id == user_api_key_dict.user_id:
|
elif key_info.user_id == user_api_key_dict.user_id:
|
||||||
return True
|
return True
|
||||||
|
elif await TeamMemberPermissionChecks.user_belongs_to_keys_team(
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
existing_key_row=key_info,
|
||||||
|
):
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,7 @@ async def new_team( # noqa: PLR0915
|
||||||
- team_alias: Optional[str] - User defined team alias
|
- team_alias: Optional[str] - User defined team alias
|
||||||
- team_id: Optional[str] - The team id of the user. If none passed, we'll generate it.
|
- team_id: Optional[str] - The team id of the user. If none passed, we'll generate it.
|
||||||
- members_with_roles: List[{"role": "admin" or "user", "user_id": "<user-id>"}] - A list of users and their roles in the team. Get user_id when making a new user via `/user/new`.
|
- members_with_roles: List[{"role": "admin" or "user", "user_id": "<user-id>"}] - A list of users and their roles in the team. Get user_id when making a new user via `/user/new`.
|
||||||
|
- team_member_permissions: Optional[List[str]] - A list of routes that non-admin team members can access. example: ["/key/generate", "/key/update", "/key/delete"]
|
||||||
- metadata: Optional[dict] - Metadata for team, store information for team. Example metadata = {"extra_info": "some info"}
|
- metadata: Optional[dict] - Metadata for team, store information for team. Example metadata = {"extra_info": "some info"}
|
||||||
- tpm_limit: Optional[int] - The TPM (Tokens Per Minute) limit for this team - all keys with this team_id will have at max this TPM limit
|
- tpm_limit: Optional[int] - The TPM (Tokens Per Minute) limit for this team - all keys with this team_id will have at max this TPM limit
|
||||||
- rpm_limit: Optional[int] - The RPM (Requests Per Minute) limit for this team - all keys associated with this team_id will have at max this RPM limit
|
- rpm_limit: Optional[int] - The RPM (Requests Per Minute) limit for this team - all keys associated with this team_id will have at max this RPM limit
|
||||||
|
@ -420,6 +421,7 @@ async def update_team(
|
||||||
Parameters:
|
Parameters:
|
||||||
- team_id: str - The team id of the user. Required param.
|
- team_id: str - The team id of the user. Required param.
|
||||||
- team_alias: Optional[str] - User defined team alias
|
- team_alias: Optional[str] - User defined team alias
|
||||||
|
- team_member_permissions: Optional[List[str]] - A list of routes that non-admin team members can access. example: ["/key/generate", "/key/update", "/key/delete"]
|
||||||
- metadata: Optional[dict] - Metadata for team, store information for team. Example metadata = {"team": "core-infra", "app": "app2", "email": "ishaan@berri.ai" }
|
- metadata: Optional[dict] - Metadata for team, store information for team. Example metadata = {"team": "core-infra", "app": "app2", "email": "ishaan@berri.ai" }
|
||||||
- tpm_limit: Optional[int] - The TPM (Tokens Per Minute) limit for this team - all keys with this team_id will have at max this TPM limit
|
- tpm_limit: Optional[int] - The TPM (Tokens Per Minute) limit for this team - all keys with this team_id will have at max this TPM limit
|
||||||
- rpm_limit: Optional[int] - The RPM (Requests Per Minute) limit for this team - all keys associated with this team_id will have at max this RPM limit
|
- rpm_limit: Optional[int] - The RPM (Requests Per Minute) limit for this team - all keys associated with this team_id will have at max this RPM limit
|
||||||
|
|
|
@ -10,7 +10,6 @@ from litellm.proxy._types import (
|
||||||
Member,
|
Member,
|
||||||
ProxyErrorTypes,
|
ProxyErrorTypes,
|
||||||
ProxyException,
|
ProxyException,
|
||||||
Span,
|
|
||||||
UserAPIKeyAuth,
|
UserAPIKeyAuth,
|
||||||
)
|
)
|
||||||
from litellm.proxy.auth.auth_checks import get_team_object
|
from litellm.proxy.auth.auth_checks import get_team_object
|
||||||
|
@ -57,7 +56,6 @@ class TeamMemberPermissionChecks:
|
||||||
route: KeyManagementRoutes,
|
route: KeyManagementRoutes,
|
||||||
prisma_client: PrismaClient,
|
prisma_client: PrismaClient,
|
||||||
user_api_key_cache: DualCache,
|
user_api_key_cache: DualCache,
|
||||||
parent_otel_span: Optional[Span],
|
|
||||||
existing_key_row: LiteLLM_VerificationToken,
|
existing_key_row: LiteLLM_VerificationToken,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
|
@ -129,7 +127,7 @@ class TeamMemberPermissionChecks:
|
||||||
route=route, allowed_routes=team_member_permissions
|
route=route, allowed_routes=team_member_permissions
|
||||||
):
|
):
|
||||||
raise ProxyException(
|
raise ProxyException(
|
||||||
message=f"Team member does not have permissions for endpoint: {route}. You only have access to the following endpoints: {team_member_permissions}",
|
message=f"Team member does not have permissions for endpoint: {route}. You only have access to the following endpoints: {team_member_permissions} for team {team_table.team_id}",
|
||||||
type=ProxyErrorTypes.team_member_permission_error,
|
type=ProxyErrorTypes.team_member_permission_error,
|
||||||
param=route,
|
param=route,
|
||||||
code=401,
|
code=401,
|
||||||
|
|
488
tests/otel_tests/test_team_member_permissions.py
Normal file
488
tests/otel_tests/test_team_member_permissions.py
Normal file
|
@ -0,0 +1,488 @@
|
||||||
|
"""
|
||||||
|
1. Default permissions for members in a team - allowed to call /key/info and /key/health
|
||||||
|
- Create a team, create a member in a team (role = "user")
|
||||||
|
|
||||||
|
|
||||||
|
Invalid Permissions:
|
||||||
|
- User tries creating a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
- User tries editing a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
- User tries deleting a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
- User tries regenerating a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
|
||||||
|
Valid Permissions:
|
||||||
|
- User tries calling /key/info with team_id, expect to get valid response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
2. Permissions - members allowd to edit, delete keys but not allowed to create keys
|
||||||
|
- Create a team with member_permissions = ["/key/update", "/key/delete", "/key/info"]
|
||||||
|
- Create a member in the team with role = "user"
|
||||||
|
|
||||||
|
Valid Permissions:
|
||||||
|
- User tries editing a key with team_id = team_id -> expect to pass. Valid Permissions
|
||||||
|
- User tries deleting a key with team_id = team_id -> expect to pass. Valid Permissions
|
||||||
|
|
||||||
|
|
||||||
|
- User tries creating a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
- User tries regenerating a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
- User tries calling /key/info with team_id, expect to get valid response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
3. Permissions - members allowed to create keys but not allowed to edit, delete keys
|
||||||
|
- Create a team with member_permissions = ["/key/generate"]
|
||||||
|
- Create a member in the team with role = "user"
|
||||||
|
|
||||||
|
Valid Permissions:
|
||||||
|
- User tries creating a key with team_id = team_id -> expect to pass. Valid Permissions
|
||||||
|
|
||||||
|
Invalid Permissions:
|
||||||
|
- User tries editing a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
- User tries deleting a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
- User tries regenerating a key with team_id = team_id -> expect to fail. Invalid Permissions
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import asyncio
|
||||||
|
import aiohttp, openai
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
from litellm.proxy._types import ProxyErrorTypes
|
||||||
|
from typing import Optional
|
||||||
|
LITELLM_MASTER_KEY = "sk-1234"
|
||||||
|
|
||||||
|
async def create_team(session, key, member_permissions=None):
|
||||||
|
url = "http://0.0.0.0:4000/team/new"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"team_member_permissions": member_permissions
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
raise Exception(response_text)
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def create_user(session, key, user_id, team_id=None):
|
||||||
|
url = "http://0.0.0.0:4000/user/new"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"user_id": user_id
|
||||||
|
}
|
||||||
|
if team_id:
|
||||||
|
data["team_id"] = team_id
|
||||||
|
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
raise Exception(response_text)
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def add_team_member(session, key, team_id, user_id, role="user"):
|
||||||
|
url = "http://0.0.0.0:4000/team/member_add"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"team_id": team_id,
|
||||||
|
"member": {
|
||||||
|
"role": role,
|
||||||
|
"user_id": user_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("Adding team member with data: ", data)
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
raise Exception(response_text)
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def generate_key(session, key, team_id=None, user_id=None):
|
||||||
|
url = "http://0.0.0.0:4000/key/generate"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
data = {}
|
||||||
|
if team_id:
|
||||||
|
data["team_id"] = team_id
|
||||||
|
if user_id:
|
||||||
|
data["user_id"] = user_id
|
||||||
|
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
return {"status": status, "error": response_text}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def key_info(session, key, key_id):
|
||||||
|
url = f"http://0.0.0.0:4000/key/info?key={key_id}"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.get(url, headers=headers) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
return {"status": status, "error": response_text}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def update_key(
|
||||||
|
session: aiohttp.ClientSession,
|
||||||
|
key: str,
|
||||||
|
key_id: str,
|
||||||
|
team_id: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Update a key
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key: key to use for authentication
|
||||||
|
key_id: key to update
|
||||||
|
"""
|
||||||
|
url = "http://0.0.0.0:4000/key/update"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"key": key_id,
|
||||||
|
"metadata": {"updated": True}
|
||||||
|
}
|
||||||
|
if team_id:
|
||||||
|
data["team_id"] = team_id
|
||||||
|
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
return {"status": status, "error": response_text}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def delete_key(session, key, key_id):
|
||||||
|
url = "http://0.0.0.0:4000/key/delete"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"keys": [key_id]
|
||||||
|
}
|
||||||
|
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
return {"status": status, "error": response_text}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
async def regenerate_key(session, key, key_id, team_id=None):
|
||||||
|
url = "http://0.0.0.0:4000/key/regenerate"
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"key": key_id
|
||||||
|
}
|
||||||
|
if team_id:
|
||||||
|
data["team_id"] = team_id
|
||||||
|
|
||||||
|
async with session.post(url, headers=headers, json=data) as response:
|
||||||
|
status = response.status
|
||||||
|
response_text = await response.text()
|
||||||
|
|
||||||
|
if status != 200:
|
||||||
|
return {"status": status, "error": response_text}
|
||||||
|
|
||||||
|
return await response.json()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio()
|
||||||
|
async def test_default_member_permissions():
|
||||||
|
"""
|
||||||
|
Test default permissions for members in a team - allowed to call /key/info and /key/health
|
||||||
|
"""
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
master_key = LITELLM_MASTER_KEY
|
||||||
|
|
||||||
|
# Create a team
|
||||||
|
team_data = await create_team(
|
||||||
|
session=session,
|
||||||
|
key=master_key
|
||||||
|
)
|
||||||
|
team_id = team_data["team_id"]
|
||||||
|
|
||||||
|
# create a team key
|
||||||
|
team_key_data = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
team_key = team_key_data["key"]
|
||||||
|
|
||||||
|
# create a user
|
||||||
|
user_data = await create_user(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
user_id=f"user_{uuid.uuid4().hex[:8]}",
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
user_id = user_data["user_id"]
|
||||||
|
|
||||||
|
# Create a user key
|
||||||
|
print("New user data: ", user_data)
|
||||||
|
|
||||||
|
# Create a user key
|
||||||
|
user_key_data = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
user_id=user_id
|
||||||
|
)
|
||||||
|
print("new user key: ", user_key_data)
|
||||||
|
user_key = user_key_data["key"]
|
||||||
|
|
||||||
|
# Test invalid permissions
|
||||||
|
# User tries creating a key with team_id
|
||||||
|
print("Regular team member trying to create a key with team_id. Expecting error.")
|
||||||
|
create_result = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
print("result: ", create_result)
|
||||||
|
assert "status" in create_result and create_result["status"] == 401, "User should not be able to create keys for team"
|
||||||
|
error_data = json.loads(create_result["error"])
|
||||||
|
print("error response =", json.dumps(error_data, indent=4))
|
||||||
|
assert error_data["error"]["type"] == ProxyErrorTypes.team_member_permission_error.value, "Error should be a team member permission error"
|
||||||
|
|
||||||
|
# User tries editing a key with team_id
|
||||||
|
print("Regular team member trying to edit a key with team_id. Expecting error.")
|
||||||
|
update_result = await update_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=team_key,
|
||||||
|
team_id="ATTACKER_TEAM_ID"
|
||||||
|
)
|
||||||
|
assert "status" in update_result and update_result["status"] == 401, "User should not be able to update keys for team"
|
||||||
|
error_data = json.loads(update_result["error"])
|
||||||
|
print("error response =", json.dumps(error_data, indent=4))
|
||||||
|
assert error_data["error"]["type"] == ProxyErrorTypes.team_member_permission_error.value, "Error should be a team member permission error"
|
||||||
|
|
||||||
|
# User tries deleting a key with team_id
|
||||||
|
print("Regular team member trying to delete a key with team_id. Expecting error.")
|
||||||
|
delete_result = await delete_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=team_key,
|
||||||
|
)
|
||||||
|
assert "status" in delete_result and delete_result["status"] == 401, "User should not be able to delete keys for team"
|
||||||
|
error_data = json.loads(delete_result["error"])
|
||||||
|
print("error response =", json.dumps(error_data, indent=4))
|
||||||
|
assert error_data["error"]["type"] == ProxyErrorTypes.team_member_permission_error.value, "Error should be a team member permission error"
|
||||||
|
|
||||||
|
# User tries regenerating a key with team_id
|
||||||
|
print("Regular team member trying to regenerate a key with team_id. Expecting error.")
|
||||||
|
regenerate_result = await regenerate_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=team_key,
|
||||||
|
)
|
||||||
|
assert "status" in regenerate_result and regenerate_result["status"] == 401, "User should not be able to regenerate keys for team"
|
||||||
|
error_data = json.loads(regenerate_result["error"])
|
||||||
|
print("error response =", json.dumps(error_data, indent=4))
|
||||||
|
assert error_data["error"]["type"] == ProxyErrorTypes.team_member_permission_error.value, "Error should be a team member permission error"
|
||||||
|
|
||||||
|
# Test valid permissions
|
||||||
|
# User tries calling /key/info with team_id
|
||||||
|
print("Regular team member trying to get key info with team_id. Expecting success.")
|
||||||
|
info_result = await key_info(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=team_key,
|
||||||
|
)
|
||||||
|
print("info result =", info_result)
|
||||||
|
assert "status" not in info_result, "Admin should be able to get key info"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio()
|
||||||
|
async def test_edit_delete_permissions():
|
||||||
|
"""
|
||||||
|
Test permissions - members allowed to edit, delete keys but not allowed to create keys
|
||||||
|
"""
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
master_key = LITELLM_MASTER_KEY
|
||||||
|
|
||||||
|
# Create a team with specific member permissions
|
||||||
|
team_data = await create_team(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
member_permissions=["/key/update", "/key/delete", "/key/info"]
|
||||||
|
)
|
||||||
|
team_id = team_data["team_id"]
|
||||||
|
|
||||||
|
# create a user in team=team_id
|
||||||
|
user_data = await create_user(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
user_id=f"user_{uuid.uuid4().hex[:8]}",
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
user_id = user_data["user_id"]
|
||||||
|
|
||||||
|
# Generate an admin key for the team
|
||||||
|
admin_key_data = await generate_key(session, master_key, team_id)
|
||||||
|
admin_key = admin_key_data["key"]
|
||||||
|
key_id = admin_key_data["key"]
|
||||||
|
|
||||||
|
# Create a user key
|
||||||
|
user_key_data = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
user_id=user_id
|
||||||
|
)
|
||||||
|
user_key = user_key_data["key"]
|
||||||
|
|
||||||
|
# Test valid permissions
|
||||||
|
# User tries editing a key with team_id
|
||||||
|
update_result = await update_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=key_id,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
assert "status" not in update_result, "User should be able to update keys for team"
|
||||||
|
|
||||||
|
# User tries deleting a key with team_id - test this last
|
||||||
|
delete_result = await delete_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=key_id
|
||||||
|
)
|
||||||
|
assert "status" not in delete_result, "User should be able to delete keys for team"
|
||||||
|
|
||||||
|
# Test invalid permissions
|
||||||
|
# User tries creating a key with team_id
|
||||||
|
create_result = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
assert "status" in create_result and create_result["status"] != 200, "User should not be able to create keys for team"
|
||||||
|
|
||||||
|
# User tries regenerating a key with team_id
|
||||||
|
regenerate_result = await regenerate_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=key_id,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
assert "status" in regenerate_result and regenerate_result["status"] != 200, "User should not be able to regenerate keys for team"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio()
|
||||||
|
async def test_create_permissions():
|
||||||
|
"""
|
||||||
|
Test permissions - members allowed to create keys but not allowed to edit, delete keys
|
||||||
|
"""
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
master_key = LITELLM_MASTER_KEY
|
||||||
|
|
||||||
|
# Create a team with specific member permissions
|
||||||
|
team_data = await create_team(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
member_permissions=["/key/generate"]
|
||||||
|
)
|
||||||
|
team_id = team_data["team_id"]
|
||||||
|
|
||||||
|
# Create a user in the team
|
||||||
|
user_id = f"user_{uuid.uuid4().hex[:8]}"
|
||||||
|
await add_team_member(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
team_id=team_id,
|
||||||
|
user_id=user_id,
|
||||||
|
role="user"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Generate an admin key for the team
|
||||||
|
admin_key_data = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
admin_key = admin_key_data["key"]
|
||||||
|
key_id = admin_key_data["key"]
|
||||||
|
|
||||||
|
# Create a user key
|
||||||
|
user_key_data = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=master_key,
|
||||||
|
user_id=user_id
|
||||||
|
)
|
||||||
|
user_key = user_key_data["key"]
|
||||||
|
|
||||||
|
# Test valid permissions
|
||||||
|
# User tries creating a key with team_id
|
||||||
|
create_result = await generate_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
print("success, user created key for team=", create_result)
|
||||||
|
assert "key" in create_result, "User should be able to create keys for team"
|
||||||
|
assert create_result["team_id"] == team_id, "User should be able to create keys for team"
|
||||||
|
assert "status" not in create_result, "User should be able to create keys for team"
|
||||||
|
|
||||||
|
# Test invalid permissions
|
||||||
|
# User tries editing a key with team_id
|
||||||
|
update_result = await update_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=key_id,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
assert "status" in update_result and update_result["status"] != 200, "User should not be able to update keys for team"
|
||||||
|
|
||||||
|
# User tries deleting a key with team_id
|
||||||
|
delete_result = await delete_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=key_id
|
||||||
|
)
|
||||||
|
assert "status" in delete_result and delete_result["status"] != 200, "User should not be able to delete keys for team"
|
||||||
|
|
||||||
|
# User tries regenerating a key with team_id
|
||||||
|
regenerate_result = await regenerate_key(
|
||||||
|
session=session,
|
||||||
|
key=user_key,
|
||||||
|
key_id=key_id,
|
||||||
|
team_id=team_id
|
||||||
|
)
|
||||||
|
assert "status" in regenerate_result and regenerate_result["status"] != 200, "User should not be able to regenerate keys for team"
|
|
@ -661,6 +661,7 @@ def test_team_key_generation_team_member_check():
|
||||||
team_member=Member(role="admin", user_id="test_user_id"),
|
team_member=Member(role="admin", user_id="test_user_id"),
|
||||||
),
|
),
|
||||||
data=GenerateKeyRequest(),
|
data=GenerateKeyRequest(),
|
||||||
|
route=KeyManagementRoutes.KEY_GENERATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
team_table = LiteLLM_TeamTableCachedObj(
|
team_table = LiteLLM_TeamTableCachedObj(
|
||||||
|
@ -679,6 +680,7 @@ def test_team_key_generation_team_member_check():
|
||||||
team_member=Member(role="user", user_id="test_user_id"),
|
team_member=Member(role="user", user_id="test_user_id"),
|
||||||
),
|
),
|
||||||
data=GenerateKeyRequest(),
|
data=GenerateKeyRequest(),
|
||||||
|
route=KeyManagementRoutes.KEY_GENERATE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -739,13 +741,26 @@ def test_key_generation_required_params_check(
|
||||||
|
|
||||||
if expected_result:
|
if expected_result:
|
||||||
if key_type == "team_key":
|
if key_type == "team_key":
|
||||||
assert _team_key_generation_check(team_table, user_api_key_dict, input_data)
|
assert _team_key_generation_check(
|
||||||
|
team_table=team_table,
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
data=input_data,
|
||||||
|
route=KeyManagementRoutes.KEY_GENERATE,
|
||||||
|
)
|
||||||
elif key_type == "personal_key":
|
elif key_type == "personal_key":
|
||||||
assert _personal_key_generation_check(user_api_key_dict, input_data)
|
assert _personal_key_generation_check(
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
data=input_data,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if key_type == "team_key":
|
if key_type == "team_key":
|
||||||
with pytest.raises(HTTPException):
|
with pytest.raises(HTTPException):
|
||||||
_team_key_generation_check(team_table, user_api_key_dict, input_data)
|
_team_key_generation_check(
|
||||||
|
team_table=team_table,
|
||||||
|
user_api_key_dict=user_api_key_dict,
|
||||||
|
data=input_data,
|
||||||
|
route=KeyManagementRoutes.KEY_GENERATE,
|
||||||
|
)
|
||||||
elif key_type == "personal_key":
|
elif key_type == "personal_key":
|
||||||
with pytest.raises(HTTPException):
|
with pytest.raises(HTTPException):
|
||||||
_personal_key_generation_check(user_api_key_dict, input_data)
|
_personal_key_generation_check(user_api_key_dict, input_data)
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { PencilAltIcon, PlusIcon, TrashIcon } from "@heroicons/react/outline";
|
||||||
import MemberModal from "./edit_membership";
|
import MemberModal from "./edit_membership";
|
||||||
import UserSearchModal from "@/components/common_components/user_search_modal";
|
import UserSearchModal from "@/components/common_components/user_search_modal";
|
||||||
import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key";
|
import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key";
|
||||||
|
import { isAdminRole } from "@/utils/roles";
|
||||||
|
|
||||||
export interface TeamData {
|
export interface TeamData {
|
||||||
team_id: string;
|
team_id: string;
|
||||||
|
@ -296,6 +296,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
{/* Member Permissions Panel */}
|
{/* Member Permissions Panel */}
|
||||||
|
{canEditTeam && (
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<MemberPermissions
|
<MemberPermissions
|
||||||
teamId={teamId}
|
teamId={teamId}
|
||||||
|
@ -303,6 +304,7 @@ const TeamInfoView: React.FC<TeamInfoProps> = ({
|
||||||
canEditTeam={canEditTeam}
|
canEditTeam={canEditTeam}
|
||||||
/>
|
/>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Settings Panel */}
|
{/* Settings Panel */}
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue