Extract auth middleware from service (#27028)

Related #27027

Extract the router logic from `services/auth/middleware.go` into
`routers/web` <-> `routers/common` <-> `routers/api`.
This commit is contained in:
KN4CK3R 2023-09-12 08:15:16 +02:00 committed by GitHub
parent 7818121d50
commit 591f586bf1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 262 additions and 266 deletions

View file

@ -6,6 +6,7 @@ package web
import (
gocontext "context"
"net/http"
"strings"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
@ -19,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/modules/web/routing"
"code.gitea.io/gitea/routers/common"
"code.gitea.io/gitea/routers/web/admin"
@ -46,7 +48,7 @@ import (
"gitea.com/go-chi/captcha"
"github.com/NYTimes/gziphandler"
"github.com/go-chi/chi/v5/middleware"
chi_middleware "github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/cors"
"github.com/prometheus/client_golang/prometheus"
)
@ -95,6 +97,109 @@ func buildAuthGroup() *auth_service.Group {
return group
}
func webAuth(authMethod auth_service.Method) func(*context.Context) {
return func(ctx *context.Context) {
ar, err := common.AuthShared(ctx.Base, ctx.Session, authMethod)
if err != nil {
log.Error("Failed to verify user: %v", err)
ctx.Error(http.StatusUnauthorized, "Verify")
return
}
ctx.Doer = ar.Doer
ctx.IsSigned = ar.Doer != nil
ctx.IsBasicAuth = ar.IsBasicAuth
if ctx.Doer == nil {
// ensure the session uid is deleted
_ = ctx.Session.Delete("uid")
}
}
}
// verifyAuthWithOptions checks authentication according to options
func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Context) {
return func(ctx *context.Context) {
// Check prohibit login users.
if ctx.IsSigned {
if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(http.StatusOK, "user/auth/activate")
return
}
if !ctx.Doer.IsActive || ctx.Doer.ProhibitLogin {
log.Info("Failed authentication attempt for %s from %s", ctx.Doer.Name, ctx.RemoteAddr())
ctx.Data["Title"] = ctx.Tr("auth.prohibit_login")
ctx.HTML(http.StatusOK, "user/auth/prohibit_login")
return
}
if ctx.Doer.MustChangePassword {
if ctx.Req.URL.Path != "/user/settings/change_password" {
if strings.HasPrefix(ctx.Req.UserAgent(), "git") {
ctx.Error(http.StatusUnauthorized, ctx.Tr("auth.must_change_password"))
return
}
ctx.Data["Title"] = ctx.Tr("auth.must_change_password")
ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password"
if ctx.Req.URL.Path != "/user/events" {
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
}
ctx.Redirect(setting.AppSubURL + "/user/settings/change_password")
return
}
} else if ctx.Req.URL.Path == "/user/settings/change_password" {
// make sure that the form cannot be accessed by users who don't need this
ctx.Redirect(setting.AppSubURL + "/")
return
}
}
// Redirect to dashboard (or alternate location) if user tries to visit any non-login page.
if options.SignOutRequired && ctx.IsSigned && ctx.Req.URL.RequestURI() != "/" {
ctx.RedirectToFirst(ctx.FormString("redirect_to"))
return
}
if !options.SignOutRequired && !options.DisableCSRF && ctx.Req.Method == "POST" {
ctx.Csrf.Validate(ctx)
if ctx.Written() {
return
}
}
if options.SignInRequired {
if !ctx.IsSigned {
if ctx.Req.URL.Path != "/user/events" {
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
}
ctx.Redirect(setting.AppSubURL + "/user/login")
return
} else if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm {
ctx.Data["Title"] = ctx.Tr("auth.active_your_account")
ctx.HTML(http.StatusOK, "user/auth/activate")
return
}
}
// Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !ctx.IsSigned &&
len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 {
if ctx.Req.URL.Path != "/user/events" {
middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI())
}
ctx.Redirect(setting.AppSubURL + "/user/login")
return
}
if options.AdminRequired {
if !ctx.Doer.IsAdmin {
ctx.Error(http.StatusForbidden)
return
}
ctx.Data["PageIsAdmin"] = true
}
}
}
func ctxDataSet(args ...any) func(ctx *context.Context) {
return func(ctx *context.Context) {
for i := 0; i < len(args); i += 2 {
@ -144,10 +249,10 @@ func Routes() *web.Route {
mid = append(mid, common.Sessioner(), context.Contexter())
// Get user from session if logged in.
mid = append(mid, auth_service.Auth(buildAuthGroup()))
mid = append(mid, webAuth(buildAuthGroup()))
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
mid = append(mid, middleware.GetHead)
mid = append(mid, chi_middleware.GetHead)
if setting.API.EnableSwagger {
// Note: The route is here but no in API routes because it renders a web page
@ -168,12 +273,12 @@ func Routes() *web.Route {
// registerRoutes register routes
func registerRoutes(m *web.Route) {
reqSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: true})
reqSignOut := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignOutRequired: true})
reqSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true})
reqSignOut := verifyAuthWithOptions(&common.VerifyOptions{SignOutRequired: true})
// TODO: rename them to "optSignIn", which means that the "sign-in" could be optional, depends on the VerifyOptions (RequireSignInView)
ignSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
ignExploreSignIn := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
ignSignInAndCsrf := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{DisableCSRF: true})
ignSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView})
ignExploreSignIn := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: setting.Service.RequireSignInView || setting.Service.Explore.RequireSigninView})
ignSignInAndCsrf := verifyAuthWithOptions(&common.VerifyOptions{DisableCSRF: true})
validation.AddBindingRules()
linkAccountEnabled := func(ctx *context.Context) {
@ -543,7 +648,7 @@ func registerRoutes(m *web.Route) {
m.Get("/avatar/{hash}", user.AvatarByEmailHash)
adminReq := auth_service.VerifyAuthWithOptions(&auth_service.VerifyOptions{SignInRequired: true, AdminRequired: true})
adminReq := verifyAuthWithOptions(&common.VerifyOptions{SignInRequired: true, AdminRequired: true})
// ***** START: Admin *****
m.Group("/admin", func() {