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>
tokarchuk/v1.17
Lunny Xiao 3 years ago committed by GitHub
parent d6fa138e7c
commit 6526733a58
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      routers/api/v1/api.go
  2. 12
      routers/api/v1/auth.go
  3. 20
      routers/api/v1/auth_windows.go
  4. 12
      routers/web/auth.go
  5. 20
      routers/web/auth_windows.go
  6. 27
      routers/web/web.go
  7. 62
      services/auth/auth.go
  8. 20
      services/auth/group.go
  9. 10
      services/auth/placeholder.go
  10. 10
      services/auth/sspi_windows.go

@ -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,

@ -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) {}

@ -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{})
}
}

@ -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) {}

@ -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
)
// Methods returns the instances of all registered methods // SharedSession the session auth should only be used by web, but now both web and API/v1
func Methods() []Method { // will use it. We can remove this after Web removed dependent API/v1
return authMethods SharedSession = &Session{}
} )
// 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{})
}
}

Loading…
Cancel
Save