* fix build * take flash error message back and fix more windows lint error * performance optimization * own step to check lint for windows Co-authored-by: 6543 <6543@obermui.de>tags/v1.15.0-dev
@@ -33,6 +33,18 @@ steps: | |||||
GOSUMDB: sum.golang.org | GOSUMDB: sum.golang.org | ||||
TAGS: bindata sqlite sqlite_unlock_notify | TAGS: bindata sqlite sqlite_unlock_notify | ||||
- name: lint-backend-windows | |||||
pull: always | |||||
image: golang:1.15 | |||||
commands: | |||||
- make golangci-lint vet | |||||
environment: | |||||
GOPROXY: https://goproxy.cn # proxy.golang.org is blocked in China, this proxy is not | |||||
GOSUMDB: sum.golang.org | |||||
TAGS: bindata sqlite sqlite_unlock_notify | |||||
GOOS: windows | |||||
GOARCH: amd64 | |||||
- name: lint-backend-gogit | - name: lint-backend-gogit | ||||
pull: always | pull: always | ||||
image: golang:1.15 | image: golang:1.15 | ||||
@@ -47,7 +47,7 @@ func (b *Basic) IsEnabled() bool { | |||||
// "Authorization" header of the request and returns the corresponding user object for that | // "Authorization" header of the request and returns the corresponding user object for that | ||||
// name/token on successful validation. | // name/token on successful validation. | ||||
// Returns nil if header is empty or validation fails. | // Returns nil if header is empty or validation fails. | ||||
func (b *Basic) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User { | |||||
func (b *Basic) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | |||||
baHead := req.Header.Get("Authorization") | baHead := req.Header.Get("Authorization") | ||||
if len(baHead) == 0 { | if len(baHead) == 0 { | ||||
return nil | return nil | ||||
@@ -40,5 +40,5 @@ type SingleSignOn interface { | |||||
// or a new user object (with id = 0) populated with the information that was found | // or a new user object (with id = 0) populated with the information that was found | ||||
// in the authentication data (username or email). | // in the authentication data (username or email). | ||||
// Returns nil if verification fails. | // Returns nil if verification fails. | ||||
VerifyAuthData(http *http.Request, store DataStore, sess SessionStore) *models.User | |||||
VerifyAuthData(http *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User | |||||
} | } |
@@ -114,7 +114,7 @@ func (o *OAuth2) IsEnabled() bool { | |||||
// or the "Authorization" header and returns the corresponding user object for that ID. | // or the "Authorization" header and returns the corresponding user object for that ID. | ||||
// If verification is successful returns an existing user object. | // If verification is successful returns an existing user object. | ||||
// Returns nil if verification fails. | // Returns nil if verification fails. | ||||
func (o *OAuth2) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User { | |||||
func (o *OAuth2) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | |||||
if !models.HasEngine { | if !models.HasEngine { | ||||
return nil | return nil | ||||
} | } | ||||
@@ -60,7 +60,7 @@ func (r *ReverseProxy) IsEnabled() bool { | |||||
// If a username is available in the "setting.ReverseProxyAuthUser" header an existing | // If a username is available in the "setting.ReverseProxyAuthUser" header an existing | ||||
// user object is returned (populated with username or email found in header). | // user object is returned (populated with username or email found in header). | ||||
// Returns nil if header is empty. | // Returns nil if header is empty. | ||||
func (r *ReverseProxy) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User { | |||||
func (r *ReverseProxy) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | |||||
username := r.getUserName(req) | username := r.getUserName(req) | ||||
if len(username) == 0 { | if len(username) == 0 { | ||||
return nil | return nil | ||||
@@ -39,7 +39,7 @@ func (s *Session) IsEnabled() bool { | |||||
// VerifyAuthData checks if there is a user uid stored in the session and returns the user | // VerifyAuthData checks if there is a user uid stored in the session and returns the user | ||||
// object for that uid. | // object for that uid. | ||||
// Returns nil if there is no user uid stored in the session. | // Returns nil if there is no user uid stored in the session. | ||||
func (s *Session) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User { | |||||
func (s *Session) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | |||||
user := SessionUser(sess) | user := SessionUser(sess) | ||||
if user != nil { | if user != nil { | ||||
return user | return user | ||||
@@ -7,19 +7,17 @@ package sso | |||||
import ( | import ( | ||||
"errors" | "errors" | ||||
"net/http" | "net/http" | ||||
"reflect" | |||||
"strings" | "strings" | ||||
"code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
"code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
"code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
"gitea.com/macaron/macaron" | |||||
"gitea.com/macaron/session" | |||||
"code.gitea.io/gitea/modules/templates" | |||||
gouuid "github.com/google/uuid" | gouuid "github.com/google/uuid" | ||||
"github.com/quasoft/websspi" | "github.com/quasoft/websspi" | ||||
"github.com/unrolled/render" | |||||
) | ) | ||||
const ( | const ( | ||||
@@ -41,6 +39,7 @@ var ( | |||||
// On successful authentication returns a valid user object. | // On successful authentication returns a valid user object. | ||||
// Returns nil if authentication fails. | // Returns nil if authentication fails. | ||||
type SSPI struct { | type SSPI struct { | ||||
rnd *render.Render | |||||
} | } | ||||
// Init creates a new global websspi.Authenticator object | // Init creates a new global websspi.Authenticator object | ||||
@@ -48,7 +47,18 @@ func (s *SSPI) Init() error { | |||||
config := websspi.NewConfig() | config := websspi.NewConfig() | ||||
var err error | var err error | ||||
sspiAuth, err = websspi.New(config) | sspiAuth, err = websspi.New(config) | ||||
return err | |||||
if err != nil { | |||||
return err | |||||
} | |||||
s.rnd = render.New(render.Options{ | |||||
Extensions: []string{".tmpl"}, | |||||
Directory: "templates", | |||||
Funcs: templates.NewFuncMap(), | |||||
Asset: templates.GetAsset, | |||||
AssetNames: templates.GetAssetNames, | |||||
IsDevelopment: setting.RunMode != "prod", | |||||
}) | |||||
return nil | |||||
} | } | ||||
// Free releases resources used by the global websspi.Authenticator object | // Free releases resources used by the global websspi.Authenticator object | ||||
@@ -65,8 +75,8 @@ func (s *SSPI) IsEnabled() bool { | |||||
// If authentication is successful, returs the corresponding user object. | // If authentication is successful, returs the corresponding user object. | ||||
// If negotiation should continue or authentication fails, immediately returns a 401 HTTP | // If negotiation should continue or authentication fails, immediately returns a 401 HTTP | ||||
// response code, as required by the SPNEGO protocol. | // response code, as required by the SPNEGO protocol. | ||||
func (s *SSPI) VerifyAuthData(req *http.Request, store DataStore, sess SessionStore) *models.User { | |||||
if !s.shouldAuthenticate(ctx) { | |||||
func (s *SSPI) VerifyAuthData(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *models.User { | |||||
if !s.shouldAuthenticate(req) { | |||||
return nil | return nil | ||||
} | } | ||||
@@ -76,22 +86,29 @@ func (s *SSPI) VerifyAuthData(req *http.Request, store DataStore, sess SessionSt | |||||
return nil | return nil | ||||
} | } | ||||
userInfo, outToken, err := sspiAuth.Authenticate(req, ctx.Resp) | |||||
userInfo, outToken, err := sspiAuth.Authenticate(req, w) | |||||
if err != nil { | if err != nil { | ||||
log.Warn("Authentication failed with error: %v\n", err) | log.Warn("Authentication failed with error: %v\n", err) | ||||
sspiAuth.AppendAuthenticateHeader(ctx.Resp, outToken) | |||||
sspiAuth.AppendAuthenticateHeader(w, outToken) | |||||
// Include the user login page in the 401 response to allow the user | // Include the user login page in the 401 response to allow the user | ||||
// to login with another authentication method if SSPI authentication | // to login with another authentication method if SSPI authentication | ||||
// fails | // fails | ||||
addFlashErr(ctx, ctx.Tr("auth.sspi_auth_failed")) | |||||
ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn | |||||
ctx.Data["EnableSSPI"] = true | |||||
ctx.HTML(401, string(tplSignIn)) | |||||
store.GetData()["Flash"] = map[string]string{ | |||||
"ErrMsg": err.Error(), | |||||
} | |||||
store.GetData()["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn | |||||
store.GetData()["EnableSSPI"] = true | |||||
err := s.rnd.HTML(w, 401, string(tplSignIn), templates.BaseVars().Merge(store.GetData())) | |||||
if err != nil { | |||||
log.Error("%v", err) | |||||
} | |||||
return nil | return nil | ||||
} | } | ||||
if outToken != "" { | if outToken != "" { | ||||
sspiAuth.AppendAuthenticateHeader(ctx.Resp, outToken) | |||||
sspiAuth.AppendAuthenticateHeader(w, outToken) | |||||
} | } | ||||
username := sanitizeUsername(userInfo.Username, cfg) | username := sanitizeUsername(userInfo.Username, cfg) | ||||
@@ -110,7 +127,7 @@ func (s *SSPI) VerifyAuthData(req *http.Request, store DataStore, sess SessionSt | |||||
log.Error("User '%s' not found", username) | log.Error("User '%s' not found", username) | ||||
return nil | return nil | ||||
} | } | ||||
user, err = s.newUser(ctx, username, cfg) | |||||
user, err = s.newUser(username, cfg) | |||||
if err != nil { | if err != nil { | ||||
log.Error("CreateUser: %v", err) | log.Error("CreateUser: %v", err) | ||||
return nil | return nil | ||||
@@ -118,8 +135,8 @@ func (s *SSPI) VerifyAuthData(req *http.Request, store DataStore, sess SessionSt | |||||
} | } | ||||
// Make sure requests to API paths and PWA resources do not create a new session | // Make sure requests to API paths and PWA resources do not create a new session | ||||
if !isAPIPath(ctx) && !isAttachmentDownload(ctx) { | |||||
handleSignIn(ctx, sess, user) | |||||
if !isAPIPath(req) && !isAttachmentDownload(req) { | |||||
handleSignIn(w, req, sess, user) | |||||
} | } | ||||
return user | return user | ||||
@@ -146,7 +163,7 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) { | |||||
if path == "/user/login" { | if path == "/user/login" { | ||||
if req.FormValue("user_name") != "" && req.FormValue("password") != "" { | if req.FormValue("user_name") != "" && req.FormValue("password") != "" { | ||||
shouldAuth = false | shouldAuth = false | ||||
} else if ctx.Req.FormValue("auth_with_sspi") == "1" { | |||||
} else if req.FormValue("auth_with_sspi") == "1" { | |||||
shouldAuth = true | shouldAuth = true | ||||
} | } | ||||
} else if isInternalPath(req) { | } else if isInternalPath(req) { | ||||
@@ -217,20 +234,6 @@ func sanitizeUsername(username string, cfg *models.SSPIConfig) string { | |||||
return username | return username | ||||
} | } | ||||
// addFlashErr adds an error message to the Flash object mapped to a macaron.Context | |||||
func addFlashErr(ctx *macaron.Context, err string) { | |||||
fv := ctx.GetVal(reflect.TypeOf(&session.Flash{})) | |||||
if !fv.IsValid() { | |||||
return | |||||
} | |||||
flash, ok := fv.Interface().(*session.Flash) | |||||
if !ok { | |||||
return | |||||
} | |||||
flash.Error(err) | |||||
ctx.Data["Flash"] = flash | |||||
} | |||||
// init registers the SSPI auth method as the last method in the list. | // init 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 | // 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 | // fails (or if negotiation should continue), which would prevent other authentication methods | ||||
@@ -12,7 +12,7 @@ import ( | |||||
// SignedInUser returns the user object of signed user. | // SignedInUser returns the user object of signed user. | ||||
// It returns a bool value to indicate whether user uses basic auth or not. | // It returns a bool value to indicate whether user uses basic auth or not. | ||||
func SignedInUser(req *http.Request, ds DataStore, sess SessionStore) (*models.User, bool) { | |||||
func SignedInUser(req *http.Request, w http.ResponseWriter, ds DataStore, sess SessionStore) (*models.User, bool) { | |||||
if !models.HasEngine { | if !models.HasEngine { | ||||
return nil, false | return nil, false | ||||
} | } | ||||
@@ -22,7 +22,7 @@ func SignedInUser(req *http.Request, ds DataStore, sess SessionStore) (*models.U | |||||
if !ssoMethod.IsEnabled() { | if !ssoMethod.IsEnabled() { | ||||
continue | continue | ||||
} | } | ||||
user := ssoMethod.VerifyAuthData(req, ds, sess) | |||||
user := ssoMethod.VerifyAuthData(req, w, ds, sess) | |||||
if user != nil { | if user != nil { | ||||
_, isBasic := ssoMethod.(*Basic) | _, isBasic := ssoMethod.(*Basic) | ||||
return user, isBasic | return user, isBasic | ||||
@@ -309,7 +309,7 @@ func Contexter() macaron.Handler { | |||||
} | } | ||||
// Get user from session if logged in. | // Get user from session if logged in. | ||||
ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req.Request, ctx, ctx.Session) | |||||
ctx.User, ctx.IsBasicAuth = sso.SignedInUser(ctx.Req.Request, c.Resp, ctx, ctx.Session) | |||||
if ctx.User != nil { | if ctx.User != nil { | ||||
ctx.IsSigned = true | ctx.IsSigned = true | ||||
@@ -73,7 +73,7 @@ func (g *Manager) start() { | |||||
// Make SVC process | // Make SVC process | ||||
run := svc.Run | run := svc.Run | ||||
isInteractive, err := svc.IsAnInteractiveSession() | |||||
isInteractive, err := svc.IsWindowsService() | |||||
if err != nil { | if err != nil { | ||||
log.Error("Unable to ascertain if running as an Interactive Session: %v", err) | log.Error("Unable to ascertain if running as an Interactive Session: %v", err) | ||||
return | return | ||||
@@ -81,7 +81,9 @@ func (g *Manager) start() { | |||||
if isInteractive { | if isInteractive { | ||||
run = debug.Run | run = debug.Run | ||||
} | } | ||||
go run(WindowsServiceName, g) | |||||
go func() { | |||||
_ = run(WindowsServiceName, g) | |||||
}() | |||||
} | } | ||||
// Execute makes Manager implement svc.Handler | // Execute makes Manager implement svc.Handler | ||||
@@ -23,7 +23,7 @@ func enableVTMode(console windows.Handle) bool { | |||||
// https://docs.microsoft.com/en-us/windows/console/setconsolemode | // https://docs.microsoft.com/en-us/windows/console/setconsolemode | ||||
// It only works on windows 10. Earlier terminals will fail with an err which we will | // It only works on windows 10. Earlier terminals will fail with an err which we will | ||||
// handle to say don't color | // handle to say don't color | ||||
mode = mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | |||||
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING | |||||
err = windows.SetConsoleMode(console, mode) | err = windows.SetConsoleMode(console, mode) | ||||
return err == nil | return err == nil | ||||
} | } | ||||
@@ -75,7 +75,7 @@ func Recovery() func(next http.Handler) http.Handler { | |||||
} | } | ||||
// Get user from session if logged in. | // Get user from session if logged in. | ||||
user, _ := sso.SignedInUser(req, &store, sess) | |||||
user, _ := sso.SignedInUser(req, w, &store, sess) | |||||
if user != nil { | if user != nil { | ||||
store.Data["IsSigned"] = true | store.Data["IsSigned"] = true | ||||
store.Data["SignedUser"] = user | store.Data["SignedUser"] = user | ||||