Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix invalid issues in project boards #22865

Draft
wants to merge 95 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 92 commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
16836c2
fix edit/close/delete projects in org
yp05327 Feb 11, 2023
91cbfce
fix
yp05327 Feb 11, 2023
3f76a8f
fix lint
zeripath Feb 11, 2023
192a6d4
improve edit/close/delete permission
yp05327 Feb 12, 2023
26564fb
fix write projects permission of users in teams
yp05327 Feb 12, 2023
a266778
fix no access team permission of projects/packages
yp05327 Feb 12, 2023
99a3351
fix code tab permission in org team unit
yp05327 Feb 12, 2023
810de4e
fix
yp05327 Feb 12, 2023
39f8366
remove noused canAccessProjects
yp05327 Feb 12, 2023
33c57bc
test add issue
yp05327 Feb 14, 2023
5242c79
test add porject.TypeUser
yp05327 Feb 14, 2023
f92c5e0
remove TypeUser
yp05327 Feb 15, 2023
feb43e0
fix TODO: repoID is 0 in user/org projects
yp05327 Feb 15, 2023
3e6ae97
remove break in addUpdateIssueProject
yp05327 Feb 15, 2023
49a9595
fix
yp05327 Feb 15, 2023
a608963
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Feb 16, 2023
a3396b9
fix redundant break statement
yp05327 Feb 16, 2023
005568f
change ProjectList to project.List
yp05327 Feb 16, 2023
adb83e6
imporve variable name
yp05327 Feb 16, 2023
6ebb80d
revert the change of issue-action project data-url
yp05327 Feb 16, 2023
87d98fc
add TODO
yp05327 Feb 16, 2023
ebb6e83
fix retrieve projects in an issue
yp05327 Feb 17, 2023
762cfa6
fix 'new project' button in repo projects page
yp05327 Feb 17, 2023
2579105
Merge branch 'go-gitea:main' into fix-edit-close-delete-projects-of-org
yp05327 Feb 17, 2023
ec104a2
improve project type
yp05327 Feb 17, 2023
c0677c3
fix wrong project link
yp05327 Feb 17, 2023
f9c3351
fix permission check in projectboard
yp05327 Feb 18, 2023
6e668ef
remove used code
yp05327 Feb 18, 2023
a4a75a3
fix var-naming
yp05327 Feb 18, 2023
6235923
fix misspelling
yp05327 Feb 18, 2023
a75dedb
add db migration
yp05327 Feb 18, 2023
0d2869d
fix permission check in issue.CanRetrievedByDoer
yp05327 Feb 18, 2023
2d11075
Merge branch 'go-gitea:main' into fix-edit-close-delete-projects-of-org
yp05327 Feb 18, 2023
e022704
rename GetOwner to LoadOwner
yp05327 Feb 18, 2023
5850d85
rename GetOwner to LoadOwner
yp05327 Feb 18, 2023
2b398fd
fix incorrect issue num
yp05327 Feb 19, 2023
87668f7
fix incorrect permission check in repo project
yp05327 Feb 19, 2023
4c1c419
fix incorrect open/closed issuenum in project list
yp05327 Feb 19, 2023
72d059d
binding a issue to a project needs Write perm
yp05327 Feb 19, 2023
7be7b02
fix incorrect open/close issue num in project list
yp05327 Feb 19, 2023
43336a2
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 7, 2023
0a22729
remove projects.Name
yp05327 Mar 7, 2023
951c4f1
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 9, 2023
05f6415
remove migration
yp05327 Mar 9, 2023
e0475b7
revert project type
yp05327 Mar 9, 2023
eac2724
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 10, 2023
d1d5f30
move canWriteProjects
yp05327 Mar 10, 2023
17b2eb2
update new design
yp05327 Mar 10, 2023
8868100
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 10, 2023
a824862
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 13, 2023
aaf9ef8
remove
yp05327 Mar 13, 2023
5adbb7d
remove unnecessary code
yp05327 Mar 13, 2023
894f960
add permission check for collaborations
yp05327 Mar 13, 2023
cd10524
improve performance
yp05327 Mar 13, 2023
3fba5cc
fix incorrect issue num in project list page
yp05327 Mar 13, 2023
f16fc72
update
yp05327 Mar 13, 2023
f0cd211
add err check
yp05327 Mar 13, 2023
396f11e
fix conflicts
yp05327 Mar 15, 2023
cf84ed7
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 15, 2023
cad44d9
update
yp05327 Mar 17, 2023
b9227fa
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 17, 2023
77a2c67
add nolint
yp05327 Mar 17, 2023
c92cb2e
improve search logic and performance
yp05327 Mar 17, 2023
42af698
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 17, 2023
5e42b0e
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Apr 14, 2023
1a6da57
fix
yp05327 Apr 14, 2023
265e7d3
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Apr 20, 2023
32dd6a0
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Apr 24, 2023
5dc2aa0
fix typo
yp05327 Apr 26, 2023
d761487
rename
yp05327 Apr 26, 2023
62efaae
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 May 1, 2023
0d6757d
remove archived repo check
yp05327 May 15, 2023
b78d62e
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 May 17, 2023
a5cf0a4
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 May 18, 2023
8f3d51d
move UnitGlobalDisabled
yp05327 May 18, 2023
752ae93
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 May 25, 2023
77ac2bc
fix
yp05327 May 25, 2023
4a70ec2
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Jun 5, 2023
c90b29e
Merge branch 'main' into fix-edit-close-delete-projects-of-org
GiteaBot Jun 5, 2023
598cddf
Merge branch 'main' into fix-edit-close-delete-projects-of-org
GiteaBot Jun 6, 2023
14122ea
Merge branch 'main' into fix-edit-close-delete-projects-of-org
GiteaBot Jun 6, 2023
cdb454a
only check repo unit permission
yp05327 Jun 6, 2023
79a6d4e
improve FilterWritableByDoer
yp05327 Jun 7, 2023
821fce2
improve UnitGlobalDisabled check
yp05327 Jun 7, 2023
e22dcfa
fix
yp05327 Jun 7, 2023
f924720
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Jul 10, 2023
cedeb38
fix
yp05327 Jul 10, 2023
4313a8c
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Jul 25, 2023
e0c91cb
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Aug 3, 2023
d2fad57
Merge branch 'main' into fix-edit-close-delete-projects-of-org
yp05327 Mar 6, 2024
0621ed5
fix
yp05327 Mar 6, 2024
a48cdba
fix lint
yp05327 Mar 6, 2024
b229467
remove issueList.FilterValidByDoer
yp05327 Mar 6, 2024
004f4d2
improve(bug public individual project cannot display the issues in pu…
yp05327 Mar 7, 2024
80922b1
fix issue search bug
yp05327 Mar 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions models/issues/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import (
"slices"

"code.gitea.io/gitea/models/db"
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
Expand Down Expand Up @@ -442,6 +444,28 @@ func (issue *Issue) IsPoster(uid int64) bool {
return issue.OriginalAuthorID == 0 && issue.PosterID == uid
}

func (issues IssueList) FilterValidByDoer(ctx context.Context, doer *user_model.User) (IssueList, error) {
repos, err := issues.LoadRepositories(ctx)
if err != nil {
return nil, err
}

validRepoIDs := make(container.Set[int64], len(repos))
for _, repo := range repos {
if access_model.CheckRepoUnitUser(ctx, repo, doer, unit.TypeIssues) {
validRepoIDs.Add(repo.ID)
}
}

issueList := issues[:0]
for _, issue := range issues {
if validRepoIDs.Contains(issue.RepoID) {
issueList = append(issueList, issue)
}
}
return issueList, nil
}

// GetTasks returns the amount of tasks in the issues content
func (issue *Issue) GetTasks() int {
return len(issueTasksPat.FindAllStringIndex(issue.Content, -1))
Expand Down
65 changes: 57 additions & 8 deletions models/issues/issue_project.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import (

"code.gitea.io/gitea/models/db"
project_model "code.gitea.io/gitea/models/project"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
)

// LoadProject load the project the issue was assigned to
Expand Down Expand Up @@ -48,30 +50,40 @@ func (issue *Issue) ProjectBoardID(ctx context.Context) int64 {
}

// LoadIssuesFromBoard load issues assigned to this board
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList, error) {
func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board, doer *user_model.User, isClosed optional.Option[bool]) (IssueList, error) {
issueList := make(IssueList, 0, 10)

if b.ID > 0 {
issues, err := Issues(ctx, &IssuesOptions{
ProjectBoardID: b.ID,
ProjectID: b.ProjectID,
SortType: "project-column-sorting",
IsClosed: isClosed,
})
if err != nil {
return nil, err
}
issueList = issues
issues, err = issues.FilterValidByDoer(ctx, doer)
yp05327 marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return nil, err
}
issueList = append(issueList, issues...)
}

if b.Default {
issues, err := Issues(ctx, &IssuesOptions{
ProjectBoardID: db.NoConditionID,
ProjectID: b.ProjectID,
SortType: "project-column-sorting",
IsClosed: isClosed,
})
if err != nil {
return nil, err
}
issues, err = issues.FilterValidByDoer(ctx, doer)
if err != nil {
return nil, err
}
issueList = append(issueList, issues...)
}

Expand All @@ -83,18 +95,52 @@ func LoadIssuesFromBoard(ctx context.Context, b *project_model.Board) (IssueList
}

// LoadIssuesFromBoardList load issues assigned to the boards
func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList) (map[int64]IssueList, error) {
func LoadIssuesFromBoardList(ctx context.Context, bs project_model.BoardList, doer *user_model.User, isClosed optional.Option[bool]) (map[int64]IssueList, error) {
if unit.TypeIssues.UnitGlobalDisabled() {
return nil, nil
}
issuesMap := make(map[int64]IssueList, len(bs))
for i := range bs {
il, err := LoadIssuesFromBoard(ctx, bs[i])
for _, b := range bs {
il, err := LoadIssuesFromBoard(ctx, b, doer, isClosed)
if err != nil {
return nil, err
}
issuesMap[bs[i].ID] = il
issuesMap[b.ID] = il
}
return issuesMap, nil
}

// NumIssuesInProjects returns counter of all issues assigned to a project list which doer can access
func NumIssuesInProjects(ctx context.Context, pl project_model.ProjectList, doer *user_model.User, 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, isClosed)
if err != nil {
return nil, err
}
numMap[p.ID] = num
}

return numMap, nil
}

// NumIssuesInProject returns counter of all issues assigned to a project which doer can access
func NumIssuesInProject(ctx context.Context, p *project_model.Project, doer *user_model.User, isClosed optional.Option[bool]) (int, error) {
numIssuesInProject := int(0)
bs, err := p.GetBoards(ctx)
if err != nil {
return 0, err
}
im, err := LoadIssuesFromBoardList(ctx, bs, doer, isClosed)
if err != nil {
return 0, err
}
for _, il := range im {
numIssuesInProject += len(il)
}
return numIssuesInProject, nil
}

// ChangeProjectAssign changes the project associated with an issue
func ChangeProjectAssign(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
ctx, committer, err := db.TxContext(ctx)
Expand Down Expand Up @@ -123,8 +169,11 @@ func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.U
if err != nil {
return err
}
if newProject.RepoID != issue.RepoID && newProject.OwnerID != issue.Repo.OwnerID {
return fmt.Errorf("issue's repository is not the same as project's repository")

if canWriteByDoer, err := newProject.CanWriteByDoer(ctx, issue.Repo, doer); err != nil {
return err
} else if !canWriteByDoer {
return fmt.Errorf("doer have no write permission to project [id:%d]", newProjectID)
}
}

Expand Down
14 changes: 14 additions & 0 deletions models/perm/access/repo_permission.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,3 +424,17 @@ func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *u

return perm.CanRead(unitType)
}

// CheckRepoUnitWriteUser check whether user could write the unit of this repository
func CheckRepoUnitWriteUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
if user != nil && user.IsAdmin {
return true
}
perm, err := GetUserRepoPermission(ctx, repo, user)
if err != nil {
log.Error("GetUserRepoPermission: %w", err)
return false
}

return perm.CanWrite(unitType)
}
2 changes: 2 additions & 0 deletions models/project/board.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type Board struct {
ProjectID int64 `xorm:"INDEX NOT NULL"`
CreatorID int64 `xorm:"NOT NULL"`

Project *Project `xorm:"-"`

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}
Expand Down
153 changes: 151 additions & 2 deletions models/project/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import (
"html/template"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
Expand All @@ -35,6 +40,9 @@ type (

// Type is used to identify the type of project in question and ownership
Type uint8

// List is used to identify a list of projects
ProjectList []*Project //nolint
)

const (
Expand Down Expand Up @@ -153,14 +161,155 @@ func (p *Project) IconName() string {
return "octicon-project-symlink"
}

func (p *Project) IsOrganizationProject() bool {
return p.Type == TypeOrganization
func (p *Project) IsIndividualProject() bool {
return p.Type == TypeIndividual
}

func (p *Project) IsRepositoryProject() bool {
return p.Type == TypeRepository
}

func (p *Project) IsOrganizationProject() bool {
return p.Type == TypeOrganization
}

func (pl ProjectList) getOwnerIDs() []int64 {
ids := make(container.Set[int64], len(pl))
for _, p := range pl {
if p.OwnerID == 0 {
continue
}
ids.Add(p.OwnerID)
}
return ids.Values()
}

func (pl ProjectList) getRepoIDs() []int64 {
ids := make(container.Set[int64], len(pl))
for _, p := range pl {
if p.RepoID == 0 {
continue
}
ids.Add(p.RepoID)
}
return ids.Values()
}

func (pl ProjectList) LoadOwners(ctx context.Context) ([]*user_model.User, error) {
if len(pl) == 0 {
return nil, nil
}

userIDs := pl.getOwnerIDs()
usersMap := make(map[int64]*user_model.User, len(userIDs))
if err := db.GetEngine(ctx).
Where("id > 0").
In("id", userIDs).
Find(&usersMap); err != nil {
return nil, fmt.Errorf("find users: %w", err)
}
for _, p := range pl {
if p.OwnerID > 0 && p.Owner == nil {
p.Owner = usersMap[p.OwnerID]
}
}
users := make([]*user_model.User, 0, len(usersMap))
for _, u := range usersMap {
if u != nil {
users = append(users, u)
}
}
return users, nil
}

func (pl ProjectList) LoadRepositories(ctx context.Context) (repo_model.RepositoryList, error) {
if len(pl) == 0 {
return nil, nil
}

repoIDs := pl.getRepoIDs()
repos := make(map[int64]*repo_model.Repository, len(repoIDs))
if err := db.GetEngine(ctx).
In("id", repoIDs).
Find(&repos); err != nil {
return nil, fmt.Errorf("find repository: %w", err)
}
for _, p := range pl {
if p.RepoID > 0 && p.Repo == nil {
p.Repo = repos[p.RepoID]
}
}
return repo_model.ValuesRepository(repos), nil
}

func (pl ProjectList) FilterWritableByDoer(ctx context.Context, repo *repo_model.Repository, doer *user_model.User) (ProjectList, error) {
// non-login user have no write permission
if doer == nil {
return nil, nil
}

// Check valid individual/organization projects
owners, err := pl.LoadOwners(ctx)
if err != nil {
return nil, err
}
validOwnerIDs := make(container.Set[int64], len(owners))
for _, owner := range owners {
if CheckUserUnitWriteDoer(ctx, owner, doer, unit.TypeProjects) {
validOwnerIDs.Add(owner.ID)
}
}

// Check valid repo projects
repos, err := pl.LoadRepositories(ctx)
if err != nil {
return nil, err
}
validRepoIDs := make(container.Set[int64], len(repos))
for _, repo := range repos {
if access_model.CheckRepoUnitWriteUser(ctx, repo, doer, unit.TypeProjects) {
validRepoIDs.Add(repo.ID)
}
}

projectList := pl[:0]
for _, p := range pl {
if (p.RepoID > 0 && p.RepoID == repo.ID && validRepoIDs.Contains(p.RepoID)) ||
(p.OwnerID > 0 && p.OwnerID == repo.OwnerID && validOwnerIDs.Contains(p.OwnerID)) {
projectList = append(projectList, p)
}
}
return projectList, nil
}

// CheckUserUnitWriteDoer return whether doer have write permission to the individual/organization project
func CheckUserUnitWriteDoer(ctx context.Context, user, doer *user_model.User, unitType unit.Type) bool {
if user == nil {
return false
}
if user.IsIndividual() && user.ID != doer.ID {
return false
}
if user.IsOrganization() && (*organization.Organization)(user).UnitPermission(ctx, doer, unitType) < perm.AccessModeWrite {
return false
}
return true
}

// CanWriteByDoer return whether doer have write permission to the project
func (p Project) CanWriteByDoer(ctx context.Context, repo *repo_model.Repository, doer *user_model.User) (bool, error) {
var err error
projectList := ProjectList{&p}
projectList, err = projectList.FilterWritableByDoer(ctx, repo, doer)
if err != nil {
return false, err
}
if len(projectList) == 0 {
return false, nil
}
return true, nil
}

func init() {
db.RegisterModel(new(Project))
}
Expand Down
Loading
Loading