Let web and API routes have different auth methods group (#19168)
* remove the global methods but create dynamiclly * Fix lint * Fix windows lint * Fix windows lint * some improvements Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
parent
d6fa138e7c
commit
6526733a58
10 changed files with 140 additions and 80 deletions
|
@ -563,6 +563,26 @@ func bind(obj interface{}) http.HandlerFunc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
|
||||||
|
// in the session (if there is a user id stored in session other plugins might return the user
|
||||||
|
// object for that id).
|
||||||
|
//
|
||||||
|
// The Session plugin is expected to be executed second, in order to skip authentication
|
||||||
|
// for users that have already signed in.
|
||||||
|
func buildAuthGroup() *auth.Group {
|
||||||
|
group := auth.NewGroup(
|
||||||
|
&auth.OAuth2{},
|
||||||
|
&auth.Basic{}, // FIXME: this should be removed once we don't allow basic auth in API
|
||||||
|
auth.SharedSession, // FIXME: this should be removed once all UI don't reference API/v1, see https://github.com/go-gitea/gitea/pull/16052
|
||||||
|
)
|
||||||
|
if setting.Service.EnableReverseProxyAuth {
|
||||||
|
group.Add(&auth.ReverseProxy{})
|
||||||
|
}
|
||||||
|
specialAdd(group)
|
||||||
|
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
// Routes registers all v1 APIs routes to web application.
|
// Routes registers all v1 APIs routes to web application.
|
||||||
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
|
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
|
||||||
m := web.NewRoute()
|
m := web.NewRoute()
|
||||||
|
@ -583,8 +603,13 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
|
||||||
}
|
}
|
||||||
m.Use(context.APIContexter())
|
m.Use(context.APIContexter())
|
||||||
|
|
||||||
|
group := buildAuthGroup()
|
||||||
|
if err := group.Init(); err != nil {
|
||||||
|
log.Error("Could not initialize '%s' auth method, error: %s", group.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
// Get user from session if logged in.
|
// Get user from session if logged in.
|
||||||
m.Use(context.APIAuth(auth.NewGroup(auth.Methods()...)))
|
m.Use(context.APIAuth(group))
|
||||||
|
|
||||||
m.Use(context.ToggleAPI(&context.ToggleOptions{
|
m.Use(context.ToggleAPI(&context.ToggleOptions{
|
||||||
SignInRequired: setting.Service.RequireSignInView,
|
SignInRequired: setting.Service.RequireSignInView,
|
||||||
|
|
12
routers/api/v1/auth.go
Normal file
12
routers/api/v1/auth.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package v1
|
||||||
|
|
||||||
|
import auth_service "code.gitea.io/gitea/services/auth"
|
||||||
|
|
||||||
|
func specialAdd(group *auth_service.Group) {}
|
20
routers/api/v1/auth_windows.go
Normal file
20
routers/api/v1/auth_windows.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2022 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 v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/auth"
|
||||||
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// specialAdd registers the SSPI auth method as the last method in the list.
|
||||||
|
// The SSPI plugin is expected to be executed last, as it returns 401 status code if negotiation
|
||||||
|
// fails (or if negotiation should continue), which would prevent other authentication methods
|
||||||
|
// to execute at all.
|
||||||
|
func specialAdd(group *auth_service.Group) {
|
||||||
|
if auth.IsSSPIEnabled() {
|
||||||
|
group.Add(&auth_service.SSPI{})
|
||||||
|
}
|
||||||
|
}
|
12
routers/web/auth.go
Normal file
12
routers/web/auth.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Copyright 2022 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.
|
||||||
|
|
||||||
|
//go:build !windows
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package web
|
||||||
|
|
||||||
|
import auth_service "code.gitea.io/gitea/services/auth"
|
||||||
|
|
||||||
|
func specialAdd(group *auth_service.Group) {}
|
20
routers/web/auth_windows.go
Normal file
20
routers/web/auth_windows.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2022 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 web
|
||||||
|
|
||||||
|
import (
|
||||||
|
"code.gitea.io/gitea/models/auth"
|
||||||
|
auth_service "code.gitea.io/gitea/services/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
// specialAdd registers the SSPI auth method as the last method in the list.
|
||||||
|
// The SSPI plugin is expected to be executed last, as it returns 401 status code if negotiation
|
||||||
|
// fails (or if negotiation should continue), which would prevent other authentication methods
|
||||||
|
// to execute at all.
|
||||||
|
func specialAdd(group *auth_service.Group) {
|
||||||
|
if auth.IsSSPIEnabled() {
|
||||||
|
group.Add(&auth_service.SSPI{})
|
||||||
|
}
|
||||||
|
}
|
|
@ -73,6 +73,26 @@ func CorsHandler() func(next http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
|
||||||
|
// in the session (if there is a user id stored in session other plugins might return the user
|
||||||
|
// object for that id).
|
||||||
|
//
|
||||||
|
// The Session plugin is expected to be executed second, in order to skip authentication
|
||||||
|
// for users that have already signed in.
|
||||||
|
func buildAuthGroup() *auth_service.Group {
|
||||||
|
group := auth_service.NewGroup(
|
||||||
|
&auth_service.OAuth2{}, // FIXME: this should be removed and only applied in download and oauth realted routers
|
||||||
|
&auth_service.Basic{}, // FIXME: this should be removed and only applied in download and git/lfs routers
|
||||||
|
auth_service.SharedSession,
|
||||||
|
)
|
||||||
|
if setting.Service.EnableReverseProxyAuth {
|
||||||
|
group.Add(&auth_service.ReverseProxy{})
|
||||||
|
}
|
||||||
|
specialAdd(group)
|
||||||
|
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
|
||||||
// Routes returns all web routes
|
// Routes returns all web routes
|
||||||
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
|
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
|
||||||
routes := web.NewRoute()
|
routes := web.NewRoute()
|
||||||
|
@ -160,8 +180,13 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
|
||||||
// Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary
|
// Removed: toolbox.Toolboxer middleware will provide debug information which seems unnecessary
|
||||||
common = append(common, context.Contexter())
|
common = append(common, context.Contexter())
|
||||||
|
|
||||||
|
group := buildAuthGroup()
|
||||||
|
if err := group.Init(); err != nil {
|
||||||
|
log.Error("Could not initialize '%s' auth method, error: %s", group.Name(), err)
|
||||||
|
}
|
||||||
|
|
||||||
// Get user from session if logged in.
|
// Get user from session if logged in.
|
||||||
common = append(common, context.Auth(auth_service.NewGroup(auth_service.Methods()...)))
|
common = append(common, context.Auth(group))
|
||||||
|
|
||||||
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
|
// GetHead allows a HEAD request redirect to GET if HEAD method is not defined for that route
|
||||||
common = append(common, middleware.GetHead)
|
common = append(common, middleware.GetHead)
|
||||||
|
|
|
@ -8,7 +8,6 @@ package auth
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -21,75 +20,22 @@ import (
|
||||||
"code.gitea.io/gitea/modules/web/middleware"
|
"code.gitea.io/gitea/modules/web/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// authMethods contains the list of authentication plugins in the order they are expected to be
|
|
||||||
// executed.
|
|
||||||
//
|
|
||||||
// The OAuth2 plugin is expected to be executed first, as it must ignore the user id stored
|
|
||||||
// in the session (if there is a user id stored in session other plugins might return the user
|
|
||||||
// object for that id).
|
|
||||||
//
|
|
||||||
// The Session plugin is expected to be executed second, in order to skip authentication
|
|
||||||
// for users that have already signed in.
|
|
||||||
var authMethods = []Method{
|
|
||||||
&OAuth2{},
|
|
||||||
&Basic{},
|
|
||||||
&Session{},
|
|
||||||
}
|
|
||||||
|
|
||||||
// The purpose of the following three function variables is to let the linter know that
|
// The purpose of the following three function variables is to let the linter know that
|
||||||
// those functions are not dead code and are actually being used
|
// those functions are not dead code and are actually being used
|
||||||
var (
|
var (
|
||||||
_ = handleSignIn
|
_ = handleSignIn
|
||||||
|
|
||||||
|
// SharedSession the session auth should only be used by web, but now both web and API/v1
|
||||||
|
// will use it. We can remove this after Web removed dependent API/v1
|
||||||
|
SharedSession = &Session{}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Methods returns the instances of all registered methods
|
|
||||||
func Methods() []Method {
|
|
||||||
return authMethods
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register adds the specified instance to the list of available methods
|
|
||||||
func Register(method Method) {
|
|
||||||
authMethods = append(authMethods, method)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Init should be called exactly once when the application starts to allow plugins
|
// Init should be called exactly once when the application starts to allow plugins
|
||||||
// to allocate necessary resources
|
// to allocate necessary resources
|
||||||
func Init() {
|
func Init() {
|
||||||
if setting.Service.EnableReverseProxyAuth {
|
|
||||||
Register(&ReverseProxy{})
|
|
||||||
}
|
|
||||||
specialInit()
|
|
||||||
for _, method := range Methods() {
|
|
||||||
initializable, ok := method.(Initializable)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := initializable.Init()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Could not initialize '%s' auth method, error: %s", reflect.TypeOf(method).String(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
webauthn.Init()
|
webauthn.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Free should be called exactly once when the application is terminating to allow Auth plugins
|
|
||||||
// to release necessary resources
|
|
||||||
func Free() {
|
|
||||||
for _, method := range Methods() {
|
|
||||||
freeable, ok := method.(Freeable)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err := freeable.Free()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Could not free '%s' auth method, error: %s", reflect.TypeOf(method).String(), err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isAttachmentDownload check if request is a file download (GET) with URL to an attachment
|
// isAttachmentDownload check if request is a file download (GET) with URL to an attachment
|
||||||
func isAttachmentDownload(req *http.Request) bool {
|
func isAttachmentDownload(req *http.Request) bool {
|
||||||
return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET"
|
return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET"
|
||||||
|
|
|
@ -6,6 +6,8 @@ package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
@ -30,6 +32,24 @@ func NewGroup(methods ...Method) *Group {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add adds a new method to group
|
||||||
|
func (b *Group) Add(method Method) {
|
||||||
|
b.methods = append(b.methods, method)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns group's methods name
|
||||||
|
func (b *Group) Name() string {
|
||||||
|
names := make([]string, 0, len(b.methods))
|
||||||
|
for _, m := range b.methods {
|
||||||
|
if n, ok := m.(Named); ok {
|
||||||
|
names = append(names, n.Name())
|
||||||
|
} else {
|
||||||
|
names = append(names, reflect.TypeOf(m).Elem().Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(names, ",")
|
||||||
|
}
|
||||||
|
|
||||||
// Init does nothing as the Basic implementation does not need to allocate any resources
|
// Init does nothing as the Basic implementation does not need to allocate any resources
|
||||||
func (b *Group) Init() error {
|
func (b *Group) Init() error {
|
||||||
for _, method := range b.methods {
|
for _, method := range b.methods {
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
// Copyright 2021 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.
|
|
||||||
|
|
||||||
//go:build !windows
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package auth
|
|
||||||
|
|
||||||
func specialInit() {}
|
|
|
@ -244,13 +244,3 @@ func sanitizeUsername(username string, cfg *sspi.Source) string {
|
||||||
username = replaceSeparators(username, cfg)
|
username = replaceSeparators(username, cfg)
|
||||||
return username
|
return username
|
||||||
}
|
}
|
||||||
|
|
||||||
// specialInit registers the SSPI auth method as the last method in the list.
|
|
||||||
// The SSPI plugin is expected to be executed last, as it returns 401 status code if negotiation
|
|
||||||
// fails (or if negotiation should continue), which would prevent other authentication methods
|
|
||||||
// to execute at all.
|
|
||||||
func specialInit() {
|
|
||||||
if auth.IsSSPIEnabled() {
|
|
||||||
Register(&SSPI{})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Reference in a new issue