1
1
mirror of https://github.com/go-gitea/gitea synced 2025-07-22 18:28:37 +00:00

Refactor repository transfer (#33211)

- Both have `RejectTransfer` and `CancelTransfer` because the permission
checks are not the same. `CancelTransfer` can be done by the doer or
those who have admin permission to access this repository.
`RejectTransfer` can be done by the receiver user if it's an individual
or those who can create repositories if it's an organization.

- Some tests are wrong, this PR corrects them.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Lunny Xiao
2025-01-29 21:40:44 -08:00
committed by GitHub
parent 48183d2b05
commit f88dbf86b3
10 changed files with 302 additions and 222 deletions

View File

@@ -15,6 +15,7 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
@@ -161,12 +162,16 @@ func AcceptTransfer(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
err := acceptOrRejectRepoTransfer(ctx, true)
if ctx.Written() {
return
}
err := repo_service.AcceptTransferOwnership(ctx, ctx.Repo.Repository, ctx.Doer)
if err != nil {
ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
switch {
case repo_model.IsErrNoPendingTransfer(err):
ctx.Error(http.StatusNotFound, "AcceptTransferOwnership", err)
case errors.Is(err, util.ErrPermissionDenied):
ctx.Error(http.StatusForbidden, "AcceptTransferOwnership", err)
default:
ctx.Error(http.StatusInternalServerError, "AcceptTransferOwnership", err)
}
return
}
@@ -199,40 +204,18 @@ func RejectTransfer(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"
err := acceptOrRejectRepoTransfer(ctx, false)
if ctx.Written() {
return
}
err := repo_service.RejectRepositoryTransfer(ctx, ctx.Repo.Repository, ctx.Doer)
if err != nil {
ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
switch {
case repo_model.IsErrNoPendingTransfer(err):
ctx.Error(http.StatusNotFound, "RejectRepositoryTransfer", err)
case errors.Is(err, util.ErrPermissionDenied):
ctx.Error(http.StatusForbidden, "RejectRepositoryTransfer", err)
default:
ctx.Error(http.StatusInternalServerError, "RejectRepositoryTransfer", err)
}
return
}
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
}
func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
if err != nil {
if repo_model.IsErrNoPendingTransfer(err) {
ctx.NotFound()
return nil
}
return err
}
if err := repoTransfer.LoadAttributes(ctx); err != nil {
return err
}
if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
ctx.Error(http.StatusForbidden, "CanUserAcceptTransfer", nil)
return fmt.Errorf("user does not have permissions to do this")
}
if accept {
return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
}
return repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
}

View File

@@ -309,6 +309,36 @@ const (
tplStarUnstar templates.TplName = "repo/star_unstar"
)
func acceptTransfer(ctx *context.Context) {
err := repo_service.AcceptTransferOwnership(ctx, ctx.Repo.Repository, ctx.Doer)
if err == nil {
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
ctx.Redirect(ctx.Repo.Repository.Link())
return
}
handleActionError(ctx, err)
}
func rejectTransfer(ctx *context.Context) {
err := repo_service.RejectRepositoryTransfer(ctx, ctx.Repo.Repository, ctx.Doer)
if err == nil {
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
ctx.Redirect(ctx.Repo.Repository.Link())
return
}
handleActionError(ctx, err)
}
func handleActionError(ctx *context.Context, err error) {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
} else if errors.Is(err, util.ErrPermissionDenied) {
ctx.Error(http.StatusNotFound)
} else {
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
}
}
// Action response for actions to a repository
func Action(ctx *context.Context) {
var err error
@@ -322,9 +352,11 @@ func Action(ctx *context.Context) {
case "unstar":
err = repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
case "accept_transfer":
err = acceptOrRejectRepoTransfer(ctx, true)
acceptTransfer(ctx)
return
case "reject_transfer":
err = acceptOrRejectRepoTransfer(ctx, false)
rejectTransfer(ctx)
return
case "desc": // FIXME: this is not used
if !ctx.Repo.IsOwner() {
ctx.Error(http.StatusNotFound)
@@ -337,12 +369,8 @@ func Action(ctx *context.Context) {
}
if err != nil {
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
} else {
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
return
}
handleActionError(ctx, err)
return
}
switch ctx.PathParam("action") {
@@ -377,41 +405,6 @@ func Action(ctx *context.Context) {
ctx.RedirectToCurrentSite(ctx.FormString("redirect_to"), ctx.Repo.RepoLink)
}
func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error {
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
if err != nil {
return err
}
if err := repoTransfer.LoadAttributes(ctx); err != nil {
return err
}
if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
return errors.New("user does not have enough permissions")
}
if accept {
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
ctx.Repo.GitRepo = nil
}
if err := repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil {
return err
}
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
} else {
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
return err
}
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
}
ctx.Redirect(ctx.Repo.Repository.Link())
return nil
}
// RedirectDownload return a file based on the following infos:
func RedirectDownload(ctx *context.Context) {
var (

View File

@@ -832,16 +832,10 @@ func SettingsPost(ctx *context.Context) {
} else {
ctx.ServerError("GetPendingRepositoryTransfer", err)
}
return
}
if err := repoTransfer.LoadAttributes(ctx); err != nil {
ctx.ServerError("LoadRecipient", err)
return
}
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
if err := repo_service.CancelRepositoryTransfer(ctx, repoTransfer, ctx.Doer); err != nil {
ctx.ServerError("CancelRepositoryTransfer", err)
return
}