From ee8a2de565ceb577798302f8841defe104cec069 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Mon, 2 Dec 2019 23:29:04 -0600 Subject: [PATCH 1/9] 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 8d11cfc293e1..ae7a0e25b4d9 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 4fc8511b8c62..924063d45bac 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 3ca4cc54c2ed..06cfa21140c9 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 3e183b6c98ff..3e7393efb6ef 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 c66d7aae6572..a8c3be972256 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 From 53ffb2e6a9d6dcb11eee2f8299e2a5a5f284d73c Mon Sep 17 00:00:00 2001 From: jolheiser Date: Tue, 3 Dec 2019 12:35:34 -0600 Subject: [PATCH 2/9] Add push-create to SSH serv Signed-off-by: jolheiser --- routers/private/serv.go | 117 +++++++++++++++++++++++++++++----------- 1 file changed, 87 insertions(+), 30 deletions(-) diff --git a/routers/private/serv.go b/routers/private/serv.go index c4508b4cb5e0..03cf5d395615 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -98,44 +98,44 @@ func ServCommand(ctx *macaron.Context) { } // Now get the Repository and set the results section + repoExist := true repo, err := models.GetRepositoryByOwnerAndName(results.OwnerName, results.RepoName) if err != nil { if models.IsErrRepoNotExist(err) { - ctx.JSON(http.StatusNotFound, map[string]interface{}{ + repoExist = false + } else { + log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ "results": results, - "type": "ErrRepoNotExist", - "err": fmt.Sprintf("Cannot find repository %s/%s", results.OwnerName, results.RepoName), + "type": "InternalServerError", + "err": fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err), }) return } - log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err) - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "results": results, - "type": "InternalServerError", - "err": fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err), - }) - return } - repo.OwnerName = ownerName - results.RepoID = repo.ID - if repo.IsBeingCreated() { - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ - "results": results, - "type": "InternalServerError", - "err": "Repository is being created, you could retry after it finished", - }) - return - } + if repoExist { + repo.OwnerName = ownerName + results.RepoID = repo.ID - // We can shortcut at this point if the repo is a mirror - if mode > models.AccessModeRead && repo.IsMirror { - ctx.JSON(http.StatusUnauthorized, map[string]interface{}{ - "results": results, - "type": "ErrMirrorReadOnly", - "err": fmt.Sprintf("Mirror Repository %s/%s is read-only", results.OwnerName, results.RepoName), - }) - return + if repo.IsBeingCreated() { + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "results": results, + "type": "InternalServerError", + "err": "Repository is being created, you could retry after it finished", + }) + return + } + + // We can shortcut at this point if the repo is a mirror + if mode > models.AccessModeRead && repo.IsMirror { + ctx.JSON(http.StatusUnauthorized, map[string]interface{}{ + "results": results, + "type": "ErrMirrorReadOnly", + "err": fmt.Sprintf("Mirror Repository %s/%s is read-only", results.OwnerName, results.RepoName), + }) + return + } } // Get the Public Key represented by the keyID @@ -161,6 +161,16 @@ func ServCommand(ctx *macaron.Context) { results.KeyID = key.ID results.UserID = key.OwnerID + // If repo doesn't exist, deploy key doesn't make sense + if !repoExist && key.Type == models.KeyTypeDeploy { + ctx.JSON(http.StatusNotFound, map[string]interface{}{ + "results": results, + "type": "ErrRepoNotExist", + "err": fmt.Sprintf("Cannot find repository %s/%s", results.OwnerName, results.RepoName), + }) + return + } + // Deploy Keys have ownerID set to 0 therefore we can't use the owner // So now we need to check if the key is a deploy key // We'll keep hold of the deploy key here for permissions checking @@ -220,7 +230,7 @@ func ServCommand(ctx *macaron.Context) { } // Don't allow pushing if the repo is archived - if mode > models.AccessModeRead && repo.IsArchived { + if repoExist && mode > models.AccessModeRead && repo.IsArchived { ctx.JSON(http.StatusUnauthorized, map[string]interface{}{ "results": results, "type": "ErrRepoIsArchived", @@ -230,7 +240,7 @@ func ServCommand(ctx *macaron.Context) { } // Permissions checking: - if mode > models.AccessModeRead || repo.IsPrivate || setting.Service.RequireSignInView { + if repoExist && (mode > models.AccessModeRead || repo.IsPrivate || setting.Service.RequireSignInView) { if key.Type == models.KeyTypeDeploy { if deployKey.Mode < mode { ctx.JSON(http.StatusUnauthorized, map[string]interface{}{ @@ -265,6 +275,36 @@ func ServCommand(ctx *macaron.Context) { } } + // We already know we aren't using a deploy key + if !repoExist { + if user.IsOrganization() && !setting.Repository.EnablePushCreateOrg { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "results": results, + "type": "ErrForbidden", + "err": "Push to create is not enabled for organizations.", + }) + return + } + if !user.IsOrganization() && !setting.Repository.EnablePushCreateUser { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ + "results": results, + "type": "ErrForbidden", + "err": "Push to create is not enabled for users.", + }) + return + } + repo, err = pushCreateRepo(user, results.RepoName) + if err != nil { + log.Error("pushCreateRepo: %v", err) + ctx.JSON(http.StatusNotFound, map[string]interface{}{ + "results": results, + "type": "ErrRepoNotExist", + "err": fmt.Sprintf("Cannot find repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err), + }) + return + } + } + // Finally if we're trying to touch the wiki we should init it if results.IsWiki { if err = repo.InitWiki(); err != nil { @@ -291,3 +331,20 @@ func ServCommand(ctx *macaron.Context) { ctx.JSON(http.StatusOK, results) // We will update the keys in a different call. } + +func pushCreateRepo(user *models.User, repoName string) (*models.Repository, error) { + repo, err := models.CreateRepository(user, user, models.CreateRepoOptions{ + Name: repoName, + IsPrivate: true, + }) + if err == nil { + return repo, nil + } + + if repo != nil { + if errDelete := models.DeleteRepository(user, user.ID, repo.ID); errDelete != nil { + log.Error("DeleteRepository: %v", errDelete) + } + } + return repo, err +} From f1e48e2cbcbc49c91b375d910f68e77975a93f4e Mon Sep 17 00:00:00 2001 From: jolheiser Date: Tue, 3 Dec 2019 12:51:56 -0600 Subject: [PATCH 3/9] Cannot push for another user unless admin Signed-off-by: jolheiser --- routers/private/serv.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/routers/private/serv.go b/routers/private/serv.go index 03cf5d395615..f19353dfda19 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -293,13 +293,13 @@ func ServCommand(ctx *macaron.Context) { }) return } - repo, err = pushCreateRepo(user, results.RepoName) + repo, err = pushCreateRepo(user, ownerName, results.RepoName) if err != nil { log.Error("pushCreateRepo: %v", err) ctx.JSON(http.StatusNotFound, map[string]interface{}{ "results": results, "type": "ErrRepoNotExist", - "err": fmt.Sprintf("Cannot find repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err), + "err": fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName), }) return } @@ -332,7 +332,11 @@ func ServCommand(ctx *macaron.Context) { // We will update the keys in a different call. } -func pushCreateRepo(user *models.User, repoName string) (*models.Repository, error) { +func pushCreateRepo(user *models.User, ownerName, repoName string) (*models.Repository, error) { + if !user.IsAdmin && user.Name != ownerName { + return nil, fmt.Errorf("cannot push-create repository for another user") + } + repo, err := models.CreateRepository(user, user, models.CreateRepoOptions{ Name: repoName, IsPrivate: true, From c4cb271a7adcd1c62ca5edc236584a321db4b56b Mon Sep 17 00:00:00 2001 From: jolheiser Date: Tue, 3 Dec 2019 12:59:54 -0600 Subject: [PATCH 4/9] Get owner in case admin pushes for another user Signed-off-by: jolheiser --- routers/private/serv.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/routers/private/serv.go b/routers/private/serv.go index f19353dfda19..079bcebffe2e 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -293,7 +293,18 @@ func ServCommand(ctx *macaron.Context) { }) return } - repo, err = pushCreateRepo(user, ownerName, results.RepoName) + + owner, err := models.GetUserByName(ownerName) + if err != nil { + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "results": results, + "type": "InternalServerError", + "err": fmt.Sprintf("Unable to get owner: %s %v", results.OwnerName, err), + }) + return + } + + repo, err = pushCreateRepo(user, owner, results.RepoName) if err != nil { log.Error("pushCreateRepo: %v", err) ctx.JSON(http.StatusNotFound, map[string]interface{}{ @@ -332,12 +343,12 @@ func ServCommand(ctx *macaron.Context) { // We will update the keys in a different call. } -func pushCreateRepo(user *models.User, ownerName, repoName string) (*models.Repository, error) { - if !user.IsAdmin && user.Name != ownerName { +func pushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repository, error) { + if !authUser.IsAdmin && authUser.ID != owner.ID { return nil, fmt.Errorf("cannot push-create repository for another user") } - repo, err := models.CreateRepository(user, user, models.CreateRepoOptions{ + repo, err := models.CreateRepository(authUser, owner, models.CreateRepoOptions{ Name: repoName, IsPrivate: true, }) @@ -346,7 +357,7 @@ func pushCreateRepo(user *models.User, ownerName, repoName string) (*models.Repo } if repo != nil { - if errDelete := models.DeleteRepository(user, user.ID, repo.ID); errDelete != nil { + if errDelete := models.DeleteRepository(authUser, owner.ID, repo.ID); errDelete != nil { log.Error("DeleteRepository: %v", errDelete) } } From 3d9fb5ec93842dab00d886715b557c2ac0f9fdb0 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Tue, 3 Dec 2019 14:05:44 -0600 Subject: [PATCH 5/9] Set new repo ID in result Signed-off-by: jolheiser --- routers/private/serv.go | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/private/serv.go b/routers/private/serv.go index 079bcebffe2e..9f0777c590e2 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -314,6 +314,7 @@ func ServCommand(ctx *macaron.Context) { }) return } + results.RepoID = repo.ID } // Finally if we're trying to touch the wiki we should init it From e69ee23bb57d12f2a0c44a65194d13e557f47e54 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 4 Dec 2019 10:14:27 -0600 Subject: [PATCH 6/9] Update to service and use new org perms Signed-off-by: jolheiser --- routers/private/serv.go | 26 +++++++++++++++----------- routers/repo/http.go | 22 ++++++++-------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/routers/private/serv.go b/routers/private/serv.go index 9f0777c590e2..2e6c0d5b93b7 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/setting" + repo_service "code.gitea.io/gitea/services/repository" "gitea.com/macaron/macaron" ) @@ -345,22 +346,25 @@ func ServCommand(ctx *macaron.Context) { } func pushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repository, error) { - if !authUser.IsAdmin && authUser.ID != owner.ID { - return nil, fmt.Errorf("cannot push-create repository for another user") + if !authUser.IsAdmin { + if owner.IsOrganization() { + if ok, err := owner.CanCreateOrgRepo(authUser.ID); err != nil { + return nil, err + } else if !ok { + return nil, fmt.Errorf("cannot push-create repository for 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{ + repo, err := repo_service.CreateRepository(authUser, owner, models.CreateRepoOptions{ Name: repoName, IsPrivate: true, }) - if err == nil { - return repo, nil + if err != nil { + return nil, err } - if repo != nil { - if errDelete := models.DeleteRepository(authUser, owner.ID, repo.ID); errDelete != nil { - log.Error("DeleteRepository: %v", errDelete) - } - } - return repo, err + return repo, nil } diff --git a/routers/repo/http.go b/routers/repo/http.go index a8c3be972256..3b2addeeacd8 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -28,6 +28,7 @@ import ( "code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + repo_service "code.gitea.io/gitea/services/repository" ) // HTTP implmentation git smart HTTP protocol @@ -354,32 +355,25 @@ func HTTP(ctx *context.Context) { func pushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repository, error) { if !authUser.IsAdmin { if owner.IsOrganization() { - team, err := owner.GetOwnerTeam() - if err != nil { + if ok, err := owner.CanCreateOrgRepo(authUser.ID); 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 !ok { + return nil, fmt.Errorf("cannot push-create repository for 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{ + repo, err := repo_service.CreateRepository(authUser, owner, models.CreateRepoOptions{ Name: repoName, IsPrivate: true, }) - if err == nil { - return repo, nil + if err != nil { + return nil, err } - if repo != nil { - if errDelete := models.DeleteRepository(authUser, owner.ID, repo.ID); errDelete != nil { - log.Error("DeleteRepository: %v", errDelete) - } - } - return repo, err + return repo, nil } type serviceConfig struct { From 00ab55dca1819979bfd62418941a288020c46864 Mon Sep 17 00:00:00 2001 From: jolheiser Date: Wed, 11 Dec 2019 12:02:09 -0600 Subject: [PATCH 7/9] Move pushCreateRepo to services Signed-off-by: jolheiser --- routers/private/serv.go | 26 +------------------------- routers/repo/http.go | 26 +------------------------- services/repository/repository.go | 26 ++++++++++++++++++++++++++ 3 files changed, 28 insertions(+), 50 deletions(-) diff --git a/routers/private/serv.go b/routers/private/serv.go index 2e6c0d5b93b7..2a143745d471 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -305,7 +305,7 @@ func ServCommand(ctx *macaron.Context) { return } - repo, err = pushCreateRepo(user, owner, results.RepoName) + repo, err = repo_service.PushCreateRepo(user, owner, results.RepoName) if err != nil { log.Error("pushCreateRepo: %v", err) ctx.JSON(http.StatusNotFound, map[string]interface{}{ @@ -344,27 +344,3 @@ func ServCommand(ctx *macaron.Context) { ctx.JSON(http.StatusOK, results) // We will update the keys in a different call. } - -func pushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repository, error) { - if !authUser.IsAdmin { - if owner.IsOrganization() { - if ok, err := owner.CanCreateOrgRepo(authUser.ID); err != nil { - return nil, err - } else if !ok { - return nil, fmt.Errorf("cannot push-create repository for org") - } - } else if authUser.ID != owner.ID { - return nil, fmt.Errorf("cannot push-create repository for another user") - } - } - - repo, err := repo_service.CreateRepository(authUser, owner, models.CreateRepoOptions{ - Name: repoName, - IsPrivate: true, - }) - if err != nil { - return nil, err - } - - return repo, nil -} diff --git a/routers/repo/http.go b/routers/repo/http.go index 3b2addeeacd8..b97feed51d90 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -290,7 +290,7 @@ func HTTP(ctx *context.Context) { ctx.HandleText(http.StatusForbidden, "Push to create is not enabled for users.") return } - repo, err = pushCreateRepo(authUser, owner, reponame) + repo, err = repo_service.PushCreateRepo(authUser, owner, reponame) if err != nil { log.Error("pushCreateRepo: %v", err) ctx.Status(http.StatusNotFound) @@ -352,30 +352,6 @@ 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() { - if ok, err := owner.CanCreateOrgRepo(authUser.ID); err != nil { - return nil, err - } else if !ok { - return nil, fmt.Errorf("cannot push-create repository for org") - } - } else if authUser.ID != owner.ID { - return nil, fmt.Errorf("cannot push-create repository for another user") - } - } - - repo, err := repo_service.CreateRepository(authUser, owner, models.CreateRepoOptions{ - Name: repoName, - IsPrivate: true, - }) - if err != nil { - return nil, err - } - - return repo, nil -} - type serviceConfig struct { UploadPack bool ReceivePack bool diff --git a/services/repository/repository.go b/services/repository/repository.go index 5135435c7816..33268338de10 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -8,6 +8,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" + "fmt" ) // CreateRepository creates a repository for the user/organization. @@ -54,3 +55,28 @@ func DeleteRepository(doer *models.User, repo *models.Repository) error { return nil } + +// PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace +func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repository, error) { + if !authUser.IsAdmin { + if owner.IsOrganization() { + if ok, err := owner.CanCreateOrgRepo(authUser.ID); err != nil { + return nil, err + } else if !ok { + return nil, fmt.Errorf("cannot push-create repository for org") + } + } else if authUser.ID != owner.ID { + return nil, fmt.Errorf("cannot push-create repository for another user") + } + } + + repo, err := CreateRepository(authUser, owner, models.CreateRepoOptions{ + Name: repoName, + IsPrivate: true, + }) + if err != nil { + return nil, err + } + + return repo, nil +} From 74b419676e45ecaabb27e32e35f805d3b9c04e9b Mon Sep 17 00:00:00 2001 From: jolheiser Date: Thu, 12 Dec 2019 08:29:28 -0600 Subject: [PATCH 8/9] Fix import order Signed-off-by: jolheiser --- services/repository/repository.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/repository/repository.go b/services/repository/repository.go index 33268338de10..2fb45bb6173d 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -5,10 +5,11 @@ package repository import ( + "fmt" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" - "fmt" ) // CreateRepository creates a repository for the user/organization. From 81935b9e836db14549685fa49c37b3a27e42979e Mon Sep 17 00:00:00 2001 From: jolheiser Date: Fri, 13 Dec 2019 08:40:57 -0600 Subject: [PATCH 9/9] Changes for @guillep2k * Check owner (not user) in SSH * Add basic tests for created repos (private, not empty) Signed-off-by: jolheiser --- integrations/git_test.go | 6 ++++++ routers/private/serv.go | 24 ++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/integrations/git_test.go b/integrations/git_test.go index 1f5807eaa3d0..7d37555f061c 100644 --- a/integrations/git_test.go +++ b/integrations/git_test.go @@ -458,5 +458,11 @@ func doPushCreate(ctx APITestContext, u *url.URL) func(t *testing.T) { setting.Repository.EnablePushCreateUser = true _, err = git.NewCommand("push", "origin", "master").RunInDir(tmpDir) assert.NoError(t, err) + + // Fetch repo from database + repo, err := models.GetRepositoryByOwnerAndName(ctx.Username, ctx.Reponame) + assert.NoError(t, err) + assert.False(t, repo.IsEmpty) + assert.True(t, repo.IsPrivate) } } diff --git a/routers/private/serv.go b/routers/private/serv.go index 2a143745d471..64fd671309ff 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -278,29 +278,29 @@ func ServCommand(ctx *macaron.Context) { // We already know we aren't using a deploy key if !repoExist { - if user.IsOrganization() && !setting.Repository.EnablePushCreateOrg { - ctx.JSON(http.StatusForbidden, map[string]interface{}{ + owner, err := models.GetUserByName(ownerName) + if err != nil { + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ "results": results, - "type": "ErrForbidden", - "err": "Push to create is not enabled for organizations.", + "type": "InternalServerError", + "err": fmt.Sprintf("Unable to get owner: %s %v", results.OwnerName, err), }) return } - if !user.IsOrganization() && !setting.Repository.EnablePushCreateUser { + + if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg { ctx.JSON(http.StatusForbidden, map[string]interface{}{ "results": results, "type": "ErrForbidden", - "err": "Push to create is not enabled for users.", + "err": "Push to create is not enabled for organizations.", }) return } - - owner, err := models.GetUserByName(ownerName) - if err != nil { - ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser { + ctx.JSON(http.StatusForbidden, map[string]interface{}{ "results": results, - "type": "InternalServerError", - "err": fmt.Sprintf("Unable to get owner: %s %v", results.OwnerName, err), + "type": "ErrForbidden", + "err": "Push to create is not enabled for users.", }) return }