Skip to content

Commit 8d6815c

Browse files
committed
Revert "(UI) - Security Improvement, move to JWT Auth for Admin UI Sessions (#8995)"
This reverts commit 01a44a4.
1 parent 207f41c commit 8d6815c

File tree

17 files changed

+539
-1105
lines changed

17 files changed

+539
-1105
lines changed

litellm/proxy/_types.py

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ class LiteLLMRoutes(enum.Enum):
272272
"/key/health",
273273
"/team/info",
274274
"/team/list",
275-
"/organization/info",
276275
"/organization/list",
277276
"/team/available",
278277
"/user/info",
@@ -283,11 +282,6 @@ class LiteLLMRoutes(enum.Enum):
283282
"/health",
284283
"/key/list",
285284
"/user/filter/ui",
286-
"/user/list",
287-
"/user/available_roles",
288-
"/guardrails/list",
289-
"/cache/ping",
290-
"/get/config/callbacks",
291285
]
292286

293287
# NOTE: ROUTES ONLY FOR MASTER KEY - only the Master Key should be able to Reset Spend
@@ -306,8 +300,6 @@ class LiteLLMRoutes(enum.Enum):
306300
"/user/update",
307301
"/user/delete",
308302
"/user/info",
309-
# user invitation management
310-
"/invitation/new",
311303
# team
312304
"/team/new",
313305
"/team/update",
@@ -317,20 +309,6 @@ class LiteLLMRoutes(enum.Enum):
317309
"/team/block",
318310
"/team/unblock",
319311
"/team/available",
320-
# team member management
321-
"/team/member_add",
322-
"/team/member_update",
323-
"/team/member_delete",
324-
# organization management
325-
"/organization/new",
326-
"/organization/update",
327-
"/organization/delete",
328-
"/organization/info",
329-
"/organization/list",
330-
# organization member management
331-
"/organization/member_add",
332-
"/organization/member_update",
333-
"/organization/member_delete",
334312
# model
335313
"/model/new",
336314
"/model/update",
@@ -377,32 +355,20 @@ class LiteLLMRoutes(enum.Enum):
377355
"/sso",
378356
"/sso/get/ui_settings",
379357
"/login",
380-
"/sso/session/validate",
381358
"/key/info",
382359
"/config",
383360
"/spend",
384361
"/model/info",
385-
"/model/metrics",
386-
"/model/metrics/{sub_path}",
387-
"/model/settings",
388-
"/get/litellm_model_cost_map",
389-
"/model/streaming_metrics",
390362
"/v2/model/info",
391363
"/v2/key/info",
392364
"/models",
393365
"/v1/models",
394366
"/global/spend",
395367
"/global/spend/logs",
396-
"/spend/logs/ui",
397-
"/spend/logs/ui/{id}",
398368
"/global/spend/keys",
399369
"/global/spend/models",
400370
"/global/predict/spend/logs",
401371
"/global/activity",
402-
"/global/activity/{sub_path}",
403-
"/global/activity/exceptions",
404-
"/global/activity/exceptions/{sub_path}",
405-
"/global/all_end_users",
406372
"/health/services",
407373
] + info_routes
408374

@@ -2493,7 +2459,6 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase):
24932459
"spend_tracking_routes",
24942460
"global_spend_tracking_routes",
24952461
"info_routes",
2496-
"ui_routes",
24972462
]
24982463
team_id_jwt_field: Optional[str] = None
24992464
team_id_upsert: bool = False

