fix: 配置 API 掩码方案兼容管理 UI
- GET /api/v2/config 返回完整配置的 JSON 字符串(敏感字段掩码为 ********),UI 编辑器可用 - PUT 保存时剔除掩码字段,防止掩码写回覆盖真实密钥(deep_merge 只更新真实改动)
This commit is contained in:
@@ -3493,8 +3493,39 @@ class APIv2(APIv1):
|
|||||||
print(trace)
|
print(trace)
|
||||||
handler.send_json_response({"error": str(e)}, 500)
|
handler.send_json_response({"error": str(e)}, 500)
|
||||||
|
|
||||||
|
# 配置脱敏: 敏感字段以掩码显示, 保存时剔除掩码值避免覆盖真实密钥
|
||||||
|
_CONFIG_MASK = '********'
|
||||||
|
_CONFIG_SENSITIVE_KEYS = {
|
||||||
|
'auth_pass', 'auth_token', 'auth_secret', 'smtp_password',
|
||||||
|
'password', 'secret_key', 'access_key', 'private_key',
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _mask_config(cls, data):
|
||||||
|
"""递归掩码敏感字段(保留 JSON 结构,供 UI 编辑非敏感字段)"""
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return {k: (cls._CONFIG_MASK if k in cls._CONFIG_SENSITIVE_KEYS and v else cls._mask_config(v))
|
||||||
|
for k, v in data.items()}
|
||||||
|
if isinstance(data, list):
|
||||||
|
return [cls._mask_config(v) for v in data]
|
||||||
|
return data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _unmask_config(cls, data):
|
||||||
|
"""递归剔除值为掩码的敏感字段(防止掩码写回覆盖真实值)"""
|
||||||
|
if isinstance(data, dict):
|
||||||
|
result = {}
|
||||||
|
for k, v in data.items():
|
||||||
|
if k in cls._CONFIG_SENSITIVE_KEYS and v == cls._CONFIG_MASK:
|
||||||
|
continue # 掩码值 = 未修改,保留服务器现有值
|
||||||
|
result[k] = cls._unmask_config(v)
|
||||||
|
return result
|
||||||
|
if isinstance(data, list):
|
||||||
|
return [cls._unmask_config(v) for v in data]
|
||||||
|
return data
|
||||||
|
|
||||||
def api_get_config(self, handler):
|
def api_get_config(self, handler):
|
||||||
"""获取配置文件内容 (settings.json)"""
|
"""获取配置文件内容 (settings.json, 敏感字段掩码化, 返回 JSON 字符串兼容 UI)"""
|
||||||
try:
|
try:
|
||||||
import os
|
import os
|
||||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
@@ -3512,28 +3543,18 @@ class APIv2(APIv1):
|
|||||||
}, 404)
|
}, 404)
|
||||||
return
|
return
|
||||||
|
|
||||||
# 脱敏: 不返回 settings.json 原文(含 auth_pass/auth_token 等密钥),
|
|
||||||
# 只返回精选非敏感字段
|
|
||||||
try:
|
try:
|
||||||
with open(config_path, 'r', encoding='utf-8') as f:
|
with open(config_path, 'r', encoding='utf-8') as f:
|
||||||
raw = json.load(f)
|
raw = json.load(f)
|
||||||
except Exception:
|
except Exception:
|
||||||
raw = {}
|
raw = {}
|
||||||
|
|
||||||
safe_keys = [
|
masked = self._mask_config(raw)
|
||||||
'server_name', 'host', 'port', 'base_dir', 'api_version',
|
config_str = json.dumps(masked, ensure_ascii=False, indent=2)
|
||||||
'directory_listing', 'enable_stats', 'show_hash', 'ignore_hidden',
|
|
||||||
'enable_range', 'max_workers', 'timeout', 'max_upload_size',
|
|
||||||
'enable_ws', 'enable_sse', 'enable_monitor', 'monitor_interval',
|
|
||||||
'enable_sync', 'enable_mirrors', 'auth_type', 'log_level',
|
|
||||||
'cache_size', 'cache_ttl', 'sort_by', 'sort_reverse',
|
|
||||||
'max_search_results', 'session_timeout', 'sync_interval',
|
|
||||||
]
|
|
||||||
safe_config = {k: raw.get(k) for k in safe_keys if k in raw}
|
|
||||||
|
|
||||||
handler.send_json_response({
|
handler.send_json_response({
|
||||||
"success": True,
|
"success": True,
|
||||||
"config": safe_config,
|
"config": config_str,
|
||||||
"path": config_path,
|
"path": config_path,
|
||||||
"filename": os.path.basename(config_path)
|
"filename": os.path.basename(config_path)
|
||||||
})
|
})
|
||||||
@@ -3574,6 +3595,9 @@ class APIv2(APIv1):
|
|||||||
}, 400)
|
}, 400)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# 剔除掩码字段(值为 ******** 表示未修改, 防止覆盖真实密钥)
|
||||||
|
updates = self._unmask_config(updates)
|
||||||
|
|
||||||
# 读取现有配置
|
# 读取现有配置
|
||||||
import os
|
import os
|
||||||
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|||||||
Reference in New Issue
Block a user