You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@answer.apache.org by li...@apache.org on 2026/08/28 06:31:31 UTC
(answer) 01/04: fix(security): enforce personal comment visibility
This is an automated email from the ASF dual-hosted git repository.
LinkinStars pushed a commit to branch fix/2.0.3/user
in repository https://gitbox.apache.org/repos/asf/answer.git
commit a52cc0aeb2b071ad73fc918c75a9b799b13352a2
Author: LinkinStars <li...@foxmail.com>
AuthorDate: Tue Aug 25 16:15:06 2026 +0800
fix(security): enforce personal comment visibility
---
internal/controller/comment_controller.go | 6 +-
internal/schema/comment_schema.go | 4 +-
internal/service/comment/comment_service.go | 44 ++++++----
.../comment/comment_service_visibility_test.go | 95 ++++++++++++++++++++++
4 files changed, 129 insertions(+), 20 deletions(-)
diff --git a/internal/controller/comment_controller.go b/internal/controller/comment_controller.go
index b9beead9..e0f18a8a 100644
--- a/internal/controller/comment_controller.go
+++ b/internal/controller/comment_controller.go
@@ -280,7 +280,11 @@ func (cc *CommentController) GetCommentPersonalWithPage(ctx *gin.Context) {
return
}
- req.UserID = middleware.GetLoginUserIDFromContext(ctx)
+ req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
+ if len(req.Username) == 0 {
+ req.UserID = req.LoginUserID
+ }
+ req.IsAdminModerator = middleware.GetUserIsAdminModerator(ctx)
resp, err := cc.commentService.GetCommentPersonalWithPage(ctx, req)
handler.HandleResponse(ctx, err, resp)
diff --git a/internal/schema/comment_schema.go b/internal/schema/comment_schema.go
index a9c8a21a..8297c880 100644
--- a/internal/schema/comment_schema.go
+++ b/internal/schema/comment_schema.go
@@ -232,7 +232,9 @@ type GetCommentPersonalWithPageReq struct {
// username
Username string `validate:"omitempty,gt=0,lte=100" form:"username"`
// user id
- UserID string `json:"-"`
+ UserID string `json:"-"`
+ LoginUserID string `json:"-"`
+ IsAdminModerator bool `json:"-"`
}
// GetCommentPersonalWithPageResp comment response
diff --git a/internal/service/comment/comment_service.go b/internal/service/comment/comment_service.go
index 0decd584..455ab864 100644
--- a/internal/service/comment/comment_service.go
+++ b/internal/service/comment/comment_service.go
@@ -539,32 +539,40 @@ func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *s
}
resp := make([]*schema.GetCommentPersonalWithPageResp, 0)
for _, comment := range commentList {
+ if len(comment.ObjectID) == 0 {
+ continue
+ }
+ objInfo, err := cs.objectInfoService.GetInfo(ctx, comment.ObjectID)
+ if err != nil {
+ log.Error(err)
+ continue
+ }
+ if !canViewPersonalComment(objInfo, req.LoginUserID, req.IsAdminModerator) {
+ continue
+ }
commentResp := &schema.GetCommentPersonalWithPageResp{
- CommentID: comment.ID,
- CreatedAt: comment.CreatedAt.Unix(),
- ObjectID: comment.ObjectID,
- Content: comment.ParsedText, // todo trim
+ CommentID: comment.ID,
+ CreatedAt: comment.CreatedAt.Unix(),
+ ObjectID: comment.ObjectID,
+ Content: comment.ParsedText, // todo trim
+ ObjectType: objInfo.ObjectType,
+ Title: objInfo.Title,
+ UrlTitle: htmltext.UrlTitle(objInfo.Title),
+ QuestionID: objInfo.QuestionID,
+ AnswerID: objInfo.AnswerID,
}
- if len(comment.ObjectID) > 0 {
- objInfo, err := cs.objectInfoService.GetInfo(ctx, comment.ObjectID)
- if err != nil {
- log.Error(err)
- } else {
- commentResp.ObjectType = objInfo.ObjectType
- commentResp.Title = objInfo.Title
- commentResp.UrlTitle = htmltext.UrlTitle(objInfo.Title)
- commentResp.QuestionID = objInfo.QuestionID
- commentResp.AnswerID = objInfo.AnswerID
- if objInfo.QuestionStatus == entity.QuestionStatusDeleted {
- commentResp.Title = "Deleted question"
- }
- }
+ if objInfo.QuestionStatus == entity.QuestionStatusDeleted {
+ commentResp.Title = "Deleted question"
}
resp = append(resp, commentResp)
}
return pager.NewPageModel(total, resp), nil
}
+func canViewPersonalComment(objInfo *schema.SimpleObjectInfo, userID string, isAdminModerator bool) bool {
+ return objInfo.CheckVisibility(userID, isAdminModerator) == nil
+}
+
func (cs *CommentService) notificationQuestionComment(ctx context.Context, questionUserID,
questionID, questionTitle, commentID, commentUserID, commentSummary string) {
if questionUserID == commentUserID {
diff --git a/internal/service/comment/comment_service_visibility_test.go b/internal/service/comment/comment_service_visibility_test.go
new file mode 100644
index 00000000..69be2a96
--- /dev/null
+++ b/internal/service/comment/comment_service_visibility_test.go
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package comment
+
+import (
+ "testing"
+
+ "github.com/apache/answer/internal/base/constant"
+ "github.com/apache/answer/internal/entity"
+ "github.com/apache/answer/internal/schema"
+)
+
+func TestCanViewPersonalComment(t *testing.T) {
+ testCases := []struct {
+ name string
+ status int
+ show int
+ viewer string
+ isAdmin bool
+ expected bool
+ }{
+ {"anonymous can view public question", entity.QuestionStatusAvailable, entity.QuestionShow, "", false, true},
+ {"anonymous cannot view hidden question", entity.QuestionStatusAvailable, entity.QuestionHide, "", false, false},
+ {"anonymous cannot view pending question", entity.QuestionStatusPending, entity.QuestionShow, "", false, false},
+ {"anonymous cannot view deleted question", entity.QuestionStatusDeleted, entity.QuestionShow, "", false, false},
+ {"question owner can view hidden question", entity.QuestionStatusAvailable, entity.QuestionHide, "question-owner", false, true},
+ {"moderator can view deleted question", entity.QuestionStatusDeleted, entity.QuestionShow, "", true, true},
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ objInfo := &schema.SimpleObjectInfo{
+ ObjectType: constant.QuestionObjectType,
+ QuestionCreatorUserID: "question-owner",
+ QuestionStatus: testCase.status,
+ QuestionShow: testCase.show,
+ }
+ if got := canViewPersonalComment(objInfo, testCase.viewer, testCase.isAdmin); got != testCase.expected {
+ t.Fatalf("canViewPersonalComment() = %v, want %v", got, testCase.expected)
+ }
+ })
+ }
+}
+
+func TestCanViewPersonalCommentOnAnswer(t *testing.T) {
+ testCases := []struct {
+ name string
+ viewer string
+ isAdmin bool
+ info *schema.SimpleObjectInfo
+ expected bool
+ }{
+ {
+ name: "anonymous cannot view answer on hidden question",
+ info: &schema.SimpleObjectInfo{ObjectType: constant.AnswerObjectType, ObjectCreatorUserID: "answer-owner", QuestionID: "question-id", QuestionCreatorUserID: "question-owner", AnswerStatus: entity.AnswerStatusAvailable, QuestionStatus: entity.QuestionStatusAvailable, QuestionShow: entity.QuestionHide},
+ expected: false,
+ },
+ {
+ name: "question owner can view answer on hidden question",
+ viewer: "question-owner",
+ info: &schema.SimpleObjectInfo{ObjectType: constant.AnswerObjectType, ObjectCreatorUserID: "answer-owner", QuestionID: "question-id", QuestionCreatorUserID: "question-owner", AnswerStatus: entity.AnswerStatusAvailable, QuestionStatus: entity.QuestionStatusAvailable, QuestionShow: entity.QuestionHide},
+ expected: true,
+ },
+ {
+ name: "anonymous cannot view pending answer",
+ info: &schema.SimpleObjectInfo{ObjectType: constant.AnswerObjectType, ObjectCreatorUserID: "answer-owner", QuestionID: "question-id", QuestionCreatorUserID: "question-owner", AnswerStatus: entity.AnswerStatusPending, QuestionStatus: entity.QuestionStatusAvailable, QuestionShow: entity.QuestionShow},
+ expected: false,
+ },
+ }
+
+ for _, testCase := range testCases {
+ t.Run(testCase.name, func(t *testing.T) {
+ if got := canViewPersonalComment(testCase.info, testCase.viewer, testCase.isAdmin); got != testCase.expected {
+ t.Fatalf("canViewPersonalComment() = %v, want %v", got, testCase.expected)
+ }
+ })
+ }
+}