1
1
mirror of https://github.com/go-gitea/gitea synced 2024-12-23 17:14:27 +00:00
gitea/routers/api/v1/repo/transfer.go
KN4CK3R c337ff0ec7
Add user blocking (#29028)
Fixes #17453

This PR adds the abbility to block a user from a personal account or
organization to restrict how the blocked user can interact with the
blocker. The docs explain what's the consequence of blocking a user.

Screenshots:


![grafik](https://github.com/go-gitea/gitea/assets/1666336/4ed884f3-e06a-4862-afd3-3b8aa2488dc6)


![grafik](https://github.com/go-gitea/gitea/assets/1666336/ae6d4981-f252-4f50-a429-04f0f9f1cdf1)


![grafik](https://github.com/go-gitea/gitea/assets/1666336/ca153599-5b0f-4b4a-90fe-18bdfd6f0b6b)

---------

Co-authored-by: Lauris BH <lauris@nix.lv>
2024-03-04 08:16:03 +00:00

240 lines
6.7 KiB
Go

// Copyright 2020 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package repo
import (
"errors"
"fmt"
"net/http"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
access_model "code.gitea.io/gitea/models/perm/access"
repo_model "code.gitea.io/gitea/models/repo"
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/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/convert"
repo_service "code.gitea.io/gitea/services/repository"
)
// Transfer transfers the ownership of a repository
func Transfer(ctx *context.APIContext) {
// 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"
opts := web.GetForm(ctx).(*api.TransferRepoOption)
newOwner, err := user_model.GetUserByName(ctx, opts.NewOwner)
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
return
}
ctx.InternalServerError(err)
return
}
if newOwner.Type == user_model.UserTypeOrganization {
if !ctx.Doer.IsAdmin && newOwner.Visibility == api.VisibleTypePrivate && !organization.OrgFromUser(newOwner).HasMemberWithUserID(ctx, ctx.Doer.ID) {
// The user shouldn't know about this organization
ctx.Error(http.StatusNotFound, "", "The new owner does not exist or cannot be found")
return
}
}
var teams []*organization.Team
if opts.TeamIDs != nil {
if !newOwner.IsOrganization() {
ctx.Error(http.StatusUnprocessableEntity, "repoTransfer", "Teams can only be added to organization-owned repositories")
return
}
org := convert.ToOrganization(ctx, organization.OrgFromUser(newOwner))
for _, tID := range *opts.TeamIDs {
team, err := organization.GetTeamByID(ctx, tID)
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)
}
}
if ctx.Repo.GitRepo != nil {
ctx.Repo.GitRepo.Close()
ctx.Repo.GitRepo = nil
}
oldFullname := ctx.Repo.Repository.FullName()
if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil {
if models.IsErrRepoTransferInProgress(err) {
ctx.Error(http.StatusConflict, "StartRepositoryTransfer", err)
return
}
if repo_model.IsErrRepoAlreadyExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "StartRepositoryTransfer", err)
return
}
if errors.Is(err, user_model.ErrBlockedUser) {
ctx.Error(http.StatusForbidden, "BlockedUser", err)
} else {
ctx.InternalServerError(err)
}
return
}
if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer {
log.Trace("Repository transfer initiated: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
return
}
log.Trace("Repository transferred: %s -> %s", oldFullname, ctx.Repo.Repository.FullName())
ctx.JSON(http.StatusAccepted, convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeAdmin}))
}
// 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, ctx.Repo.Repository, ctx.Repo.Permission))
}
// 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, ctx.Repo.Repository, ctx.Repo.Permission))
}
func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
repoTransfer, err := models.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
if err != nil {
if models.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)
}