code.gitea.io/gitea@v1.21.7/services/issue/assignee.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package issue 5 6 import ( 7 "context" 8 9 issues_model "code.gitea.io/gitea/models/issues" 10 "code.gitea.io/gitea/models/organization" 11 "code.gitea.io/gitea/models/perm" 12 access_model "code.gitea.io/gitea/models/perm/access" 13 "code.gitea.io/gitea/models/unit" 14 user_model "code.gitea.io/gitea/models/user" 15 "code.gitea.io/gitea/modules/log" 16 notify_service "code.gitea.io/gitea/services/notify" 17 ) 18 19 // DeleteNotPassedAssignee deletes all assignees who aren't passed via the "assignees" array 20 func DeleteNotPassedAssignee(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assignees []*user_model.User) (err error) { 21 var found bool 22 oriAssignes := make([]*user_model.User, len(issue.Assignees)) 23 _ = copy(oriAssignes, issue.Assignees) 24 25 for _, assignee := range oriAssignes { 26 found = false 27 for _, alreadyAssignee := range assignees { 28 if assignee.ID == alreadyAssignee.ID { 29 found = true 30 break 31 } 32 } 33 34 if !found { 35 // This function also does comments and hooks, which is why we call it separately instead of directly removing the assignees here 36 if _, _, err := ToggleAssigneeWithNotify(ctx, issue, doer, assignee.ID); err != nil { 37 return err 38 } 39 } 40 } 41 42 return nil 43 } 44 45 // ToggleAssigneeWithNoNotify changes a user between assigned and not assigned for this issue, and make issue comment for it. 46 func ToggleAssigneeWithNotify(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, assigneeID int64) (removed bool, comment *issues_model.Comment, err error) { 47 removed, comment, err = issues_model.ToggleIssueAssignee(ctx, issue, doer, assigneeID) 48 if err != nil { 49 return false, nil, err 50 } 51 52 assignee, err := user_model.GetUserByID(ctx, assigneeID) 53 if err != nil { 54 return false, nil, err 55 } 56 57 notify_service.IssueChangeAssignee(ctx, doer, issue, assignee, removed, comment) 58 59 return removed, comment, err 60 } 61 62 // ReviewRequest add or remove a review request from a user for this PR, and make comment for it. 63 func ReviewRequest(ctx context.Context, issue *issues_model.Issue, doer, reviewer *user_model.User, isAdd bool) (comment *issues_model.Comment, err error) { 64 if isAdd { 65 comment, err = issues_model.AddReviewRequest(ctx, issue, reviewer, doer) 66 } else { 67 comment, err = issues_model.RemoveReviewRequest(ctx, issue, reviewer, doer) 68 } 69 70 if err != nil { 71 return nil, err 72 } 73 74 if comment != nil { 75 notify_service.PullRequestReviewRequest(ctx, doer, issue, reviewer, isAdd, comment) 76 } 77 78 return comment, err 79 } 80 81 // IsValidReviewRequest Check permission for ReviewRequest 82 func IsValidReviewRequest(ctx context.Context, reviewer, doer *user_model.User, isAdd bool, issue *issues_model.Issue, permDoer *access_model.Permission) error { 83 if reviewer.IsOrganization() { 84 return issues_model.ErrNotValidReviewRequest{ 85 Reason: "Organization can't be added as reviewer", 86 UserID: doer.ID, 87 RepoID: issue.Repo.ID, 88 } 89 } 90 if doer.IsOrganization() { 91 return issues_model.ErrNotValidReviewRequest{ 92 Reason: "Organization can't be doer to add reviewer", 93 UserID: doer.ID, 94 RepoID: issue.Repo.ID, 95 } 96 } 97 98 permReviewer, err := access_model.GetUserRepoPermission(ctx, issue.Repo, reviewer) 99 if err != nil { 100 return err 101 } 102 103 if permDoer == nil { 104 permDoer = new(access_model.Permission) 105 *permDoer, err = access_model.GetUserRepoPermission(ctx, issue.Repo, doer) 106 if err != nil { 107 return err 108 } 109 } 110 111 lastreview, err := issues_model.GetReviewByIssueIDAndUserID(ctx, issue.ID, reviewer.ID) 112 if err != nil && !issues_model.IsErrReviewNotExist(err) { 113 return err 114 } 115 116 var pemResult bool 117 if isAdd { 118 pemResult = permReviewer.CanAccessAny(perm.AccessModeRead, unit.TypePullRequests) 119 if !pemResult { 120 return issues_model.ErrNotValidReviewRequest{ 121 Reason: "Reviewer can't read", 122 UserID: doer.ID, 123 RepoID: issue.Repo.ID, 124 } 125 } 126 127 if doer.ID == issue.PosterID && issue.OriginalAuthorID == 0 && lastreview != nil && lastreview.Type != issues_model.ReviewTypeRequest { 128 return nil 129 } 130 131 pemResult = doer.ID == issue.PosterID 132 if !pemResult { 133 pemResult = permDoer.CanAccessAny(perm.AccessModeWrite, unit.TypePullRequests) 134 } 135 if !pemResult { 136 pemResult, err = issues_model.IsOfficialReviewer(ctx, issue, doer) 137 if err != nil { 138 return err 139 } 140 if !pemResult { 141 return issues_model.ErrNotValidReviewRequest{ 142 Reason: "Doer can't choose reviewer", 143 UserID: doer.ID, 144 RepoID: issue.Repo.ID, 145 } 146 } 147 } 148 149 if reviewer.ID == issue.PosterID && issue.OriginalAuthorID == 0 { 150 return issues_model.ErrNotValidReviewRequest{ 151 Reason: "poster of pr can't be reviewer", 152 UserID: doer.ID, 153 RepoID: issue.Repo.ID, 154 } 155 } 156 } else { 157 if lastreview != nil && lastreview.Type == issues_model.ReviewTypeRequest && lastreview.ReviewerID == doer.ID { 158 return nil 159 } 160 161 pemResult = permDoer.IsAdmin() 162 if !pemResult { 163 return issues_model.ErrNotValidReviewRequest{ 164 Reason: "Doer is not admin", 165 UserID: doer.ID, 166 RepoID: issue.Repo.ID, 167 } 168 } 169 } 170 171 return nil 172 } 173 174 // IsValidTeamReviewRequest Check permission for ReviewRequest Team 175 func IsValidTeamReviewRequest(ctx context.Context, reviewer *organization.Team, doer *user_model.User, isAdd bool, issue *issues_model.Issue) error { 176 if doer.IsOrganization() { 177 return issues_model.ErrNotValidReviewRequest{ 178 Reason: "Organization can't be doer to add reviewer", 179 UserID: doer.ID, 180 RepoID: issue.Repo.ID, 181 } 182 } 183 184 permission, err := access_model.GetUserRepoPermission(ctx, issue.Repo, doer) 185 if err != nil { 186 log.Error("Unable to GetUserRepoPermission for %-v in %-v#%d", doer, issue.Repo, issue.Index) 187 return err 188 } 189 190 if isAdd { 191 if issue.Repo.IsPrivate { 192 hasTeam := organization.HasTeamRepo(ctx, reviewer.OrgID, reviewer.ID, issue.RepoID) 193 194 if !hasTeam { 195 return issues_model.ErrNotValidReviewRequest{ 196 Reason: "Reviewing team can't read repo", 197 UserID: doer.ID, 198 RepoID: issue.Repo.ID, 199 } 200 } 201 } 202 203 doerCanWrite := permission.CanAccessAny(perm.AccessModeWrite, unit.TypePullRequests) 204 if !doerCanWrite && doer.ID != issue.PosterID { 205 official, err := issues_model.IsOfficialReviewer(ctx, issue, doer) 206 if err != nil { 207 log.Error("Unable to Check if IsOfficialReviewer for %-v in %-v#%d", doer, issue.Repo, issue.Index) 208 return err 209 } 210 if !official { 211 return issues_model.ErrNotValidReviewRequest{ 212 Reason: "Doer can't choose reviewer", 213 UserID: doer.ID, 214 RepoID: issue.Repo.ID, 215 } 216 } 217 } 218 } else if !permission.IsAdmin() { 219 return issues_model.ErrNotValidReviewRequest{ 220 Reason: "Only admin users can remove team requests. Doer is not admin", 221 UserID: doer.ID, 222 RepoID: issue.Repo.ID, 223 } 224 } 225 226 return nil 227 } 228 229 // TeamReviewRequest add or remove a review request from a team for this PR, and make comment for it. 230 func TeamReviewRequest(ctx context.Context, issue *issues_model.Issue, doer *user_model.User, reviewer *organization.Team, isAdd bool) (comment *issues_model.Comment, err error) { 231 if isAdd { 232 comment, err = issues_model.AddTeamReviewRequest(ctx, issue, reviewer, doer) 233 } else { 234 comment, err = issues_model.RemoveTeamReviewRequest(ctx, issue, reviewer, doer) 235 } 236 237 if err != nil { 238 return nil, err 239 } 240 241 if comment == nil || !isAdd { 242 return nil, nil 243 } 244 245 // notify all user in this team 246 if err := comment.LoadIssue(ctx); err != nil { 247 return nil, err 248 } 249 250 members, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{ 251 TeamID: reviewer.ID, 252 }) 253 if err != nil { 254 return nil, err 255 } 256 257 for _, member := range members { 258 if member.ID == comment.Issue.PosterID { 259 continue 260 } 261 comment.AssigneeID = member.ID 262 notify_service.PullRequestReviewRequest(ctx, doer, issue, member, isAdd, comment) 263 } 264 265 return comment, err 266 }