mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-11-01 04:47:03 +08:00
fix(system): 公开设置不再返回存储的连接配置
/system/setting/public/{key} 只要求登录用户,白名单中的 Storages 原样返回
整个存储列表,每项的 config 保存着各存储的连接配置和凭据(SMB 用户名密码、
alist 账号、115 token 等)。设置合同已将 Storages 标记为 sensitive,
/system/settings 默认会脱敏,只有这个公开接口直接返回原值。
前端 v3 已改用 /storage/options 读取存储名称和类型,公开接口的 Storages
目前只剩 Agent 的 config.public.get 和旧版前端页面会用到,它们只需要
name、type。因此保留该 key,改为与 /storage/options 相同的投影:只返回
name(缺省时取 type)和 type,跳过没有 type 的项。所有调用者返回相同
结果,超级管理员仍可通过 /system/setting/Storages 读取完整配置。
白名单内其他配置(目录、站点范围、集数定位规则、默认订阅规则、Follow
订阅者)不含连接凭据,保持原值返回。同步更新 docs/mcp-api.md 的接口说明。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -110,6 +110,17 @@ _PUBLIC_SYSTEM_CONFIG_KEYS = {
|
||||
_PUBLIC_SETTINGS_KEYS = {"PLUGIN_MARKET"}
|
||||
|
||||
|
||||
def _project_public_storages(value: Any) -> list[dict[str, Any]]:
|
||||
"""存储配置只公开名称和类型,与 /storage/options 一致,config 中的连接配置不返回。"""
|
||||
if not isinstance(value, list):
|
||||
return []
|
||||
return [
|
||||
{"name": item.get("name") or item["type"], "type": item["type"]}
|
||||
for item in value
|
||||
if isinstance(item, dict) and item.get("type")
|
||||
]
|
||||
|
||||
|
||||
async def _get_image_proxy_allowed_domains() -> set[str]:
|
||||
"""合并站点快照与当前图片代理配置,仍逐请求执行 DNS 安全校验。"""
|
||||
runtime_settings = get_runtime_settings()
|
||||
@@ -660,7 +671,7 @@ async def get_progress(
|
||||
)
|
||||
async def get_public_setting(key: str, _: ApiPrincipal = Depends(get_current_active_user_async)) -> _SchemaResponse:
|
||||
"""
|
||||
查询普通用户可读取的非敏感系统设置
|
||||
查询普通用户可读取的非敏感系统设置,存储配置只返回名称和类型
|
||||
"""
|
||||
if key in _PUBLIC_SETTINGS_KEYS:
|
||||
return _SchemaResponse(
|
||||
@@ -669,7 +680,10 @@ async def get_public_setting(key: str, _: ApiPrincipal = Depends(get_current_act
|
||||
)
|
||||
if key not in _PUBLIC_SYSTEM_CONFIG_KEYS:
|
||||
raise HTTPException(status_code=404, detail="配置项不存在")
|
||||
value = get_configured_system_config().get(_PUBLIC_SYSTEM_CONFIG_KEYS[key])
|
||||
config_key = _PUBLIC_SYSTEM_CONFIG_KEYS[key]
|
||||
value = get_configured_system_config().get(config_key)
|
||||
if config_key is SystemConfigKey.Storages:
|
||||
value = _project_public_storages(value)
|
||||
return _SchemaResponse(success=True, data={"value": value})
|
||||
|
||||
|
||||
|
||||
@@ -743,7 +743,7 @@ AMLL 使用无需鉴权的原生搜索与获取接口,先尝试 ISRC,再核
|
||||
| GET | `/api/v1/dashboard/schedule` | 查询所有后台定时服务,包含当前完成百分比、进度文本和执行状态 |
|
||||
| GET | `/api/v1/dashboard/schedule/{job_id}/progress` | 查询指定后台定时服务的实时进度详情 |
|
||||
| GET | `/api/v1/dashboard/schedule2/{job_id}/progress` | 使用 API_TOKEN 查询指定后台定时服务的实时进度详情 |
|
||||
| GET | `/api/v1/system/setting/public/{key}` | 登录用户读取白名单内非敏感系统设置,仅支持目录、存储、站点范围、默认订阅规则、Follow 订阅者和插件市场地址等前端必需配置 |
|
||||
| GET | `/api/v1/system/setting/public/{key}` | 登录用户读取白名单内非敏感系统设置,仅支持目录、存储、站点范围、默认订阅规则、Follow 订阅者和插件市场地址等前端必需配置;存储只返回 `name` 和 `type`,与 `/api/v1/storage/options` 一致,不含连接配置 |
|
||||
| POST | `/api/v1/system/setting/PLUGIN_MARKET/sync-wiki` | 管理员从 MoviePilot Wiki 的插件文档同步公开插件仓库清单,和本地 `PLUGIN_MARKET` 合并去重后写入配置 |
|
||||
| GET | `/api/v1/system/module-catalog` | 查询宿主模块及其服务类型目录,供前端选择器构造选项 |
|
||||
| GET | `/api/v1/system/modulelist` | 查询已启用模块,保留 `name` 原始中文字段,并提供 `name_i18n` 和 `name_key` 给多语言前端展示 |
|
||||
|
||||
@@ -232,6 +232,65 @@ def test_system_public_setting_allows_only_non_sensitive_keys(monkeypatch):
|
||||
assert exc_info.value.detail == "配置项不存在"
|
||||
|
||||
|
||||
def test_system_public_setting_storages_returns_name_and_type_only(monkeypatch):
|
||||
"""公开设置读取存储时只返回名称和类型,与 /storage/options 一致,不含连接配置。"""
|
||||
storages = [
|
||||
{"type": "local", "name": "本地存储", "config": {}},
|
||||
{
|
||||
"type": "smb",
|
||||
"name": "NAS",
|
||||
"config": {"host": "192.168.1.2", "username": "smb-user", "password": "smb-password"},
|
||||
},
|
||||
{"type": "u115", "config": {"refresh_token": "u115-refresh", "access_token": "u115-access"}},
|
||||
{"name": "缺少类型", "config": {"token": "untyped-token"}},
|
||||
]
|
||||
fake_config = SimpleNamespace(get=lambda key: storages if key == SystemConfigKey.Storages else None)
|
||||
monkeypatch.setattr(system_endpoint, "get_configured_system_config", lambda: fake_config)
|
||||
monkeypatch.setattr("app.application.storage.get_configured_system_config", lambda: fake_config)
|
||||
|
||||
response = asyncio.run(system_endpoint.get_public_setting(SystemConfigKey.Storages.value))
|
||||
|
||||
assert response.success is True
|
||||
assert response.data == {
|
||||
"value": [
|
||||
{"name": "本地存储", "type": "local"},
|
||||
{"name": "NAS", "type": "smb"},
|
||||
{"name": "u115", "type": "u115"},
|
||||
]
|
||||
}
|
||||
assert response.data["value"] == [option.model_dump() for option in storage_endpoint.storage_options()]
|
||||
serialized = response.model_dump_json()
|
||||
for value in ("smb-user", "smb-password", "192.168.1.2", "u115-refresh", "u115-access", "untyped-token"):
|
||||
assert value not in serialized
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"config_key",
|
||||
[
|
||||
SystemConfigKey.Directories,
|
||||
SystemConfigKey.IndexerSites,
|
||||
SystemConfigKey.EpisodeFormatRuleTable,
|
||||
SystemConfigKey.DefaultMovieSubscribeConfig,
|
||||
SystemConfigKey.DefaultTvSubscribeConfig,
|
||||
SystemConfigKey.DefaultMusicSubscribeConfig,
|
||||
SystemConfigKey.FollowSubscribers,
|
||||
],
|
||||
)
|
||||
def test_system_public_setting_returns_other_keys_as_stored(monkeypatch, config_key):
|
||||
"""存储以外的公开配置仍按原值返回。"""
|
||||
stored = [{"name": "配置项", "config": {"nested": True}}]
|
||||
monkeypatch.setattr(
|
||||
system_endpoint,
|
||||
"get_configured_system_config",
|
||||
lambda: SimpleNamespace(get=lambda key: stored if key == config_key else None),
|
||||
)
|
||||
|
||||
response = asyncio.run(system_endpoint.get_public_setting(config_key.value))
|
||||
|
||||
assert response.success is True
|
||||
assert response.data == {"value": stored}
|
||||
|
||||
|
||||
def test_system_ping_returns_success():
|
||||
"""服务存活检测接口返回标准成功响应。"""
|
||||
response = asyncio.run(system_endpoint.ping())
|
||||
|
||||
Reference in New Issue
Block a user