mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-02-23 11:55:48 -05:00
fix(sec): permission check for project issue
- Do an access check when loading issues for a project column, currently this is not done and exposes the title, labels and existence of a private issue that the viewer of the project board may not have access to. - The number of issues cannot be calculated in a efficient manner and stored in the database because their number may vary depending on the visibility of the repositories participating in the project. The previous implementation used the pre-calculated numbers stored in each project, which did not reflect that potential variation. - The code is derived from https://github.com/go-gitea/gitea/pull/22865 (cherry picked from commit 2193afaeb9954a5778f5a47aafd0e6fbbf48d000)
This commit is contained in:
parent
57ad0b868d
commit
77fc232e5b
7 changed files with 83 additions and 43 deletions
|
@ -7,8 +7,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
project_model "code.gitea.io/gitea/models/project"
|
project_model "code.gitea.io/gitea/models/project"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/optional"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,22 +50,29 @@ func (issue *Issue) ProjectColumnID(ctx context.Context) int64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadIssuesFromColumn load issues assigned to this column
|
// LoadIssuesFromColumn load issues assigned to this column
|
||||||
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueList, error) {
|
func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column, doer *user_model.User, org *org_model.Organization, isClosed optional.Option[bool]) (IssueList, error) {
|
||||||
issueList, err := Issues(ctx, &IssuesOptions{
|
issueOpts := &IssuesOptions{
|
||||||
ProjectColumnID: b.ID,
|
ProjectColumnID: b.ID,
|
||||||
ProjectID: b.ProjectID,
|
ProjectID: b.ProjectID,
|
||||||
SortType: "project-column-sorting",
|
SortType: "project-column-sorting",
|
||||||
})
|
IsClosed: isClosed,
|
||||||
|
}
|
||||||
|
if doer != nil {
|
||||||
|
issueOpts.User = doer
|
||||||
|
issueOpts.Org = org
|
||||||
|
} else {
|
||||||
|
issueOpts.AllPublic = true
|
||||||
|
}
|
||||||
|
|
||||||
|
issueList, err := Issues(ctx, issueOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Default {
|
if b.Default {
|
||||||
issues, err := Issues(ctx, &IssuesOptions{
|
issueOpts.ProjectColumnID = db.NoConditionID
|
||||||
ProjectColumnID: db.NoConditionID,
|
|
||||||
ProjectID: b.ProjectID,
|
issues, err := Issues(ctx, issueOpts)
|
||||||
SortType: "project-column-sorting",
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -78,10 +87,10 @@ func LoadIssuesFromColumn(ctx context.Context, b *project_model.Column) (IssueLi
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadIssuesFromColumnList load issues assigned to the columns
|
// LoadIssuesFromColumnList load issues assigned to the columns
|
||||||
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList) (map[int64]IssueList, error) {
|
func LoadIssuesFromColumnList(ctx context.Context, bs project_model.ColumnList, doer *user_model.User, org *org_model.Organization, isClosed optional.Option[bool]) (map[int64]IssueList, error) {
|
||||||
issuesMap := make(map[int64]IssueList, len(bs))
|
issuesMap := make(map[int64]IssueList, len(bs))
|
||||||
for i := range bs {
|
for i := range bs {
|
||||||
il, err := LoadIssuesFromColumn(ctx, bs[i])
|
il, err := LoadIssuesFromColumn(ctx, bs[i], doer, org, isClosed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -160,3 +169,36 @@ func IssueAssignOrRemoveProject(ctx context.Context, issue *Issue, doer *user_mo
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NumIssuesInProjects returns the amount of issues assigned to one of the project
|
||||||
|
// in the list which the doer can access.
|
||||||
|
func NumIssuesInProjects(ctx context.Context, pl []*project_model.Project, doer *user_model.User, org *org_model.Organization, isClosed optional.Option[bool]) (map[int64]int, error) {
|
||||||
|
numMap := make(map[int64]int, len(pl))
|
||||||
|
for _, p := range pl {
|
||||||
|
num, err := NumIssuesInProject(ctx, p, doer, org, isClosed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
numMap[p.ID] = num
|
||||||
|
}
|
||||||
|
|
||||||
|
return numMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NumIssuesInProject returns the amount of issues assigned to the project which
|
||||||
|
// the doer can access.
|
||||||
|
func NumIssuesInProject(ctx context.Context, p *project_model.Project, doer *user_model.User, org *org_model.Organization, isClosed optional.Option[bool]) (int, error) {
|
||||||
|
numIssuesInProject := int(0)
|
||||||
|
bs, err := p.GetColumns(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
im, err := LoadIssuesFromColumnList(ctx, bs, doer, org, isClosed)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
for _, il := range im {
|
||||||
|
numIssuesInProject += len(il)
|
||||||
|
}
|
||||||
|
return numIssuesInProject, nil
|
||||||
|
}
|
||||||
|
|
|
@ -57,20 +57,6 @@ func (Column) TableName() string {
|
||||||
return "project_board" // TODO: the legacy table name should be project_column
|
return "project_board" // TODO: the legacy table name should be project_column
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumIssues return counter of all issues assigned to the column
|
|
||||||
func (c *Column) NumIssues(ctx context.Context) int {
|
|
||||||
total, err := db.GetEngine(ctx).Table("project_issue").
|
|
||||||
Where("project_id=?", c.ProjectID).
|
|
||||||
And("project_board_id=?", c.ID).
|
|
||||||
GroupBy("issue_id").
|
|
||||||
Cols("issue_id").
|
|
||||||
Count()
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(total)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
|
func (c *Column) GetIssues(ctx context.Context) ([]*ProjectIssue, error) {
|
||||||
issues := make([]*ProjectIssue, 0, 5)
|
issues := make([]*ProjectIssue, 0, 5)
|
||||||
if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).
|
if err := db.GetEngine(ctx).Where("project_id=?", c.ProjectID).
|
||||||
|
|
|
@ -34,20 +34,6 @@ func deleteProjectIssuesByProjectID(ctx context.Context, projectID int64) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NumIssues return counter of all issues assigned to a project
|
|
||||||
func (p *Project) NumIssues(ctx context.Context) int {
|
|
||||||
c, err := db.GetEngine(ctx).Table("project_issue").
|
|
||||||
Where("project_id=?", p.ID).
|
|
||||||
GroupBy("issue_id").
|
|
||||||
Cols("issue_id").
|
|
||||||
Count()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("NumIssues: %v", err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return int(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumClosedIssues return counter of closed issues assigned to a project
|
// NumClosedIssues return counter of closed issues assigned to a project
|
||||||
func (p *Project) NumClosedIssues(ctx context.Context) int {
|
func (p *Project) NumClosedIssues(ctx context.Context) int {
|
||||||
c, err := db.GetEngine(ctx).Table("project_issue").
|
c, err := db.GetEngine(ctx).Table("project_issue").
|
||||||
|
|
|
@ -126,6 +126,19 @@ func Projects(ctx *context.Context) {
|
||||||
ctx.Data["PageIsViewProjects"] = true
|
ctx.Data["PageIsViewProjects"] = true
|
||||||
ctx.Data["SortType"] = sortType
|
ctx.Data["SortType"] = sortType
|
||||||
|
|
||||||
|
numOpenIssues, err := issues_model.NumIssuesInProjects(ctx, projects, ctx.Doer, ctx.Org.Organization, optional.Some(false))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("NumIssuesInProjects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
numClosedIssues, err := issues_model.NumIssuesInProjects(ctx, projects, ctx.Doer, ctx.Org.Organization, optional.Some(true))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("NumIssuesInProjects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["NumOpenIssuesInProject"] = numOpenIssues
|
||||||
|
ctx.Data["NumClosedIssuesInProject"] = numClosedIssues
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplProjects)
|
ctx.HTML(http.StatusOK, tplProjects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +345,7 @@ func ViewProject(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns)
|
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns, ctx.Doer, ctx.Org.Organization, optional.None[bool]())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("LoadIssuesOfColumns", err)
|
ctx.ServerError("LoadIssuesOfColumns", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -125,6 +125,19 @@ func Projects(ctx *context.Context) {
|
||||||
ctx.Data["IsProjectsPage"] = true
|
ctx.Data["IsProjectsPage"] = true
|
||||||
ctx.Data["SortType"] = sortType
|
ctx.Data["SortType"] = sortType
|
||||||
|
|
||||||
|
numOpenIssues, err := issues_model.NumIssuesInProjects(ctx, projects, ctx.Doer, ctx.Org.Organization, optional.Some(false))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("NumIssuesInProjects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
numClosedIssues, err := issues_model.NumIssuesInProjects(ctx, projects, ctx.Doer, ctx.Org.Organization, optional.Some(true))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("NumIssuesInProjects", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["NumOpenIssuesInProject"] = numOpenIssues
|
||||||
|
ctx.Data["NumClosedIssuesInProject"] = numClosedIssues
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplProjects)
|
ctx.HTML(http.StatusOK, tplProjects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,7 +323,7 @@ func ViewProject(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns)
|
issuesMap, err := issues_model.LoadIssuesFromColumnList(ctx, columns, ctx.Doer, nil, optional.None[bool]())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("LoadIssuesOfColumns", err)
|
ctx.ServerError("LoadIssuesOfColumns", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -49,11 +49,11 @@
|
||||||
<div class="group">
|
<div class="group">
|
||||||
<div class="flex-text-block">
|
<div class="flex-text-block">
|
||||||
{{svg "octicon-issue-opened" 14}}
|
{{svg "octicon-issue-opened" 14}}
|
||||||
{{ctx.Locale.PrettyNumber (.NumOpenIssues ctx)}} {{ctx.Locale.Tr "repo.issues.open_title"}}
|
{{ctx.Locale.PrettyNumber (index $.NumOpenIssuesInProject .ID)}} {{ctx.Locale.Tr "repo.issues.open_title"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-text-block">
|
<div class="flex-text-block">
|
||||||
{{svg "octicon-check" 14}}
|
{{svg "octicon-check" 14}}
|
||||||
{{ctx.Locale.PrettyNumber (.NumClosedIssues ctx)}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
|
{{ctx.Locale.PrettyNumber (index $.NumClosedIssuesInProject .ID)}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
|
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
|
<div class="project-column-header{{if $canWriteProject}} tw-cursor-grab{{end}}">
|
||||||
<div class="ui large label project-column-title tw-py-1">
|
<div class="ui large label project-column-title tw-py-1">
|
||||||
<div class="ui small circular grey label project-column-issue-count">
|
<div class="ui small circular grey label project-column-issue-count">
|
||||||
{{.NumIssues ctx}}
|
{{len (index $.IssuesMap .ID)}}
|
||||||
</div>
|
</div>
|
||||||
<span class="project-column-title-label">{{.Title}}</span>
|
<span class="project-column-title-label">{{.Title}}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue