mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
fix(proxy): close the last three surfaces that bypassed config ownership
The two dashboard toggles under litellm_settings wrote through save_config, so the refusal applied, but they mutated the litellm module global first: a refused write still took effect in the running process until the next reload. Both now check before they mutate. /config/field/delete drops the stored key without touching the store, so a deleted key kept reading back from the process. It now refreshes the store like the other write paths. /config/list reported source and editable for the general_settings rows but not for the litellm_settings ones, so the dashboard would have shown a config-declared toggle as editable.
This commit is contained in:
parent
8e67a33fc3
commit
23ee8ad1c3
2 changed files with 84 additions and 1 deletions
|
|
@ -17589,6 +17589,7 @@ async def _persist_general_settings_ui_litellm_field(
|
|||
field_name: str, value: object, user_api_key_dict: UserAPIKeyAuth
|
||||
) -> dict:
|
||||
validated: Final = _validate_general_settings_ui_litellm_value(field_name, value)
|
||||
proxy_config.reject_config_owned_writes(section_name="litellm_settings", changed_keys={field_name: validated})
|
||||
config: Final = await proxy_config.get_config()
|
||||
before_value: Final = config.get("litellm_settings", {}).get(field_name)
|
||||
setattr(litellm, field_name, validated)
|
||||
|
|
@ -17601,9 +17602,10 @@ async def _persist_general_settings_ui_litellm_field(
|
|||
|
||||
|
||||
async def _reset_general_settings_ui_litellm_field(field_name: str, user_api_key_dict: UserAPIKeyAuth) -> dict:
|
||||
default_value: Final = _general_settings_ui_litellm_default(_GENERAL_SETTINGS_UI_LITELLM_FIELDS[field_name])
|
||||
proxy_config.reject_config_owned_writes(section_name="litellm_settings", changed_keys={field_name: default_value})
|
||||
config: Final = await proxy_config.get_config()
|
||||
before_value: Final = config.get("litellm_settings", {}).get(field_name)
|
||||
default_value: Final = _general_settings_ui_litellm_default(_GENERAL_SETTINGS_UI_LITELLM_FIELDS[field_name])
|
||||
setattr(litellm, field_name, default_value)
|
||||
if "litellm_settings" in config:
|
||||
config["litellm_settings"].pop(field_name, None)
|
||||
|
|
@ -17768,6 +17770,7 @@ async def get_config_list(
|
|||
stored_in_db_litellm = False
|
||||
else:
|
||||
stored_in_db_litellm = None
|
||||
_litellm_source = proxy_config.litellm_settings.source(litellm_field_name)
|
||||
return_val.append(
|
||||
ConfigList(
|
||||
field_name=litellm_field_name,
|
||||
|
|
@ -17779,6 +17782,8 @@ async def get_config_list(
|
|||
field_options=list(spec.get("options", ())) or None,
|
||||
field_tab=spec.get("tab"),
|
||||
nested_fields=None,
|
||||
source=_litellm_source,
|
||||
editable=_litellm_source != "config",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -17857,6 +17862,7 @@ async def delete_config_general_settings(
|
|||
},
|
||||
)
|
||||
await invalidate_config_param("general_settings")
|
||||
proxy_config.settings.apply_db_row("general_settings", general_settings)
|
||||
asyncio.create_task(
|
||||
create_config_audit_log(
|
||||
"general_settings", "deleted", before_general_settings, general_settings, user_api_key_dict
|
||||
|
|
|
|||
|
|
@ -11542,6 +11542,83 @@ async def test_update_config_general_settings_emits_audit_log(monkeypatch):
|
|||
assert before["some_api_key"] != "sk-stored-secret"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delete_config_general_settings_is_visible_to_the_next_read(monkeypatch):
|
||||
import litellm.proxy.proxy_server as proxy_server_module
|
||||
from litellm.proxy._types import ConfigFieldDelete
|
||||
from litellm.proxy.config_resolvers import SettingsStore
|
||||
from litellm.proxy.proxy_server import delete_config_general_settings, get_config_general_settings
|
||||
|
||||
fake = _fake_prisma_with_config({"max_request_size_mb": 42})
|
||||
monkeypatch.setattr(proxy_server_module, "prisma_client", fake)
|
||||
|
||||
settings = SettingsStore("general_settings")
|
||||
settings.load_yaml({})
|
||||
settings.apply_db_row("general_settings", {"max_request_size_mb": 42})
|
||||
monkeypatch.setattr(proxy_server_module.proxy_config, "settings", settings)
|
||||
|
||||
admin = UserAPIKeyAuth(api_key="hashed-admin", user_id="admin-1", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||
await delete_config_general_settings(
|
||||
data=ConfigFieldDelete(field_name="max_request_size_mb", config_type="general_settings"),
|
||||
user_api_key_dict=admin,
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as excinfo:
|
||||
await get_config_general_settings(field_name="max_request_size_mb", user_api_key_dict=admin)
|
||||
assert excinfo.value.status_code == 400
|
||||
assert "is not set" in excinfo.value.detail["error"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_litellm_field_write_refuses_a_key_the_config_file_declares(monkeypatch):
|
||||
import litellm.proxy.proxy_server as proxy_server_module
|
||||
from litellm.proxy._types import ConfigFieldUpdate
|
||||
from litellm.proxy.proxy_server import ProxyConfig, update_config_general_settings
|
||||
|
||||
pc = ProxyConfig()
|
||||
pc._load_yaml_settings_stores({"litellm_settings": {"enable_anthropic_prompt_caching": True}})
|
||||
monkeypatch.setattr(proxy_server_module, "proxy_config", pc)
|
||||
monkeypatch.setattr(litellm, "enable_anthropic_prompt_caching", True)
|
||||
|
||||
fake = _fake_prisma_with_config({})
|
||||
monkeypatch.setattr(proxy_server_module, "prisma_client", fake)
|
||||
|
||||
admin = UserAPIKeyAuth(api_key="hashed-admin", user_id="admin-1", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||
with pytest.raises(HTTPException) as excinfo:
|
||||
await update_config_general_settings(
|
||||
data=ConfigFieldUpdate(
|
||||
field_name="enable_anthropic_prompt_caching", field_value=False, config_type="general_settings"
|
||||
),
|
||||
user_api_key_dict=admin,
|
||||
)
|
||||
|
||||
assert excinfo.value.status_code == 400
|
||||
assert excinfo.value.detail["keys"] == ["enable_anthropic_prompt_caching"]
|
||||
assert litellm.enable_anthropic_prompt_caching is True
|
||||
fake.db.litellm_config.upsert.assert_not_awaited()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_litellm_field_reset_refuses_a_key_the_config_file_declares(monkeypatch):
|
||||
import litellm.proxy.proxy_server as proxy_server_module
|
||||
from litellm.proxy.proxy_server import ProxyConfig, _reset_general_settings_ui_litellm_field
|
||||
|
||||
pc = ProxyConfig()
|
||||
pc._load_yaml_settings_stores({"litellm_settings": {"enable_anthropic_prompt_caching": True}})
|
||||
monkeypatch.setattr(proxy_server_module, "proxy_config", pc)
|
||||
monkeypatch.setattr(litellm, "enable_anthropic_prompt_caching", True)
|
||||
|
||||
fake = _fake_prisma_with_config({})
|
||||
monkeypatch.setattr(proxy_server_module, "prisma_client", fake)
|
||||
|
||||
admin = UserAPIKeyAuth(api_key="hashed-admin", user_id="admin-1", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||
with pytest.raises(HTTPException) as excinfo:
|
||||
await _reset_general_settings_ui_litellm_field("enable_anthropic_prompt_caching", admin)
|
||||
|
||||
assert excinfo.value.status_code == 400
|
||||
assert litellm.enable_anthropic_prompt_caching is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_config_general_settings_refuses_a_key_the_config_file_declares(monkeypatch):
|
||||
import litellm.proxy.proxy_server as proxy_server_module
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue