FileCodeBox Go 重写版 v2.5.6(安全审计修复版)
Go 1.27.1 (Gin+GORM) + Vue 3 文件快传服务: - 安全审计全部修复(docs/security-audit-2026-09-05.md): bcrypt 密码哈希与自动升级、presign 直传服务端大小/内容校验、 全局请求体上限、依赖升级(govulncheck 0 命中)、janitor 后台清理、 管理端审计动作落库、/admin CORS 收紧、通知内容白名单净化、 会话默认 7 天、限流缓存故障降级、robots.txt 端点等 - 前端:取件链接复制修复(不再重复拼接提取码)、markdown 净化器加固 - Redis 支持库号(FCB_REDIS_DB / redis://…/db URL) - 文档:docs/api/* 与 openapi.yaml 同步最新行为(robots.txt、 提码 5 位起、chunk 32MiB 上限、admin 审计动作等) 验证:gofmt/go vet/go test 全绿;二进制端到端冒烟通过
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
// settings 包双方言测试:Manager 全流程(ensure 行、KV 读写合并、Reload、
|
||||
// UpdateKV 屏蔽内部键、SystemStart)分别在 sqlite(默认)与 postgres(FCB_TEST_PG_DSN)上执行。
|
||||
package settings_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"filecodebox/internal/config"
|
||||
"filecodebox/internal/database"
|
||||
"filecodebox/internal/settings"
|
||||
)
|
||||
|
||||
// pgTestDSN 返回 Postgres 测试连接串;未设置 FCB_TEST_PG_DSN 时跳过。
|
||||
func pgTestDSN(t *testing.T) string {
|
||||
t.Helper()
|
||||
dsn := os.Getenv("FCB_TEST_PG_DSN")
|
||||
if dsn == "" {
|
||||
t.Skip("未设置 FCB_TEST_PG_DSN,跳过 Postgres 双方言用例")
|
||||
}
|
||||
return dsn
|
||||
}
|
||||
|
||||
// newTestManager 按方言构造库 + Manager(已完成 Migrate)。
|
||||
func newTestManager(t *testing.T, driver, dsn string) (*settings.Manager, *gorm.DB, func()) {
|
||||
t.Helper()
|
||||
if dsn == "" {
|
||||
dsn = filepath.Join(t.TempDir(), "settings-test.db")
|
||||
}
|
||||
ctx := context.Background()
|
||||
db, err := database.Open(ctx, database.Options{Driver: driver, DSN: dsn})
|
||||
if err != nil {
|
||||
t.Fatalf("[%s] Open: %v", driver, err)
|
||||
}
|
||||
if err := database.Migrate(ctx, db); err != nil {
|
||||
_ = database.Close(db)
|
||||
t.Fatalf("[%s] Migrate: %v", driver, err)
|
||||
}
|
||||
// Config 仅作内存载体(驱动不回连数据库),postgres 模式给占位 DSN 以通过校验
|
||||
t.Setenv("FCB_DB_DRIVER", driver)
|
||||
if driver == "postgres" {
|
||||
t.Setenv("FCB_DB_DSN", dsn)
|
||||
} else {
|
||||
t.Setenv("FCB_DB_DSN", "")
|
||||
}
|
||||
cfg, err := config.New()
|
||||
if err != nil {
|
||||
_ = database.Close(db)
|
||||
t.Fatalf("[%s] config.New: %v", driver, err)
|
||||
}
|
||||
mgr, err := settings.NewManager(ctx, db, cfg)
|
||||
if err != nil {
|
||||
_ = database.Close(db)
|
||||
t.Fatalf("[%s] NewManager: %v", driver, err)
|
||||
}
|
||||
return mgr, db, func() { _ = database.Close(db) }
|
||||
}
|
||||
|
||||
// runManagerSuite 双方言共用的 Manager 行为断言。
|
||||
func runManagerSuite(t *testing.T, mgr *settings.Manager) {
|
||||
t.Helper()
|
||||
ctx := context.Background()
|
||||
|
||||
// 1. 初始:未初始化(admin_token 空)
|
||||
if mgr.IsInitialized() {
|
||||
t.Fatal("初始 admin_token 为空应视为未初始化")
|
||||
}
|
||||
|
||||
// 2. UpdateKV 写入策略键 → Reload 后读取生效
|
||||
patch := map[string]any{
|
||||
settings.KeyBackgroundURL: "https://example.com/bg.png",
|
||||
settings.KeyFooterText: "自建部署,仅供内部演示",
|
||||
settings.KeyFooterBeian: "京ICP备2024000001号-1",
|
||||
settings.KeyNotifyEnabled: 0,
|
||||
settings.KeyMaxSaveSeconds: 86400,
|
||||
"_internal_secret": "must-drop", // 下划线内部键必须被拒
|
||||
}
|
||||
if err := mgr.UpdateKV(ctx, patch); err != nil {
|
||||
t.Fatalf("UpdateKV 失败: %v", err)
|
||||
}
|
||||
if err := mgr.Reload(ctx); err != nil {
|
||||
t.Fatalf("Reload 失败: %v", err)
|
||||
}
|
||||
cfg := mgr.Get()
|
||||
if got := cfg.GetString(settings.KeyBackgroundURL); got != "https://example.com/bg.png" {
|
||||
t.Fatalf("background_url 未生效: %q", got)
|
||||
}
|
||||
if got := cfg.GetString(settings.KeyFooterBeian); got != "京ICP备2024000001号-1" {
|
||||
t.Fatalf("footer_beian 未生效: %q", got)
|
||||
}
|
||||
if cfg.GetBool(settings.KeyNotifyEnabled) {
|
||||
t.Fatal("notify_enabled=0 应生效")
|
||||
}
|
||||
if got := cfg.MaxSaveSeconds(); got != 86400 {
|
||||
t.Fatalf("max_save_seconds 未生效: %d", got)
|
||||
}
|
||||
if _, ok := cfg.Get("_internal_secret"); ok {
|
||||
t.Fatal("下划线内部键不应进入运行时配置")
|
||||
}
|
||||
|
||||
// 3. KV 合并语义:二次 UpdateKV 不覆盖未提及键
|
||||
if err := mgr.UpdateKV(ctx, map[string]any{settings.KeyNotifyEnabled: 1}); err != nil {
|
||||
t.Fatalf("二次 UpdateKV: %v", err)
|
||||
}
|
||||
if err := mgr.Reload(ctx); err != nil {
|
||||
t.Fatalf("二次 Reload: %v", err)
|
||||
}
|
||||
cfg = mgr.Get()
|
||||
if !cfg.GetBool(settings.KeyNotifyEnabled) {
|
||||
t.Fatal("notify_enabled 二次写入应生效")
|
||||
}
|
||||
if got := cfg.GetString(settings.KeyFooterText); got == "" {
|
||||
t.Fatal("二次写入不应清空 footer_text")
|
||||
}
|
||||
|
||||
// 4. SystemStart:sys_start 键写入且为毫秒时间戳
|
||||
mgr.SystemStart(ctx)
|
||||
|
||||
// 5. 敏感键判定(双模式一致)
|
||||
if !settings.IsSensitiveKey("admin_token") || !settings.IsSensitiveKey("jwt_secret") {
|
||||
t.Fatal("admin_token/jwt_secret 应为敏感键")
|
||||
}
|
||||
if settings.IsSensitiveKey("footer_text") {
|
||||
t.Fatal("footer_text 不应为敏感键")
|
||||
}
|
||||
|
||||
// 6. KV schema 表完整性:全部键可从默认值读取
|
||||
for _, e := range settings.KVSchema() {
|
||||
if _, ok := cfg.Get(e.Key); !ok {
|
||||
t.Fatalf("schema 键 %q 在默认配置中不存在", e.Key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerSQLite(t *testing.T) {
|
||||
mgr, _, closeFn := newTestManager(t, "sqlite", "")
|
||||
defer closeFn()
|
||||
runManagerSuite(t, mgr)
|
||||
}
|
||||
|
||||
func TestManagerPostgres(t *testing.T) {
|
||||
dsn := pgTestDSN(t)
|
||||
mgr, _, closeFn := newTestManager(t, "postgres", dsn)
|
||||
defer closeFn()
|
||||
runManagerSuite(t, mgr)
|
||||
}
|
||||
Reference in New Issue
Block a user