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 "/"
This commit is contained in:
zeripath 2019-06-01 16:00:21 +01:00 committed by Lunny Xiao
parent 8a343dda39
commit 356854fc5f
25 changed files with 806 additions and 982 deletions

View file

@ -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)
}
res, err := private.HookPostReceive(repoUser, repoName, private.HookOptions{
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
RefFullName: refFullName,
UserID: pusherID,
UserName: pusherName,
})
if res == nil {
fail("Internal Server Error", 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
}
pr, err := private.ActivePullRequest(baseRepo.ID, repo.ID, baseRepo.DefaultBranch, branch)
if err != nil {
log.GitLogger.Error("get active pr: %v", err)
break
}
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

View file

@ -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)
}
os.Setenv(models.EnvPusherName, user.Name)
os.Setenv(models.EnvPusherID, fmt.Sprintf("%d", user.ID))
}
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())
}
}
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)
}
}

View file

@ -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:

View file

@ -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)
}

View file

@ -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),

View file

@ -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()

View file

@ -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
}

View file

@ -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
}

84
modules/private/hook.go Normal file
View file

@ -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, ""
}

View file

@ -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
}

View file

@ -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

View file

@ -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
}

View file

@ -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
}

106
modules/private/serv.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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")

View file

@ -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,
})
}
}

209
routers/private/hook.go Normal file
View file

@ -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 = strings.TrimPrefix(refFullName, git.TagPrefix)
}
// 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 := models.PushUpdate(branch, models.PushUpdateOptions{
RefFullName: refFullName,
OldCommitID: oldCommitID,
NewCommitID: newCommitID,
PusherID: userID,
PusherName: userName,
RepoUserName: ownerName,
RepoName: repoName,
}); err != nil {
log.Error("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Failed to Update: %s/%s Branch: %s Error: %v", ownerName, repoName, branch, err),
})
return
}
}
if newCommitID != git.EmptySHA && strings.HasPrefix(refFullName, git.BranchPrefix) {
repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
if err != nil {
log.Error("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Failed to get repository: %s/%s Error: %v", ownerName, repoName, err),
})
return
}
repo.OwnerName = ownerName
pullRequestAllowed := repo.AllowsPulls()
if !pullRequestAllowed {
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": false,
})
return
}
baseRepo := repo
if repo.IsFork {
if err := repo.GetBaseRepo(); err != nil {
log.Error("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf("Failed to get Base Repository of Forked repository: %-v Error: %v", repo, err),
})
return
}
baseRepo = repo.BaseRepo
}
if !repo.IsFork && branch == baseRepo.DefaultBranch {
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": false,
})
return
}
pr, err := models.GetUnmergedPullRequest(repo.ID, baseRepo.ID, branch, baseRepo.DefaultBranch)
if err != nil && !models.IsErrPullRequestNotExist(err) {
log.Error("Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": fmt.Sprintf(
"Failed to get active PR in: %-v Branch: %s to: %-v Branch: %s Error: %v", repo, branch, baseRepo, baseRepo.DefaultBranch, err),
})
return
}
if pr == nil {
if repo.IsFork {
branch = fmt.Sprintf("%s:%s", repo.OwnerName, branch)
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": true,
"create": true,
"branch": branch,
"url": fmt.Sprintf("%s/compare/%s...%s", baseRepo.HTMLURL(), util.PathEscapeSegments(baseRepo.DefaultBranch), util.PathEscapeSegments(branch)),
})
} else {
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": true,
"create": false,
"branch": branch,
"url": fmt.Sprintf("%s/pulls/%d", baseRepo.HTMLURL(), pr.Index),
})
}
return
}
ctx.JSON(http.StatusOK, map[string]interface{}{
"message": false,
})
return
}

View file

