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 }