restrict v3

This commit is contained in:
tamakyi
2026-10-18 21:36:03 +08:00
parent 6111546bbd
commit efb19606a7
2 changed files with 29 additions and 9 deletions
+16
View File
@@ -171,3 +171,19 @@ L4 整页停用、举报按钮与箱主面板筛选、邮件告警。分享卡
3. 新增 1b「停用页是否部署」+ 文案断言,dry-run 也能验 i18n/模板。
教训(已与既有坑同类):**302 / 200 都不能单独证明处置生效**,必须先确立「基线 200」这个前提。
### 二次实跑:7/7 全绿(真实环境)
`DOMAIN=admin6`:基线 200 → L4 302 → L1 200 + 分享卡 302(qid=73)→ 撤销后 200。
证明 L1 分享卡拦截、L4 整页停用、撤销、以及 L4→L1 的**替换语义**在真实库上都按预期工作。
**随后发现并修掉一个顺序缺陷(L2)**:原实现把「强制私密」放在 `moderation.Check` 之前,
一旦审核失败(内容违规或审核源不可用,fail-closed),题目已被改成私密——箱主一次失败的编辑
会让原有公开内容凭空消失。现已移到审核通过之后、`AnswerByID` 之前,仍满足
「任何时刻不出现公开+已作答」(此刻它是未作答的私密题)。编译 EXIT=0、gofmt 干净。
⚠️ 该修复需重新部署才可在测试站手测 L2/L3。
L2/L3 的手测断言(比看 flash 可靠):
- L2 后 `select is_private, answer from questions where id=<qid>` → `t` 且 answer 非空,公开页不显示;
- L3 后 `answer` 仍为空串(未被写入),`is_read` 不变。
- 干扰项:回答会走七牛审核,审核不可用会 fail-closed 报「审核服务不可用」,那不是处置问题。
+13 -9
View File
@@ -178,15 +178,6 @@ func PublishAnswer(ctx context.Context, pageUser *db.User, question *db.Question
ctx.Success("question/item")
return
}
// L2:先把该题置为私密再写入回答,任何时刻都不会出现「公开 + 已作答」的中间态。
if db.RestrictionAtLeast(restrictionLevel, db.RestrictionL2) {
if err := db.Questions.SetPrivate(ctx.Request().Context(), question.ID); err != nil {
logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to force question private")
ctx.SetInternalError()
ctx.Success("question/item")
return
}
}
// 🚨 Content security check.
// 统一守卫:按箱主「审核严格度」裁决是否放行(审核源不可用仍为 fail-closed,
@@ -198,6 +189,19 @@ func PublishAnswer(ctx context.Context, pageUser *db.User, question *db.Question
return
}
// L2 收窄公开:审核通过后再把该题置为私密,然后写入回答。
// 顺序不能提前到审核之前 —— 审核失败(违规或审核源不可用,fail-closed)会直接返回,
// 那时题目已经被改成私密,等于箱主一次失败的编辑让原有公开内容凭空消失。
// 放在这里仍然满足「任何时刻都不出现公开 + 已作答」:此刻它还是一条未作答的私密题。
if db.RestrictionAtLeast(restrictionLevel, db.RestrictionL2) {
if err := db.Questions.SetPrivate(ctx.Request().Context(), question.ID); err != nil {
logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to force question private")
ctx.SetInternalError()
ctx.Success("question/item")
return
}
}
if len(f.Images) > 0 {
image := f.Images[0]