@ -76,19 +76,10 @@ func CheckUnitUser(ctx *macaron.Context) {
// These APIs will be invoked by internal commands for example `gitea serv` and etc.
func RegisterRoutes(m *macaron.Macaron) {
m.Group("/", func() {
m.Get("/ssh/:id", GetPublicKeyByID)
m.Get("/ssh/:id/user", GetUserByKeyID)
m.Post("/ssh/:id/update", UpdatePublicKey)
m.Post("/repositories/:repoid/keys/:keyid/update", UpdateDeployKey)
m.Get("/repositories/:repoid/user/:userid/checkunituser", CheckUnitUser)
m.Get("/repositories/:repoid/has-keys/:keyid", HasDeployKey)
m.Get("/repositories/:repoid/keys/:keyid", GetDeployKey)
m.Get("/repositories/:repoid/wiki/init", InitWiki)
m.Post("/push/update", PushUpdate)
m.Get("/protectedbranch/:pbid/:userid", CanUserPush)
m.Get("/repo/:owner/:repo", GetRepositoryByOwnerAndName)
m.Get("/branch/:id/*", GetProtectedBranchBy)
m.Get("/repository/:rid", GetRepository)
m.Get("/active-pull-request", GetActivePullRequest)
m.Post("/ssh/:id/update/:repoid", UpdatePublicKeyInRepo)
m.Get("/hook/pre-receive/:owner/:repo", HookPreReceive)
m.Get("/hook/post-receive/:owner/:repo", HookPostReceive)
m.Get("/serv/none/:keyid", ServNoCommand)
m.Get("/serv/command/:keyid/:owner/:repo", ServCommand)
}, CheckInternalToken)
}

View file

@ -12,12 +12,23 @@ import (
macaron "gopkg.in/macaron.v1"
)
// UpdateDeployKey update deploy key updates
func UpdateDeployKey(ctx *macaron.Context) {
// UpdatePublicKeyInRepo update public key and deploy key updates
func UpdatePublicKeyInRepo(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
repoID := ctx.ParamsInt64(":repoid")
keyID := ctx.ParamsInt64(":keyid")
if err := models.UpdatePublicKeyUpdated(keyID); err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
deployKey, err := models.GetDeployKeyByRepo(keyID, repoID)
if err != nil {
if models.IsErrDeployKeyNotExist(err) {
ctx.PlainText(200, []byte("success"))
return
}
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
@ -30,73 +41,6 @@ func UpdateDeployKey(ctx *macaron.Context) {
})
return
}
ctx.PlainText(200, []byte("success"))
}
// UpdatePublicKey update publick key updates
func UpdatePublicKey(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
if err := models.UpdatePublicKeyUpdated(keyID); err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.PlainText(200, []byte("success"))
}
//GetPublicKeyByID chainload to models.GetPublicKeyByID
func GetPublicKeyByID(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
key, err := models.GetPublicKeyByID(keyID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(200, key)
}
//GetUserByKeyID chainload to models.GetUserByKeyID
func GetUserByKeyID(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":id")
user, err := models.GetUserByKeyID(keyID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(200, user)
}
//GetDeployKey chainload to models.GetDeployKey
func GetDeployKey(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":repoid")
keyID := ctx.ParamsInt64(":keyid")
dKey, err := models.GetDeployKeyByRepo(keyID, repoID)
if err != nil {
if models.IsErrDeployKeyNotExist(err) {
ctx.JSON(404, []byte("not found"))
return
}
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(200, dKey)
}
//HasDeployKey chainload to models.HasDeployKey
func HasDeployKey(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":repoid")
keyID := ctx.ParamsInt64(":keyid")
if models.HasDeployKey(keyID, repoID) {
ctx.PlainText(200, []byte("success"))
return
}
ctx.PlainText(404, []byte("not found"))
}

View file

@ -1,47 +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"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
macaron "gopkg.in/macaron.v1"
)
// PushUpdate update public key updates
func PushUpdate(ctx *macaron.Context) {
var opt models.PushUpdateOptions
if err := json.NewDecoder(ctx.Req.Request.Body).Decode(&opt); err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
branch := strings.TrimPrefix(opt.RefFullName, git.BranchPrefix)
if len(branch) == 0 || opt.PusherID <= 0 {
ctx.Error(404)
log.Trace("PushUpdate: branch or secret is empty, or pusher ID is not valid")
return
}
err := models.PushUpdate(branch, opt)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.Error(404)
} else {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
}
return
}
ctx.Status(202)
}

View file

@ -1,83 +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 (
"net/http"
"code.gitea.io/gitea/models"
macaron "gopkg.in/macaron.v1"
)
// GetRepository return the default branch of a repository
func GetRepository(ctx *macaron.Context) {
repoID := ctx.ParamsInt64(":rid")
repository, err := models.GetRepositoryByID(repoID)
repository.MustOwnerName()
allowPulls := repository.AllowsPulls()
// put it back to nil because json unmarshal can't unmarshal it
repository.Units = nil
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
if repository.IsFork {
repository.GetBaseRepo()
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
repository.BaseRepo.MustOwnerName()
allowPulls = repository.BaseRepo.AllowsPulls()
// put it back to nil because json unmarshal can't unmarshal it
repository.BaseRepo.Units = nil
}
ctx.JSON(http.StatusOK, struct {
Repository *models.Repository
AllowPullRequest bool
}{
Repository: repository,
AllowPullRequest: allowPulls,
})
}
// GetActivePullRequest return an active pull request when it exists or an empty object
func GetActivePullRequest(ctx *macaron.Context) {
baseRepoID := ctx.QueryInt64("baseRepoID")
headRepoID := ctx.QueryInt64("headRepoID")
baseBranch := ctx.QueryTrim("baseBranch")
if len(baseBranch) == 0 {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": "QueryTrim failed",
})
return
}
headBranch := ctx.QueryTrim("headBranch")
if len(headBranch) == 0 {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": "QueryTrim failed",
})
return
}
pr, err := models.GetUnmergedPullRequest(headRepoID, baseRepoID, headBranch, baseBranch)
if err != nil && !models.IsErrPullRequestNotExist(err) {
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.JSON(http.StatusOK, pr)
}

