From 061abe60045212acf8c3f5c49b5cc758b4cbcde9 Mon Sep 17 00:00:00 2001 From: Gusted Date: Sat, 2 Nov 2024 20:55:30 +0100 Subject: [PATCH] fix: don't show private forks in forks list - If a repository is forked to a private or limited user/organization, the fork should not be visible in the list of forks depending on the doer requesting the list of forks. - Added integration testing for web and API route. --- models/repo/fork.go | 10 ++++--- routers/api/v1/repo/fork.go | 4 +-- routers/web/repo/view.go | 10 +++---- tests/integration/api_fork_test.go | 43 +++++++++++++++++++++++++++++ tests/integration/repo_fork_test.go | 32 +++++++++++++++++++++ 5 files changed, 88 insertions(+), 11 deletions(-) diff --git a/models/repo/fork.go b/models/repo/fork.go index 07cd31c269..632e91c2bb 100644 --- a/models/repo/fork.go +++ b/models/repo/fork.go @@ -7,6 +7,7 @@ import ( "context" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "xorm.io/builder" @@ -54,9 +55,9 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error) return &forkedRepo, nil } -// GetForks returns all the forks of the repository -func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) { - sess := db.GetEngine(ctx) +// GetForks returns all the forks of the repository that are visible to the user. +func GetForks(ctx context.Context, repo *Repository, user *user_model.User, listOptions db.ListOptions) ([]*Repository, int64, error) { + sess := db.GetEngine(ctx).Where(AccessibleRepositoryCondition(user, unit.TypeInvalid)) var forks []*Repository if listOptions.Page == 0 { @@ -66,7 +67,8 @@ func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) sess = db.SetSessionPagination(sess, &listOptions) } - return forks, sess.Find(&forks, &Repository{ForkID: repo.ID}) + count, err := sess.FindAndCount(&forks, &Repository{ForkID: repo.ID}) + return forks, count, err } // IncrementRepoForkNum increment repository fork number diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 97aaffd103..c9dc9681c9 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -56,7 +56,7 @@ func ListForks(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - forks, err := repo_model.GetForks(ctx, ctx.Repo.Repository, utils.GetListOptions(ctx)) + forks, total, err := repo_model.GetForks(ctx, ctx.Repo.Repository, ctx.Doer, utils.GetListOptions(ctx)) if err != nil { ctx.Error(http.StatusInternalServerError, "GetForks", err) return @@ -71,7 +71,7 @@ func ListForks(ctx *context.APIContext) { apiForks[i] = convert.ToRepo(ctx, fork, permission) } - ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumForks)) + ctx.SetTotalCountHeader(total) ctx.JSON(http.StatusOK, apiForks) } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 41ff5f97f7..e177c81902 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -1232,11 +1232,8 @@ func Forks(ctx *context.Context) { page = 1 } - pager := context.NewPagination(ctx.Repo.Repository.NumForks, setting.MaxForksPerPage, page, 5) - ctx.Data["Page"] = pager - - forks, err := repo_model.GetForks(ctx, ctx.Repo.Repository, db.ListOptions{ - Page: pager.Paginater.Current(), + forks, total, err := repo_model.GetForks(ctx, ctx.Repo.Repository, ctx.Doer, db.ListOptions{ + Page: page, PageSize: setting.MaxForksPerPage, }) if err != nil { @@ -1244,6 +1241,9 @@ func Forks(ctx *context.Context) { return } + pager := context.NewPagination(int(total), setting.MaxForksPerPage, page, 5) + ctx.Data["Page"] = pager + for _, fork := range forks { if err = fork.LoadOwner(ctx); err != nil { ctx.ServerError("LoadOwner", err) diff --git a/tests/integration/api_fork_test.go b/tests/integration/api_fork_test.go index 6a6b05f700..6614f4b799 100644 --- a/tests/integration/api_fork_test.go +++ b/tests/integration/api_fork_test.go @@ -17,6 +17,8 @@ import ( "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/routers" "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" ) func TestAPIForkAsAdminIgnoringLimits(t *testing.T) { @@ -106,3 +108,44 @@ func TestAPIDisabledForkRepo(t *testing.T) { session.MakeRequest(t, req, http.StatusNotFound) }) } + +func TestAPIForkListPrivateRepo(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user5") + token := getTokenForLoggedInUser(t, session, + auth_model.AccessTokenScopeWriteRepository, + auth_model.AccessTokenScopeWriteOrganization) + org23 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23, Visibility: api.VisibleTypePrivate}) + + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{ + Organization: &org23.Name, + }).AddTokenAuth(token) + MakeRequest(t, req, http.StatusAccepted) + + t.Run("Anomynous", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks") + resp := MakeRequest(t, req, http.StatusOK) + + var forks []*api.Repository + DecodeJSON(t, resp, &forks) + + assert.Empty(t, forks) + assert.EqualValues(t, "0", resp.Header().Get("X-Total-Count")) + }) + + t.Run("Logged in", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks").AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusOK) + + var forks []*api.Repository + DecodeJSON(t, resp, &forks) + + assert.Len(t, forks, 1) + assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count")) + }) +} diff --git a/tests/integration/repo_fork_test.go b/tests/integration/repo_fork_test.go index 2627749836..b2e40671ba 100644 --- a/tests/integration/repo_fork_test.go +++ b/tests/integration/repo_fork_test.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/routers" repo_service "code.gitea.io/gitea/services/repository" @@ -238,3 +239,34 @@ func TestRepoForkToOrg(t *testing.T) { }) }) } + +func TestForkListPrivateRepo(t *testing.T) { + forkItemSelector := ".tw-flex.tw-items-center.tw-py-2" + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + session := loginUser(t, "user5") + org23 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23, Visibility: structs.VisibleTypePrivate}) + + testRepoFork(t, session, "user2", "repo1", org23.Name, "repo1") + + t.Run("Anomynous", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo1/forks") + resp := MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + htmlDoc.AssertElement(t, forkItemSelector, false) + }) + + t.Run("Logged in", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/user2/repo1/forks") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + htmlDoc.AssertElement(t, forkItemSelector, true) + }) + }) +}