From ee8a2de565ceb577798302f8841defe104cec069 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Mon, 2 Dec 2019 23:29:04 -0600 Subject: [PATCH] Refactor Signed-off-by: jolheiser --- custom/conf/app.ini.sample | 3 + .../doc/advanced/config-cheat-sheet.en-us.md | 2 + integrations/git_test.go | 52 +++++++++++ modules/setting/repository.go | 4 + routers/repo/http.go | 91 +++++++++++++++---- 5 files changed, 132 insertions(+), 20 deletions(-) diff --git a/custom/conf/app.ini.sample b/custom/conf/app.ini.sample index 8d11cfc293e14..ae7a0e25b4d9f 100644 --- a/custom/conf/app.ini.sample +++ b/custom/conf/app.ini.sample @@ -39,6 +39,9 @@ ACCESS_CONTROL_ALLOW_ORIGIN = USE_COMPAT_SSH_URI = false ; Close issues as long as a commit on any branch marks it as fixed DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH = false +; Allow users to push local repositories to Gitea and have them automatically created for a user or an org +ENABLE_PUSH_CREATE_USER = false +ENABLE_PUSH_CREATE_ORG = false [repository.editor] ; List of file extensions for which lines should be wrapped in the CodeMirror editor diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 4fc8511b8c62c..924063d45bace 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -66,6 +66,8 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. default is not to present. **WARNING**: This maybe harmful to you website if you do not give it a right value. - `DEFAULT_CLOSE_ISSUES_VIA_COMMITS_IN_ANY_BRANCH`: **false**: Close an issue if a commit on a non default branch marks it as closed. +- `ENABLE_PUSH_CREATE_USER`: **false**: Allow users to push local repositories to Gitea and have them automatically created for a user. +- `ENABLE_PUSH_CREATE_ORG`: **false**: Allow users to push local repositories to Gitea and have them automatically created for an org. ### Repository - Pull Request (`repository.pull-request`) diff --git a/integrations/git_test.go b/integrations/git_test.go index 3ca4cc54c2edf..06cfa21140c99 100644 --- a/integrations/git_test.go +++ b/integrations/git_test.go @@ -75,6 +75,8 @@ func testGit(t *testing.T, u *url.URL) { rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) }) + + t.Run("PushCreate", doPushCreate(httpContext, u)) }) t.Run("SSH", func(t *testing.T) { defer PrintCurrentTest(t)() @@ -113,6 +115,8 @@ func testGit(t *testing.T, u *url.URL) { rawTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) mediaTest(t, &forkedUserCtx, little, big, littleLFS, bigLFS) }) + + t.Run("PushCreate", doPushCreate(sshContext, sshURL)) }) }) } @@ -407,3 +411,51 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun } } + +func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { + return func(t *testing.T) { + defer PrintCurrentTest(t)() + ctx.Reponame = fmt.Sprintf("repo-tmp-push-create-%s", u.Scheme) + u.Path = ctx.GitPath() + + tmpDir, err := ioutil.TempDir("", ctx.Reponame) + assert.NoError(t, err) + + err = git.InitRepository(tmpDir, false) + assert.NoError(t, err) + + _, err = os.Create(filepath.Join(tmpDir, "test.txt")) + assert.NoError(t, err) + + err = git.AddChanges(tmpDir, true) + assert.NoError(t, err) + + err = git.CommitChanges(tmpDir, git.CommitChangesOptions{ + Committer: &git.Signature{ + Email: "user2@example.com", + Name: "User Two", + When: time.Now(), + }, + Author: &git.Signature{ + Email: "user2@example.com", + Name: "User Two", + When: time.Now(), + }, + Message: fmt.Sprintf("Testing push create @ %v", time.Now()), + }) + assert.NoError(t, err) + + _, err = git.NewCommand("remote", "add", "origin", u.String()).RunInDir(tmpDir) + assert.NoError(t, err) + + // Push to create disabled + setting.Repository.EnablePushCreateUser = false + _, err = git.NewCommand("push", "origin", "master").RunInDir(tmpDir) + assert.Error(t, err) + + // Push to create enabled + setting.Repository.EnablePushCreateUser = true + _, err = git.NewCommand("push", "origin", "master").RunInDir(tmpDir) + assert.NoError(t, err) + } +} diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 3e183b6c98ffe..3e7393efb6efc 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -35,6 +35,8 @@ var ( AccessControlAllowOrigin string UseCompatSSHURI bool DefaultCloseIssuesViaCommitsInAnyBranch bool + EnablePushCreateUser bool + EnablePushCreateOrg bool // Repository editor settings Editor struct { @@ -89,6 +91,8 @@ var ( AccessControlAllowOrigin: "", UseCompatSSHURI: false, DefaultCloseIssuesViaCommitsInAnyBranch: false, + EnablePushCreateUser: false, + EnablePushCreateOrg: false, // Repository editor settings Editor: struct { diff --git a/routers/repo/http.go b/routers/repo/http.go index c66d7aae65728..a8c3be9722563 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -100,29 +100,29 @@ func HTTP(ctx *context.Context) { return } + repoExist := true repo, err := models.GetRepositoryByName(owner.ID, reponame) if err != nil { if models.IsErrRepoNotExist(err) { - redirectRepoID, err := models.LookupRepoRedirect(owner.ID, reponame) - if err == nil { + if redirectRepoID, err := models.LookupRepoRedirect(owner.ID, reponame); err == nil { context.RedirectToRepo(ctx, redirectRepoID) - } else { - ctx.NotFoundOrServerError("GetRepositoryByName", models.IsErrRepoRedirectNotExist, err) + return } + repoExist = false } else { ctx.ServerError("GetRepositoryByName", err) + return } - return } // Don't allow pushing if the repo is archived - if repo.IsArchived && !isPull { + if repoExist && repo.IsArchived && !isPull { ctx.HandleText(http.StatusForbidden, "This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.") return } // Only public pull don't need auth. - isPublicPull := !repo.IsPrivate && isPull + isPublicPull := repoExist && !repo.IsPrivate && isPull var ( askAuth = !isPublicPull || setting.Service.RequireSignInView authUser *models.User @@ -243,20 +243,22 @@ func HTTP(ctx *context.Context) { } } - perm, err := models.GetUserRepoPermission(repo, authUser) - if err != nil { - ctx.ServerError("GetUserRepoPermission", err) - return - } + if repoExist { + perm, err := models.GetUserRepoPermission(repo, authUser) + if err != nil { + ctx.ServerError("GetUserRepoPermission", err) + return + } - if !perm.CanAccess(accessMode, unitType) { - ctx.HandleText(http.StatusForbidden, "User permission denied") - return - } + if !perm.CanAccess(accessMode, unitType) { + ctx.HandleText(http.StatusForbidden, "User permission denied") + return + } - if !isPull && repo.IsMirror { - ctx.HandleText(http.StatusForbidden, "mirror repository is read-only") - return + if !isPull && repo.IsMirror { + ctx.HandleText(http.StatusForbidden, "mirror repository is read-only") + return + } } environ = []string{ @@ -264,7 +266,6 @@ func HTTP(ctx *context.Context) { models.EnvRepoName + "=" + reponame, models.EnvPusherName + "=" + authUser.Name, models.EnvPusherID + fmt.Sprintf("=%d", authUser.ID), - models.ProtectedBranchRepoID + fmt.Sprintf("=%d", repo.ID), models.EnvIsDeployKey + "=false", } @@ -279,6 +280,25 @@ func HTTP(ctx *context.Context) { } } + if !repoExist { + if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg { + ctx.HandleText(http.StatusForbidden, "Push to create is not enabled for organizations.") + return + } + if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser { + ctx.HandleText(http.StatusForbidden, "Push to create is not enabled for users.") + return + } + repo, err = pushCreateRepo(authUser, owner, reponame) + if err != nil { + log.Error("pushCreateRepo: %v", err) + ctx.Status(http.StatusNotFound) + return + } + } + + environ = append(environ, models.ProtectedBranchRepoID+fmt.Sprintf("=%d", repo.ID)) + w := ctx.Resp r := ctx.Req.Request cfg := &serviceConfig{ @@ -331,6 +351,37 @@ func HTTP(ctx *context.Context) { ctx.NotFound("Smart Git HTTP", nil) } +func pushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repository, error) { + if !authUser.IsAdmin { + if owner.IsOrganization() { + team, err := owner.GetOwnerTeam() + if err != nil { + return nil, err + } + if !team.IsMember(authUser.ID) { + return nil, fmt.Errorf("non-owners cannot push-create repository for an org") + } + } else if authUser.ID != owner.ID { + return nil, fmt.Errorf("cannot push-create repository for another user") + } + } + + repo, err := models.CreateRepository(authUser, owner, models.CreateRepoOptions{ + Name: repoName, + IsPrivate: true, + }) + if err == nil { + return repo, nil + } + + if repo != nil { + if errDelete := models.DeleteRepository(authUser, owner.ID, repo.ID); errDelete != nil { + log.Error("DeleteRepository: %v", errDelete) + } + } + return repo, err +} + type serviceConfig struct { UploadPack bool ReceivePack bool