Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let web and API routes have different auth methods group #19168

Merged
merged 6 commits into from
Mar 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
m := web.NewRoute()
Expand All @@ -583,8 +603,13 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
}
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.
m.Use(context.APIAuth(auth.NewGroup(auth.Methods()...)))
m.Use(context.APIAuth(group))

m.Use(context.ToggleAPI(&context.ToggleOptions{
SignInRequired: setting.Service.RequireSignInView,
Expand Down
12 changes: 12 additions & 0 deletions routers/api/v1/auth.go
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions routers/api/v1/auth_windows.go
Original file line number Diff line number Diff line change
@@ -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 changes: 12 additions & 0 deletions routers/web/auth.go
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions routers/web/auth_windows.go
Original file line number Diff line number Diff line change
@@ -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{})
}
}
27 changes: 26 additions & 1 deletion routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
func Routes(sessioner func(http.Handler) http.Handler) *web.Route {
routes := web.NewRoute()
Expand Down Expand Up @@ -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
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.
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
common = append(common, middleware.GetHead)
Expand Down
62 changes: 4 additions & 58 deletions services/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ package auth
import (
"fmt"
"net/http"
"reflect"
"regexp"
"strings"

Expand All @@ -21,75 +20,22 @@ import (
"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
// those functions are not dead code and are actually being used
var (
_ = handleSignIn
)

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

// Init should be called exactly once when the application starts to allow plugins
// to allocate necessary resources
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()
}

// Free should be called exactly once when the application is terminating to allow Auth plugins
// to release necessary resources
func Free() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was that simply unused, or why can't I find a replacement for it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't find any reference for this function. It's wired.

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
func isAttachmentDownload(req *http.Request) bool {
return strings.HasPrefix(req.URL.Path, "/attachments/") && req.Method == "GET"
Expand Down
20 changes: 20 additions & 0 deletions services/auth/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package auth

import (
"net/http"
"reflect"
"strings"

"code.gitea.io/gitea/models/db"
user_model "code.gitea.io/gitea/models/user"
Expand All @@ -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
func (b *Group) Init() error {
for _, method := range b.methods {
Expand Down
10 changes: 0 additions & 10 deletions services/auth/placeholder.go

This file was deleted.

10 changes: 0 additions & 10 deletions services/auth/sspi_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,13 +244,3 @@ func sanitizeUsername(username string, cfg *sspi.Source) string {
username = replaceSeparators(username, cfg)
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{})
}
}