Move serv hook functionality & drop GitLogger (#6993)

* Move hook functionality internally

* Internalise serv logic

* Remove old internal paths

* finally remove the gitlogger

* Disallow push on archived repositories

* fix lint error

* Update modules/private/key.go

* Update routers/private/hook.go

* Update routers/private/hook.go

* Update routers/private/hook.go

* Updated routers/private/serv.go

* Fix LFS Locks over SSH

* rev-list needs to be run by the hook process

* fixup

* Improve git test

* Ensure that the lfs files are created with a different prefix

* Reduce the replication in git_test.go

* slight refactor

* Remove unnecessary "/"

* Restore ensureAnonymousClone

* Restore ensureAnonymousClone

* Run rev-list on server side

* Try passing in the alternative directories instead

* Mark test as skipped

* Improve git test

* Ensure that the lfs files are created with a different prefix
* Reduce the replication in git_test.go
* Remove unnecessary "/"
release/v1.9
zeripath 5 years ago committed by Lunny Xiao
parent 8a343dda39
commit 356854fc5f

@ -8,15 +8,14 @@ import (
"bufio"
"bytes"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/util"
"github.com/urfave/cli"
)
@ -62,12 +61,10 @@ func runHookPreReceive(c *cli.Context) error {
setup("hooks/pre-receive.log")
// the environment setted on serv command
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
username := os.Getenv(models.EnvRepoUsername)
reponame := os.Getenv(models.EnvRepoName)
userIDStr := os.Getenv(models.EnvPusherID)
repoPath := models.RepoPath(username, reponame)
userID, _ := strconv.ParseInt(os.Getenv(models.EnvPusherID), 10, 64)
buf := bytes.NewBuffer(nil)
scanner := bufio.NewScanner(os.Stdin)
@ -91,35 +88,19 @@ func runHookPreReceive(c *cli.Context) error {
// If the ref is a branch, check if it's protected
if strings.HasPrefix(refFullName, git.BranchPrefix) {
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
protectBranch, err := private.GetProtectedBranchBy(repoID, branchName)
if err != nil {
fail("Internal error", fmt.Sprintf("retrieve protected branches information failed: %v", err))
}
if protectBranch != nil && protectBranch.IsProtected() {
// check and deletion
if newCommitID == git.EmptySHA {
fail(fmt.Sprintf("branch %s is protected from deletion", branchName), "")
}
// detect force push
if git.EmptySHA != oldCommitID {
output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDir(repoPath)
if err != nil {
fail("Internal error", "Fail to detect force push: %v", err)
} else if len(output) > 0 {
fail(fmt.Sprintf("branch %s is protected from force push", branchName), "")
}
}
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
canPush, err := private.CanUserPush(protectBranch.ID, userID)
if err != nil {
fail("Internal error", "Fail to detect user can push: %v", err)
} else if !canPush {
fail(fmt.Sprintf("protected branch %s can not be pushed to", branchName), "")
}
statusCode, msg := private.HookPreReceive(username, reponame, private.HookOptions{
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
RefFullName: refFullName,
UserID: userID,
GitAlternativeObjectDirectories: os.Getenv(private.GitAlternativeObjectDirectories),
GitObjectDirectory: os.Getenv(private.GitObjectDirectory),
})
switch statusCode {
case http.StatusInternalServerError:
fail("Internal Server Error", msg)
case http.StatusForbidden:
fail(msg, "")
}
}
}
@ -145,7 +126,6 @@ func runHookPostReceive(c *cli.Context) error {
setup("hooks/post-receive.log")
// the environment setted on serv command
repoID, _ := strconv.ParseInt(os.Getenv(models.ProtectedBranchRepoID), 10, 64)
repoUser := os.Getenv(models.EnvRepoUsername)
isWiki := (os.Getenv(models.EnvRepoIsWiki) == "true")
repoName := os.Getenv(models.EnvRepoName)
@ -172,64 +152,31 @@ func runHookPostReceive(c *cli.Context) error {
newCommitID := string(fields[1])
refFullName := string(fields[2])
// Only trigger activity updates for changes to branches or
// tags. Updates to other refs (eg, refs/notes, refs/changes,
// or other less-standard refs spaces are ignored since there
// may be a very large number of them).
if strings.HasPrefix(refFullName, git.BranchPrefix) || strings.HasPrefix(refFullName, git.TagPrefix) {
if err := private.PushUpdate(models.PushUpdateOptions{
RefFullName: refFullName,
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
PusherID: pusherID,
PusherName: pusherName,
RepoUserName: repoUser,
RepoName: repoName,
}); err != nil {
log.GitLogger.Error("Update: %v", err)
}
}
if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
branch := strings.TrimPrefix(refFullName, git.BranchPrefix)
repo, pullRequestAllowed, err := private.GetRepository(repoID)
if err != nil {
log.GitLogger.Error("get repo: %v", err)
break
}
if !pullRequestAllowed {
break
}
baseRepo := repo
if repo.IsFork {
baseRepo = repo.BaseRepo
}
if !repo.IsFork && branch == baseRepo.DefaultBranch {
break
}
res, err := private.HookPostReceive(repoUser, repoName, private.HookOptions{
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
RefFullName: refFullName,
UserID: pusherID,
UserName: pusherName,
})
pr, err := private.ActivePullRequest(baseRepo.ID, repo.ID, baseRepo.DefaultBranch, branch)
if err != nil {
log.GitLogger.Error("get active pr: %v", err)
break
}
if res == nil {
fail("Internal Server Error", err)
}
fmt.Fprintln(os.Stderr, "")
if pr == nil {
if repo.IsFork {
branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
}
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", branch)
fmt.Fprintf(os.Stderr, " %s/compare/%s...%s\n", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch))
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s/pulls/%d\n", baseRepo.HTMLURL(), pr.Index)
}
fmt.Fprintln(os.Stderr, "")
if res["message"] == false {
continue
}
fmt.Fprintln(os.Stderr, "")
if res["create"] == true {
fmt.Fprintf(os.Stderr, "Create a new pull request for '%s':\n", res["branch"])
fmt.Fprintf(os.Stderr, " %s\n", res["url"])
} else {
fmt.Fprint(os.Stderr, "Visit the existing pull request:\n")
fmt.Fprintf(os.Stderr, " %s\n", res["url"])
}
fmt.Fprintln(os.Stderr, "")
}
return nil

@ -8,9 +8,11 @@ package cmd
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
@ -68,7 +70,6 @@ func setup(logPath string) {
log.DelLogger("console")
setting.NewContext()
checkLFSVersion()
log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
}
func parseCmd(cmd string) (string, string) {
@ -95,15 +96,14 @@ func fail(userMessage, logMessage string, args ...interface{}) {
if !setting.ProdMode {
fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
}
log.GitLogger.Fatal(logMessage, args...)
return
}
log.GitLogger.Close()
os.Exit(1)
}
func runServ(c *cli.Context) error {
// FIXME: This needs to internationalised
setup("serv.log")
if setting.SSH.Disabled {
@ -116,9 +116,23 @@ func runServ(c *cli.Context) error {
return nil
}
keys := strings.Split(c.Args()[0], "-")
if len(keys) != 2 || keys[0] != "key" {
fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
}
keyID := com.StrTo(keys[1]).MustInt64()
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
if len(cmd) == 0 {
println("Hi there, You've successfully authenticated, but Gitea does not provide shell access.")
key, user, err := private.ServNoCommand(keyID)
if err != nil {
fail("Internal error", "Failed to check provided key: %v", err)
}
if key.Type == models.KeyTypeDeploy {
println("Hi there! You've successfully authenticated with the deploy key named " + key.Name + ", but Gitea does not provide shell access.")
} else {
println("Hi there: " + user.Name + "! You've successfully authenticated with the key named " + key.Name + ", but Gitea does not provide shell access.")
}
println("If this is unexpected, please log in with password and setup Gitea under another user.")
return nil
}
@ -152,41 +166,19 @@ func runServ(c *cli.Context) error {
fail("Error while trying to create PPROF_DATA_PATH", "Error while trying to create PPROF_DATA_PATH: %v", err)
}
stopCPUProfiler := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username)
stopCPUProfiler, err := pprof.DumpCPUProfileForUsername(setting.PprofDataPath, username)
if err != nil {
fail("Internal Server Error", "Unable to start CPU profile: %v", err)
}
defer func() {
stopCPUProfiler()
pprof.DumpMemProfileForUsername(setting.PprofDataPath, username)
err := pprof.DumpMemProfileForUsername(setting.PprofDataPath, username)
if err != nil {
fail("Internal Server Error", "Unable to dump Mem Profile: %v", err)
}
}()
}
var (
isWiki bool
unitType = models.UnitTypeCode
unitName = "code"
)
if strings.HasSuffix(reponame, ".wiki") {
isWiki = true
unitType = models.UnitTypeWiki
unitName = "wiki"
reponame = reponame[:len(reponame)-5]
}
os.Setenv(models.EnvRepoUsername, username)
if isWiki {
os.Setenv(models.EnvRepoIsWiki, "true")
} else {
os.Setenv(models.EnvRepoIsWiki, "false")
}
os.Setenv(models.EnvRepoName, reponame)
repo, err := private.GetRepositoryByOwnerAndName(username, reponame)
if err != nil {
if strings.Contains(err.Error(), "Failed to get repository: repository does not exist") {
fail(accessDenied, "Repository does not exist: %s/%s", username, reponame)
}
fail("Internal error", "Failed to get repository: %v", err)
}
requestedMode, has := allowedCommands[verb]
if !has {
fail("Unknown git command", "Unknown git command %s", verb)
@ -202,97 +194,36 @@ func runServ(c *cli.Context) error {
}
}
// Prohibit push to mirror repositories.
if requestedMode > models.AccessModeRead && repo.IsMirror {
fail("mirror repository is read-only", "")
}
// Allow anonymous clone for public repositories.
var (
keyID int64
user *models.User
)
if requestedMode == models.AccessModeWrite || repo.IsPrivate || setting.Service.RequireSignInView {
keys := strings.Split(c.Args()[0], "-")
if len(keys) != 2 {
fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
}
key, err := private.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64())
if err != nil {
fail("Invalid key ID", "Invalid key ID[%s]: %v", c.Args()[0], err)
}
keyID = key.ID
// Check deploy key or user key.
if key.Type == models.KeyTypeDeploy {
// Now we have to get the deploy key for this repo
deployKey, err := private.GetDeployKey(key.ID, repo.ID)
if err != nil {
fail("Key access denied", "Failed to access internal api: [key_id: %d, repo_id: %d]", key.ID, repo.ID)
}
if deployKey == nil {
fail("Key access denied", "Deploy key access denied: [key_id: %d, repo_id: %d]", key.ID, repo.ID)
}
if deployKey.Mode < requestedMode {
fail("Key permission denied", "Cannot push with read-only deployment key: %d to repo_id: %d", key.ID, repo.ID)
}
// Update deploy key activity.
if err = private.UpdateDeployKeyUpdated(key.ID, repo.ID); err != nil {
fail("Internal error", "UpdateDeployKey: %v", err)
}
// FIXME: Deploy keys aren't really the owner of the repo pushing changes
// however we don't have good way of representing deploy keys in hook.go
// so for now use the owner
os.Setenv(models.EnvPusherName, username)
os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", repo.OwnerID))
} else {
user, err = private.GetUserByKeyID(key.ID)
if err != nil {
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
}
if !user.IsActive || user.ProhibitLogin {
fail("Your account is not active or has been disabled by Administrator",
"User %s is disabled and have no access to repository %s",
user.Name, repoPath)
}
mode, err := private.CheckUnitUser(user.ID, repo.ID, user.IsAdmin, unitType)
if err != nil {
fail("Internal error", "Failed to check access: %v", err)
} else if *mode < requestedMode {
clientMessage := accessDenied
if *mode >= models.AccessModeRead {
clientMessage = "You do not have sufficient authorization for this action"
}
fail(clientMessage,
"User %s does not have level %v access to repository %s's "+unitName,
user.Name, requestedMode, repoPath)
results, err := private.ServCommand(keyID, username, reponame, requestedMode, verb, lfsVerb)
if err != nil {
if private.IsErrServCommand(err) {
errServCommand := err.(private.ErrServCommand)
if errServCommand.StatusCode != http.StatusInternalServerError {
fail("Unauthorized", errServCommand.Error())
} else {
fail("Internal Server Error", errServCommand.Error())
}
os.Setenv(models.EnvPusherName, user.Name)
os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID))
}
fail("Internal Server Error", err.Error())
}
os.Setenv(models.EnvRepoIsWiki, strconv.FormatBool(results.IsWiki))
os.Setenv(models.EnvRepoName, results.RepoName)
os.Setenv(models.EnvRepoUsername, results.OwnerName)
os.Setenv(models.EnvPusherName, username)
os.Setenv(models.EnvPusherID, strconv.FormatInt(results.UserID, 10))
os.Setenv(models.ProtectedBranchRepoID, strconv.FormatInt(results.RepoID, 10))
//LFS token authentication
if verb == lfsAuthenticateVerb {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, username, repo.Name)
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
now := time.Now()
claims := jwt.MapClaims{
"repo": repo.ID,
"repo": results.RepoID,
"op": lfsVerb,
"exp": now.Add(setting.LFS.HTTPAuthExpiry).Unix(),
"nbf": now.Unix(),
}
if user != nil {
claims["user"] = user.ID
"user": results.UserID,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
@ -313,7 +244,6 @@ func runServ(c *cli.Context) error {
if err != nil {
fail("Internal error", "Failed to encode LFS json response: %v", err)
}
return nil
}
@ -329,13 +259,8 @@ func runServ(c *cli.Context) error {
} else {
gitcmd = exec.Command(verb, repoPath)
}
if isWiki {
if err = private.InitWiki(repo.ID); err != nil {
fail("Internal error", "Failed to init wiki repo: %v", err)
}
}
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", repo.ID))
os.Setenv(models.ProtectedBranchRepoID, fmt.Sprintf("%d", results.RepoID))
gitcmd.Dir = setting.RepoRootPath
gitcmd.Stdout = os.Stdout
@ -346,9 +271,9 @@ func runServ(c *cli.Context) error {
}
// Update user key activity.
if keyID > 0 {
if err = private.UpdatePublicKeyUpdated(keyID); err != nil {
fail("Internal error", "UpdatePublicKey: %v", err)
if results.KeyID > 0 {
if err = private.UpdatePublicKeyInRepo(results.KeyID, results.RepoID); err != nil {
fail("Internal error", "UpdatePublicKeyInRepo: %v", err)
}
}

@ -27,7 +27,6 @@ log groups:
* The Router logger
* The Access logger
* The XORM logger
* A logger called the `GitLogger` which is used during hooks.
There is also the go log logger.
@ -180,21 +179,6 @@ which will not be inherited from the `[log]` or relevant
* `EXPRESSION` will default to `""`
* `PREFIX` will default to `""`
### The Hook and Serv "GitLoggers"
These are less well defined loggers. Essentially these should only be
used within Gitea's subsystems and cannot be configured at present.
They will write log files in:
* `%(ROOT_PATH)/hooks/pre-receive.log`
* `%(ROOT_PATH)/hooks/update.log`
* `%(ROOT_PATH)/hooks/post-receive.log`
* `%(ROOT_PATH)/serv.log`
* `%(ROOT_PATH)/http.log`
In the future these logs may be rationalised.
## Log outputs
Gitea provides 4 possible log outputs:

@ -1,44 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package integrations
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
func assertProtectedBranch(t *testing.T, repoID int64, branchName string, isErr, canPush bool) {
reqURL := fmt.Sprintf("/api/internal/branch/%d/%s", repoID, util.PathEscapeSegments(branchName))
req := NewRequest(t, "GET", reqURL)
t.Log(reqURL)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", setting.InternalToken))
resp := MakeRequest(t, req, NoExpectedStatus)
if isErr {
assert.EqualValues(t, http.StatusInternalServerError, resp.Code)
} else {
assert.EqualValues(t, http.StatusOK, resp.Code)
var branch models.ProtectedBranch
t.Log(resp.Body.String())
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), &branch))
assert.Equal(t, canPush, !branch.IsProtected())
}
}
func TestInternal_GetProtectedBranch(t *testing.T) {
prepareTestEnv(t)
assertProtectedBranch(t, 1, "master", false, true)
assertProtectedBranch(t, 1, "dev", false, true)
assertProtectedBranch(t, 1, "lunny/dev", false, true)
}

@ -27,7 +27,7 @@ func PushingEnvironment(doer *User, repo *Repository) []string {
"GIT_COMMITTER_NAME="+sig.Name,
"GIT_COMMITTER_EMAIL="+sig.Email,
EnvRepoName+"="+repo.Name,
EnvRepoUsername+"="+repo.OwnerName,
EnvRepoUsername+"="+repo.MustOwnerName(),
EnvRepoIsWiki+"="+isWiki,
EnvPusherName+"="+doer.Name,
EnvPusherID+"="+fmt.Sprintf("%d", doer.ID),

@ -5,9 +5,7 @@
package log
import (
"fmt"
"os"
"path"
"runtime"
"strings"
)
@ -17,9 +15,7 @@ var (
DEFAULT = "default"
// NamedLoggers map of named loggers
NamedLoggers = make(map[string]*Logger)
// GitLogger logger for git
GitLogger *Logger
prefix string
prefix string
)
// NewLogger create a logger for the default logger
@ -72,19 +68,6 @@ func GetLogger(name string) *Logger {
return NamedLoggers[DEFAULT]
}
// NewGitLogger create a logger for git
// FIXME: use same log level as other loggers.
func NewGitLogger(logPath string) {
path := path.Dir(logPath)
if err := os.MkdirAll(path, os.ModePerm); err != nil {
Fatal("Failed to create dir %s: %v", path, err)
}
GitLogger = newLogger("git", 0)
GitLogger.SetLogger("file", "file", fmt.Sprintf(`{"level":"TRACE","filename":"%s","rotate":true,"maxsize":%d,"daily":true,"maxdays":7,"compress":true,"compressionLevel":-1, "stacktraceLevel":"NONE"}`, logPath, 1<<28))
}
// GetLevel returns the minimum logger level
func GetLevel() Level {
return NamedLoggers[DEFAULT].GetLevel()

@ -9,34 +9,30 @@ import (
"io/ioutil"
"runtime"
"runtime/pprof"
"code.gitea.io/gitea/modules/log"
)
// DumpMemProfileForUsername dumps a memory profile at pprofDataPath as memprofile_<username>_<temporary id>
func DumpMemProfileForUsername(pprofDataPath, username string) {
func DumpMemProfileForUsername(pprofDataPath, username string) error {
f, err := ioutil.TempFile(pprofDataPath, fmt.Sprintf("memprofile_%s_", username))
if err != nil {
log.GitLogger.Fatal("Could not create memory profile: %v", err)
return err
}
defer f.Close()
runtime.GC() // get up-to-date statistics
if err := pprof.WriteHeapProfile(f); err != nil {
log.GitLogger.Fatal("Could not write memory profile: %v", err)
}
return pprof.WriteHeapProfile(f)
}
// DumpCPUProfileForUsername dumps a CPU profile at pprofDataPath as cpuprofile_<username>_<temporary id>
// it returns the stop function which stops, writes and closes the CPU profile file
func DumpCPUProfileForUsername(pprofDataPath, username string) func() {
func DumpCPUProfileForUsername(pprofDataPath, username string) (func(), error) {
f, err := ioutil.TempFile(pprofDataPath, fmt.Sprintf("cpuprofile_%s_", username))
if err != nil {
log.GitLogger.Fatal("Could not create cpu profile: %v", err)
return nil, err
}
pprof.StartCPUProfile(f)
return func() {
pprof.StopCPUProfile()
f.Close()
}
}, nil
}

@ -1,67 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package private
import (
"encoding/json"
"fmt"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// GetProtectedBranchBy get protected branch information
func GetProtectedBranchBy(repoID int64, branchName string) (*models.ProtectedBranch, error) {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/branch/%d/%s", repoID, util.PathEscapeSegments(branchName))
log.GitLogger.Trace("GetProtectedBranchBy: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
var branch models.ProtectedBranch
if err := json.NewDecoder(resp.Body).Decode(&branch); err != nil {
return nil, err
}
defer resp.Body.Close()
// All 2XX status codes are accepted and others will return an error
if resp.StatusCode/100 != 2 {
return nil, fmt.Errorf("Failed to get protected branch: %s", decodeJSONError(resp).Err)
}
return &branch, nil
}
// CanUserPush returns if user can push
func CanUserPush(protectedBranchID, userID int64) (bool, error) {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/protectedbranch/%d/%d", protectedBranchID, userID)
log.GitLogger.Trace("CanUserPush: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return false, err
}
var canPush = make(map[string]interface{})
if err := json.NewDecoder(resp.Body).Decode(&canPush); err != nil {
return false, err
}
defer resp.Body.Close()
// All 2XX status codes are accepted and others will return an error
if resp.StatusCode/100 != 2 {
return false, fmt.Errorf("Failed to retrieve push user: %s", decodeJSONError(resp).Err)
}
return canPush["can_push"].(bool), nil
}

@ -0,0 +1,84 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package private
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"code.gitea.io/gitea/modules/setting"
)
// Git environment variables
const (
GitAlternativeObjectDirectories = "GIT_ALTERNATE_OBJECT_DIRECTORIES"
GitObjectDirectory = "GIT_OBJECT_DIRECTORY"
GitQuarantinePath = "GIT_QUARANTINE_PATH"
)
// HookOptions represents the options for the Hook calls
type HookOptions struct {
OldCommitID string
NewCommitID string
RefFullName string
UserID int64
UserName string
GitObjectDirectory string
GitAlternativeObjectDirectories string
}
// HookPreReceive check whether the provided commits are allowed
func HookPreReceive(ownerName, repoName string, opts HookOptions) (int, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/pre-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&gitObjectDirectory=%s&gitAlternativeObjectDirectories=%s",
url.PathEscape(ownerName),
url.PathEscape(repoName),
url.QueryEscape(opts.OldCommitID),
url.QueryEscape(opts.NewCommitID),
url.QueryEscape(opts.RefFullName),
opts.UserID,
url.QueryEscape(opts.GitObjectDirectory),
url.QueryEscape(opts.GitAlternativeObjectDirectories),
)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return http.StatusInternalServerError, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return resp.StatusCode, decodeJSONError(resp).Err
}
return http.StatusOK, ""
}
// HookPostReceive updates services and users
func HookPostReceive(ownerName, repoName string, opts HookOptions) (map[string]interface{}, string) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/hook/post-receive/%s/%s?old=%s&new=%s&ref=%s&userID=%d&username=%s",
url.PathEscape(ownerName),
url.PathEscape(repoName),
url.QueryEscape(opts.OldCommitID),
url.QueryEscape(opts.NewCommitID),
url.QueryEscape(opts.RefFullName),
opts.UserID,
url.QueryEscape(opts.UserName))
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, fmt.Sprintf("Unable to contact gitea: %v", err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, decodeJSONError(resp).Err
}
res := map[string]interface{}{}
_ = json.NewDecoder(resp.Body).Decode(&res)
return res, ""
}

@ -10,11 +10,8 @@ import (
"fmt"
"net"
"net/http"
"net/url"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
@ -51,49 +48,3 @@ func newInternalRequest(url, method string) *httplib.Request {
}
return req
}
// CheckUnitUser check whether user could visit the unit of this repository
func CheckUnitUser(userID, repoID int64, isAdmin bool, unitType models.UnitType) (*models.AccessMode, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/user/%d/checkunituser?isAdmin=%t&unitType=%d", repoID, userID, isAdmin, unitType)
log.GitLogger.Trace("CheckUnitUser: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Failed to CheckUnitUser: %s", decodeJSONError(resp).Err)
}
var a models.AccessMode
if err := json.NewDecoder(resp.Body).Decode(&a); err != nil {
return nil, err
}
return &a, nil
}
// GetRepositoryByOwnerAndName returns the repository by given ownername and reponame.
func GetRepositoryByOwnerAndName(ownerName, repoName string) (*models.Repository, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repo/%s/%s", url.PathEscape(ownerName), url.PathEscape(repoName))
log.GitLogger.Trace("GetRepositoryByOwnerAndName: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Failed to get repository: %s", decodeJSONError(resp).Err)
}
var repo models.Repository
if err := json.NewDecoder(resp.Body).Decode(&repo); err != nil {
return nil, err
}
return &repo, nil
}

@ -5,127 +5,15 @@
package private
import (
"encoding/json"
"fmt"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// UpdateDeployKeyUpdated update deploy key updates
func UpdateDeployKeyUpdated(keyID int64, repoID int64) error {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/keys/%d/update", repoID, keyID)
log.GitLogger.Trace("UpdateDeployKeyUpdated: %s", reqURL)
resp, err := newInternalRequest(reqURL, "POST").Response()
if err != nil {
return err
}
defer resp.Body.Close()
// All 2XX status codes are accepted and others will return an error
if resp.StatusCode/100 != 2 {
return fmt.Errorf("Failed to update deploy key: %s", decodeJSONError(resp).Err)
}
return nil
}
// GetDeployKey check if repo has deploy key
func GetDeployKey(keyID, repoID int64) (*models.DeployKey, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/keys/%d", repoID, keyID)
log.GitLogger.Trace("GetDeployKey: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
defer resp.Body.Close()
switch resp.StatusCode {
case 404:
return nil, nil
case 200:
var dKey models.DeployKey
if err := json.NewDecoder(resp.Body).Decode(&dKey); err != nil {
return nil, err
}
return &dKey, nil
default:
return nil, fmt.Errorf("Failed to get deploy key: %s", decodeJSONError(resp).Err)
}
}
// HasDeployKey check if repo has deploy key
func HasDeployKey(keyID, repoID int64) (bool, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/has-keys/%d", repoID, keyID)
log.GitLogger.Trace("HasDeployKey: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
return true, nil
}
return false, nil
}
// GetPublicKeyByID get public ssh key by his ID
func GetPublicKeyByID(keyID int64) (*models.PublicKey, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d", keyID)
log.GitLogger.Trace("GetPublicKeyByID: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Failed to get repository: %s", decodeJSONError(resp).Err)
}
var pKey models.PublicKey
if err := json.NewDecoder(resp.Body).Decode(&pKey); err != nil {
return nil, err
}
return &pKey, nil
}
// GetUserByKeyID get user attached to key
func GetUserByKeyID(keyID int64) (*models.User, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/user", keyID)
log.GitLogger.Trace("GetUserByKeyID: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("Failed to get user: %s", decodeJSONError(resp).Err)
}
var user models.User
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, err
}
return &user, nil
}
// UpdatePublicKeyUpdated update public key updates
func UpdatePublicKeyUpdated(keyID int64) error {
// UpdatePublicKeyInRepo update public key and if necessary deploy key updates
func UpdatePublicKeyInRepo(keyID, repoID int64) error {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update", keyID)
log.GitLogger.Trace("UpdatePublicKeyUpdated: %s", reqURL)
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/ssh/%d/update/%d", keyID, repoID)
resp, err := newInternalRequest(reqURL, "POST").Response()
if err != nil {
return err

@ -1,40 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package private
import (
"encoding/json"
"fmt"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// PushUpdate update publick key updates
func PushUpdate(opt models.PushUpdateOptions) error {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + "api/internal/push/update"
log.GitLogger.Trace("PushUpdate: %s", reqURL)
body, err := json.Marshal(&opt)
if err != nil {
return err
}
resp, err := newInternalRequest(reqURL, "POST").Body(body).Response()
if err != nil {
return err
}
defer resp.Body.Close()
// All 2XX status codes are accepted and others will return an error
if resp.StatusCode/100 != 2 {
return fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err)
}
return nil
}

@ -1,68 +0,0 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package private
import (
"encoding/json"
"fmt"
"net/url"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// GetRepository return the repository by its ID and a bool about if it's allowed to have PR
func GetRepository(repoID int64) (*models.Repository, bool, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repository/%d", repoID)
log.GitLogger.Trace("GetRepository: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, false, err
}
var repoInfo struct {
Repository *models.Repository
AllowPullRequest bool
}
if err := json.NewDecoder(resp.Body).Decode(&repoInfo); err != nil {
return nil, false, err
}
defer resp.Body.Close()
// All 2XX status codes are accepted and others will return an error
if resp.StatusCode/100 != 2 {
return nil, false, fmt.Errorf("failed to retrieve repository: %s", decodeJSONError(resp).Err)
}
return repoInfo.Repository, repoInfo.AllowPullRequest, nil
}
// ActivePullRequest returns an active pull request if it exists
func ActivePullRequest(baseRepoID int64, headRepoID int64, baseBranch, headBranch string) (*models.PullRequest, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/active-pull-request?baseRepoID=%d&headRepoID=%d&baseBranch=%s&headBranch=%s", baseRepoID, headRepoID, url.QueryEscape(baseBranch), url.QueryEscape(headBranch))
log.GitLogger.Trace("ActivePullRequest: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
var pr *models.PullRequest
if err := json.NewDecoder(resp.Body).Decode(&pr); err != nil {
return nil, err
}
defer resp.Body.Close()
// All 2XX status codes are accepted and others will return an error
if resp.StatusCode/100 != 2 {
return nil, fmt.Errorf("failed to retrieve pull request: %s", decodeJSONError(resp).Err)
}
return pr, nil
}

@ -0,0 +1,106 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package private
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
)
// KeyAndOwner is the response from ServNoCommand
type KeyAndOwner struct {
Key *models.PublicKey `json:"key"`
Owner *models.User `json:"user"`
}
// ServNoCommand returns information about the provided key
func ServNoCommand(keyID int64) (*models.PublicKey, *models.User, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/none/%d",
keyID)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, nil, fmt.Errorf("%s", decodeJSONError(resp).Err)
}
var keyAndOwner KeyAndOwner
if err := json.NewDecoder(resp.Body).Decode(&keyAndOwner); err != nil {
return nil, nil, err
}
return keyAndOwner.Key, keyAndOwner.Owner, nil
}
// ServCommandResults are the results of a call to the private route serv
type ServCommandResults struct {
IsWiki bool
IsDeployKey bool
KeyID int64
KeyName string
UserName string
UserID int64
OwnerName string
RepoName string
RepoID int64
}
// ErrServCommand is an error returned from ServCommmand.
type ErrServCommand struct {
Results ServCommandResults
Type string
Err string
StatusCode int
}
func (err ErrServCommand) Error() string {
return err.Err
}
// IsErrServCommand checks if an error is a ErrServCommand.
func IsErrServCommand(err error) bool {
_, ok := err.(ErrServCommand)
return ok
}
// ServCommand preps for a serv call
func ServCommand(keyID int64, ownerName, repoName string, mode models.AccessMode, verbs ...string) (*ServCommandResults, error) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
keyID,
url.PathEscape(ownerName),
url.PathEscape(repoName),
mode)
for _, verb := range verbs {
if verb != "" {
reqURL += fmt.Sprintf("&verb=%s", url.QueryEscape(verb))
}
}
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
var errServCommand ErrServCommand
if err := json.NewDecoder(resp.Body).Decode(&errServCommand); err != nil {
return nil, err
}
errServCommand.StatusCode = resp.StatusCode
return nil, errServCommand
}
var results ServCommandResults
if err := json.NewDecoder(resp.Body).Decode(&results); err != nil {
return nil, err
}
return &results, nil
}

