diff --git a/models/issue.go b/models/issue.go index 6912df6c28ac7..37704026efbfe 100644 --- a/models/issue.go +++ b/models/issue.go @@ -43,6 +43,7 @@ type Issue struct { Milestone *Milestone `xorm:"-"` Project *Project `xorm:"-"` Priority int + Confidential bool AssigneeID int64 `xorm:"-"` Assignee *User `xorm:"-"` IsClosed bool `xorm:"INDEX"` @@ -1120,6 +1121,8 @@ type IssuesOptions struct { IssueIDs []int64 UpdatedAfterUnix int64 UpdatedBeforeUnix int64 + Confidential bool + Doer *User // prioritize issues from this repo PriorityRepoID int64 IsArchived util.OptionalBool @@ -1248,6 +1251,11 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) { if len(opts.ExcludedLabelNames) > 0 { sess.And(builder.NotIn("issue.id", BuildLabelNamesIssueIDsCondition(opts.ExcludedLabelNames))) } + if !opts.Confidential || opts.Doer == nil { + sess.And("issue.confidential = ?", false) + } else { + //sess.And("issue.confidential = ?", true) + } } func applyReposCondition(sess *xorm.Session, repoIDs []int64) *xorm.Session { @@ -1345,7 +1353,8 @@ func Issues(opts *IssuesOptions) ([]*Issue, error) { return nil, fmt.Errorf("LoadAttributes: %v", err) } - return issues, nil + issues, _, err := FilterConfidentialIssue(opts.Doer, issues) + return issues, err } // CountIssues number return of issues by given conditions. @@ -1449,6 +1458,8 @@ type IssueStatsOptions struct { ReviewRequestedID int64 IsPull util.OptionalBool IssueIDs []int64 + Doer *User + Confidential bool } // GetIssueStats returns issue statistic information by given conditions. diff --git a/models/issue_confidential.go b/models/issue_confidential.go new file mode 100644 index 0000000000000..5e3706dac8461 --- /dev/null +++ b/models/issue_confidential.go @@ -0,0 +1,56 @@ +package models + +import ( + "code.gitea.io/gitea/modules/util" +) + +func FilterConfidentialIssue(doer *User, list IssueList) (IssueList, int, error) { + var cutoff int = 0 + var err error + var allowed bool + result := make(IssueList, len(list)) + + for i := range list { + if list[i].Confidential { + allowed, err = UserAllowedToLookAtConfidentialIssue(doer, list[i]) + if err != nil { + return nil, 0, err + } + if allowed { + result[i-cutoff] = list[i] + } else { + cutoff += 1 + } + } else { + result[i-cutoff] = list[i] + } + + } + return result[:len(list)-cutoff], cutoff, nil +} + +func UserAllowedToLookAtConfidentialIssue(doer *User, issue *Issue) (bool, error) { + if doer == nil { + return false, nil + } + + // Issue Creator is allowed + if issue.PosterID == doer.ID { + return true, nil + } + + // Assignees are allowed + assignees, err := GetAssigneeIDsByIssue(issue.ID) + if err != nil { + return false, err + } + if util.IsInt64InSlice(doer.ID, assignees) { + return true, nil + } + + // RepoAdmins are allowed + if err := issue.LoadRepo(); err != nil { + return false, err + } + return IsUserRepoAdmin(issue.Repo, doer) +} diff --git a/modules/structs/issue.go b/modules/structs/issue.go index a4a5baa90fdb9..bc61c8cc31440 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -50,6 +50,7 @@ type Issue struct { Ref string `json:"ref"` Labels []*Label `json:"labels"` Milestone *Milestone `json:"milestone"` + Confidential bool `json:"confidential"` // deprecated Assignee *User `json:"assignee"` Assignees []*User `json:"assignees"` @@ -87,8 +88,9 @@ type CreateIssueOption struct { // milestone id Milestone int64 `json:"milestone"` // list of label ids - Labels []int64 `json:"labels"` - Closed bool `json:"closed"` + Labels []int64 `json:"labels"` + Closed bool `json:"closed"` + Confidential bool `json:"confidential"` } // EditIssueOption options for editing an issue @@ -104,6 +106,7 @@ type EditIssueOption struct { // swagger:strfmt date-time Deadline *time.Time `json:"due_date"` RemoveDeadline *bool `json:"unset_due_date"` + Confidential *bool `json:"confidential"` } // EditDeadlineOption options for creating a deadline diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 6b46dc0fef531..5043e9b80d26f 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -188,6 +188,8 @@ func SearchIssues(ctx *context.APIContext) { SortType: "priorityrepo", PriorityRepoID: ctx.QueryInt64("priority_repo_id"), IsPull: isPull, + Doer: ctx.User, + Confidential: true, UpdatedBeforeUnix: before, UpdatedAfterUnix: since, } @@ -367,6 +369,8 @@ func ListIssues(ctx *context.APIContext) { LabelIDs: labelIDs, MilestoneIDs: mileIDs, IsPull: isPull, + Doer: ctx.User, + Confidential: true, } if issues, err = models.Issues(issuesOpt); err != nil { @@ -428,6 +432,16 @@ func GetIssue(ctx *context.APIContext) { } return } + + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, issue); err != nil || !allowed { + if err != nil { + ctx.Error(http.StatusInternalServerError, "UserAllowedToLookAtConfidentialIssue", err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + ctx.JSON(http.StatusOK, convert.ToAPIIssue(issue)) } @@ -611,6 +625,15 @@ func EditIssue(ctx *context.APIContext) { return } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, issue); err != nil || !allowed { + if err != nil { + ctx.Error(http.StatusInternalServerError, "UserAllowedToLookAtConfidentialIssue", err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + oldTitle := issue.Title if len(form.Title) > 0 { issue.Title = form.Title @@ -760,6 +783,15 @@ func UpdateIssueDeadline(ctx *context.APIContext) { return } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, issue); err != nil || !allowed { + if err != nil { + ctx.Error(http.StatusInternalServerError, "UserAllowedToLookAtConfidentialIssue", err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + var deadlineUnix timeutil.TimeStamp var deadline time.Time if form.Deadline != nil && !form.Deadline.IsZero() { diff --git a/routers/api/v1/repo/issue_reaction.go b/routers/api/v1/repo/issue_reaction.go index d0ba8dac65835..e564696345531 100644 --- a/routers/api/v1/repo/issue_reaction.go +++ b/routers/api/v1/repo/issue_reaction.go @@ -67,6 +67,19 @@ func GetIssueCommentReactions(ctx *context.APIContext) { return } + if err := comment.LoadIssue(); err != nil { + ctx.InternalServerError(err) + return + } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, comment.Issue); err != nil || !allowed { + if err != nil { + ctx.InternalServerError(err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + reactions, err := models.FindCommentReactions(comment) if err != nil { ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err) @@ -195,6 +208,15 @@ func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOp return } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, comment.Issue); err != nil || !allowed { + if err != nil { + ctx.InternalServerError(err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + if isCreateType { // PostIssueCommentReaction part reaction, err := models.CreateCommentReaction(ctx.User, comment.Issue, comment, form.Reaction) @@ -285,6 +307,15 @@ func GetIssueReactions(ctx *context.APIContext) { return } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, issue); err != nil || !allowed { + if err != nil { + ctx.InternalServerError(err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + reactions, err := models.FindIssueReactions(issue, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "FindIssueReactions", err) @@ -404,6 +435,15 @@ func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, i return } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, issue); err != nil || !allowed { + if err != nil { + ctx.InternalServerError(err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + if isCreateType { // PostIssueReaction part reaction, err := models.CreateIssueReaction(ctx.User, issue, form.Reaction) diff --git a/routers/api/v1/repo/issue_subscription.go b/routers/api/v1/repo/issue_subscription.go index 8acd378cc5e03..3b73b3aeb7440 100644 --- a/routers/api/v1/repo/issue_subscription.go +++ b/routers/api/v1/repo/issue_subscription.go @@ -131,6 +131,15 @@ func setIssueSubscription(ctx *context.APIContext, watch bool) { return } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, issue); err != nil || !allowed { + if err != nil { + ctx.InternalServerError(err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + current, err := models.CheckIssueWatch(user, issue) if err != nil { ctx.Error(http.StatusInternalServerError, "CheckIssueWatch", err) @@ -261,6 +270,15 @@ func GetIssueSubscribers(ctx *context.APIContext) { return } + if allowed, err := models.UserAllowedToLookAtConfidentialIssue(ctx.User, issue); err != nil || !allowed { + if err != nil { + ctx.InternalServerError(err) + } else { + ctx.Status(http.StatusForbidden) + } + return + } + iwl, err := models.GetIssueWatchers(issue.ID, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "GetIssueWatchers", err) diff --git a/routers/repo/issue.go b/routers/repo/issue.go index da3772ef5a693..de2fa6053638e 100644 --- a/routers/repo/issue.go +++ b/routers/repo/issue.go @@ -180,6 +180,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti ReviewRequestedID: reviewRequestedID, IsPull: isPullOption, IssueIDs: issueIDs, + Doer: ctx.User, + Confidential: true, }) if err != nil { ctx.ServerError("GetIssueStats", err) @@ -232,6 +234,8 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti LabelIDs: labelIDs, SortType: sortType, IssueIDs: issueIDs, + Doer: ctx.User, + Confidential: true, }) if err != nil { ctx.ServerError("Issues", err) diff --git a/routers/user/home.go b/routers/user/home.go index 431ffdde8e527..ea9190c848fe6 100644 --- a/routers/user/home.go +++ b/routers/user/home.go @@ -412,9 +412,11 @@ func buildIssueOverview(ctx *context.Context, unitType models.UnitType) { isPullList := unitType == models.UnitTypePullRequests opts := &models.IssuesOptions{ - IsPull: util.OptionalBoolOf(isPullList), - SortType: sortType, - IsArchived: util.OptionalBoolFalse, + IsPull: util.OptionalBoolOf(isPullList), + SortType: sortType, + IsArchived: util.OptionalBoolFalse, + Doer: ctx.User, + Confidential: true, } // Get repository IDs where User/Org/Team has access.