mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
refactor(proxy): inject settings and Redis cache into LoginThrottle.from_request
Removes the runtime import of proxy_server from login_throttle so the throttle module no longer participates in the import cycle CodeQL flagged (py/cyclic-import). Callers pass general_settings and redis_usage_cache explicitly; behavior is unchanged. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
9ca6307b9d
commit
ce82033f62
3 changed files with 27 additions and 34 deletions
|
|
@ -11,7 +11,7 @@ startup and can be reassigned later.
|
|||
|
||||
import asyncio
|
||||
import hashlib
|
||||
from collections.abc import Awaitable, Callable
|
||||
from collections.abc import Awaitable, Callable, Mapping
|
||||
from dataclasses import dataclass
|
||||
from functools import cache
|
||||
from types import MappingProxyType
|
||||
|
|
@ -134,10 +134,10 @@ class LoginThrottle:
|
|||
enabled: bool = True
|
||||
|
||||
@classmethod
|
||||
def from_request(cls, request: Request) -> "LoginThrottle":
|
||||
"""Build the throttle for this request from the live proxy settings and caches."""
|
||||
from litellm.proxy.proxy_server import general_settings, redis_usage_cache
|
||||
|
||||
def from_request(
|
||||
cls, request: Request, general_settings: Mapping[str, object] | None, redis_cache: RedisCache | None
|
||||
) -> "LoginThrottle":
|
||||
"""Build the throttle for this request from the proxy's general_settings and shared Redis cache."""
|
||||
settings: Final = general_settings or _NO_SETTINGS
|
||||
cidrs: Final = normalize_cidr_ranges(
|
||||
settings.get(TRUSTED_PROXY_RANGES_KEY), setting_name=TRUSTED_PROXY_RANGES_KEY
|
||||
|
|
@ -167,7 +167,7 @@ class LoginThrottle:
|
|||
),
|
||||
username_cache=_FAILED_LOGIN_USERNAME_CACHE,
|
||||
source_cache=_FAILED_LOGIN_SOURCE_CACHE,
|
||||
redis_cache=redis_usage_cache,
|
||||
redis_cache=redis_cache,
|
||||
enabled=not _rate_limit_disabled(),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -15853,7 +15853,7 @@ async def login(request: Request):
|
|||
password=password,
|
||||
master_key=master_key,
|
||||
prisma_client=prisma_client,
|
||||
throttle=LoginThrottle.from_request(request),
|
||||
throttle=LoginThrottle.from_request(request, general_settings, redis_usage_cache),
|
||||
general_settings=general_settings,
|
||||
)
|
||||
except ProxyException as exc:
|
||||
|
|
@ -15946,7 +15946,7 @@ async def login_v2(request: Request):
|
|||
password=password,
|
||||
master_key=master_key,
|
||||
prisma_client=prisma_client,
|
||||
throttle=LoginThrottle.from_request(request),
|
||||
throttle=LoginThrottle.from_request(request, general_settings, redis_usage_cache),
|
||||
general_settings=general_settings,
|
||||
)
|
||||
|
||||
|
|
@ -16018,7 +16018,7 @@ async def login_v3(request: Request):
|
|||
password=password,
|
||||
master_key=master_key,
|
||||
prisma_client=prisma_client,
|
||||
throttle=LoginThrottle.from_request(request),
|
||||
throttle=LoginThrottle.from_request(request, general_settings, redis_usage_cache),
|
||||
general_settings=general_settings,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1348,7 +1348,6 @@ async def test_counters_do_not_share_the_key_authentication_cache(monkeypatch):
|
|||
|
||||
monkeypatch.setenv("UI_USERNAME", "admin")
|
||||
monkeypatch.setenv("UI_PASSWORD", "right")
|
||||
monkeypatch.setattr(ps, "redis_usage_cache", None)
|
||||
|
||||
auth_cache_keys_before = set(ps.user_api_key_cache.in_memory_cache.cache_dict)
|
||||
|
||||
|
|
@ -1356,7 +1355,7 @@ async def test_counters_do_not_share_the_key_authentication_cache(monkeypatch):
|
|||
request.headers = {}
|
||||
request.client = MagicMock()
|
||||
request.client.host = "1.2.3.4"
|
||||
throttle = LoginThrottle.from_request(request)
|
||||
throttle = LoginThrottle.from_request(request, general_settings={}, redis_cache=None)
|
||||
|
||||
for i in range(25):
|
||||
with pytest.raises(ProxyException, match="Invalid credentials"):
|
||||
|
|
@ -1368,30 +1367,28 @@ async def test_counters_do_not_share_the_key_authentication_cache(monkeypatch):
|
|||
)
|
||||
|
||||
|
||||
def test_settings_that_arrive_as_environment_strings_are_honored(monkeypatch):
|
||||
def test_settings_that_arrive_as_environment_strings_are_honored():
|
||||
"""An `os.environ/VAR` reference in general_settings resolves to a string, not an int.
|
||||
|
||||
Regression: a digit string fell back to the default with only a log line, so an operator
|
||||
tightening the limits through environment substitution silently kept the stock ceilings.
|
||||
"""
|
||||
from litellm.proxy import proxy_server as ps
|
||||
from litellm.proxy.auth.login_throttle import LoginThrottle
|
||||
|
||||
monkeypatch.setattr(
|
||||
ps,
|
||||
"general_settings",
|
||||
{
|
||||
"max_failed_login_attempts": "7",
|
||||
"max_failed_login_attempts_per_source": " 70 ",
|
||||
"failed_login_window_seconds": "not-a-number",
|
||||
},
|
||||
)
|
||||
request = MagicMock()
|
||||
request.headers = {}
|
||||
request.client = MagicMock()
|
||||
request.client.host = "1.2.3.4"
|
||||
|
||||
throttle = LoginThrottle.from_request(request)
|
||||
throttle = LoginThrottle.from_request(
|
||||
request,
|
||||
general_settings={
|
||||
"max_failed_login_attempts": "7",
|
||||
"max_failed_login_attempts_per_source": " 70 ",
|
||||
"failed_login_window_seconds": "not-a-number",
|
||||
},
|
||||
redis_cache=None,
|
||||
)
|
||||
|
||||
assert throttle.max_attempts == 7
|
||||
assert throttle.max_attempts_per_source == 70
|
||||
|
|
@ -1404,41 +1401,37 @@ def test_the_disable_flag_is_read_once_not_per_login_attempt(monkeypatch):
|
|||
With a hosted secret manager in read mode that is a synchronous network call per guess, so a
|
||||
flood of wrong passwords could exhaust the secret manager even after the source was refused.
|
||||
"""
|
||||
from litellm.proxy import proxy_server as ps
|
||||
from litellm.proxy.auth import login_throttle
|
||||
|
||||
reads: Final[list[str]] = [] # mutable-ok: test-only call recorder
|
||||
monkeypatch.setattr(login_throttle, "get_secret_bool", lambda name, default: reads.append(name) or default)
|
||||
login_throttle._rate_limit_disabled.cache_clear()
|
||||
monkeypatch.setattr(ps, "general_settings", {})
|
||||
request = MagicMock()
|
||||
request.headers = {}
|
||||
request.client = MagicMock()
|
||||
request.client.host = "1.2.3.4"
|
||||
|
||||
for _ in range(50):
|
||||
assert login_throttle.LoginThrottle.from_request(request).enabled is True
|
||||
assert login_throttle.LoginThrottle.from_request(request, general_settings={}, redis_cache=None).enabled is True
|
||||
|
||||
login_throttle._rate_limit_disabled.cache_clear()
|
||||
assert reads == ["LITELLM_DISABLE_LOGIN_RATE_LIMIT"]
|
||||
|
||||
|
||||
def test_a_negative_or_boolean_setting_falls_back_to_the_default(monkeypatch):
|
||||
def test_a_negative_or_boolean_setting_falls_back_to_the_default():
|
||||
"""A limit below one would refuse everyone; a bool is a typo, not a count."""
|
||||
from litellm.proxy import proxy_server as ps
|
||||
from litellm.proxy.auth.login_throttle import LoginThrottle
|
||||
|
||||
monkeypatch.setattr(
|
||||
ps,
|
||||
"general_settings",
|
||||
{"max_failed_login_attempts": "-7", "max_failed_login_attempts_per_source": True},
|
||||
)
|
||||
request = MagicMock()
|
||||
request.headers = {}
|
||||
request.client = MagicMock()
|
||||
request.client.host = "1.2.3.4"
|
||||
|
||||
throttle = LoginThrottle.from_request(request)
|
||||
throttle = LoginThrottle.from_request(
|
||||
request,
|
||||
general_settings={"max_failed_login_attempts": "-7", "max_failed_login_attempts_per_source": True},
|
||||
redis_cache=None,
|
||||
)
|
||||
|
||||
assert throttle.max_attempts == 50
|
||||
assert throttle.max_attempts_per_source == 250
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue