You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

transfer.go 6.7KB

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