loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request 'hooks: Harden when we accept push options that change repo settings' (#3314) from earl-warren/forgejo:wip-push-options into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/3314
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
Reviewed-by: Gergely Nagy <algernon@noreply.codeberg.org>

+153
+61
routers/private/hook_pre_receive.go
··· 4 4 package private 5 5 6 6 import ( 7 + "errors" 7 8 "fmt" 8 9 "net/http" 9 10 "os" ··· 101 102 return true 102 103 } 103 104 105 + var errPermissionDenied = errors.New("permission denied for changing repo settings") 106 + 107 + func (ctx *preReceiveContext) canChangeSettings() error { 108 + if !ctx.loadPusherAndPermission() { 109 + return errPermissionDenied 110 + } 111 + 112 + if !ctx.userPerm.IsOwner() && !ctx.userPerm.IsAdmin() { 113 + return errPermissionDenied 114 + } 115 + 116 + if ctx.Repo.Repository.IsFork { 117 + return errPermissionDenied 118 + } 119 + 120 + return nil 121 + } 122 + 123 + func (ctx *preReceiveContext) validatePushOptions() error { 124 + opts := web.GetForm(ctx).(*private.HookOptions) 125 + 126 + if len(opts.GitPushOptions) == 0 { 127 + return nil 128 + } 129 + 130 + changesRepoSettings := false 131 + for key := range opts.GitPushOptions { 132 + switch key { 133 + case private.GitPushOptionRepoPrivate, private.GitPushOptionRepoTemplate: 134 + changesRepoSettings = true 135 + case "topic", "force-push", "title", "description": 136 + // Agit options 137 + default: 138 + return fmt.Errorf("unknown option %s", key) 139 + } 140 + } 141 + 142 + if changesRepoSettings { 143 + return ctx.canChangeSettings() 144 + } 145 + 146 + return nil 147 + } 148 + 149 + func (ctx *preReceiveContext) assertPushOptions() bool { 150 + if err := ctx.validatePushOptions(); err != nil { 151 + ctx.JSON(http.StatusForbidden, private.Response{ 152 + UserMsg: fmt.Sprintf("options validation failed: %v", err), 153 + }) 154 + return false 155 + } 156 + return true 157 + } 158 + 104 159 // HookPreReceive checks whether a individual commit is acceptable 105 160 func HookPreReceive(ctx *gitea_context.PrivateContext) { 106 161 opts := web.GetForm(ctx).(*private.HookOptions) ··· 110 165 env: generateGitEnv(opts), // Generate git environment for checking commits 111 166 opts: opts, 112 167 } 168 + 169 + if !ourCtx.assertPushOptions() { 170 + log.Trace("Git push options validation failed") 171 + return 172 + } 173 + log.Trace("Git push options validation succeeded") 113 174 114 175 // Iterate across the provided old commit IDs 115 176 for i := range opts.OldCommitIDs {
+92
tests/integration/git_push_test.go
··· 7 7 "fmt" 8 8 "net/url" 9 9 "testing" 10 + "time" 10 11 11 12 "code.gitea.io/gitea/models/db" 12 13 git_model "code.gitea.io/gitea/models/git" 14 + repo_model "code.gitea.io/gitea/models/repo" 13 15 "code.gitea.io/gitea/models/unittest" 14 16 user_model "code.gitea.io/gitea/models/user" 15 17 "code.gitea.io/gitea/modules/git" 18 + "code.gitea.io/gitea/modules/log" 19 + repo_module "code.gitea.io/gitea/modules/repository" 20 + "code.gitea.io/gitea/modules/test" 16 21 repo_service "code.gitea.io/gitea/services/repository" 17 22 18 23 "github.com/stretchr/testify/assert" ··· 146 151 147 152 require.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID)) 148 153 } 154 + 155 + func TestOptionsGitPush(t *testing.T) { 156 + onGiteaRun(t, testOptionsGitPush) 157 + } 158 + 159 + func testOptionsGitPush(t *testing.T, u *url.URL) { 160 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 161 + repo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_service.CreateRepoOptions{ 162 + Name: "repo-to-push", 163 + Description: "test git push", 164 + AutoInit: false, 165 + DefaultBranch: "main", 166 + IsPrivate: false, 167 + }) 168 + require.NoError(t, err) 169 + require.NotEmpty(t, repo) 170 + 171 + gitPath := t.TempDir() 172 + 173 + doGitInitTestRepository(gitPath)(t) 174 + 175 + u.Path = repo.FullName() + ".git" 176 + u.User = url.UserPassword(user.LowerName, userPassword) 177 + doGitAddRemote(gitPath, "origin", u)(t) 178 + 179 + t.Run("Unknown push options are rejected", func(t *testing.T) { 180 + logChecker, cleanup := test.NewLogChecker(log.DEFAULT, log.TRACE) 181 + logChecker.Filter("unknown option").StopMark("Git push options validation") 182 + defer cleanup() 183 + branchName := "branch0" 184 + doGitCreateBranch(gitPath, branchName)(t) 185 + doGitPushTestRepositoryFail(gitPath, "origin", branchName, "-o", "repo.template=false", "-o", "uknownoption=randomvalue")(t) 186 + logFiltered, logStopped := logChecker.Check(5 * time.Second) 187 + assert.True(t, logStopped) 188 + assert.True(t, logFiltered[0]) 189 + }) 190 + 191 + t.Run("Owner sets private & template to true via push options", func(t *testing.T) { 192 + branchName := "branch1" 193 + doGitCreateBranch(gitPath, branchName)(t) 194 + doGitPushTestRepository(gitPath, "origin", branchName, "-o", "repo.private=true", "-o", "repo.template=true")(t) 195 + repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, user.Name, "repo-to-push") 196 + require.NoError(t, err) 197 + require.True(t, repo.IsPrivate) 198 + require.True(t, repo.IsTemplate) 199 + }) 200 + 201 + t.Run("Owner sets private & template to false via push options", func(t *testing.T) { 202 + branchName := "branch2" 203 + doGitCreateBranch(gitPath, branchName)(t) 204 + doGitPushTestRepository(gitPath, "origin", branchName, "-o", "repo.private=false", "-o", "repo.template=false")(t) 205 + repo, err = repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, user.Name, "repo-to-push") 206 + require.NoError(t, err) 207 + require.False(t, repo.IsPrivate) 208 + require.False(t, repo.IsTemplate) 209 + }) 210 + 211 + // create a collaborator with write access 212 + collaborator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) 213 + u.User = url.UserPassword(collaborator.LowerName, userPassword) 214 + doGitAddRemote(gitPath, "collaborator", u)(t) 215 + repo_module.AddCollaborator(db.DefaultContext, repo, collaborator) 216 + 217 + t.Run("Collaborator with write access is allowed to push", func(t *testing.T) { 218 + branchName := "branch3" 219 + doGitCreateBranch(gitPath, branchName)(t) 220 + doGitPushTestRepository(gitPath, "collaborator", branchName)(t) 221 + }) 222 + 223 + t.Run("Collaborator with write access fails to change private & template via push options", func(t *testing.T) { 224 + logChecker, cleanup := test.NewLogChecker(log.DEFAULT, log.TRACE) 225 + logChecker.Filter("permission denied for changing repo settings").StopMark("Git push options validation") 226 + defer cleanup() 227 + branchName := "branch4" 228 + doGitCreateBranch(gitPath, branchName)(t) 229 + doGitPushTestRepositoryFail(gitPath, "collaborator", branchName, "-o", "repo.private=true", "-o", "repo.template=true")(t) 230 + repo, err = repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, user.Name, "repo-to-push") 231 + require.NoError(t, err) 232 + require.False(t, repo.IsPrivate) 233 + require.False(t, repo.IsTemplate) 234 + logFiltered, logStopped := logChecker.Check(5 * time.Second) 235 + assert.True(t, logStopped) 236 + assert.True(t, logFiltered[0]) 237 + }) 238 + 239 + require.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID)) 240 + }