@ -1,33 +0,0 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package private
import (
"fmt"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
// InitWiki initwiki via repo id
func InitWiki(repoID int64) error {
// Ask for running deliver hook and test pull request tasks.
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/wiki/init", repoID)
log.GitLogger.Trace("InitWiki: %s", reqURL)
resp, err := newInternalRequest(reqURL, "GET").Response()
if err != nil {
return err
}
defer resp.Body.Close()
// All 2XX status codes are accepted and others will return an error
if resp.StatusCode/100 != 2 {
return fmt.Errorf("Failed to init wiki: %s", decodeJSONError(resp).Err)
}
return nil
}

@ -5,7 +5,6 @@
package routers
import (
"path"
"strings"
"time"
@ -99,7 +98,6 @@ func GlobalInit() {
models.InitSyncMirrors()
models.InitDeliverHooks()
models.InitTestPullRequests()
log.NewGitLogger(path.Join(setting.LogRootPath, "http.log"))
}
if models.EnableSQLite3 {
log.Info("SQLite3 Supported")

@ -1,52 +0,0 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package private
import (
"code.gitea.io/gitea/models"
macaron "gopkg.in/macaron.v1"
)
// GetProtectedBranchBy get protected branch information
func GetProtectedBranchBy(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":id")
branchName := ctx.Params("*")
protectBranch, err := models.GetProtectedBranchBy(repoID, branchName)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
} else if protectBranch != nil {
ctx.JSON(200, protectBranch)
} else {
ctx.JSON(200, &models.ProtectedBranch{
ID: 0,
})
}
}
// CanUserPush returns if user push
func CanUserPush(ctx *macaron.Context) {
pbID := ctx.ParamsInt64(":pbid")
userID := ctx.ParamsInt64(":userid")
protectBranch, err := models.GetProtectedBranchByID(pbID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
} else if protectBranch != nil {
ctx.JSON(200, map[string]interface{}{
"can_push": protectBranch.CanUserPush(userID),
})
} else {
ctx.JSON(200, map[string]interface{}{
"can_push": false,
})
}
}

@ -0,0 +1,209 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
// Package private includes all internal routes. The package name internal is ideal but Golang is not allowed, so we use private as package name instead.
package private
import (
"fmt"
"net/http"
"os"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/util"
macaron "gopkg.in/macaron.v1"
)
// HookPreReceive checks whether a individual commit is acceptable
func HookPreReceive(ctx *macaron.Context) {
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
oldCommitID := ctx.QueryTrim("old")
newCommitID := ctx.QueryTrim("new")
refFullName := ctx.QueryTrim("ref")
userID := ctx.QueryInt64("userID")
gitObjectDirectory := ctx.QueryTrim("gitObjectDirectory")
gitAlternativeObjectDirectories := ctx.QueryTrim("gitAlternativeObjectDirectories")
branchName := strings.TrimPrefix(refFullName, git.BranchPrefix)
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err != nil {
log.Error("Unable to get repository: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
repo.OwnerName = ownerName
protectBranch, err := models.GetProtectedBranchBy(repo.ID, branchName)
if err != nil {
log.Error("Unable to get protected branch: %s in %-v Error: %v", branchName, repo, err)
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
if protectBranch != nil && protectBranch.IsProtected() {
// check and deletion
if newCommitID == git.EmptySHA {
log.Warn("Forbidden: Branch: %s in %-v is protected from deletion", branchName, repo)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("branch %s is protected from deletion", branchName),
})
return
}
// detect force push
if git.EmptySHA != oldCommitID {
env := append(os.Environ(),
private.GitAlternativeObjectDirectories+"="+gitAlternativeObjectDirectories,
private.GitObjectDirectory+"="+gitObjectDirectory,
private.GitQuarantinePath+"="+gitObjectDirectory,
)
output, err := git.NewCommand("rev-list", "--max-count=1", oldCommitID, "^"+newCommitID).RunInDirWithEnv(repo.RepoPath(), env)
if err != nil {
log.Error("Unable to detect force push between: %s and %s in %-v Error: %v", oldCommitID, newCommitID, repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Fail to detect force push: %v", err),
})
return
} else if len(output) > 0 {
log.Warn("Forbidden: Branch: %s in %-v is protected from force push", branchName, repo)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("branch %s is protected from force push", branchName),
})
return
}
}
if !protectBranch.CanUserPush(userID) {
log.Warn("Forbidden: User %d cannot push to protected branch: %s in %-v", userID, branchName, repo)
ctx.JSON(http.StatusForbidden, map[string]interface{}{
"err": fmt.Sprintf("protected branch %s can not be pushed to", branchName),
})
return
}
}
ctx.PlainText(http.StatusOK, []byte("ok"))
}
// HookPostReceive updates services and users
func HookPostReceive(ctx *macaron.Context) {
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
oldCommitID := ctx.Query("old")
newCommitID := ctx.Query("new")
refFullName := ctx.Query("ref")
userID := ctx.QueryInt64("userID")
userName := ctx.Query("username")
branch := refFullName
if strings.HasPrefix(refFullName, git.BranchPrefix) {
branch = strings.TrimPrefix(refFullName, git.BranchPrefix)
} else if strings.HasPrefix(refFullName, git.TagPrefix) {
branch =