2020-01-31 18:49:04 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package repo
import (
"fmt"
"net/http"
"code.gitea.io/gitea/models"
2022-03-29 09:29:02 +03:00
"code.gitea.io/gitea/models/organization"
2021-11-28 14:58:28 +03:00
"code.gitea.io/gitea/models/perm"
2021-12-10 04:27:50 +03:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-24 12:49:20 +03:00
user_model "code.gitea.io/gitea/models/user"
2020-01-31 18:49:04 +03:00
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
2021-01-26 18:36:53 +03:00
"code.gitea.io/gitea/modules/web"
2020-01-31 18:49:04 +03:00
repo_service "code.gitea.io/gitea/services/repository"
)
// Transfer transfers the ownership of a repository
2021-01-26 18:36:53 +03:00
func Transfer ( ctx * context . APIContext ) {
2020-01-31 18:49:04 +03:00
// swagger:operation POST /repos/{owner}/{repo}/transfer repository repoTransfer
// ---
// summary: Transfer a repo ownership
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo to transfer
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo to transfer
// type: string
// required: true
// - name: body
// in: body
// description: "Transfer Options"
// required: true
// schema:
// "$ref": "#/definitions/TransferRepoOption"
// responses:
// "202":
// "$ref": "#/responses/Repository"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
// "422":
// "$ref": "#/responses/validationError"
2021-01-26 18:36:53 +03:00
opts := web . GetForm ( ctx ) . ( * api . TransferRepoOption )
2022-05-20 17:08:52 +03:00
newOwner , err := user_model . GetUserByName ( ctx , opts . NewOwner )
2020-01-31 18:49:04 +03:00
if err != nil {
2021-11-24 12:49:20 +03:00
if user_model . IsErrUserNotExist ( err ) {
2020-08-16 23:27:08 +03:00
ctx . Error ( http . StatusNotFound , "" , "The new owner does not exist or cannot be found" )
2020-01-31 18:49:04 +03:00
return
}
ctx . InternalServerError ( err )
return
}
2021-11-24 12:49:20 +03:00
if newOwner . Type == user_model . UserTypeOrganization {
2022-03-29 09:29:02 +03:00
if ! ctx . Doer . IsAdmin && newOwner . Visibility == api . VisibleTypePrivate && ! organization . OrgFromUser ( newOwner ) . HasMemberWithUserID ( ctx . Doer . ID ) {
2020-08-16 23:27:08 +03:00
// The user shouldn't know about this organization
ctx . Error ( http . StatusNotFound , "" , "The new owner does not exist or cannot be found" )
return
}
}
2022-03-29 09:29:02 +03:00
var teams [ ] * organization . Team
2020-01-31 18:49:04 +03:00
if opts . TeamIDs != nil {
if ! newOwner . IsOrganization ( ) {
ctx . Error ( http . StatusUnprocessableEntity , "repoTransfer" , "Teams can only be added to organization-owned repositories" )
return
}
2022-03-29 09:29:02 +03:00
org := convert . ToOrganization ( organization . OrgFromUser ( newOwner ) )
2020-01-31 18:49:04 +03:00
for _ , tID := range * opts . TeamIDs {
2022-05-20 17:08:52 +03:00
team , err := organization . GetTeamByID ( ctx , tID )
2020-01-31 18:49:04 +03:00
if err != nil {
ctx . Error ( http . StatusUnprocessableEntity , "team" , fmt . Errorf ( "team %d not found" , tID ) )
return
}
if team . OrgID != org . ID {
ctx . Error ( http . StatusForbidden , "team" , fmt . Errorf ( "team %d belongs not to org %d" , tID , org . ID ) )
return
}
teams = append ( teams , team )
}
}
2021-12-21 05:01:58 +03:00
if ctx . Repo . GitRepo != nil {
ctx . Repo . GitRepo . Close ( )
ctx . Repo . GitRepo = nil
}
2022-04-21 19:05:53 +03:00
oldFullname := ctx . Repo . Repository . FullName ( )
2022-03-22 10:03:22 +03:00
if err := repo_service . StartRepositoryTransfer ( ctx . Doer , newOwner , ctx . Repo . Repository , teams ) ; err != nil {
2021-03-01 03:47:30 +03:00
if models . IsErrRepoTransferInProgress ( err ) {
2022-04-21 19:05:53 +03:00
ctx . Error ( http . StatusConflict , "StartRepositoryTransfer" , err )
2021-03-01 03:47:30 +03:00
return
}
2021-12-12 18:48:20 +03:00
if repo_model . IsErrRepoAlreadyExist ( err ) {
2022-04-21 19:05:53 +03:00
ctx . Error ( http . StatusUnprocessableEntity , "StartRepositoryTransfer" , err )
2021-03-01 03:47:30 +03:00
return
}
2020-01-31 18:49:04 +03:00
ctx . InternalServerError ( err )
return
}
2021-12-10 04:27:50 +03:00
if ctx . Repo . Repository . Status == repo_model . RepositoryPendingTransfer {
2022-04-21 19:05:53 +03:00
log . Trace ( "Repository transfer initiated: %s -> %s" , oldFullname , ctx . Repo . Repository . FullName ( ) )
2021-11-28 14:58:28 +03:00
ctx . JSON ( http . StatusCreated , convert . ToRepo ( ctx . Repo . Repository , perm . AccessModeAdmin ) )
2020-01-31 18:49:04 +03:00
return
}
2022-04-21 19:05:53 +03:00
log . Trace ( "Repository transferred: %s -> %s" , oldFullname , ctx . Repo . Repository . FullName ( ) )
2021-11-28 14:58:28 +03:00
ctx . JSON ( http . StatusAccepted , convert . ToRepo ( ctx . Repo . Repository , perm . AccessModeAdmin ) )
2020-01-31 18:49:04 +03:00
}
2021-12-24 07:26:52 +03:00
// AcceptTransfer accept a repo transfer
func AcceptTransfer ( ctx * context . APIContext ) {
// swagger:operation POST /repos/{owner}/{repo}/transfer/accept repository acceptRepoTransfer
// ---
// summary: Accept a repo transfer
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo to transfer
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo to transfer
// type: string
// required: true
// responses:
// "202":
// "$ref": "#/responses/Repository"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
err := acceptOrRejectRepoTransfer ( ctx , true )
if ctx . Written ( ) {
return
}
if err != nil {
ctx . Error ( http . StatusInternalServerError , "acceptOrRejectRepoTransfer" , err )
return
}
ctx . JSON ( http . StatusAccepted , convert . ToRepo ( ctx . Repo . Repository , ctx . Repo . AccessMode ) )
}
// RejectTransfer reject a repo transfer
func RejectTransfer ( ctx * context . APIContext ) {
// swagger:operation POST /repos/{owner}/{repo}/transfer/reject repository rejectRepoTransfer
// ---
// summary: Reject a repo transfer
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo to transfer
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo to transfer
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/Repository"
// "403":
// "$ref": "#/responses/forbidden"
// "404":
// "$ref": "#/responses/notFound"
err := acceptOrRejectRepoTransfer ( ctx , false )
if ctx . Written ( ) {
return
}
if err != nil {
ctx . Error ( http . StatusInternalServerError , "acceptOrRejectRepoTransfer" , err )
return
}
ctx . JSON ( http . StatusOK , convert . ToRepo ( ctx . Repo . Repository , ctx . Repo . AccessMode ) )
}
func acceptOrRejectRepoTransfer ( ctx * context . APIContext , accept bool ) error {
repoTransfer , err := models . GetPendingRepositoryTransfer ( ctx . Repo . Repository )
if err != nil {
if models . IsErrNoPendingTransfer ( err ) {
ctx . NotFound ( )
return nil
}
return err
}
if err := repoTransfer . LoadAttributes ( ) ; err != nil {
return err
}
2022-03-22 10:03:22 +03:00
if ! repoTransfer . CanUserAcceptTransfer ( ctx . Doer ) {
2021-12-24 07:26:52 +03:00
ctx . Error ( http . StatusForbidden , "CanUserAcceptTransfer" , nil )
return fmt . Errorf ( "user does not have permissions to do this" )
}
if accept {
return repo_service . TransferOwnership ( repoTransfer . Doer , repoTransfer . Recipient , ctx . Repo . Repository , repoTransfer . Teams )
}
return models . CancelRepositoryTransfer ( ctx . Repo . Repository )
}