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

Make SSPI auth mockable #27036

Merged
merged 5 commits into from
Sep 17, 2023
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
5 changes: 4 additions & 1 deletion routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,10 @@ func buildAuthGroup() *auth.Group {
if setting.Service.EnableReverseProxyAuthAPI {
group.Add(&auth.ReverseProxy{})
}
specialAdd(group)

if setting.IsWindows && auth_model.IsSSPIEnabled() {
group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
}

return group
}
Expand Down
10 changes: 0 additions & 10 deletions routers/api/v1/auth.go

This file was deleted.

19 changes: 0 additions & 19 deletions routers/api/v1/auth_windows.go

This file was deleted.

10 changes: 0 additions & 10 deletions routers/web/auth.go

This file was deleted.

19 changes: 0 additions & 19 deletions routers/web/auth_windows.go

This file was deleted.

6 changes: 5 additions & 1 deletion routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"strings"

auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/context"
Expand Down Expand Up @@ -92,7 +93,10 @@ func buildAuthGroup() *auth_service.Group {
if setting.Service.EnableReverseProxyAuth {
group.Add(&auth_service.ReverseProxy{})
}
specialAdd(group)

if setting.IsWindows && auth_model.IsSSPIEnabled() {
group.Add(&auth_service.SSPI{}) // it MUST be the last, see the comment of SSPI
}

return group
}
Expand Down
30 changes: 14 additions & 16 deletions services/auth/sspi_windows.go → services/auth/sspi.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,31 @@ import (
"code.gitea.io/gitea/services/auth/source/sspi"

gouuid "github.com/google/uuid"
"github.com/quasoft/websspi"
)

const (
tplSignIn base.TplName = "user/auth/signin"
)

type SSPIAuth interface {
AppendAuthenticateHeader(w http.ResponseWriter, data string)
Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error)
}

var (
// sspiAuth is a global instance of the websspi authentication package,
// which is used to avoid acquiring the server credential handle on
// every request
sspiAuth *websspi.Authenticator
sspiAuthOnce sync.Once
sspiAuth SSPIAuth // a global instance of the websspi authenticator to avoid acquiring the server credential handle on every request
sspiAuthOnce sync.Once
sspiAuthErrInit error

// Ensure the struct implements the interface.
_ Method = &SSPI{}
)

// SSPI implements the SingleSignOn interface and authenticates requests
// via the built-in SSPI module in Windows for SPNEGO authentication.
// On successful authentication returns a valid user object.
// Returns nil if authentication fails.
// 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.
type SSPI struct{}

// Name represents the name of auth method
Expand All @@ -56,15 +59,10 @@ func (s *SSPI) Name() string {
// If negotiation should continue or authentication fails, immediately returns a 401 HTTP
// response code, as required by the SPNEGO protocol.
func (s *SSPI) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) {
var errInit error
sspiAuthOnce.Do(func() {
config := websspi.NewConfig()
sspiAuth, errInit = websspi.New(config)
})
if errInit != nil {
return nil, errInit
sspiAuthOnce.Do(func() { sspiAuthErrInit = sspiAuthInit() })
if sspiAuthErrInit != nil {
return nil, sspiAuthErrInit
}

if !s.shouldAuthenticate(req) {
return nil, nil
}
Expand Down
30 changes: 30 additions & 0 deletions services/auth/sspiauth_posix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

//go:build !windows

package auth

import (
"errors"
"net/http"
)

type SSPIUserInfo struct {
Username string // Name of user, usually in the form DOMAIN\User
Groups []string // The global groups the user is a member of
}

type sspiAuthMock struct{}

func (s sspiAuthMock) AppendAuthenticateHeader(w http.ResponseWriter, data string) {
}

func (s sspiAuthMock) Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error) {
return nil, "", errors.New("not implemented")
}

func sspiAuthInit() error {
sspiAuth = &sspiAuthMock{} // TODO: we can mock the SSPI auth in tests
return nil
}
19 changes: 19 additions & 0 deletions services/auth/sspiauth_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

//go:build windows

package auth

import (
"github.com/quasoft/websspi"
)

type SSPIUserInfo = websspi.UserInfo

func sspiAuthInit() error {
var err error
config := websspi.NewConfig()
sspiAuth, err = websspi.New(config)
return err
}