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  }