286
routers/private/serv.go Normal file
View file

@ -0,0 +1,286 @@
// 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"
"strings"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private"
"code.gitea.io/gitea/modules/setting"
macaron "gopkg.in/macaron.v1"
)
// ServNoCommand returns information about the provided keyid
func ServNoCommand(ctx *macaron.Context) {
keyID := ctx.ParamsInt64(":keyid")
if keyID <= 0 {
ctx.JSON(http.StatusBadRequest, map[string]interface{}{
"err": fmt.Sprintf("Bad key id: %d", keyID),
})
}
results := private.KeyAndOwner{}
key, err := models.GetPublicKeyByID(keyID)
if err != nil {
if models.IsErrKeyNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"err": fmt.Sprintf("Cannot find key: %d", keyID),
})
return
}
log.Error("Unable to get public key: %d Error: %v", keyID, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
results.Key = key
if key.Type == models.KeyTypeUser {
user, err := models.GetUserByID(key.OwnerID)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"err": fmt.Sprintf("Cannot find owner with id: %d for key: %d", key.OwnerID, keyID),
})
return
}
log.Error("Unable to get owner with id: %d for public key: %d Error: %v", key.OwnerID, keyID, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"err": err.Error(),
})
return
}
results.Owner = user
}
ctx.JSON(http.StatusOK, &results)
return
}
// ServCommand returns information about the provided keyid
func ServCommand(ctx *macaron.Context) {
// Although we provide the verbs we don't need them at present they're just for logging purposes
keyID := ctx.ParamsInt64(":keyid")
ownerName := ctx.Params(":owner")
repoName := ctx.Params(":repo")
mode := models.AccessMode(ctx.QueryInt("mode"))
// Set the basic parts of the results to return
results := private.ServCommandResults{
RepoName: repoName,
OwnerName: ownerName,
KeyID: keyID,
}
// Now because we're not translating things properly let's just default some Engish strings here
modeString := "read"
if mode > models.AccessModeRead {
modeString = "write to"
}
// The default unit we're trying to look at is code
unitType := models.UnitTypeCode
// Unless we're a wiki...
if strings.HasSuffix(repoName, ".wiki") {
// in which case we need to look at the wiki
unitType = models.UnitTypeWiki
// And we'd better munge the reponame and tell downstream we're looking at a wiki
results.IsWiki = true
results.RepoName = repoName[:len(repoName)-5]
}
// Now get the Repository and set the results section
repo, err := models.GetRepositoryByOwnerAndName(results.OwnerName, results.RepoName)
if err != nil {
if models.IsErrRepoNotExist(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
}
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
// 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
key, err := models.GetPublicKeyByID(keyID)
if err != nil {
if models.IsErrKeyNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrKeyNotExist",
"err": fmt.Sprintf("Cannot find key: %d", keyID),
})
return
}
log.Error("Unable to get public key: %d Error: %v", keyID, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get key: %d Error: %v", keyID, err),
})
return
}
results.KeyName = key.Name
results.KeyID = key.ID
results.UserID = key.OwnerID
// 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
var deployKey *models.DeployKey
var user *models.User
if key.Type == models.KeyTypeDeploy {
results.IsDeployKey = true
var err error
deployKey, err = models.GetDeployKeyByRepo(key.ID, repo.ID)
if err != nil {
if models.IsErrDeployKeyNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrDeployKeyNotExist",
"err": fmt.Sprintf("Public (Deploy) Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName),
})
return
}
log.Error("Unable to get deploy for public (deploy) key: %d in %-v Error: %v", key.ID, repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get Deploy Key for Public Key: %d:%s in %s/%s.", key.ID, key.Name, results.OwnerName, results.RepoName),
})
return
}
results.KeyName = deployKey.Name
// 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 of the repository
results.UserName = results.OwnerName
results.UserID = repo.OwnerID
} else {
// Get the user represented by the Key
var err error
user, err = models.GetUserByID(key.OwnerID)
if err != nil {
if models.IsErrUserNotExist(err) {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrUserNotExist",
"err": fmt.Sprintf("Public Key: %d:%s owner %d does not exist.", key.ID, key.Name, key.OwnerID),
})
return
}
log.Error("Unable to get owner: %d for public key: %d:%s Error: %v", key.OwnerID, key.ID, key.Name, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get Owner: %d for Deploy Key: %d:%s in %s/%s.", key.OwnerID, key.ID, key.Name, ownerName, repoName),
})
return
}
results.UserName = user.Name
}
// Don't allow pushing if the repo is archived
if mode > models.AccessModeRead && repo.IsArchived {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrRepoIsArchived",
"err": fmt.Sprintf("Repo: %s/%s is archived.", results.OwnerName, results.RepoName),
})
return
}
// Permissions checking:
if mode > models.AccessModeRead || repo.IsPrivate || setting.Service.RequireSignInView {
if key.Type == models.KeyTypeDeploy {
if deployKey.Mode < mode {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrUnauthorized",
"err": fmt.Sprintf("Deploy Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName),
})
return
}
} else {
perm, err := models.GetUserRepoPermission(repo, user)
if err != nil {
log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Unable to get permissions for user %d:%s with key %d in %s/%s Error: %v", user.ID, user.Name, key.ID, results.OwnerName, results.RepoName, err),
})
return
}
userMode := perm.UnitAccessMode(unitType)
if userMode < mode {
ctx.JSON(http.StatusUnauthorized, map[string]interface{}{
"results": results,
"type": "ErrUnauthorized",
"err": fmt.Sprintf("User: %d:%s with Key: %d:%s is not authorized to %s %s/%s.", user.ID, user.Name, key.ID, key.Name, modeString, ownerName, repoName),
})
return
}
}
}
// Finally if we're trying to touch the wiki we should init it
if results.IsWiki {
if err = repo.InitWiki(); err != nil {
log.Error("Failed to initialize the wiki in %-v Error: %v", repo, err)
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
"results": results,
"type": "InternalServerError",
"err": fmt.Sprintf("Failed to initialize the wiki in %s/%s Error: %v", ownerName, repoName, err),
})
return
}
}
log.Debug("Serv Results:\nIsWiki: %t\nIsDeployKey: %t\nKeyID: %d\tKeyName: %s\nUserName: %s\nUserID: %d\nOwnerName: %s\nRepoName: %s\nRepoID: %d",
results.IsWiki,
results.IsDeployKey,
results.KeyID,
results.KeyName,
results.UserName,
results.UserID,
results.OwnerName,
results.RepoName,
results.RepoID)
ctx.JSON(http.StatusOK, results)
// We will update the keys in a different call.
return
}