litellm/proxy/auth/auth_checks.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -204,11 +204,9 @@ def _allowed_routes_check(user_route: str, allowed_routes: list) -> bool:
204204
"""
205205

206206
for allowed_route in allowed_routes:
207-
if allowed_route in LiteLLMRoutes.__members__ and (
208-
RouteChecks.check_route_access(
209-
route=user_route,
210-
allowed_routes=LiteLLMRoutes[allowed_route].value,
211-
)
207+
if (
208+
allowed_route in LiteLLMRoutes.__members__
209+
and user_route in LiteLLMRoutes[allowed_route].value
212210
):
213211
return True
214212
elif allowed_route == user_route:
@@ -219,18 +217,16 @@ def _allowed_routes_check(user_route: str, allowed_routes: list) -> bool:
219217
def allowed_routes_check(
220218
user_role: Literal[
221219
LitellmUserRoles.PROXY_ADMIN,
222-
LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY,
223220
LitellmUserRoles.TEAM,
224221
LitellmUserRoles.INTERNAL_USER,
225-
LitellmUserRoles.INTERNAL_USER_VIEW_ONLY,
226222
],
227223
user_route: str,
228224
litellm_proxy_roles: LiteLLM_JWTAuth,
229-
jwt_valid_token: dict,
230225
) -> bool:
231226
"""
232227
Check if user -> not admin - allowed to access these routes
233228
"""
229+
234230
if user_role == LitellmUserRoles.PROXY_ADMIN:
235231
is_allowed = _allowed_routes_check(
236232
user_route=user_route,

litellm/proxy/auth/handle_jwt.py

Lines changed: 1 addition & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
ScopeMapping,
3434
Span,
3535
)
36-
from litellm.proxy.management_helpers.ui_session_handler import UISessionHandler
3736
from litellm.proxy.utils import PrismaClient, ProxyLogging
3837

3938
from .auth_checks import (
@@ -407,60 +406,10 @@ def is_allowed_domain(self, user_email: str) -> bool:
407406
else:
408407
return False
409408

410-
def _validate_ui_token(self, token: str) -> Optional[dict]:
411-
"""
412-
Helper function to validate tokens generated for the LiteLLM UI.
413-
Returns the decoded payload if it's a valid UI token, None otherwise.
414-
"""
415-
import jwt
416-
417-
from litellm.proxy.proxy_server import master_key
418-
419-
try:
420-
# Decode without verification to check if it's a UI token
421-
unverified_payload = jwt.decode(token, options={"verify_signature": False})
422-
423-
# Check if this looks like a UI token (has specific claims that only UI tokens would have)
424-
if UISessionHandler.is_ui_session_token(unverified_payload):
425-
426-
# This looks like a UI token, now verify it with the master key
427-
if not master_key:
428-
verbose_proxy_logger.debug(
429-
"Missing LITELLM_MASTER_KEY for UI token validation"
430-
)
431-
return None
432-
433-
try:
434-
payload = jwt.decode(
435-
token,
436-
master_key,
437-
algorithms=["HS256"],
438-
audience="litellm-ui",
439-
leeway=self.leeway,
440-
)
441-
verbose_proxy_logger.debug(
442-
"Successfully validated UI token for payload: %s",
443-
json.dumps(payload, indent=4),
444-
)
445-
return payload
446-
except jwt.InvalidTokenError as e:
447-
verbose_proxy_logger.debug(f"Invalid UI token: {str(e)}")
448-
raise ValueError(
449-
f"Invalid UI token, Unable to validate token signature {str(e)}"
450-
)
451-
452-
return None # Not a UI token
453-
except Exception as e:
454-
raise e
455-
456409
async def auth_jwt(self, token: str) -> dict:
457410
# Supported algos: https://pyjwt.readthedocs.io/en/stable/algorithms.html
458411
# "Warning: Make sure not to mix symmetric and asymmetric algorithms that interpret
459412
# the key in different ways (e.g. HS* and RS*)."
460-
461-
ui_payload = self._validate_ui_token(token)
462-
if ui_payload:
463-
return ui_payload
464413
algorithms = ["RS256", "RS384", "RS512", "PS256", "PS384", "PS512"]
465414

466415
audience = os.getenv("JWT_AUDIENCE")
@@ -667,7 +616,6 @@ async def check_admin_access(
667616
user_id: Optional[str],
668617
org_id: Optional[str],
669618
api_key: str,
670-
jwt_valid_token: dict,
671619
) -> Optional[JWTAuthBuilderResult]:
672620
"""Check admin status and route access permissions"""
673621
if not jwt_handler.is_admin(scopes=scopes):
@@ -677,7 +625,6 @@ async def check_admin_access(
677625
user_role=LitellmUserRoles.PROXY_ADMIN,
678626
user_route=route,
679627
litellm_proxy_roles=jwt_handler.litellm_jwtauth,
680-
jwt_valid_token=jwt_valid_token,
681628
)
682629
if not is_allowed:
683630
allowed_routes: List[Any] = jwt_handler.litellm_jwtauth.admin_allowed_routes
@@ -751,7 +698,6 @@ async def find_team_with_model_access(
751698
user_api_key_cache: DualCache,
752699
parent_otel_span: Optional[Span],
753700
proxy_logging_obj: ProxyLogging,
754-
jwt_valid_token: dict,
755701
) -> Tuple[Optional[str], Optional[LiteLLM_TeamTable]]:
756702
"""Find first team with access to the requested model"""
757703

@@ -784,7 +730,6 @@ async def find_team_with_model_access(
784730
user_role=LitellmUserRoles.TEAM,
785731
user_route=route,
786732
litellm_proxy_roles=jwt_handler.litellm_jwtauth,
787-
jwt_valid_token=jwt_valid_token,
788733
)
789734
if is_allowed:
790735
return team_id, team_object
@@ -975,13 +920,7 @@ async def auth_builder(
975920

976921
# Check admin access
977922
admin_result = await JWTAuthManager.check_admin_access(
978-
jwt_handler=jwt_handler,
979-
scopes=scopes,
980-
route=route,
981-
user_id=user_id,
982-
org_id=org_id,
983-
api_key=api_key,
984-
jwt_valid_token=jwt_valid_token,
923+
jwt_handler, scopes, route, user_id, org_id, api_key
985924
)
986925
if admin_result:
987926
return admin_result
@@ -1013,7 +952,6 @@ async def auth_builder(
1013952
user_api_key_cache=user_api_key_cache,
1014953
parent_otel_span=parent_otel_span,
1015954
proxy_logging_obj=proxy_logging_obj,
1016-
jwt_valid_token=jwt_valid_token,
1017955
)
1018956

1019957
# Get other objects

litellm/proxy/auth/route_checks.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import re
2-
from typing import List, Optional, Set, Union
2+
from typing import List, Optional
33

44
from fastapi import HTTPException, Request, status
55

@@ -225,9 +225,7 @@ def _route_matches_pattern(route: str, pattern: str) -> bool:
225225
return False
226226

227227
@staticmethod
228-
def check_route_access(
229-
route: str, allowed_routes: Union[List[str], Set[str]]
230-
) -> bool:
228+
def check_route_access(route: str, allowed_routes: List[str]) -> bool:
231229
"""
232230
Check if a route has access by checking both exact matches and patterns
233231

