From 261fc876739f96ea6940858910d38c448edc8421 Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Wed, 17 Apr 2024 05:52:02 +0000 Subject: [PATCH] Allow admins to fork repos even when creation limits are exhausted (#3277) This is a continuation of #2728, with a test case added. Fixes #2633. I kept @zareck 's commit as is, because I believe it is correct. We can't move the check to `owner.CanForkRepo()`, because `owner` is the future owner of the forked repo, and may be an organization. We need to check the admin permission of the `doer`, like in the case of repository creation. I verified that the test fails without the `ForkRepository` change, and passes with it. Co-authored-by: Cassio Zareck Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3277 Reviewed-by: Earl Warren Co-authored-by: Gergely Nagy Co-committed-by: Gergely Nagy (cherry picked from commit ea4071ca9f0a9973b7b8676c1bb70b76cdc5f091) --- services/repository/fork.go | 2 +- tests/integration/api_fork_test.go | 63 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/services/repository/fork.go b/services/repository/fork.go index f9c13a109e..5346d880f6 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -54,7 +54,7 @@ type ForkRepoOptions struct { // ForkRepository forks a repository func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts ForkRepoOptions) (*repo_model.Repository, error) { // Fork is prohibited, if user has reached maximum limit of repositories - if !owner.CanForkRepo() { + if !doer.IsAdmin && !owner.CanForkRepo() { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, } diff --git a/tests/integration/api_fork_test.go b/tests/integration/api_fork_test.go index 87d2a10152..b80b4c6645 100644 --- a/tests/integration/api_fork_test.go +++ b/tests/integration/api_fork_test.go @@ -5,10 +5,14 @@ package integration import ( + "fmt" "net/http" "net/url" "testing" + auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" @@ -16,6 +20,65 @@ import ( "code.gitea.io/gitea/tests" ) +func TestAPIForkAsAdminIgnoringLimits(t *testing.T) { + defer tests.PrepareTestEnv(t)() + defer test.MockVariableValue(&setting.Repository.AllowForkWithoutMaximumLimit, false)() + defer test.MockVariableValue(&setting.Repository.MaxCreationLimit, 0)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + userSession := loginUser(t, user.Name) + userToken := getTokenForLoggedInUser(t, userSession, auth_model.AccessTokenScopeWriteRepository) + adminUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{IsAdmin: true}) + adminSession := loginUser(t, adminUser.Name) + adminToken := getTokenForLoggedInUser(t, adminSession, + auth_model.AccessTokenScopeWriteRepository, + auth_model.AccessTokenScopeWriteOrganization) + + originForkURL := "/api/v1/repos/user12/repo10/forks" + orgName := "fork-org" + + // Create an organization + req := NewRequestWithJSON(t, "POST", "/api/v1/orgs", &api.CreateOrgOption{ + UserName: orgName, + }).AddTokenAuth(adminToken) + MakeRequest(t, req, http.StatusCreated) + + // Create a team + teamToCreate := &api.CreateTeamOption{ + Name: "testers", + IncludesAllRepositories: true, + Permission: "write", + Units: []string{"repo.code", "repo.issues"}, + } + + req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), &teamToCreate).AddTokenAuth(adminToken) + resp := MakeRequest(t, req, http.StatusCreated) + var team api.Team + DecodeJSON(t, resp, &team) + + // Add user2 to the team + req = NewRequestf(t, "PUT", "/api/v1/teams/%d/members/user2", team.ID).AddTokenAuth(adminToken) + MakeRequest(t, req, http.StatusNoContent) + + t.Run("forking as regular user", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithJSON(t, "POST", originForkURL, &api.CreateForkOption{ + Organization: &orgName, + }).AddTokenAuth(userToken) + MakeRequest(t, req, http.StatusConflict) + }) + + t.Run("forking as an instance admin", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequestWithJSON(t, "POST", originForkURL, &api.CreateForkOption{ + Organization: &orgName, + }).AddTokenAuth(adminToken) + MakeRequest(t, req, http.StatusAccepted) + }) +} + func TestCreateForkNoLogin(t *testing.T) { defer tests.PrepareTestEnv(t)() req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{})