From b6a5e6e20dd615d829bce7d7f3ded0abf18c7727 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 27 Aug 2024 21:41:03 -0700 Subject: [PATCH 1/5] Prevent update pull refs manually --- routers/private/hook_pre_receive.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 73fe9b886cff8..3510afe5b7057 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -124,6 +124,10 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { preReceiveTag(ourCtx, refFullName) case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor(): preReceiveFor(ourCtx, refFullName) + case refFullName.IsPull(): + ctx.JSON(http.StatusForbidden, private.Response{ + UserMsg: "Can't update pull request manually.", + }) default: ourCtx.AssertCanWriteCode() } From c49fcd8275eee04f81e187e2749bd230b63ab94f Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Tue, 27 Aug 2024 23:51:31 -0700 Subject: [PATCH 2/5] Add comments for the operation --- routers/private/hook_pre_receive.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 3510afe5b7057..62947f846fa40 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -125,6 +125,8 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor(): preReceiveFor(ourCtx, refFullName) case refFullName.IsPull(): + // update/delete refs/pull/123/head are disallowed from push, the refs + // are managed by Gitea from internal operations ctx.JSON(http.StatusForbidden, private.Response{ UserMsg: "Can't update pull request manually.", }) From 13fa005b9f880862fee0ac4e3abc8ac0d9616bb6 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 28 Aug 2024 10:21:05 -0700 Subject: [PATCH 3/5] Use Update hook to prevent update pull refs but not affect other refs update --- cmd/hook.go | 12 ++++++++++++ routers/private/hook_pre_receive.go | 6 ------ 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/cmd/hook.go b/cmd/hook.go index 6e31710caff21..f0ccaa1be7e59 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -291,7 +291,19 @@ Gitea or set your environment appropriately.`, "") } func runHookUpdate(c *cli.Context) error { + if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { + return nil + } + // Update is empty and is kept only for backwards compatibility + if len(os.Args) < 3 { + return nil + } + refName := git.RefName(os.Args[len(os.Args)-3]) + if refName.IsPull() { + // ignore update to refs/pull/xxx/head, so we don't need to output any information + os.Exit(1) + } return nil } diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 62947f846fa40..73fe9b886cff8 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -124,12 +124,6 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { preReceiveTag(ourCtx, refFullName) case git.DefaultFeatures().SupportProcReceive && refFullName.IsFor(): preReceiveFor(ourCtx, refFullName) - case refFullName.IsPull(): - // update/delete refs/pull/123/head are disallowed from push, the refs - // are managed by Gitea from internal operations - ctx.JSON(http.StatusForbidden, private.Response{ - UserMsg: "Can't update pull request manually.", - }) default: ourCtx.AssertCanWriteCode() } From 0f258190e1df4c7a1bbc605ee23ee4d62b98c3ad Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Wed, 28 Aug 2024 10:27:54 -0700 Subject: [PATCH 4/5] Add a comment for update hook change --- cmd/hook.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/hook.go b/cmd/hook.go index f0ccaa1be7e59..e8a8b1f4ad247 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -290,6 +290,8 @@ Gitea or set your environment appropriately.`, "") return nil } +// runHookUpdate avoid to do heavy operations on update hook because it will be +// invoked for every ref update which does not like pre-receive and post-receive func runHookUpdate(c *cli.Context) error { if isInternal, _ := strconv.ParseBool(os.Getenv(repo_module.EnvIsInternal)); isInternal { return nil From a5326a2a0eadcd4c947edab34a26b4c13ce03a49 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 2 Sep 2024 00:09:01 -0700 Subject: [PATCH 5/5] add tests --- tests/integration/git_push_test.go | 22 ++++++++++++++++++++++ tests/test_utils.go | 1 + 2 files changed, 23 insertions(+) diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index da254fc88f653..dc0b52203a9a5 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -6,8 +6,10 @@ package integration import ( "fmt" "net/url" + "strings" "testing" + auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" "code.gitea.io/gitea/models/unittest" @@ -192,3 +194,23 @@ func runTestGitPush(t *testing.T, u *url.URL, gitOperation func(t *testing.T, gi require.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID)) } + +func TestPushPullRefs(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + baseAPITestContext := NewAPITestContext(t, "user2", "repo1", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + u.Path = baseAPITestContext.GitPath() + u.User = url.UserPassword("user2", userPassword) + + dstPath := t.TempDir() + doGitClone(dstPath, u)(t) + + cmd := git.NewCommand(git.DefaultContext, "push", "--delete", "origin", "refs/pull/2/head") + stdout, stderr, err := cmd.RunStdString(&git.RunOpts{ + Dir: dstPath, + }) + assert.Error(t, err) + assert.Empty(t, stdout) + assert.False(t, strings.Contains(stderr, "[deleted]"), "stderr: %s", stderr) + }) +} diff --git a/tests/test_utils.go b/tests/test_utils.go index 66a287ecad262..6f9592b204112 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -223,6 +223,7 @@ func PrepareTestEnv(t testing.TB, skip ...int) func() { _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "objects", "info"), 0o755) _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "heads"), 0o755) _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "tag"), 0o755) + _ = os.MkdirAll(filepath.Join(setting.RepoRootPath, ownerDir.Name(), repoDir.Name(), "refs", "pull"), 0o755) } }