Coverage for .venv/lib/python3.13/site-packages/litellm/proxy/common_utils/resource_ownership.py: 11%
47 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:01 +0000
1from typing import Final
3from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth
6def is_proxy_admin(user_api_key_dict: UserAPIKeyAuth | None) -> bool:
7 if user_api_key_dict is None: 7 ↛ 8line 7 didn't jump to line 8 because the condition on line 7 was never true
8 return False
10 return (
11 user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN
12 or user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value
13 )
16def get_resource_owner_scopes(
17 user_api_key_dict: UserAPIKeyAuth | None,
18) -> list[str]:
19 """
20 Return ownership scopes that may access a user-created proxy resource.
22 Raw user_id is included for rows created before scope prefixes existed.
23 Prefixes avoid collisions when falling back to team/org/key ownership
24 for keys that do not have a user_id.
26 Identity-less callers (no user_id, team_id, org_id, api_key, or token)
27 return ``[]`` — they share no scope with any other caller, so access
28 checks against an existing owner always fail and creates that depend
29 on a primary scope must reject up front. Returning a shared sentinel
30 here would let any two identity-less callers see each other's data.
31 """
32 if user_api_key_dict is None:
33 return []
35 scopes: Final[list[str]] = []
37 def _add(scope: str | None) -> None:
38 if scope and scope not in scopes:
39 scopes.append(scope)
41 if user_api_key_dict.user_id:
42 _add(user_api_key_dict.user_id)
43 _add(f"user:{user_api_key_dict.user_id}")
44 if user_api_key_dict.team_id:
45 _add(f"team:{user_api_key_dict.team_id}")
46 if user_api_key_dict.org_id:
47 _add(f"org:{user_api_key_dict.org_id}")
48 if user_api_key_dict.api_key:
49 _add(f"key:{user_api_key_dict.api_key}")
50 if user_api_key_dict.token:
51 _add(f"key:{user_api_key_dict.token}")
53 return scopes
56def get_primary_resource_owner_scope(
57 user_api_key_dict: UserAPIKeyAuth | None,
58) -> str | None:
59 """Return the canonical owner scope to stamp on newly-created rows.
61 ``None`` for identity-less callers — callers that depend on a primary
62 scope to record ownership must surface that as a hard error rather
63 than fall back to a shared sentinel (which would collapse every
64 identity-less caller into the same logical owner).
65 """
66 if user_api_key_dict is None:
67 return None
69 if user_api_key_dict.user_id:
70 return user_api_key_dict.user_id
71 if user_api_key_dict.team_id:
72 return f"team:{user_api_key_dict.team_id}"
73 if user_api_key_dict.org_id:
74 return f"org:{user_api_key_dict.org_id}"
75 if user_api_key_dict.api_key:
76 return f"key:{user_api_key_dict.api_key}"
77 if user_api_key_dict.token:
78 return f"key:{user_api_key_dict.token}"
79 return None
82def user_can_access_resource_owner(
83 owner: str | None,
84 user_api_key_dict: UserAPIKeyAuth | None,
85) -> bool:
86 if user_api_key_dict is None:
87 return True
88 if is_proxy_admin(user_api_key_dict):
89 return True
90 if owner is None:
91 return False
92 return owner in get_resource_owner_scopes(user_api_key_dict)