mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-21 21:30:36 +00:00
6904f6480c
Better than before, still not good enough (more work can be done in the future) And add the missing error handling in the PrivateContext "bind" middleware. By the way, picked some "TrimSpace" changes from "fix: trim whitespace from SMTP address and port - #38934" (fix #38926)
111 lines
4.7 KiB
Go
111 lines
4.7 KiB
Go
// Copyright 2017 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Package private contains all internal routes. The package name "internal" isn't usable because Golang reserves it for disabling cross-package usage.
|
|
package private
|
|
|
|
import (
|
|
"crypto/subtle"
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"gitea.dev/modules/log"
|
|
"gitea.dev/modules/private"
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/validation"
|
|
"gitea.dev/modules/web"
|
|
"gitea.dev/modules/web/middleware"
|
|
"gitea.dev/routers/common"
|
|
"gitea.dev/routers/web/misc"
|
|
"gitea.dev/services/context"
|
|
)
|
|
|
|
func authInternal(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if setting.InternalToken == "" {
|
|
log.Warn(`The INTERNAL_TOKEN setting is missing from the configuration file: %q, internal API can't work.`, setting.CustomConf)
|
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
tokens := req.Header.Get("X-Gitea-Internal-Auth") // TODO: use something like JWT or HMAC to avoid passing the token in the clear
|
|
after, found := strings.CutPrefix(tokens, "Bearer ")
|
|
authSucceeded := found && subtle.ConstantTimeCompare([]byte(after), []byte(setting.InternalToken)) == 1
|
|
if !authSucceeded {
|
|
log.Debug("Forbidden attempt to access internal url: Authorization header: %s", tokens)
|
|
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, req)
|
|
})
|
|
}
|
|
|
|
// bind binding an obj to a handler
|
|
func bind[T any](tmpl T) any {
|
|
return func(ctx *context.PrivateContext) {
|
|
form, errs := middleware.BindFormAny(ctx.Req, validation.Binder(), tmpl)
|
|
if len(errs) > 0 {
|
|
errMsg, _, _ := middleware.BuildValidationErrorForUser(form, ctx.Locale, errs)
|
|
ctx.PrivateInternalErrorf("invalid request: %v", errMsg)
|
|
}
|
|
web.SetForm(ctx, form)
|
|
}
|
|
}
|
|
|
|
// setRealIP sets RemoteAddr from the trusted X-Real-IP header set by the internal API
|
|
// client (see modules/private.NewInternalRequest); the internal API is gated by InternalToken.
|
|
// It replaces chi's deprecated middleware.RealIP, which is unsafe on public-facing endpoints.
|
|
func setRealIP(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
if ip := req.Header.Get("X-Real-IP"); net.ParseIP(ip) != nil {
|
|
req.RemoteAddr = ip
|
|
}
|
|
next.ServeHTTP(w, req)
|
|
})
|
|
}
|
|
|
|
// Routes registers all internal APIs routes to web application.
|
|
// These APIs will be invoked by internal commands for example `gitea serv` and etc.
|
|
func Routes() *web.Router {
|
|
r := web.NewRouter()
|
|
r.AfterRouting(context.PrivateContexter())
|
|
r.AfterRouting(authInternal)
|
|
// Log the real ip address of the request from SSH is really helpful for diagnosing sometimes.
|
|
// Since internal API will be sent only from Gitea sub commands and it's under control (checked by InternalToken), we can trust the headers.
|
|
r.AfterRouting(setRealIP)
|
|
|
|
r.Get("/dummy", misc.DummyOK)
|
|
r.Post("/ssh/authorized_keys", AuthorizedPublicKeyByContent)
|
|
r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo)
|
|
r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog)
|
|
r.Post("/hook/pre-receive/{owner}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookPreReceive)
|
|
r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext(), bind(private.HookOptions{}), HookPostReceive)
|
|
r.Post("/hook/proc-receive/{owner}/{repo}", context.OverrideContext(), RepoAssignment, bind(private.HookOptions{}), HookProcReceive)
|
|
r.Get("/serv/none/{keyid}", ServNoCommand)
|
|
r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand)
|
|
r.Post("/manager/shutdown", Shutdown)
|
|
r.Post("/manager/restart", Restart)
|
|
r.Post("/manager/reload-templates", ReloadTemplates)
|
|
r.Post("/manager/flush-queues", bind(private.FlushOptions{}), FlushQueues)
|
|
r.Post("/manager/pause-logging", PauseLogging)
|
|
r.Post("/manager/resume-logging", ResumeLogging)
|
|
r.Post("/manager/release-and-reopen-logging", ReleaseReopenLogging)
|
|
r.Post("/manager/set-log-sql", SetLogSQL)
|
|
r.Get("/manager/processes", Processes)
|
|
r.Post("/mail/send", SendEmail)
|
|
r.Post("/restore_repo", RestoreRepo)
|
|
r.Post("/actions/generate_actions_runner_token", GenerateActionsRunnerToken)
|
|
|
|
r.Group("/repo", func() {
|
|
// FIXME: it is not right to use context.Contexter here because all routes here should use PrivateContext
|
|
// Fortunately, the LFS handlers are able to handle requests without a complete web context
|
|
common.AddOwnerRepoGitLFSRoutes(r, func(ctx *context.PrivateContext) {
|
|
webContext := &context.Context{Base: ctx.Base} // see above, it shouldn't manually construct the web context
|
|
ctx.SetContextValue(context.WebContextKey, webContext) // FIXME: this is not ideal but no other way at the moment
|
|
})
|
|
})
|
|
|
|
return r
|
|
}
|