2019-10-28 05:11:50 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-10-28 05:11:50 +03:00
package issue
import (
2022-04-28 14:48:48 +03:00
"context"
2022-06-13 12:37:59 +03:00
issues_model "code.gitea.io/gitea/models/issues"
2022-03-29 09:29:02 +03:00
"code.gitea.io/gitea/models/organization"
2021-11-28 14:58:28 +03:00
"code.gitea.io/gitea/models/perm"
2022-05-11 13:09:36 +03:00
access_model "code.gitea.io/gitea/models/perm/access"
2024-02-24 15:38:43 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-09 22:57:58 +03:00
"code.gitea.io/gitea/models/unit"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2020-10-20 21:18:25 +03:00
"code.gitea.io/gitea/modules/log"
2023-09-05 21:37:47 +03:00
notify_service "code.gitea.io/gitea/services/notify"
2019-10-28 05:11:50 +03:00
)
// DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array
2023-04-14 21:18:28 +03:00
func DeleteNotPassedAssignee ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , assignees [ ] * user_model . User ) ( err error ) {
2019-10-28 05:11:50 +03:00
var found bool
2022-05-20 17:08:52 +03:00
oriAssignes := make ( [ ] * user_model . User , len ( issue . Assignees ) )
_ = copy ( oriAssignes , issue . Assignees )
2019-10-28 05:11:50 +03:00
2022-05-20 17:08:52 +03:00
for _ , assignee := range oriAssignes {
2019-10-28 05:11:50 +03:00
found = false
for _ , alreadyAssignee := range assignees {
if assignee . ID == alreadyAssignee . ID {
found = true
break
}
}
if ! found {
2021-07-08 14:38:13 +03:00
// This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here
2023-08-10 05:39:21 +03:00
if _ , _ , err := ToggleAssigneeWithNotify ( ctx , issue , doer , assignee . ID ) ; err != nil {
2019-10-28 05:11:50 +03:00
return err
}
}
}
return nil
}
2023-08-10 05:39:21 +03:00
// ToggleAssigneeWithNoNotify changes a user between assigned and not assigned for this issue, and make issue comment for it.
func ToggleAssigneeWithNotify ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , assigneeID int64 ) ( removed bool , comment * issues_model . Comment , err error ) {
2023-04-14 21:18:28 +03:00
removed , comment , err = issues_model . ToggleIssueAssignee ( ctx , issue , doer , assigneeID )
2019-10-28 05:11:50 +03:00
if err != nil {
2023-07-07 08:31:56 +03:00
return false , nil , err
2019-10-28 05:11:50 +03:00
}
2023-07-07 08:31:56 +03:00
assignee , err := user_model . GetUserByID ( ctx , assigneeID )
if err != nil {
return false , nil , err
2019-10-28 05:11:50 +03:00
}
2023-09-05 21:37:47 +03:00
notify_service . IssueChangeAssignee ( ctx , doer , issue , assignee , removed , comment )
2019-10-28 05:11:50 +03:00
2022-06-20 13:02:49 +03:00
return removed , comment , err
2019-10-28 05:11:50 +03:00
}
2020-04-06 19:33:34 +03:00
2020-10-12 22:55:13 +03:00
// ReviewRequest add or remove a review request from a user for this PR, and make comment for it.
2023-04-14 21:18:28 +03:00
func ReviewRequest ( ctx context . Context , issue * issues_model . Issue , doer , reviewer * user_model . User , isAdd bool ) ( comment * issues_model . Comment , err error ) {
2020-04-06 19:33:34 +03:00
if isAdd {
2023-08-10 05:39:21 +03:00
comment , err = issues_model . AddReviewRequest ( ctx , issue , reviewer , doer )
2020-04-06 19:33:34 +03:00
} else {
2023-08-10 05:39:21 +03:00
comment , err = issues_model . RemoveReviewRequest ( ctx , issue , reviewer , doer )
2020-04-06 19:33:34 +03:00
}
if err != nil {
2020-10-20 21:18:25 +03:00
return nil , err
2020-04-06 19:33:34 +03:00
}
if comment != nil {
2023-09-05 21:37:47 +03:00
notify_service . PullRequestReviewRequest ( ctx , doer , issue , reviewer , isAdd , comment )
2020-04-06 19:33:34 +03:00
}
2022-06-20 13:02:49 +03:00
return comment , err
2020-10-20 21:18:25 +03:00
}
// IsValidReviewRequest Check permission for ReviewRequest
2022-06-13 12:37:59 +03:00
func IsValidReviewRequest ( ctx context . Context , reviewer , doer * user_model . User , isAdd bool , issue * issues_model . Issue , permDoer * access_model . Permission ) error {
2020-10-20 21:18:25 +03:00
if reviewer . IsOrganization ( ) {
2022-06-13 12:37:59 +03:00
return issues_model . ErrNotValidReviewRequest {
2020-10-20 21:18:25 +03:00
Reason : "Organization can't be added as reviewer" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
}
if doer . IsOrganization ( ) {
2022-06-13 12:37:59 +03:00
return issues_model . ErrNotValidReviewRequest {
2020-10-20 21:18:25 +03:00
Reason : "Organization can't be doer to add reviewer" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
}
2022-05-11 13:09:36 +03:00
permReviewer , err := access_model . GetUserRepoPermission ( ctx , issue . Repo , reviewer )
2020-10-20 21:18:25 +03:00
if err != nil {
return err
}
if permDoer == nil {
2022-05-11 13:09:36 +03:00
permDoer = new ( access_model . Permission )
* permDoer , err = access_model . GetUserRepoPermission ( ctx , issue . Repo , doer )
2020-10-20 21:18:25 +03:00
if err != nil {
return err
}
}
2022-06-13 12:37:59 +03:00
lastreview , err := issues_model . GetReviewByIssueIDAndUserID ( ctx , issue . ID , reviewer . ID )
if err != nil && ! issues_model . IsErrReviewNotExist ( err ) {
2020-10-20 21:18:25 +03:00
return err
}
2024-02-24 15:38:43 +03:00
canDoerChangeReviewRequests := CanDoerChangeReviewRequests ( ctx , doer , issue . Repo , issue )
2020-10-20 21:18:25 +03:00
if isAdd {
2024-02-24 15:38:43 +03:00
if ! permReviewer . CanAccessAny ( perm . AccessModeRead , unit . TypePullRequests ) {
2022-06-13 12:37:59 +03:00
return issues_model . ErrNotValidReviewRequest {
2020-10-20 21:18:25 +03:00
Reason : "Reviewer can't read" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
}
if reviewer . ID == issue . PosterID && issue . OriginalAuthorID == 0 {
2022-06-13 12:37:59 +03:00
return issues_model . ErrNotValidReviewRequest {
2020-10-20 21:18:25 +03:00
Reason : "poster of pr can't be reviewer" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
}
2024-02-24 15:38:43 +03:00
if canDoerChangeReviewRequests {
2020-10-20 21:18:25 +03:00
return nil
}
2024-02-24 15:38:43 +03:00
if doer . ID == issue . PosterID && issue . OriginalAuthorID == 0 && lastreview != nil && lastreview . Type != issues_model . ReviewTypeRequest {
return nil
}
return issues_model . ErrNotValidReviewRequest {
Reason : "Doer can't choose reviewer" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
2020-10-20 21:18:25 +03:00
}
}
2024-02-24 15:38:43 +03:00
if canDoerChangeReviewRequests {
return nil
}
if lastreview != nil && lastreview . Type == issues_model . ReviewTypeRequest && lastreview . ReviewerID == doer . ID {
return nil
}
return issues_model . ErrNotValidReviewRequest {
Reason : "Doer can't remove reviewer" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
2020-10-20 21:18:25 +03:00
}
// IsValidTeamReviewRequest Check permission for ReviewRequest Team
2022-06-13 12:37:59 +03:00
func IsValidTeamReviewRequest ( ctx context . Context , reviewer * organization . Team , doer * user_model . User , isAdd bool , issue * issues_model . Issue ) error {
2020-10-20 21:18:25 +03:00
if doer . IsOrganization ( ) {
2022-06-13 12:37:59 +03:00
return issues_model . ErrNotValidReviewRequest {
2020-10-20 21:18:25 +03:00
Reason : "Organization can't be doer to add reviewer" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
}
2024-02-24 15:38:43 +03:00
canDoerChangeReviewRequests := CanDoerChangeReviewRequests ( ctx , doer , issue . Repo , issue )
2020-10-20 21:18:25 +03:00
if isAdd {
if issue . Repo . IsPrivate {
2022-04-28 14:48:48 +03:00
hasTeam := organization . HasTeamRepo ( ctx , reviewer . OrgID , reviewer . ID , issue . RepoID )
2020-10-20 21:18:25 +03:00
if ! hasTeam {
2022-06-13 12:37:59 +03:00
return issues_model . ErrNotValidReviewRequest {
2020-10-20 21:18:25 +03:00
Reason : "Reviewing team can't read repo" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
}
}
2024-02-24 15:38:43 +03:00
if canDoerChangeReviewRequests {
return nil
2020-10-20 21:18:25 +03:00
}
2024-02-24 15:38:43 +03:00
2022-06-13 12:37:59 +03:00
return issues_model . ErrNotValidReviewRequest {
2024-02-24 15:38:43 +03:00
Reason : "Doer can't choose reviewer" ,
2020-10-20 21:18:25 +03:00
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
}
2024-02-24 15:38:43 +03:00
if canDoerChangeReviewRequests {
return nil
}
return issues_model . ErrNotValidReviewRequest {
Reason : "Doer can't remove reviewer" ,
UserID : doer . ID ,
RepoID : issue . Repo . ID ,
}
2020-04-06 19:33:34 +03:00
}
2020-10-12 22:55:13 +03:00
// TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it.
2023-04-14 21:18:28 +03:00
func TeamReviewRequest ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , reviewer * organization . Team , isAdd bool ) ( comment * issues_model . Comment , err error ) {
2020-10-12 22:55:13 +03:00
if isAdd {
2023-08-10 05:39:21 +03:00
comment , err = issues_model . AddTeamReviewRequest ( ctx , issue , reviewer , doer )
2020-10-12 22:55:13 +03:00
} else {
2023-08-10 05:39:21 +03:00
comment , err = issues_model . RemoveTeamReviewRequest ( ctx , issue , reviewer , doer )
2020-10-12 22:55:13 +03:00
}
if err != nil {
2023-07-07 08:31:56 +03:00
return nil , err
2020-10-12 22:55:13 +03:00
}
if comment == nil || ! isAdd {
2023-07-07 08:31:56 +03:00
return nil , nil
2020-10-12 22:55:13 +03:00
}
2024-03-19 08:28:43 +03:00
return comment , teamReviewRequestNotify ( ctx , issue , doer , reviewer , isAdd , comment )
}
2024-04-27 11:03:49 +03:00
func ReviewRequestNotify ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , reviewNotifiers [ ] * ReviewRequestNotifier ) {
for _ , reviewNotifier := range reviewNotifiers {
if reviewNotifier . Reviewer != nil {
notify_service . PullRequestReviewRequest ( ctx , issue . Poster , issue , reviewNotifier . Reviewer , reviewNotifier . IsAdd , reviewNotifier . Comment )
} else if reviewNotifier . ReviewTeam != nil {
if err := teamReviewRequestNotify ( ctx , issue , issue . Poster , reviewNotifier . ReviewTeam , reviewNotifier . IsAdd , reviewNotifier . Comment ) ; err != nil {
2024-03-19 08:28:43 +03:00
log . Error ( "teamReviewRequestNotify: %v" , err )
}
}
}
}
// teamReviewRequestNotify notify all user in this team
func teamReviewRequestNotify ( ctx context . Context , issue * issues_model . Issue , doer * user_model . User , reviewer * organization . Team , isAdd bool , comment * issues_model . Comment ) error {
2020-10-12 22:55:13 +03:00
// notify all user in this team
2023-07-07 08:31:56 +03:00
if err := comment . LoadIssue ( ctx ) ; err != nil {
2024-03-19 08:28:43 +03:00
return err
2020-10-12 22:55:13 +03:00
}
2023-04-14 21:18:28 +03:00
members , err := organization . GetTeamMembers ( ctx , & organization . SearchMembersOptions {
2022-03-29 09:29:02 +03:00
TeamID : reviewer . ID ,
} )
if err != nil {
2024-03-19 08:28:43 +03:00
return err
2020-10-12 22:55:13 +03:00
}
2022-03-29 09:29:02 +03:00
for _ , member := range members {
2020-10-12 22:55:13 +03:00
if member . ID == comment . Issue . PosterID {
continue
}
comment . AssigneeID = member . ID
2023-09-05 21:37:47 +03:00
notify_service . PullRequestReviewRequest ( ctx , doer , issue , member , isAdd , comment )
2020-10-12 22:55:13 +03:00
}
2024-03-19 08:28:43 +03:00
return err
2020-10-12 22:55:13 +03:00
}
2024-02-24 15:38:43 +03:00
// CanDoerChangeReviewRequests returns if the doer can add/remove review requests of a PR
func CanDoerChangeReviewRequests ( ctx context . Context , doer * user_model . User , repo * repo_model . Repository , issue * issues_model . Issue ) bool {
// The poster of the PR can change the reviewers
if doer . ID == issue . PosterID {
return true
}
// The owner of the repo can change the reviewers
if doer . ID == repo . OwnerID {
return true
}
// Collaborators of the repo can change the reviewers
isCollaborator , err := repo_model . IsCollaborator ( ctx , repo . ID , doer . ID )
if err != nil {
log . Error ( "IsCollaborator: %v" , err )
return false
}
if isCollaborator {
return true
}
// If the repo's owner is an organization, members of teams with read permission on pull requests can change reviewers
if repo . Owner . IsOrganization ( ) {
teams , err := organization . GetTeamsWithAccessToRepo ( ctx , repo . OwnerID , repo . ID , perm . AccessModeRead )
if err != nil {
log . Error ( "GetTeamsWithAccessToRepo: %v" , err )
return false
}
for _ , team := range teams {
if ! team . UnitEnabled ( ctx , unit . TypePullRequests ) {
continue
}
isMember , err := organization . IsTeamMember ( ctx , repo . OwnerID , team . ID , doer . ID )
if err != nil {
log . Error ( "IsTeamMember: %v" , err )
continue
}
if isMember {
return true
}
}
}
return false
}