From 617a2433a3dd2e32188574ac7b3765bec5082123 Mon Sep 17 00:00:00 2001
From: Antoine GIRARD <sapk@users.noreply.github.com>
Date: Tue, 30 Oct 2018 07:20:13 +0100
Subject: [PATCH] Make gitea serv use api/internal (#4886)

* Start to move to internal/private

* Add GetPublicKeyByID

* Add HasDeployKey

* Add private.UpdateDeployKeyUpdated

* Add private.GetUserByKeyID

* Add private.AccessLevel

* Add private.CheckUnitUser

* Fix mistakes I made

* Some cleaning + moving code to separate files

* Fix error handling

* Remove useless error handling for setup

* lint: fix comment on exported func

* fix copyright header

* Fix order of args
---
 cmd/serv.go                 |  55 +++++++----------
 modules/private/branch.go   |   2 +-
 modules/private/internal.go |  69 +++++++++++++++++----
 modules/private/key.go      | 116 ++++++++++++++++++++++++++++++++++++
 routers/private/internal.go |  58 ++++++++++++++++--
 routers/private/key.go      |  84 ++++++++++++++++++++++++++
 6 files changed, 332 insertions(+), 52 deletions(-)
 create mode 100644 modules/private/key.go
 create mode 100644 routers/private/key.go

diff --git a/cmd/serv.go b/cmd/serv.go
index b532b9549..ca042e2b2 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -19,7 +19,6 @@ import (
 	"code.gitea.io/gitea/modules/pprof"
 	"code.gitea.io/gitea/modules/private"
 	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/modules/util"
 
 	"github.com/Unknwon/com"
 	"github.com/dgrijalva/jwt-go"
@@ -49,20 +48,9 @@ var CmdServ = cli.Command{
 	},
 }
 
-func setup(logPath string) error {
+func setup(logPath string) {
 	setting.NewContext()
 	log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
-	models.LoadConfigs()
-
-	if setting.UseSQLite3 || setting.UseTiDB {
-		workPath := setting.AppWorkPath
-		if err := os.Chdir(workPath); err != nil {
-			log.GitLogger.Fatal(4, "Failed to change directory %s: %v", workPath, err)
-		}
-	}
-
-	setting.NewXORMLogService(true)
-	return models.SetEngine()
 }
 
 func parseCmd(cmd string) (string, string) {
@@ -101,10 +89,7 @@ func runServ(c *cli.Context) error {
 	if c.IsSet("config") {
 		setting.CustomConf = c.String("config")
 	}
-
-	if err := setup("serv.log"); err != nil {
-		fail("System init failed", fmt.Sprintf("setup: %v", err))
-	}
+	setup("serv.log")
 
 	if setting.SSH.Disabled {
 		println("Gitea: SSH has been disabled")
@@ -175,9 +160,9 @@ func runServ(c *cli.Context) error {
 	}
 	os.Setenv(models.EnvRepoName, reponame)
 
-	repo, err := models.GetRepositoryByOwnerAndName(username, reponame)
+	repo, err := private.GetRepositoryByOwnerAndName(username, reponame)
 	if err != nil {
-		if models.IsErrRepoNotExist(err) {
+		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)
@@ -214,7 +199,7 @@ func runServ(c *cli.Context) error {
 			fail("Key ID format error", "Invalid key argument: %s", c.Args()[0])
 		}
 
-		key, err := models.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64())
+		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)
 		}
@@ -225,23 +210,22 @@ func runServ(c *cli.Context) error {
 			if key.Mode < requestedMode {
 				fail("Key permission denied", "Cannot push with deployment key: %d", key.ID)
 			}
+
 			// Check if this deploy key belongs to current repository.
-			if !models.HasDeployKey(key.ID, repo.ID) {
+			has, err := private.HasDeployKey(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 !has {
 				fail("Key access denied", "Deploy key access denied: [key_id: %d, repo_id: %d]", key.ID, repo.ID)
 			}
 
 			// Update deploy key activity.
-			deployKey, err := models.GetDeployKeyByRepo(key.ID, repo.ID)
-			if err != nil {
-				fail("Internal error", "GetDeployKey: %v", err)
-			}
-
-			deployKey.UpdatedUnix = util.TimeStampNow()
-			if err = models.UpdateDeployKeyCols(deployKey, "updated_unix"); err != nil {
+			if err = private.UpdateDeployKeyUpdated(key.ID, repo.ID); err != nil {
 				fail("Internal error", "UpdateDeployKey: %v", err)
 			}
 		} else {
-			user, err = models.GetUserByKeyID(key.ID)
+			user, err = private.GetUserByKeyID(key.ID)
 			if err != nil {
 				fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
 			}
@@ -252,12 +236,12 @@ func runServ(c *cli.Context) error {
 					user.Name, repoPath)
 			}
 
-			mode, err := models.AccessLevel(user.ID, repo)
+			mode, err := private.AccessLevel(user.ID, repo.ID)
 			if err != nil {
 				fail("Internal error", "Failed to check access: %v", err)
-			} else if mode < requestedMode {
+			} else if *mode < requestedMode {
 				clientMessage := accessDenied
-				if mode >= models.AccessModeRead {
+				if *mode >= models.AccessModeRead {
 					clientMessage = "You do not have sufficient authorization for this action"
 				}
 				fail(clientMessage,
@@ -265,7 +249,11 @@ func runServ(c *cli.Context) error {
 					user.Name, requestedMode, repoPath)
 			}
 
-			if !repo.CheckUnitUser(user.ID, user.IsAdmin, unitType) {
+			check, err := private.CheckUnitUser(user.ID, repo.ID, user.IsAdmin, unitType)
+			if err != nil {
+				fail("You do not have allowed for this action", "Failed to access internal api: [user.Name: %s, repoPath: %s]", user.Name, repoPath)
+			}
+			if !check {
 				fail("You do not have allowed for this action",
 					"User %s does not have allowed access to repository %s 's code",
 					user.Name, repoPath)
@@ -325,7 +313,6 @@ func runServ(c *cli.Context) error {
 	} else {
 		gitcmd = exec.Command(verb, repoPath)
 	}
-
 	if isWiki {
 		if err = repo.InitWiki(); err != nil {
 			fail("Internal error", "Failed to init wiki repo: %v", err)
diff --git a/modules/private/branch.go b/modules/private/branch.go
index fed66d29f..cadbf6c88 100644
--- a/modules/private/branch.go
+++ b/modules/private/branch.go
@@ -33,7 +33,7 @@ func GetProtectedBranchBy(repoID int64, branchName string) (*models.ProtectedBra
 
 	// All 2XX status codes are accepted and others will return an error
 	if resp.StatusCode/100 != 2 {
-		return nil, fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err)
+		return nil, fmt.Errorf("Failed to get protected branch: %s", decodeJSONError(resp).Err)
 	}
 
 	return &branch, nil
diff --git a/modules/private/internal.go b/modules/private/internal.go
index ac2fe56b8..f4ac1c515 100644
--- a/modules/private/internal.go
+++ b/modules/private/internal.go
@@ -11,6 +11,7 @@ import (
 	"net"
 	"net/http"
 
+	"code.gitea.io/gitea/models"
 	"code.gitea.io/gitea/modules/httplib"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/setting"
@@ -49,22 +50,66 @@ func newInternalRequest(url, method string) *httplib.Request {
 	return req
 }
 
-// UpdatePublicKeyUpdated update publick key updates
-func UpdatePublicKeyUpdated(keyID 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)
+// CheckUnitUser check whether user could visit the unit of this repository
+func CheckUnitUser(userID, repoID int64, isAdmin bool, unitType models.UnitType) (bool, error) {
+	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/user/%d/checkunituser?isAdmin=%t&unitType=%d", repoID, userID, isAdmin, unitType)
+	log.GitLogger.Trace("AccessLevel: %s", reqURL)
 
-	resp, err := newInternalRequest(reqURL, "POST").Response()
+	resp, err := newInternalRequest(reqURL, "GET").Response()
 	if err != nil {
-		return err
+		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 fmt.Errorf("Failed to update public key: %s", decodeJSONError(resp).Err)
+	if resp.StatusCode == 200 {
+		return true, nil
 	}
-	return nil
+	return false, nil
+}
+
+// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
+// user does not have access.
+func AccessLevel(userID, repoID int64) (*models.AccessMode, error) {
+	reqURL := setting.LocalURL + fmt.Sprintf("api/internal/repositories/%d/user/%d/accesslevel", repoID, userID)
+	log.GitLogger.Trace("AccessLevel: %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 access level: %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", ownerName, 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
 }
diff --git a/modules/private/key.go b/modules/private/key.go
new file mode 100644
index 000000000..86d0a730d
--- /dev/null
+++ b/modules/private/key.go
@@ -0,0 +1,116 @@
+// 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"
+
+	"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
+}
+
+// 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 {
+	// 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)
+
+	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 public key: %s", decodeJSONError(resp).Err)
+	}
+	return nil
+}
diff --git a/routers/private/internal.go b/routers/private/internal.go
index 96021d8fe..23e012264 100644
--- a/routers/private/internal.go
+++ b/routers/private/internal.go
@@ -23,26 +23,74 @@ func CheckInternalToken(ctx *macaron.Context) {
 	}
 }
 
-// UpdatePublicKey update publick key updates
-func UpdatePublicKey(ctx *macaron.Context) {
-	keyID := ctx.ParamsInt64(":id")
-	if err := models.UpdatePublicKeyUpdated(keyID); err != nil {
+//GetRepositoryByOwnerAndName chainload to models.GetRepositoryByOwnerAndName
+func GetRepositoryByOwnerAndName(ctx *macaron.Context) {
+	//TODO use repo.Get(ctx *context.APIContext) ?
+	ownerName := ctx.Params(":owner")
+	repoName := ctx.Params(":repo")
+	repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName)
+	if err != nil {
 		ctx.JSON(500, map[string]interface{}{
 			"err": err.Error(),
 		})
 		return
 	}
+	ctx.JSON(200, repo)
+}
 
-	ctx.PlainText(200, []byte("success"))
+//AccessLevel chainload to models.AccessLevel
+func AccessLevel(ctx *macaron.Context) {
+	repoID := ctx.ParamsInt64(":repoid")
+	userID := ctx.ParamsInt64(":userid")
+	repo, err := models.GetRepositoryByID(repoID)
+	if err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"err": err.Error(),
+		})
+		return
+	}
+	al, err := models.AccessLevel(userID, repo)
+	if err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"err": err.Error(),
+		})
+		return
+	}
+	ctx.JSON(200, al)
+}
+
+//CheckUnitUser chainload to models.CheckUnitUser
+func CheckUnitUser(ctx *macaron.Context) {
+	repoID := ctx.ParamsInt64(":repoid")
+	userID := ctx.ParamsInt64(":userid")
+	repo, err := models.GetRepositoryByID(repoID)
+	if err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"err": err.Error(),
+		})
+		return
+	}
+	if repo.CheckUnitUser(userID, ctx.QueryBool("isAdmin"), models.UnitType(ctx.QueryInt("unitType"))) {
+		ctx.PlainText(200, []byte("success"))
+		return
+	}
+	ctx.PlainText(404, []byte("no access"))
 }
 
 // RegisterRoutes registers all internal APIs routes to web application.
 // 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/accesslevel", AccessLevel)
+		m.Get("/repositories/:repoid/user/:userid/checkunituser", CheckUnitUser)
+		m.Get("/repositories/:repoid/has-keys/:keyid", HasDeployKey)
 		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)
diff --git a/routers/private/key.go b/routers/private/key.go
new file mode 100644
index 000000000..9cc116578
--- /dev/null
+++ b/routers/private/key.go
@@ -0,0 +1,84 @@
+// 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 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 (
+	"code.gitea.io/gitea/models"
+	"code.gitea.io/gitea/modules/util"
+
+	macaron "gopkg.in/macaron.v1"
+)
+
+// UpdateDeployKey update deploy key updates
+func UpdateDeployKey(ctx *macaron.Context) {
+	repoID := ctx.ParamsInt64(":repoid")
+	keyID := ctx.ParamsInt64(":keyid")
+	deployKey, err := models.GetDeployKeyByRepo(keyID, repoID)
+	if err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"err": err.Error(),
+		})
+		return
+	}
+	deployKey.UpdatedUnix = util.TimeStampNow()
+	if err = models.UpdateDeployKeyCols(deployKey, "updated_unix"); err != nil {
+		ctx.JSON(500, map[string]interface{}{
+			"err": err.Error(),
+		})
+		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)
+}
+
+//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"))
+}