Skip to content

Commit 8531ca0

Browse files
authored
Make SSPI auth mockable (#27036)
Before, the SSPI auth is only complied for Windows, it's difficult to test and it breaks a lot. Now, make the SSPI auth mockable and testable.
1 parent 47b8788 commit 8531ca0

File tree

9 files changed

+72
-76
lines changed

9 files changed

+72
-76
lines changed

routers/api/v1/api.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -705,7 +705,10 @@ func buildAuthGroup() *auth.Group {
705705
if setting.Service.EnableReverseProxyAuthAPI {
706706
group.Add(&auth.ReverseProxy{})
707707
}
708-
specialAdd(group)
708+
709+
if setting.IsWindows && auth_model.IsSSPIEnabled() {
710+
group.Add(&auth.SSPI{}) // it MUST be the last, see the comment of SSPI
711+
}
709712

710713
return group
711714
}

routers/api/v1/auth.go

Lines changed: 0 additions & 10 deletions
This file was deleted.

routers/api/v1/auth_windows.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

routers/web/auth.go

Lines changed: 0 additions & 10 deletions
This file was deleted.

routers/web/auth_windows.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

routers/web/web.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"net/http"
99
"strings"
1010

11+
auth_model "code.gitea.io/gitea/models/auth"
1112
"code.gitea.io/gitea/models/perm"
1213
"code.gitea.io/gitea/models/unit"
1314
"code.gitea.io/gitea/modules/context"
@@ -92,7 +93,10 @@ func buildAuthGroup() *auth_service.Group {
9293
if setting.Service.EnableReverseProxyAuth {
9394
group.Add(&auth_service.ReverseProxy{})
9495
}
95-
specialAdd(group)
96+
97+
if setting.IsWindows && auth_model.IsSSPIEnabled() {
98+
group.Add(&auth_service.SSPI{}) // it MUST be the last, see the comment of SSPI
99+
}
96100

97101
return group
98102
}

services/auth/sspi_windows.go renamed to services/auth/sspi.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,28 +22,31 @@ import (
2222
"code.gitea.io/gitea/services/auth/source/sspi"
2323

2424
gouuid "github.com/google/uuid"
25-
"github.com/quasoft/websspi"
2625
)
2726

2827
const (
2928
tplSignIn base.TplName = "user/auth/signin"
3029
)
3130

31+
type SSPIAuth interface {
32+
AppendAuthenticateHeader(w http.ResponseWriter, data string)
33+
Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error)
34+
}
35+
3236
var (
33-
// sspiAuth is a global instance of the websspi authentication package,
34-
// which is used to avoid acquiring the server credential handle on
35-
// every request
36-
sspiAuth *websspi.Authenticator
37-
sspiAuthOnce sync.Once
37+
sspiAuth SSPIAuth // a global instance of the websspi authenticator to avoid acquiring the server credential handle on every request
38+
sspiAuthOnce sync.Once
39+
sspiAuthErrInit error
3840

3941
// Ensure the struct implements the interface.
4042
_ Method = &SSPI{}
4143
)
4244

4345
// SSPI implements the SingleSignOn interface and authenticates requests
4446
// via the built-in SSPI module in Windows for SPNEGO authentication.
45-
// On successful authentication returns a valid user object.
46-
// Returns nil if authentication fails.
47+
// The SSPI plugin is expected to be executed last, as it returns 401 status code if negotiation
48+
// fails (or if negotiation should continue), which would prevent other authentication methods
49+
// to execute at all.
4750
type SSPI struct{}
4851

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

services/auth/sspiauth_posix.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
//go:build !windows
5+
6+
package auth
7+
8+
import (
9+
"errors"
10+
"net/http"
11+
)
12+
13+
type SSPIUserInfo struct {
14+
Username string // Name of user, usually in the form DOMAIN\User
15+
Groups []string // The global groups the user is a member of
16+
}
17+
18+
type sspiAuthMock struct{}
19+
20+
func (s sspiAuthMock) AppendAuthenticateHeader(w http.ResponseWriter, data string) {
21+
}
22+
23+
func (s sspiAuthMock) Authenticate(r *http.Request, w http.ResponseWriter) (userInfo *SSPIUserInfo, outToken string, err error) {
24+
return nil, "", errors.New("not implemented")
25+
}
26+
27+
func sspiAuthInit() error {
28+
sspiAuth = &sspiAuthMock{} // TODO: we can mock the SSPI auth in tests
29+
return nil
30+
}

services/auth/sspiauth_windows.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
//go:build windows
5+
6+
package auth
7+
8+
import (
9+
"github.com/quasoft/websspi"
10+
)
11+
12+
type SSPIUserInfo = websspi.UserInfo
13+
14+
func sspiAuthInit() error {
15+
var err error
16+
config := websspi.NewConfig()
17+
sspiAuth, err = websspi.New(config)
18+
return err
19+
}

0 commit comments

Comments
 (0)