View file

@ -1,34 +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"
)
// InitWiki initilizes wiki via repo id
func InitWiki(ctx *macaron.Context) {
repoID := ctx.ParamsInt64("repoid")
repo, err := models.GetRepositoryByID(repoID)
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
err = repo.InitWiki()
if err != nil {
ctx.JSON(500, map[string]interface{}{
"err": err.Error(),
})
return
}
ctx.Status(202)
}

View file

@ -351,7 +351,7 @@ func gitCommand(dir string, args ...string) []byte {
cmd.Dir = dir
out, err := cmd.Output()
if err != nil {
log.GitLogger.Error(fmt.Sprintf("%v - %s", err, out))
log.Error("%v - %s", err, out)
}
return out
}
@ -409,7 +409,7 @@ func serviceRPC(h serviceHandler, service string) {
if h.r.Header.Get("Content-Encoding") == "gzip" {
reqBody, err = gzip.NewReader(reqBody)
if err != nil {
log.GitLogger.Error("Fail to create gzip reader: %v", err)
log.Error("Fail to create gzip reader: %v", err)
h.w.WriteHeader(http.StatusInternalServerError)
return
}
@ -428,7 +428,7 @@ func serviceRPC(h serviceHandler, service string) {
cmd.Stdin = reqBody
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
log.GitLogger.Error("Fail to serve RPC(%s): %v - %v", service, err, stderr)
log.Error("Fail to serve RPC(%s): %v - %v", service, err, stderr)
return
}
}
@ -541,7 +541,7 @@ func HTTPBackend(ctx *context.Context, cfg *serviceConfig) http.HandlerFunc {
file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
dir, err := getGitRepoPath(m[1])
if err != nil {
log.GitLogger.Error(err.Error())
log.Error(err.Error())
ctx.NotFound("HTTPBackend", err)
return
}