code.gitea.io/gitea@v1.22.3/models/repo_transfer.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package models
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  
    11  	"code.gitea.io/gitea/models/db"
    12  	"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  	"code.gitea.io/gitea/modules/log"
    16  	"code.gitea.io/gitea/modules/timeutil"
    17  
    18  	"xorm.io/builder"
    19  )
    20  
    21  // RepoTransfer is used to manage repository transfers
    22  type RepoTransfer struct {
    23  	ID          int64 `xorm:"pk autoincr"`
    24  	DoerID      int64
    25  	Doer        *user_model.User `xorm:"-"`
    26  	RecipientID int64
    27  	Recipient   *user_model.User `xorm:"-"`
    28  	RepoID      int64
    29  	TeamIDs     []int64
    30  	Teams       []*organization.Team `xorm:"-"`
    31  
    32  	CreatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL created"`
    33  	UpdatedUnix timeutil.TimeStamp `xorm:"INDEX NOT NULL updated"`
    34  }
    35  
    36  func init() {
    37  	db.RegisterModel(new(RepoTransfer))
    38  }
    39  
    40  // LoadAttributes fetches the transfer recipient from the database
    41  func (r *RepoTransfer) LoadAttributes(ctx context.Context) error {
    42  	if r.Recipient == nil {
    43  		u, err := user_model.GetUserByID(ctx, r.RecipientID)
    44  		if err != nil {
    45  			return err
    46  		}
    47  
    48  		r.Recipient = u
    49  	}
    50  
    51  	if r.Recipient.IsOrganization() && len(r.TeamIDs) != len(r.Teams) {
    52  		for _, v := range r.TeamIDs {
    53  			team, err := organization.GetTeamByID(ctx, v)
    54  			if err != nil {
    55  				return err
    56  			}
    57  
    58  			if team.OrgID != r.Recipient.ID {
    59  				return fmt.Errorf("team %d belongs not to org %d", v, r.Recipient.ID)
    60  			}
    61  
    62  			r.Teams = append(r.Teams, team)
    63  		}
    64  	}
    65  
    66  	if r.Doer == nil {
    67  		u, err := user_model.GetUserByID(ctx, r.DoerID)
    68  		if err != nil {
    69  			return err
    70  		}
    71  
    72  		r.Doer = u
    73  	}
    74  
    75  	return nil
    76  }
    77  
    78  // CanUserAcceptTransfer checks if the user has the rights to accept/decline a repo transfer.
    79  // For user, it checks if it's himself
    80  // For organizations, it checks if the user is able to create repos
    81  func (r *RepoTransfer) CanUserAcceptTransfer(ctx context.Context, u *user_model.User) bool {
    82  	if err := r.LoadAttributes(ctx); err != nil {
    83  		log.Error("LoadAttributes: %v", err)
    84  		return false
    85  	}
    86  
    87  	if !r.Recipient.IsOrganization() {
    88  		return r.RecipientID == u.ID
    89  	}
    90  
    91  	allowed, err := organization.CanCreateOrgRepo(ctx, r.RecipientID, u.ID)
    92  	if err != nil {
    93  		log.Error("CanCreateOrgRepo: %v", err)
    94  		return false
    95  	}
    96  
    97  	return allowed
    98  }
    99  
   100  type PendingRepositoryTransferOptions struct {
   101  	RepoID      int64
   102  	SenderID    int64
   103  	RecipientID int64
   104  }
   105  
   106  func (opts *PendingRepositoryTransferOptions) ToConds() builder.Cond {
   107  	cond := builder.NewCond()
   108  	if opts.RepoID != 0 {
   109  		cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
   110  	}
   111  	if opts.SenderID != 0 {
   112  		cond = cond.And(builder.Eq{"doer_id": opts.SenderID})
   113  	}
   114  	if opts.RecipientID != 0 {
   115  		cond = cond.And(builder.Eq{"recipient_id": opts.RecipientID})
   116  	}
   117  	return cond
   118  }
   119  
   120  func GetPendingRepositoryTransfers(ctx context.Context, opts *PendingRepositoryTransferOptions) ([]*RepoTransfer, error) {
   121  	transfers := make([]*RepoTransfer, 0, 10)
   122  	return transfers, db.GetEngine(ctx).
   123  		Where(opts.ToConds()).
   124  		Find(&transfers)
   125  }
   126  
   127  // GetPendingRepositoryTransfer fetches the most recent and ongoing transfer
   128  // process for the repository
   129  func GetPendingRepositoryTransfer(ctx context.Context, repo *repo_model.Repository) (*RepoTransfer, error) {
   130  	transfers, err := GetPendingRepositoryTransfers(ctx, &PendingRepositoryTransferOptions{RepoID: repo.ID})
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	if len(transfers) != 1 {
   136  		return nil, ErrNoPendingRepoTransfer{RepoID: repo.ID}
   137  	}
   138  
   139  	return transfers[0], nil
   140  }
   141  
   142  func DeleteRepositoryTransfer(ctx context.Context, repoID int64) error {
   143  	_, err := db.GetEngine(ctx).Where("repo_id = ?", repoID).Delete(&RepoTransfer{})
   144  	return err
   145  }
   146  
   147  // TestRepositoryReadyForTransfer make sure repo is ready to transfer
   148  func TestRepositoryReadyForTransfer(status repo_model.RepositoryStatus) error {
   149  	switch status {
   150  	case repo_model.RepositoryBeingMigrated:
   151  		return errors.New("repo is not ready, currently migrating")
   152  	case repo_model.RepositoryPendingTransfer:
   153  		return ErrRepoTransferInProgress{}
   154  	}
   155  	return nil
   156  }
   157  
   158  // CreatePendingRepositoryTransfer transfer a repo from one owner to a new one.
   159  // it marks the repository transfer as "pending"
   160  func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.User, repoID int64, teams []*organization.Team) error {
   161  	return db.WithTx(ctx, func(ctx context.Context) error {
   162  		repo, err := repo_model.GetRepositoryByID(ctx, repoID)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		// Make sure repo is ready to transfer
   168  		if err := TestRepositoryReadyForTransfer(repo.Status); err != nil {
   169  			return err
   170  		}
   171  
   172  		repo.Status = repo_model.RepositoryPendingTransfer
   173  		if err := repo_model.UpdateRepositoryCols(ctx, repo, "status"); err != nil {
   174  			return err
   175  		}
   176  
   177  		// Check if new owner has repository with same name.
   178  		if has, err := repo_model.IsRepositoryModelExist(ctx, newOwner, repo.Name); err != nil {
   179  			return fmt.Errorf("IsRepositoryExist: %w", err)
   180  		} else if has {
   181  			return repo_model.ErrRepoAlreadyExist{
   182  				Uname: newOwner.LowerName,
   183  				Name:  repo.Name,
   184  			}
   185  		}
   186  
   187  		transfer := &RepoTransfer{
   188  			RepoID:      repo.ID,
   189  			RecipientID: newOwner.ID,
   190  			CreatedUnix: timeutil.TimeStampNow(),
   191  			UpdatedUnix: timeutil.TimeStampNow(),
   192  			DoerID:      doer.ID,
   193  			TeamIDs:     make([]int64, 0, len(teams)),
   194  		}
   195  
   196  		for k := range teams {
   197  			transfer.TeamIDs = append(transfer.TeamIDs, teams[k].ID)
   198  		}
   199  
   200  		return db.Insert(ctx, transfer)
   201  	})
   202  }