Skip to content

Commit f88dbf8

Browse files
lunnywxiaoguang
andauthored
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 <[email protected]>
1 parent 48183d2 commit f88dbf8

File tree

10 files changed

+297
-217
lines changed

10 files changed

+297
-217
lines changed

models/repo/transfer.go

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type RepoTransfer struct { //nolint
6868
RecipientID int64
6969
Recipient *user_model.User `xorm:"-"`
7070
RepoID int64
71+
Repo *Repository `xorm:"-"`
7172
TeamIDs []int64
7273
Teams []*organization.Team `xorm:"-"`
7374

@@ -79,48 +80,65 @@ func init() {
7980
db.RegisterModel(new(RepoTransfer))
8081
}
8182

82-
// LoadAttributes fetches the transfer recipient from the database
83-
func (r *RepoTransfer) LoadAttributes(ctx context.Context) error {
83+
func (r *RepoTransfer) LoadRecipient(ctx context.Context) error {
8484
if r.Recipient == nil {
8585
u, err := user_model.GetUserByID(ctx, r.RecipientID)
8686
if err != nil {
8787
return err
8888
}
89-
9089
r.Recipient = u
9190
}
9291

93-
if r.Recipient.IsOrganization() && len(r.TeamIDs) != len(r.Teams) {
94-
for _, v := range r.TeamIDs {
95-
team, err := organization.GetTeamByID(ctx, v)
96-
if err != nil {
97-
return err
98-
}
92+
return nil
93+
}
9994

100-
if team.OrgID != r.Recipient.ID {
101-
return fmt.Errorf("team %d belongs not to org %d", v, r.Recipient.ID)
102-
}
95+
func (r *RepoTransfer) LoadRepo(ctx context.Context) error {
96+
if r.Repo == nil {
97+
repo, err := GetRepositoryByID(ctx, r.RepoID)
98+
if err != nil {
99+
return err
100+
}
101+
r.Repo = repo
102+
}
103+
104+
return nil
105+
}
106+
107+
// LoadAttributes fetches the transfer recipient from the database
108+
func (r *RepoTransfer) LoadAttributes(ctx context.Context) error {
109+
if err := r.LoadRecipient(ctx); err != nil {
110+
return err
111+
}
103112

113+
if r.Recipient.IsOrganization() && r.Teams == nil {
114+
teamsMap, err := organization.GetTeamsByIDs(ctx, r.TeamIDs)
115+
if err != nil {
116+
return err
117+
}
118+
for _, team := range teamsMap {
104119
r.Teams = append(r.Teams, team)
105120
}
106121
}
107122

123+
if err := r.LoadRepo(ctx); err != nil {
124+
return err
125+
}
126+
108127
if r.Doer == nil {
109128
u, err := user_model.GetUserByID(ctx, r.DoerID)
110129
if err != nil {
111130
return err
112131
}
113-
114132
r.Doer = u
115133
}
116134

117135
return nil
118136
}
119137

120-
// CanUserAcceptTransfer checks if the user has the rights to accept/decline a repo transfer.
138+
// CanUserAcceptOrRejectTransfer checks if the user has the rights to accept/decline a repo transfer.
121139
// For user, it checks if it's himself
122140
// For organizations, it checks if the user is able to create repos
123-
func (r *RepoTransfer) CanUserAcceptTransfer(ctx context.Context, u *user_model.User) bool {
141+
func (r *RepoTransfer) CanUserAcceptOrRejectTransfer(ctx context.Context, u *user_model.User) bool {
124142
if err := r.LoadAttributes(ctx); err != nil {
125143
log.Error("LoadAttributes: %v", err)
126144
return false
@@ -166,6 +184,10 @@ func GetPendingRepositoryTransfers(ctx context.Context, opts *PendingRepositoryT
166184
Find(&transfers)
167185
}
168186

187+
func IsRepositoryTransferExist(ctx context.Context, repoID int64) (bool, error) {
188+
return db.GetEngine(ctx).Where("repo_id = ?", repoID).Exist(new(RepoTransfer))
189+
}
190+
169191
// GetPendingRepositoryTransfer fetches the most recent and ongoing transfer
170192
// process for the repository
171193
func GetPendingRepositoryTransfer(ctx context.Context, repo *Repository) (*RepoTransfer, error) {
@@ -206,11 +228,26 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m
206228
return err
207229
}
208230

231+
if _, err := user_model.GetUserByID(ctx, newOwner.ID); err != nil {
232+
return err
233+
}
234+
209235
// Make sure repo is ready to transfer
210236
if err := TestRepositoryReadyForTransfer(repo.Status); err != nil {
211237
return err
212238
}
213239

240+
exist, err := IsRepositoryTransferExist(ctx, repo.ID)
241+
if err != nil {
242+
return err
243+
}
244+
if exist {
245+
return ErrRepoTransferInProgress{
246+
Uname: repo.Owner.LowerName,
247+
Name: repo.Name,
248+
}
249+
}
250+
214251
repo.Status = RepositoryPendingTransfer
215252
if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil {
216253
return err

routers/api/v1/repo/transfer.go

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
user_model "code.gitea.io/gitea/models/user"
1616
"code.gitea.io/gitea/modules/log"
1717
api "code.gitea.io/gitea/modules/structs"
18+
"code.gitea.io/gitea/modules/util"
1819
"code.gitea.io/gitea/modules/web"
1920
"code.gitea.io/gitea/services/context"
2021
"code.gitea.io/gitea/services/convert"
@@ -161,12 +162,16 @@ func AcceptTransfer(ctx *context.APIContext) {
161162
// "404":
162163
// "$ref": "#/responses/notFound"
163164

164-
err := acceptOrRejectRepoTransfer(ctx, true)
165-
if ctx.Written() {
166-
return
167-
}
165+
err := repo_service.AcceptTransferOwnership(ctx, ctx.Repo.Repository, ctx.Doer)
168166
if err != nil {
169-
ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
167+
switch {
168+
case repo_model.IsErrNoPendingTransfer(err):
169+
ctx.Error(http.StatusNotFound, "AcceptTransferOwnership", err)
170+
case errors.Is(err, util.ErrPermissionDenied):
171+
ctx.Error(http.StatusForbidden, "AcceptTransferOwnership", err)
172+
default:
173+
ctx.Error(http.StatusInternalServerError, "AcceptTransferOwnership", err)
174+
}
170175
return
171176
}
172177

@@ -199,40 +204,18 @@ func RejectTransfer(ctx *context.APIContext) {
199204
// "404":
200205
// "$ref": "#/responses/notFound"
201206

202-
err := acceptOrRejectRepoTransfer(ctx, false)
203-
if ctx.Written() {
204-
return
205-
}
207+
err := repo_service.RejectRepositoryTransfer(ctx, ctx.Repo.Repository, ctx.Doer)
206208
if err != nil {
207-
ctx.Error(http.StatusInternalServerError, "acceptOrRejectRepoTransfer", err)
209+
switch {
210+
case repo_model.IsErrNoPendingTransfer(err):
211+
ctx.Error(http.StatusNotFound, "RejectRepositoryTransfer", err)
212+
case errors.Is(err, util.ErrPermissionDenied):
213+
ctx.Error(http.StatusForbidden, "RejectRepositoryTransfer", err)
214+
default:
215+
ctx.Error(http.StatusInternalServerError, "RejectRepositoryTransfer", err)
216+
}
208217
return
209218
}
210219

211220
ctx.JSON(http.StatusOK, convert.ToRepo(ctx, ctx.Repo.Repository, ctx.Repo.Permission))
212221
}
213-
214-
func acceptOrRejectRepoTransfer(ctx *context.APIContext, accept bool) error {
215-
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
216-
if err != nil {
217-
if repo_model.IsErrNoPendingTransfer(err) {
218-
ctx.NotFound()
219-
return nil
220-
}
221-
return err
222-
}
223-
224-
if err := repoTransfer.LoadAttributes(ctx); err != nil {
225-
return err
226-
}
227-
228-
if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
229-
ctx.Error(http.StatusForbidden, "CanUserAcceptTransfer", nil)
230-
return fmt.Errorf("user does not have permissions to do this")
231-
}
232-
233-
if accept {
234-
return repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams)
235-
}
236-
237-
return repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository)
238-
}

routers/web/repo/repo.go

Lines changed: 36 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,36 @@ const (
309309
tplStarUnstar templates.TplName = "repo/star_unstar"
310310
)
311311

312+
func acceptTransfer(ctx *context.Context) {
313+
err := repo_service.AcceptTransferOwnership(ctx, ctx.Repo.Repository, ctx.Doer)
314+
if err == nil {
315+
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
316+
ctx.Redirect(ctx.Repo.Repository.Link())
317+
return
318+
}
319+
handleActionError(ctx, err)
320+
}
321+
322+
func rejectTransfer(ctx *context.Context) {
323+
err := repo_service.RejectRepositoryTransfer(ctx, ctx.Repo.Repository, ctx.Doer)
324+
if err == nil {
325+
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
326+
ctx.Redirect(ctx.Repo.Repository.Link())
327+
return
328+
}
329+
handleActionError(ctx, err)
330+
}
331+
332+
func handleActionError(ctx *context.Context, err error) {
333+
if errors.Is(err, user_model.ErrBlockedUser) {
334+
ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
335+
} else if errors.Is(err, util.ErrPermissionDenied) {
336+
ctx.Error(http.StatusNotFound)
337+
} else {
338+
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
339+
}
340+
}
341+
312342
// Action response for actions to a repository
313343
func Action(ctx *context.Context) {
314344
var err error
@@ -322,9 +352,11 @@ func Action(ctx *context.Context) {
322352
case "unstar":
323353
err = repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
324354
case "accept_transfer":
325-
err = acceptOrRejectRepoTransfer(ctx, true)
355+
acceptTransfer(ctx)
356+
return
326357
case "reject_transfer":
327-
err = acceptOrRejectRepoTransfer(ctx, false)
358+
rejectTransfer(ctx)
359+
return
328360
case "desc": // FIXME: this is not used
329361
if !ctx.Repo.IsOwner() {
330362
ctx.Error(http.StatusNotFound)
@@ -337,12 +369,8 @@ func Action(ctx *context.Context) {
337369
}
338370

339371
if err != nil {
340-
if errors.Is(err, user_model.ErrBlockedUser) {
341-
ctx.Flash.Error(ctx.Tr("repo.action.blocked_user"))
342-
} else {
343-
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err)
344-
return
345-
}
372+
handleActionError(ctx, err)
373+
return
346374
}
347375

348376
switch ctx.PathParam("action") {
@@ -377,41 +405,6 @@ func Action(ctx *context.Context) {
377405
ctx.RedirectToCurrentSite(ctx.FormString("redirect_to"), ctx.Repo.RepoLink)
378406
}
379407

380-
func acceptOrRejectRepoTransfer(ctx *context.Context, accept bool) error {
381-
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
382-
if err != nil {
383-
return err
384-
}
385-
386-
if err := repoTransfer.LoadAttributes(ctx); err != nil {
387-
return err
388-
}
389-
390-
if !repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer) {
391-
return errors.New("user does not have enough permissions")
392-
}
393-
394-
if accept {
395-
if ctx.Repo.GitRepo != nil {
396-
ctx.Repo.GitRepo.Close()
397-
ctx.Repo.GitRepo = nil
398-
}
399-
400-
if err := repo_service.TransferOwnership(ctx, repoTransfer.Doer, repoTransfer.Recipient, ctx.Repo.Repository, repoTransfer.Teams); err != nil {
401-
return err
402-
}
403-
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.success"))
404-
} else {
405-
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
406-
return err
407-
}
408-
ctx.Flash.Success(ctx.Tr("repo.settings.transfer.rejected"))
409-
}
410-
411-
ctx.Redirect(ctx.Repo.Repository.Link())
412-
return nil
413-
}
414-
415408
// RedirectDownload return a file based on the following infos:
416409
func RedirectDownload(ctx *context.Context) {
417410
var (

routers/web/repo/setting/setting.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -832,16 +832,10 @@ func SettingsPost(ctx *context.Context) {
832832
} else {
833833
ctx.ServerError("GetPendingRepositoryTransfer", err)
834834
}
835-
836-
return
837-
}
838-
839-
if err := repoTransfer.LoadAttributes(ctx); err != nil {
840-
ctx.ServerError("LoadRecipient", err)
841835
return
842836
}
843837

844-
if err := repo_service.CancelRepositoryTransfer(ctx, ctx.Repo.Repository); err != nil {
838+
if err := repo_service.CancelRepositoryTransfer(ctx, repoTransfer, ctx.Doer); err != nil {
845839
ctx.ServerError("CancelRepositoryTransfer", err)
846840
return
847841
}

services/context/repo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ func RepoAssignment(ctx *Context) {
653653

654654
ctx.Data["RepoTransfer"] = repoTransfer
655655
if ctx.Doer != nil {
656-
ctx.Data["CanUserAcceptTransfer"] = repoTransfer.CanUserAcceptTransfer(ctx, ctx.Doer)
656+
ctx.Data["CanUserAcceptOrRejectTransfer"] = repoTransfer.CanUserAcceptOrRejectTransfer(ctx, ctx.Doer)
657657
}
658658
}
659659

services/notify/notify.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,9 +272,9 @@ func MigrateRepository(ctx context.Context, doer, u *user_model.User, repo *repo
272272
}
273273

274274
// TransferRepository notifies create repository to notifiers
275-
func TransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newOwnerName string) {
275+
func TransferRepository(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, oldOwnerName string) {
276276
for _, notifier := range notifiers {
277-
notifier.TransferRepository(ctx, doer, repo, newOwnerName)
277+
notifier.TransferRepository(ctx, doer, repo, oldOwnerName)
278278
}
279279
}
280280

0 commit comments

Comments
 (0)