Push to create repo (#8419)

* Refactor

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Add push-create to SSH serv

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Cannot push for another user unless admin

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Get owner in case admin pushes for another user

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Set new repo ID in result

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Update to service and use new org perms

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Move pushCreateRepo to services

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Fix import order

Signed-off-by: jolheiser <john.olheiser@gmail.com>

* Changes for @guillep2k

* Check owner (not user) in SSH
* Add basic tests for created repos (private, not empty)

Signed-off-by: jolheiser <john.olheiser@gmail.com>
This commit is contained in:
John Olheiser 2019-12-14 20:49:52 -06:00 committed by Lunny Xiao
parent 47c24be293
commit 6715677b2b
7 changed files with 219 additions and 51 deletions

View file

@ -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"
)
@ -98,44 +99,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
}
// 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 repoExist {
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
}
// 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 +162,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 +231,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 +241,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 +276,48 @@ func ServCommand(ctx *macaron.Context) {
}
}
// We already know we aren't using a deploy key
if !repoExist {
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
}
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 organizations.",
})
return
}
if !owner.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 = repo_service.PushCreateRepo(user, owner, 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", results.OwnerName, results.RepoName),
})
return
}
results.RepoID = repo.ID
}
// Finally if we're trying to touch the wiki we should init it
if results.IsWiki {
if err = repo.InitWiki(); err != nil {

View file

@ -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
@ -100,29 +101,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 +244,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 +267,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 +281,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 = repo_service.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{