| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980 |
- package handler
- import (
- "os"
- "regexp"
- "testing"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- )
- // ---------------------------------------------------------------------------
- // 覆盖目标:审计 M-4 的路由 wiring 静态检查 ——
- // `/api/product/fetchInitialCredentials` 必须挂载 `serverCtx.JwtAuth` 中间件,
- // 且必须位于 /api/product 前缀组内,不能被错放到其他无鉴权/错前缀块中。
- //
- // 为什么这条 wiring 需要独立钉死:
- // 1. routes.go 由 goctl 生成,回归时若有人用 `goctl api go -api ... -dir .`
- // 覆写此文件,可能把新路由吞掉或挪到无 JwtAuth 包裹的块(例如误标
- // `@handler` 在 pub 组)。静态检查能最早拦截。
- // 2. M-4 的安全假设是"只有超级管理员能消费 ticket"。RequireSuperAdmin 依赖
- // JwtAuth 中间件写入 UserDetails;若 JwtAuth 被去掉,handler 自身仅能回
- // 401(无 ctx),**但任何持有前端伪造 UserDetails 注入方式**的攻击者都会
- // 直接绕过 —— wiring 锚点确保这条防线永远在最外层。
- // ---------------------------------------------------------------------------
- // TC-0967: routes.go 必须把 /product/fetchInitialCredentials 挂到 JwtAuth 包裹块,
- // 并且处于 /api/product 前缀下(而不是 /api 或其他无超管审查的位置)。
- func TestRoutes_FetchInitialCredentialsJwtAuthWired(t *testing.T) {
- raw, err := os.ReadFile("./routes.go")
- require.NoError(t, err, "必须能读到 internal/handler/routes.go")
- src := string(raw)
- // go-zero 生成的 AddRoutes 块结构:
- // server.AddRoutes(
- // rest.WithMiddlewares([]rest.Middleware{serverCtx.XxxMiddleware}, []rest.Route{
- // {...Path: "/a"...},
- // {...Path: "/fetchInitialCredentials"...},
- // ...
- // }...),
- // rest.WithPrefix("/api/product"),
- // )
- // 我们需要:
- // 1. 定位到 /fetchInitialCredentials 所在的 AddRoutes 块整段;
- // 2. 从块里摘出 rest.Middleware{...} 列表做字符串断言;
- // 3. 从块里摘出 rest.WithPrefix("...") 的 prefix 做断言。
- // 简单起见,按"向上/向下扩展"的方式提取:以 "server.AddRoutes(" 为起点、往下到首个
- // "rest.WithPrefix(\"...\")" 为止的整段。
- addRoutesBlockRe := regexp.MustCompile(
- `(?s)server\.AddRoutes\(\s*rest\.WithMiddlewares\(\s*\[\]rest\.Middleware\{([^}]*)\}[\s\S]*?"/fetchInitialCredentials"[\s\S]*?rest\.WithPrefix\("([^"]+)"\)`,
- )
- m := addRoutesBlockRe.FindStringSubmatch(src)
- require.NotEmpty(t, m,
- "routes.go 中 /fetchInitialCredentials 必须位于 server.AddRoutes(rest.WithMiddlewares(...), rest.WithPrefix(...)) 结构块里;未匹配说明路由被剥离或迁移到其他结构")
- middlewaresList := m[1]
- prefix := m[2]
- assert.Contains(t, middlewaresList, "serverCtx.JwtAuth",
- "M-4:/product/fetchInitialCredentials 必须挂载 JwtAuth 中间件,否则 RequireSuperAdmin 的上下文前置条件不成立;实际中间件列表=%q", middlewaresList)
- assert.Equal(t, "/api/product", prefix,
- "M-4:/fetchInitialCredentials 必须位于 /api/product 前缀组下;实际 prefix=%q", prefix)
- }
- // TC-0968: 防御性 wiring 检查 —— 绝不允许把 fetchInitialCredentials 挂到任何
- // *非 JwtAuth* 的中间件块中。此用例是 TC-0967 的"反证":哪怕有人把 JwtAuth 改名
- // 成另一条鉴权中间件但语义错位,也会被这里拦住。
- func TestRoutes_FetchInitialCredentialsNotInRateLimitGroup(t *testing.T) {
- raw, err := os.ReadFile("./routes.go")
- require.NoError(t, err)
- src := string(raw)
- // 检查"限流中间件包裹块内"是否误引入了 fetchInitialCredentials
- for _, name := range []string{"AdminLoginRateLimit", "ProductLoginRateLimit", "RefreshTokenRateLimit", "SyncRateLimit"} {
- re := regexp.MustCompile(`(?s)rest\.WithMiddlewares\(\s*\[\]rest\.Middleware\{[^}]*?` + name + `[^}]*?\}[^)]*?"/fetchInitialCredentials"`)
- assert.False(t, re.MatchString(src),
- "M-4:/fetchInitialCredentials 绝不能被挂到 %s 中间件组(会绕过 JwtAuth / RequireSuperAdmin)", name)
- }
- }
|