route: no session for routes without UI (#6066)

Not all routes need session, register session and CSRF middleware as global is a waste of resource, and creating a lot one-time off yet never used session records.
This commit is contained in:
ᴜɴᴋɴᴡᴏɴ 2020-04-05 06:36:08 +08:00 committed by GitHub
parent bae1d6ccd8
commit 07818d5fa5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 529 additions and 532 deletions

View File

@ -9,8 +9,6 @@ import (
"github.com/microcosm-cc/bluemonday"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/context"
)
func ipynbSanitizer() *bluemonday.Policy {
@ -24,13 +22,13 @@ func ipynbSanitizer() *bluemonday.Policy {
func SanitizeIpynb() macaron.Handler {
p := ipynbSanitizer()
return func(c *context.Context) {
return func(c *macaron.Context) {
html, err := c.Req.Body().String()
if err != nil {
c.Error(err, "read body")
c.Error(http.StatusInternalServerError, "read body")
return
}
c.PlainText(http.StatusOK, p.Sanitize(html))
c.PlainText(http.StatusOK, []byte(p.Sanitize(html)))
}
}

View File

@ -9,14 +9,14 @@ import (
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/authutil"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
)
func MetricsFilter() macaron.Handler {
return func(c *context.Context) {
return func(w http.ResponseWriter, r *http.Request) {
if !conf.Prometheus.Enabled {
c.Status(http.StatusNotFound)
w.WriteHeader(http.StatusNotFound)
return
}
@ -24,6 +24,10 @@ func MetricsFilter() macaron.Handler {
return
}
c.RequireBasicAuth(conf.Prometheus.BasicAuthUsername, conf.Prometheus.BasicAuthPassword)
username, password := authutil.DecodeBasic(r.Header)
if username != conf.Prometheus.BasicAuthUsername || password != conf.Prometheus.BasicAuthPassword {
w.WriteHeader(http.StatusForbidden)
return
}
}
}

View File

@ -144,25 +144,6 @@ func newMacaron() *macaron.Macaron {
m.Use(captcha.Captchaer(captcha.Options{
SubURL: conf.Server.Subpath,
}))
m.Use(session.Sessioner(session.Options{
Provider: conf.Session.Provider,
ProviderConfig: conf.Session.ProviderConfig,
CookieName: conf.Session.CookieName,
CookiePath: conf.Server.Subpath,
Gclifetime: conf.Session.GCInterval,
Maxlifetime: conf.Session.MaxLifeTime,
Secure: conf.Session.CookieSecure,
}))
m.Use(csrf.Csrfer(csrf.Options{
Secret: conf.Security.SecretKey,
Header: "X-CSRF-Token",
Cookie: conf.Session.CSRFCookieName,
CookieDomain: conf.Server.URL.Hostname(),
CookiePath: conf.Server.Subpath,
CookieHttpOnly: true,
SetCookie: true,
Secure: conf.Server.URL.Scheme == "https",
}))
m.Use(toolbox.Toolboxer(m, toolbox.Options{
HealthCheckFuncs: []*toolbox.HealthCheckFuncDesc{
{
@ -171,7 +152,6 @@ func newMacaron() *macaron.Macaron {
},
},
}))
m.Use(context.Contexter())
return m
}
@ -185,16 +165,13 @@ func runWeb(c *cli.Context) error {
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: conf.Auth.RequireSigninView})
ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
bindIgnErr := binding.BindIgnErr
m.SetAutoHead(true)
// FIXME: not all route need go through same middlewares.
// Especially some AJAX requests, we can reduce middleware number to improve performance.
// Routers.
m.Group("", func() {
m.Get("/", ignSignIn, route.Home)
m.Group("/explore", func() {
m.Get("", func(c *context.Context) {
@ -634,39 +611,60 @@ func runWeb(c *cli.Context) error {
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", repo.MustBeNotBare, context.RepoRef(), repo.CompareDiff)
}, ignSignIn, context.RepoAssignment())
m.Group("/:username/:reponame", func() {
m.Get("", repo.Home)
m.Get("/stars", repo.Stars)
m.Get("/watchers", repo.Watchers)
m.Head("/tasks/trigger", repo.TriggerTask) // TODO: Without session and CSRF
}, ignSignIn, context.RepoAssignment(), context.RepoRef())
m.Group("/:username", func() {
m.Get("/:reponame", ignSignIn, context.RepoAssignment(), context.RepoRef(), repo.Home)
m.Group("/:reponame", func() {
m.Head("/tasks/trigger", repo.TriggerTask)
})
// Use the regexp to match the repository name
// Duplicated route to enable different ways of accessing same set of URLs,
// e.g. with or without ".git" suffix.
m.Group("/:reponame([\\d\\w-_\\.]+\\.git$)", func() {
m.Get("", ignSignIn, context.RepoAssignment(), context.RepoRef(), repo.Home)
m.Group("/info/lfs", func() {
lfs.RegisterRoutes(m.Router)
}, ignSignInAndCsrf)
m.Route("/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
})
m.Route("/:reponame/*", "GET,POST,OPTIONS", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
})
// ***** END: Repository *****
// **********************
// ----- API routes -----
// **********************
// TODO: Without session and CSRF
m.Group("/api", func() {
apiv1.RegisterRoutes(m)
}, ignSignIn)
},
session.Sessioner(session.Options{
Provider: conf.Session.Provider,
ProviderConfig: conf.Session.ProviderConfig,
CookieName: conf.Session.CookieName,
CookiePath: conf.Server.Subpath,
Gclifetime: conf.Session.GCInterval,
Maxlifetime: conf.Session.MaxLifeTime,
Secure: conf.Session.CookieSecure,
}),
csrf.Csrfer(csrf.Options{
Secret: conf.Security.SecretKey,
Header: "X-CSRF-Token",
Cookie: conf.Session.CSRFCookieName,
CookieDomain: conf.Server.URL.Hostname(),
CookiePath: conf.Server.Subpath,
CookieHttpOnly: true,
SetCookie: true,
Secure: conf.Server.URL.Scheme == "https",
}),
context.Contexter(),
)
// ***************************
// ----- HTTP Git routes -----
// ***************************
m.Group("/:username/:reponame", func() {
m.Group("/info/lfs", func() {
lfs.RegisterRoutes(m.Router)
})
m.Route("/*", "GET,POST,OPTIONS", repo.HTTPContexter(), repo.HTTP)
})
// ***************************
// ----- Internal routes -----
// ***************************
m.Group("/-", func() {
m.Get("/metrics", app.MetricsFilter(), promhttp.Handler()) // "/-/metrics"
@ -675,16 +673,18 @@ func runWeb(c *cli.Context) error {
})
})
// robots.txt
m.Get("/robots.txt", func(c *context.Context) {
// **********************
// ----- robots.txt -----
// **********************
m.Get("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
if conf.HasRobotsTxt {
c.ServeFileContent(filepath.Join(conf.CustomDir(), "robots.txt"))
http.ServeFile(w, r, filepath.Join(conf.CustomDir(), "robots.txt"))
} else {
c.NotFound()
w.WriteHeader(http.StatusNotFound)
}
})
// Not found handler.
m.NotFound(route.NotFound)
// Flag for port number in case first time run conflict.

View File

@ -7,14 +7,12 @@ package context
import (
"net/http"
"net/url"
"strings"
"github.com/go-macaron/csrf"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/auth"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/tool"
)
type ToggleOptions struct {
@ -95,18 +93,3 @@ func Toggle(options *ToggleOptions) macaron.Handler {
}
}
}
// RequireBasicAuth verifies HTTP Basic Authentication header with given credentials.
func (c *Context) RequireBasicAuth(username, password string) {
fields := strings.Fields(c.Req.Header.Get("Authorization"))
if len(fields) != 2 || fields[0] != "Basic" {
c.Status(http.StatusUnauthorized)
return
}
uname, passwd, _ := tool.BasicAuthDecode(fields[1])
if uname != username || passwd != password {
c.Status(http.StatusForbidden)
return
}
}

View File

@ -93,7 +93,7 @@ func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (b
}
// HasAccess returns true if someone has the request access level. User can be nil!
// Deprecated: Use Perms.HasAccess instead.
// Deprecated: Use Perms.Authorize instead.
func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) {
return hasAccess(x, userID, repo, testMode)
}

View File

@ -5,7 +5,12 @@
package route
import (
"fmt"
"net/http"
"github.com/go-macaron/i18n"
"github.com/unknwon/paginater"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
@ -157,7 +162,7 @@ func ExploreOrganizations(c *context.Context) {
})
}
func NotFound(c *context.Context) {
c.Data["Title"] = "Page Not Found"
c.NotFound()
func NotFound(c *macaron.Context, l i18n.Locale) {
c.Data["Title"] = l.Tr("status.page_not_found")
c.HTML(http.StatusNotFound, fmt.Sprintf("status/%d", http.StatusNotFound))
}

View File

@ -12,10 +12,10 @@ import (
"os"
"strconv"
"gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lfsutil"
"gogs.io/gogs/internal/strutil"
@ -28,7 +28,7 @@ const (
)
// GET /{owner}/{repo}.git/info/lfs/object/basic/{oid}
func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID) {
func serveBasicDownload(c *macaron.Context, repo *db.Repository, oid lfsutil.OID) {
object, err := db.LFS.GetObjectByOID(repo.ID, oid)
if err != nil {
if db.IsErrLFSObjectNotExist(err) {
@ -63,7 +63,7 @@ func serveBasicDownload(c *context.Context, repo *db.Repository, oid lfsutil.OID
}
// PUT /{owner}/{repo}.git/info/lfs/object/basic/{oid}
func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID) {
func serveBasicUpload(c *macaron.Context, repo *db.Repository, oid lfsutil.OID) {
// NOTE: LFS client will retry upload the same object if there was a partial failure,
// therefore we would like to skip ones that already exist.
_, err := db.LFS.GetObjectByOID(repo.ID, oid)
@ -91,7 +91,7 @@ func serveBasicUpload(c *context.Context, repo *db.Repository, oid lfsutil.OID)
}
// POST /{owner}/{repo}.git/info/lfs/object/basic/verify
func serveBasicVerify(c *context.Context, repo *db.Repository) {
func serveBasicVerify(c *macaron.Context, repo *db.Repository) {
var request basicVerifyRequest
defer c.Req.Request.Body.Close()
err := json.NewDecoder(c.Req.Request.Body).Decode(&request)

View File

@ -9,17 +9,17 @@ import (
"net/http"
jsoniter "github.com/json-iterator/go"
"gopkg.in/macaron.v1"
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lfsutil"
"gogs.io/gogs/internal/strutil"
)
// POST /{owner}/{repo}.git/info/lfs/object/batch
func serveBatch(c *context.Context, owner *db.User, repo *db.Repository) {
func serveBatch(c *macaron.Context, owner *db.User, repo *db.Repository) {
var request batchRequest
defer c.Req.Request.Body.Close()
err := jsoniter.NewDecoder(c.Req.Request.Body).Decode(&request)

View File

@ -13,7 +13,6 @@ import (
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/authutil"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lfsutil"
)
@ -44,7 +43,7 @@ func authenticate() macaron.Handler {
})
}
return func(c *context.Context) {
return func(c *macaron.Context) {
username, password := authutil.DecodeBasic(c.Req.Header)
if username == "" {
askCredentials(c.Resp)
@ -59,7 +58,7 @@ func authenticate() macaron.Handler {
}
if err == nil && user.IsEnabledTwoFactor() {
c.PlainText(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`)
c.Error(http.StatusBadRequest, `Users with 2FA enabled are not allowed to authenticate via username and password.`)
return
}
@ -98,7 +97,7 @@ func authenticate() macaron.Handler {
// authorize tries to authorize the user to the context repository with given access mode.
func authorize(mode db.AccessMode) macaron.Handler {
return func(c *context.Context, user *db.User) {
return func(c *macaron.Context, user *db.User) {
username := c.Params(":username")
reponame := strings.TrimSuffix(c.Params(":reponame"), ".git")
@ -137,7 +136,7 @@ func authorize(mode db.AccessMode) macaron.Handler {
// verifyHeader checks if the HTTP header contains given value.
// When not, response given "failCode" as status code.
func verifyHeader(key, value string, failCode int) macaron.Handler {
return func(c *context.Context) {
return func(c *macaron.Context) {
if !strings.Contains(c.Req.Header.Get(key), value) {
c.Status(failCode)
return
@ -147,10 +146,10 @@ func verifyHeader(key, value string, failCode int) macaron.Handler {
// verifyOID checks if the ":oid" URL parameter is valid.
func verifyOID() macaron.Handler {
return func(c *context.Context) {
return func(c *macaron.Context) {
oid := lfsutil.OID(c.Params(":oid"))
if !lfsutil.ValidOID(oid) {
c.PlainText(http.StatusBadRequest, "Invalid oid")
c.Error(http.StatusBadRequest, "Invalid oid")
return
}

View File

@ -21,14 +21,13 @@ import (
log "unknwon.dev/clog/v2"
"gogs.io/gogs/internal/conf"
"gogs.io/gogs/internal/context"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/lazyregexp"
"gogs.io/gogs/internal/tool"
)
type HTTPContext struct {
*context.Context
*macaron.Context
OwnerName string
OwnerSalt string
RepoID int64
@ -37,17 +36,17 @@ type HTTPContext struct {
}
// askCredentials responses HTTP header and status which informs client to provide credentials.
func askCredentials(c *context.Context, status int, text string) {
c.Resp.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
c.PlainText(status, text)
func askCredentials(c *macaron.Context, status int, text string) {
c.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
c.Error(status, text)
}
func HTTPContexter() macaron.Handler {
return func(c *context.Context) {
return func(c *macaron.Context) {
if len(conf.HTTP.AccessControlAllowOrigin) > 0 {
// Set CORS headers for browser-based git clients
c.Resp.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
c.Resp.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
c.Header().Set("Access-Control-Allow-Origin", conf.HTTP.AccessControlAllowOrigin)
c.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, User-Agent")
// Handle preflight OPTIONS request
if c.Req.Method == "OPTIONS" {
@ -64,15 +63,25 @@ func HTTPContexter() macaron.Handler {
strings.HasSuffix(c.Req.URL.Path, "git-upload-pack") ||
c.Req.Method == "GET"
owner, err := db.GetUserByName(ownerName)
owner, err := db.Users.GetByUsername(ownerName)
if err != nil {
c.NotFoundOrError(err, "get user by name")
if db.IsErrUserNotExist(err) {
c.Status(http.StatusNotFound)
} else {
c.Status(http.StatusInternalServerError)
log.Error("Failed to get user [name: %s]: %v", ownerName, err)
}
return
}
repo, err := db.GetRepositoryByName(owner.ID, repoName)
repo, err := db.Repos.GetByName(owner.ID, repoName)
if err != nil {
c.NotFoundOrError(err, "get repository by name")
if db.IsErrRepoNotExist(err) {
c.Status(http.StatusNotFound)
} else {
c.Status(http.StatusInternalServerError)
log.Error("Failed to get repository [owner_id: %d, name: %s]: %v", owner.ID, repoName, err)
}
return
}
@ -114,7 +123,8 @@ func HTTPContexter() macaron.Handler {
authUser, err := db.Users.Authenticate(authUsername, authPassword, -1)
if err != nil && !db.IsErrUserNotExist(err) {
c.Error(err, "authenticate user")
c.Status(http.StatusInternalServerError)
log.Error("Failed to authenticate user [name: %s]: %v", authUsername, err)
return
}
@ -125,7 +135,8 @@ func HTTPContexter() macaron.Handler {
if db.IsErrAccessTokenNotExist(err) {
askCredentials(c, http.StatusUnauthorized, "")
} else {
c.Error(err, "get access token by SHA")
c.Status(http.StatusInternalServerError)
log.Error("Failed to get access token [sha: %s]: %v", authUsername, err)
}
return
}
@ -134,11 +145,12 @@ func HTTPContexter() macaron.Handler {
log.Error("Failed to update access token: %v", err)
}
authUser, err = db.GetUserByID(token.UserID)
authUser, err = db.Users.GetByID(token.UserID)
if err != nil {
// Once we found token, we're supposed to find its related user,
// thus any error is unexpected.
c.Error(err, "get user by ID")
c.Status(http.StatusInternalServerError)
log.Error("Failed to get user [id: %d]: %v", token.UserID, err)
return
}
} else if authUser.IsEnabledTwoFactor() {
@ -147,23 +159,19 @@ Please create and use personal access token on user settings page`)
return
}
log.Trace("HTTPGit - Authenticated user: %s", authUser.Name)
log.Trace("[Git] Authenticated user: %s", authUser.Name)
mode := db.AccessModeWrite
if isPull {
mode = db.AccessModeRead
}
has, err := db.HasAccess(authUser.ID, repo, mode)
if err != nil {
c.Error(err, "check access")
return
} else if !has {
if !db.Perms.Authorize(authUser.ID, repo, mode) {
askCredentials(c, http.StatusForbidden, "User permission denied")
return
}
if !isPull && repo.IsMirror {
c.PlainText(http.StatusForbidden, "Mirror repository is read-only")
c.Error(http.StatusForbidden, "Mirror repository is read-only")
return
}
@ -390,7 +398,7 @@ func HTTP(c *HTTPContext) {
// but we only want to output this message only if user is really trying to access
// Git HTTP endpoints.
if conf.Repository.DisableHTTPGit {
c.PlainText(http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled")
c.Error(http.StatusForbidden, "Interacting with repositories by HTTP protocol is disabled")
return
}