code.gitea.io/gitea@v1.22.3/services/user/block.go (about) 1 // Copyright 2024 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package user 5 6 import ( 7 "context" 8 9 "code.gitea.io/gitea/models" 10 "code.gitea.io/gitea/models/db" 11 issues_model "code.gitea.io/gitea/models/issues" 12 org_model "code.gitea.io/gitea/models/organization" 13 repo_model "code.gitea.io/gitea/models/repo" 14 user_model "code.gitea.io/gitea/models/user" 15 repo_service "code.gitea.io/gitea/services/repository" 16 ) 17 18 func CanBlockUser(ctx context.Context, doer, blocker, blockee *user_model.User) bool { 19 if blocker.ID == blockee.ID { 20 return false 21 } 22 if doer.ID == blockee.ID { 23 return false 24 } 25 26 if blockee.IsOrganization() { 27 return false 28 } 29 30 if user_model.IsUserBlockedBy(ctx, blockee, blocker.ID) { 31 return false 32 } 33 34 if blocker.IsOrganization() { 35 org := org_model.OrgFromUser(blocker) 36 if isMember, _ := org.IsOrgMember(ctx, blockee.ID); isMember { 37 return false 38 } 39 if isAdmin, _ := org.IsOwnedBy(ctx, doer.ID); !isAdmin && !doer.IsAdmin { 40 return false 41 } 42 } else if !doer.IsAdmin && doer.ID != blocker.ID { 43 return false 44 } 45 46 return true 47 } 48 49 func CanUnblockUser(ctx context.Context, doer, blocker, blockee *user_model.User) bool { 50 if doer.ID == blockee.ID { 51 return false 52 } 53 54 if !user_model.IsUserBlockedBy(ctx, blockee, blocker.ID) { 55 return false 56 } 57 58 if blocker.IsOrganization() { 59 org := org_model.OrgFromUser(blocker) 60 if isAdmin, _ := org.IsOwnedBy(ctx, doer.ID); !isAdmin && !doer.IsAdmin { 61 return false 62 } 63 } else if !doer.IsAdmin && doer.ID != blocker.ID { 64 return false 65 } 66 67 return true 68 } 69 70 func BlockUser(ctx context.Context, doer, blocker, blockee *user_model.User, note string) error { 71 if blockee.IsOrganization() { 72 return user_model.ErrBlockOrganization 73 } 74 75 if !CanBlockUser(ctx, doer, blocker, blockee) { 76 return user_model.ErrCanNotBlock 77 } 78 79 return db.WithTx(ctx, func(ctx context.Context) error { 80 // unfollow each other 81 if err := user_model.UnfollowUser(ctx, blocker.ID, blockee.ID); err != nil { 82 return err 83 } 84 if err := user_model.UnfollowUser(ctx, blockee.ID, blocker.ID); err != nil { 85 return err 86 } 87 88 // unstar each other 89 if err := unstarRepos(ctx, blocker, blockee); err != nil { 90 return err 91 } 92 if err := unstarRepos(ctx, blockee, blocker); err != nil { 93 return err 94 } 95 96 // unwatch each others repositories 97 if err := unwatchRepos(ctx, blocker, blockee); err != nil { 98 return err 99 } 100 if err := unwatchRepos(ctx, blockee, blocker); err != nil { 101 return err 102 } 103 104 // unassign each other from issues 105 if err := unassignIssues(ctx, blocker, blockee); err != nil { 106 return err 107 } 108 if err := unassignIssues(ctx, blockee, blocker); err != nil { 109 return err 110 } 111 112 // remove each other from repository collaborations 113 if err := removeCollaborations(ctx, blocker, blockee); err != nil { 114 return err 115 } 116 if err := removeCollaborations(ctx, blockee, blocker); err != nil { 117 return err 118 } 119 120 // cancel each other repository transfers 121 if err := cancelRepositoryTransfers(ctx, blocker, blockee); err != nil { 122 return err 123 } 124 if err := cancelRepositoryTransfers(ctx, blockee, blocker); err != nil { 125 return err 126 } 127 128 return db.Insert(ctx, &user_model.Blocking{ 129 BlockerID: blocker.ID, 130 BlockeeID: blockee.ID, 131 Note: note, 132 }) 133 }) 134 } 135 136 func unstarRepos(ctx context.Context, starrer, repoOwner *user_model.User) error { 137 opts := &repo_model.StarredReposOptions{ 138 ListOptions: db.ListOptions{ 139 Page: 1, 140 PageSize: 25, 141 }, 142 StarrerID: starrer.ID, 143 RepoOwnerID: repoOwner.ID, 144 } 145 146 for { 147 repos, err := repo_model.GetStarredRepos(ctx, opts) 148 if err != nil { 149 return err 150 } 151 152 if len(repos) == 0 { 153 return nil 154 } 155 156 for _, repo := range repos { 157 if err := repo_model.StarRepo(ctx, starrer, repo, false); err != nil { 158 return err 159 } 160 } 161 162 opts.Page++ 163 } 164 } 165 166 func unwatchRepos(ctx context.Context, watcher, repoOwner *user_model.User) error { 167 opts := &repo_model.WatchedReposOptions{ 168 ListOptions: db.ListOptions{ 169 Page: 1, 170 PageSize: 25, 171 }, 172 WatcherID: watcher.ID, 173 RepoOwnerID: repoOwner.ID, 174 } 175 176 for { 177 repos, _, err := repo_model.GetWatchedRepos(ctx, opts) 178 if err != nil { 179 return err 180 } 181 182 if len(repos) == 0 { 183 return nil 184 } 185 186 for _, repo := range repos { 187 if err := repo_model.WatchRepo(ctx, watcher, repo, false); err != nil { 188 return err 189 } 190 } 191 192 opts.Page++ 193 } 194 } 195 196 func cancelRepositoryTransfers(ctx context.Context, sender, recipient *user_model.User) error { 197 transfers, err := models.GetPendingRepositoryTransfers(ctx, &models.PendingRepositoryTransferOptions{ 198 SenderID: sender.ID, 199 RecipientID: recipient.ID, 200 }) 201 if err != nil { 202 return err 203 } 204 205 for _, transfer := range transfers { 206 repo, err := repo_model.GetRepositoryByID(ctx, transfer.RepoID) 207 if err != nil { 208 return err 209 } 210 211 if err := repo_service.CancelRepositoryTransfer(ctx, repo); err != nil { 212 return err 213 } 214 } 215 216 return nil 217 } 218 219 func unassignIssues(ctx context.Context, assignee, repoOwner *user_model.User) error { 220 opts := &issues_model.AssignedIssuesOptions{ 221 ListOptions: db.ListOptions{ 222 Page: 1, 223 PageSize: 25, 224 }, 225 AssigneeID: assignee.ID, 226 RepoOwnerID: repoOwner.ID, 227 } 228 229 for { 230 issues, _, err := issues_model.GetAssignedIssues(ctx, opts) 231 if err != nil { 232 return err 233 } 234 235 if len(issues) == 0 { 236 return nil 237 } 238 239 for _, issue := range issues { 240 if err := issue.LoadAssignees(ctx); err != nil { 241 return err 242 } 243 244 if _, _, err := issues_model.ToggleIssueAssignee(ctx, issue, assignee, assignee.ID); err != nil { 245 return err 246 } 247 } 248 249 opts.Page++ 250 } 251 } 252 253 func removeCollaborations(ctx context.Context, repoOwner, collaborator *user_model.User) error { 254 opts := &repo_model.FindCollaborationOptions{ 255 ListOptions: db.ListOptions{ 256 Page: 1, 257 PageSize: 25, 258 }, 259 CollaboratorID: collaborator.ID, 260 RepoOwnerID: repoOwner.ID, 261 } 262 263 for { 264 collaborations, _, err := repo_model.GetCollaborators(ctx, opts) 265 if err != nil { 266 return err 267 } 268 269 if len(collaborations) == 0 { 270 return nil 271 } 272 273 for _, collaboration := range collaborations { 274 repo, err := repo_model.GetRepositoryByID(ctx, collaboration.Collaboration.RepoID) 275 if err != nil { 276 return err 277 } 278 279 if err := repo_service.DeleteCollaboration(ctx, repo, collaborator); err != nil { 280 return err 281 } 282 } 283 284 opts.Page++ 285 } 286 } 287 288 func UnblockUser(ctx context.Context, doer, blocker, blockee *user_model.User) error { 289 if blockee.IsOrganization() { 290 return user_model.ErrBlockOrganization 291 } 292 293 if !CanUnblockUser(ctx, doer, blocker, blockee) { 294 return user_model.ErrCanNotUnblock 295 } 296 297 return db.WithTx(ctx, func(ctx context.Context) error { 298 block, err := user_model.GetBlocking(ctx, blocker.ID, blockee.ID) 299 if err != nil { 300 return err 301 } 302 if block != nil { 303 _, err = db.DeleteByID[user_model.Blocking](ctx, block.ID) 304 return err 305 } 306 return nil 307 }) 308 }