litellm/proxy/auth/user_api_key_auth.py

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
from litellm.proxy.auth.route_checks import RouteChecks
5252
from litellm.proxy.auth.service_account_checks import service_account_checks
5353
from litellm.proxy.common_utils.http_parsing_utils import _read_request_body
54-
from litellm.proxy.management_helpers.ui_session_handler import UISessionHandler
5554
from litellm.proxy.utils import PrismaClient, ProxyLogging, _to_ns
5655
from litellm.types.services import ServiceTypes
5756

@@ -336,7 +335,6 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
336335
"pass_through_endpoints", None
337336
)
338337
passed_in_key: Optional[str] = None
339-
cookie_token: Optional[str] = None
340338
if isinstance(api_key, str):
341339
passed_in_key = api_key
342340
api_key = _get_bearer_token(api_key=api_key)
@@ -346,10 +344,6 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
346344
api_key = anthropic_api_key_header
347345
elif isinstance(google_ai_studio_api_key_header, str):
348346
api_key = google_ai_studio_api_key_header
349-
elif cookie_token := UISessionHandler._get_ui_session_token_from_cookies(
350-
request
351-
):
352-
api_key = cookie_token
353347
elif pass_through_endpoints is not None:
354348
for endpoint in pass_through_endpoints:
355349
if endpoint.get("path", "") == route:
@@ -426,10 +420,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915
426420
if general_settings.get("enable_oauth2_proxy_auth", False) is True:
427421
return await handle_oauth2_proxy_request(request=request)
428422

429-
if (
430-
general_settings.get("enable_jwt_auth", False) is True
431-
or cookie_token is not None
432-
):
423+
if general_settings.get("enable_jwt_auth", False) is True:
433424
from litellm.proxy.proxy_server import premium_user
434425

435426
if premium_user is not True:

0 commit comments

Comments
 (0)