From f5439ae93a1240302925e1d8ff89043dd480a64b Mon Sep 17 00:00:00 2001 From: dsh Date: Fri, 28 Aug 2026 19:48:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=87=E7=AB=A0=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E7=BC=96=E8=BE=91=E6=8C=89=E9=92=AE=E6=8C=89=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E6=94=BE=E5=BC=80=E2=80=94=E2=80=94=E4=BD=9C=E8=80=85?= =?UTF-8?q?=E5=8F=AF=E7=BC=96=E8=BE=91=E8=87=AA=E5=B7=B1=E7=9A=84=E6=96=87?= =?UTF-8?q?=E7=AB=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - renderArticleDetail 计算 CanEdit/EditURL:管理员(/admin/articles/:id/edit) 或登录用户且为文章作者(/my/articles/:id/edit)可见 - templates/pages/article.html 改用 .CanEdit/.EditURL 渲染 - my 编辑页/接口本身有 author_id 所有权约束,暴露链接无越权风险 - 新增 TestArticleDetailEditButton:作者/管理员可见、他人/匿名不可见 --- handlers/article_edit_test.go | 68 +++++++++++++++++++++++++++++++++++ handlers/home.go | 17 +++++++++ handlers/security_test.go | 1 + templates/pages/article.html | 4 +-- 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 handlers/article_edit_test.go diff --git a/handlers/article_edit_test.go b/handlers/article_edit_test.go new file mode 100644 index 0000000..c16b4f1 --- /dev/null +++ b/handlers/article_edit_test.go @@ -0,0 +1,68 @@ +package handlers + +import ( + "net/http" + "strconv" + "strings" + "testing" + + "go_blog/models" +) + +// TestArticleDetailEditButton 验证文章页编辑按钮的可见性: +// - 文章作者(普通用户)可见,链接指向 /my/articles/:id/edit +// - 管理员对所有文章可见,链接指向 /admin/articles/:id/edit +// - 其他登录用户与未登录访客不可见 +func TestArticleDetailEditButton(t *testing.T) { + e := newSecurityTestEnv(t) + + var aliceArt models.Article + if err := e.db.Where("slug = ?", "alice-post").First(&aliceArt).Error; err != nil { + t.Fatalf("alice article not found: %v", err) + } + id := strconv.FormatUint(uint64(aliceArt.ID), 10) + myEdit := "/my/articles/" + id + "/edit" + adminEdit := "/admin/articles/" + id + "/edit" + + // 未登录访客:两种链接都不得出现。 + w := e.do(http.MethodGet, "/article/alice-post", "", nil, "") + if w.Code != http.StatusOK { + t.Fatalf("anonymous GET article: status = %d", w.Code) + } + if strings.Contains(w.Body.String(), myEdit) || strings.Contains(w.Body.String(), adminEdit) { + t.Fatal("anonymous viewer must not see any edit button") + } + + // 文章作者:看到 /my/articles/:id/edit。 + alice := e.login(t, "alice") + w = e.do(http.MethodGet, "/article/alice-post", alice, nil, "") + if w.Code != http.StatusOK { + t.Fatalf("author GET article: status = %d", w.Code) + } + if !strings.Contains(w.Body.String(), myEdit) { + t.Fatal("author should see their own edit button") + } + if strings.Contains(w.Body.String(), adminEdit) { + t.Fatal("author must not see the admin edit button") + } + + // 其他作者:不可见。 + bob := e.login(t, "bob") + w = e.do(http.MethodGet, "/article/alice-post", bob, nil, "") + if w.Code != http.StatusOK { + t.Fatalf("other author GET article: status = %d", w.Code) + } + if strings.Contains(w.Body.String(), myEdit) || strings.Contains(w.Body.String(), adminEdit) { + t.Fatal("other author must not see the edit button") + } + + // 管理员:看到 /admin/articles/:id/edit。 + admin := e.login(t, "admin") + w = e.do(http.MethodGet, "/article/alice-post", admin, nil, "") + if w.Code != http.StatusOK { + t.Fatalf("admin GET article: status = %d", w.Code) + } + if !strings.Contains(w.Body.String(), adminEdit) { + t.Fatal("admin should see the admin edit button") + } +} diff --git a/handlers/home.go b/handlers/home.go index 601acc1..6225d4c 100644 --- a/handlers/home.go +++ b/handlers/home.go @@ -248,6 +248,23 @@ func renderArticleDetail(c *gin.Context, db *gorm.DB, article *models.Article, f data["CommentError"] = formErr data["CommentNotice"] = notice data["MaxCommentLength"] = MaxCommentLength + + // 文章页编辑按钮:管理员可编辑全部文章;登录用户仅可编辑自己的文章 + // (普通作者跳转 /my/articles/:id/edit,编辑页/接口均有 author_id 所有权约束)。 + canEdit := false + editURL := "" + uid := userIDFromSession(c) + role, _ := c.Get("role") + if r, _ := role.(string); r == models.RoleAdmin { + canEdit = true + editURL = fmt.Sprintf("/admin/articles/%d/edit", article.ID) + } else if uid != 0 && uid == article.AuthorID { + canEdit = true + editURL = fmt.Sprintf("/my/articles/%d/edit", article.ID) + } + data["CanEdit"] = canEdit + data["EditURL"] = editURL + c.HTML(http.StatusOK, "article", data) } diff --git a/handlers/security_test.go b/handlers/security_test.go index a4b7670..449ec29 100644 --- a/handlers/security_test.go +++ b/handlers/security_test.go @@ -90,6 +90,7 @@ func newSecurityTestEnv(t *testing.T) *securityTestEnv { r.GET("/login", LoginPage()) r.GET("/register", RegisterPage(db)) r.GET("/rss", RSSFeed(db)) + r.GET("/article/:slug", ArticleDetail(db)) api := r.Group("/api") { diff --git a/templates/pages/article.html b/templates/pages/article.html index 00540ea..1be6421 100644 --- a/templates/pages/article.html +++ b/templates/pages/article.html @@ -7,8 +7,8 @@ ← {{index .Tr "article_back_home"}} - {{if eq .Role "admin"}} -