code.gitea.io/gitea@v1.21.7/routers/api/v1/repo/transfer.go (about)

     1  // Copyright 2020 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package repo
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  
    10  	"code.gitea.io/gitea/models"
    11  	"code.gitea.io/gitea/models/organization"
    12  	"code.gitea.io/gitea/models/perm"
    13  	access_model "code.gitea.io/gitea/models/perm/access"
    14  	repo_model "code.gitea.io/gitea/models/repo"
    15  	user_model "code.gitea.io/gitea/models/user"
    16  	"code.gitea.io/gitea/modules/context"
    17  	"code.gitea.io/gitea/modules/log"
    18  	api "code.gitea.io/gitea/modules/structs"
    19  	"code.gitea.io/gitea/modules/web"
    20  	"code.gitea.io/gitea/services/convert"
    21  	repo_service "code.gitea.io/gitea/services/repository"
    22  )
    23  
    24  // Transfer transfers the ownership of a repository
    25  func Transfer(ctx *context.APIContext) {
    26  	// swagger:operation POST /repos/{owner}/{repo}/transfer repository repoTransfer
    27  	// ---
    28  	// summary: Transfer a repo ownership
    29  	// produces:
    30  	// - application/json
    31  	// parameters:
    32  	// - name: owner
    33  	//   in: path
    34  	//   description: owner of the repo to transfer
    35  	//   type: string
    36  	//   required: true
    37  	// - name: repo
    38  	//   in: path
    39  	//   description: name of the repo to transfer
    40  	//   type: string
    41  	//   required: true
    42  	// - name: body
    43  	//   in: body
    44  	//   description: "Transfer Options"
    45  	//   required: true
    46  	//   schema:
    47  	//     "$ref": "#/definitions/TransferRepoOption"
    48  	// responses:
    49  	//   "202":
    50  	//     "$ref": "#/responses/Repository"
    51  	//   "403":
    52  	//     "$ref": "#/responses/forbidden"
    53  	//   "404":
    54  	//     "$ref": "#/responses/notFound"
    55  	//   "422":
    56  	//     "$ref": "#/responses/validationError"
    57  
    58  	opts := web.GetForm(ctx).(*api.TransferRepoOption)
    59  
    60  	newOwner, err := user_model.GetUserByName(ctx, opts.NewOwner)
    61  	if err != nil {
    62  		if user_model.IsErrUserNotExist(err) {
    63  			ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
    64  			return
    65  		}
    66  		ctx.InternalServerError(err)
    67  		return
    68  	}
    69  
    70  	if newOwner.Type == user_model.UserTypeOrganization {
    71  		if !ctx.Doer.IsAdmin && newOwner.Visibility == api.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx.Doer.ID) {
    72  			// The user shouldn't know about this organization
    73  			ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
    74  			return
    75  		}
    76  	}
    77  
    78  	var teams []*organization.Team
    79  	if opts.TeamIDs != nil {
    80  		if !newOwner.IsOrganization() {
    81  			ctx.Error(http.StatusUnprocessableEntity, "repoTransfer", "Teams can only be added to organization-owned repositories")
    82  			return
    83  		}
    84  
    85  		org := convert.ToOrganization(ctx, organization.OrgFromUser(newOwner))
    86  		for _, tID := range *opts.TeamIDs {
    87  			team, err := organization.GetTeamByID(ctx, tID)
    88  			if err != nil {
    89  				ctx.Error(http.StatusUnprocessableEntity, "team", fmt.Errorf("team %d not found", tID))
    90  				return
    91  			}
    92  
    93  			if team.OrgID != org.ID {
    94  				ctx.Error(http.StatusForbidden, "team", fmt.Errorf("team %d belongs not to org %d", tID, org.ID))
    95  				return
    96  			}
    97  
    98  			teams = append(teams, team)
    99  		}
   100  	}
   101  
   102  	if ctx.Repo.GitRepo != nil {
   103  		ctx.Repo.GitRepo.Close()
   104  		ctx.Repo.GitRepo = nil
   105  	}
   106  
   107  	oldFullname := ctx.Repo.Repository.FullName()
   108  
   109  	if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil {
   110  		if models.IsErrRepoTransferInProgress(err) {
   111  			ctx.Error(http.StatusConflict, "StartRepositoryTransfer", err)
   112  			return
   113  		}
   114  
   115  		if repo_model.IsErrRepoAlreadyExist(err) {
   116  			ctx.Error(http.StatusUnprocessableEntity, "StartRepositoryTransfer", err)
   117  			return
   118  		}
   119  
   120  		ctx.InternalServerError(err)
   121  		return
   122  	}
   123  
   124  	if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
   125  		log.Trace("Repository transfer initiated: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
   126  		ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
   127  		return
   128  	}
   129  
   130  	log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
   131  	ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
   132  }
   133  
   134  // AcceptTransfer accept a repo transfer
   135  func AcceptTransfer(ctx *context.APIContext) {
   136  	// swagger:operation POST /repos/{owner}/{repo}/transfer/accept repository acceptRepoTransfer
   137  	// ---
   138  	// summary: Accept a repo transfer
   139  	// produces:
   140  	// - application/json
   141  	// parameters:
   142  	// - name: owner
   143  	//   in: path
   144  	//   description: owner of the repo to transfer
   145  	//   type: string
   146  	//   required: true
   147  	// - name: repo
   148  	//   in: path
   149  	//   description: name of the repo to transfer
   150  	//   type: string
   151  	//   required: true
   152  	// responses:
   153  	//   "202":
   154  	//     "$ref": "#/responses/Repository"
   155  	//   "403":
   156  	//     "$ref": "#/responses/forbidden"
   157  	//   "404":
   158  	//     "$ref": "#/responses/notFound"
   159  
   160  	err := acceptOrRejectRepoTransfer(ctx, true)
   161  	if ctx.Written() {
   162  		return
   163  	}
   164  	if err != nil {
   165  		ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
   166  		return
   167  	}
   168  
   169  	ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
   170  }
   171  
   172  // RejectTransfer reject a repo transfer
   173  func RejectTransfer(ctx *context.APIContext) {
   174  	// swagger:operation POST /repos/{owner}/{repo}/transfer/reject repository rejectRepoTransfer
   175  	// ---
   176  	// summary: Reject a repo transfer
   177  	// produces:
   178  	// - application/json
   179  	// parameters:
   180  	// - name: owner
   181  	//   in: path
   182  	//   description: owner of the repo to transfer
   183  	//   type: string
   184  	//   required: true
   185  	// - name: repo
   186  	//   in: path
   187  	//   description: name of the repo to transfer
   188  	//   type: string
   189  	//   required: true
   190  	// responses:
   191  	//   "200":
   192  	//     "$ref": "#/responses/Repository"
   193  	//   "403":
   194  	//     "$ref": "#/responses/forbidden"
   195  	//   "404":
   196  	//     "$ref": "#/responses/notFound"
   197  
   198  	err := acceptOrRejectRepoTransfer(ctx, false)
   199  	if ctx.Written() {
   200  		return
   201  	}
   202  	if err != nil {
   203  		ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
   204  		return
   205  	}
   206  
   207  	ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
   208  }
   209  
   210  func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
   211  	repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
   212  	if err != nil {
   213  		if models.IsErrNoPendingTransfer(err) {
   214  			ctx.NotFound()
   215  			return nil
   216  		}
   217  		return err
   218  	}
   219  
   220  	if err := repoTransfer.LoadAttributes(ctx); err != nil {
   221  		return err
   222  	}
   223  
   224  	if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
   225  		ctx.Error(http.StatusForbidden, "CanUserAcceptTransfer", nil)
   226  		return fmt.Errorf("user does not have permissions to do this")
   227  	}
   228  
   229  	if accept {
   230  		return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
   231  	}
   232  
   233  	return models.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
   234  }