diff --git a/.drone.yml b/.drone.yml index e97d65e5d..716de169a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -33,6 +33,18 @@ steps: GOSUMDB: sum.golang.org 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 pull: always image: golang:1.15 diff --git a/modules/auth/sso/basic.go b/modules/auth/sso/basic.go index 2db1147fc..d2d25c6ce 100644 --- a/modules/auth/sso/basic.go +++ b/modules/auth/sso/basic.go @@ -47,7 +47,7 @@ func (b *Basic) IsEnabled() bool { // "Authorization" header of the request and returns the corresponding user object for that // name/token on successful validation. // 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") if len(baHead) == 0 { return nil diff --git a/modules/auth/sso/interface.go b/modules/auth/sso/interface.go index 5f7089a2d..c957fad02 100644 --- a/modules/auth/sso/interface.go +++ b/modules/auth/sso/interface.go @@ -40,5 +40,5 @@ type SingleSignOn interface { // or a new user object (with id = 0) populated with the information that was found // in the authentication data (username or email). // 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 } diff --git a/modules/auth/sso/oauth2.go b/modules/auth/sso/oauth2.go index 5c15ed425..fc22e2728 100644 --- a/modules/auth/sso/oauth2.go +++ b/modules/auth/sso/oauth2.go @@ -114,7 +114,7 @@ func (o *OAuth2) IsEnabled() bool { // or the "Authorization" header and returns the corresponding user object for that ID. // If verification is successful returns an existing user object. // 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 { return nil } diff --git a/modules/auth/sso/reverseproxy.go b/modules/auth/sso/reverseproxy.go index c1bd4e395..ca9450e71 100644 --- a/modules/auth/sso/reverseproxy.go +++ b/modules/auth/sso/reverseproxy.go @@ -60,7 +60,7 @@ func (r *ReverseProxy) IsEnabled() bool { // If a username is available in the "setting.ReverseProxyAuthUser" header an existing // user object is returned (populated with username or email found in header). // 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) if len(username) == 0 { return nil diff --git a/modules/auth/sso/session.go b/modules/auth/sso/session.go index b64f477e0..7a546577d 100644 --- a/modules/auth/sso/session.go +++ b/modules/auth/sso/session.go @@ -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 // object for that uid. // 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) if user != nil { return user diff --git a/modules/auth/sso/sspi_windows.go b/modules/auth/sso/sspi_windows.go index 5850dcf6e..44dfa81aa 100644 --- a/modules/auth/sso/sspi_windows.go +++ b/modules/auth/sso/sspi_windows.go @@ -7,19 +7,17 @@ package sso import ( "errors" "net/http" - "reflect" "strings" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/log" "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" "github.com/quasoft/websspi" + "github.com/unrolled/render" ) const ( @@ -41,6 +39,7 @@ var ( // On successful authentication returns a valid user object. // Returns nil if authentication fails. type SSPI struct { + rnd *render.Render } // Init creates a new global websspi.Authenticator object @@ -48,7 +47,18 @@ func (s *SSPI) Init() error { config := websspi.NewConfig() var err error 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 @@ -65,8 +75,8 @@ func (s *SSPI) IsEnabled() bool { // If authentication is successful, returs the corresponding user object. // If negotiation should continue or authentication fails, immediately returns a 401 HTTP // 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 } @@ -76,22 +86,29 @@ func (s *SSPI) VerifyAuthData(req *http.Request, store DataStore, sess SessionSt return nil } - userInfo, outToken, err := sspiAuth.Authenticate(req, ctx.Resp) + userInfo, outToken, err := sspiAuth.Authenticate(req, w) if err != nil { 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 // to login with another authentication method if SSPI authentication // 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 } if outToken != "" { - sspiAuth.AppendAuthenticateHeader(ctx.Resp, outToken) + sspiAuth.AppendAuthenticateHeader(w, outToken) } 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) return nil } - user, err = s.newUser(ctx, username, cfg) + user, err = s.newUser(username, cfg) if err != nil { log.Error("CreateUser: %v", err) 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 - if !isAPIPath(ctx) && !isAttachmentDownload(ctx) { - handleSignIn(ctx, sess, user) + if !isAPIPath(req) && !isAttachmentDownload(req) { + handleSignIn(w, req, sess, user) } return user @@ -146,7 +163,7 @@ func (s *SSPI) shouldAuthenticate(req *http.Request) (shouldAuth bool) { if path == "/user/login" { if req.FormValue("user_name") != "" && req.FormValue("password") != "" { shouldAuth = false - } else if ctx.Req.FormValue("auth_with_sspi") == "1" { + } else if req.FormValue("auth_with_sspi") == "1" { shouldAuth = true } } else if isInternalPath(req) { @@ -217,20 +234,6 @@ func sanitizeUsername(username string, cfg *models.SSPIConfig) string { 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. // 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 diff --git a/modules/auth/sso/user.go b/modules/auth/sso/user.go index 69bbebccc..48eebb1e9 100644 --- a/modules/auth/sso/user.go +++ b/modules/auth/sso/user.go @@ -12,7 +12,7 @@ import ( // SignedInUser returns the user object of signed user. // 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 { return nil, false } @@ -22,7 +22,7 @@ func SignedInUser(req *http.Request, ds DataStore, sess SessionStore) (*models.U if !ssoMethod.IsEnabled() { continue } - user := ssoMethod.VerifyAuthData(req, ds, sess) + user := ssoMethod.VerifyAuthData(req, w, ds, sess) if user != nil { _, isBasic := ssoMethod.(*Basic) return user, isBasic diff --git a/modules/context/context.go b/modules/context/context.go index c35a19480..1ee31e0eb 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -309,7 +309,7 @@ func Contexter() macaron.Handler { } // 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 { ctx.IsSigned = true diff --git a/modules/graceful/manager_windows.go b/modules/graceful/manager_windows.go index d412e94f9..1ebdaa197 100644 --- a/modules/graceful/manager_windows.go +++ b/modules/graceful/manager_windows.go @@ -73,7 +73,7 @@ func (g *Manager) start() { // Make SVC process run := svc.Run - isInteractive, err := svc.IsAnInteractiveSession() + isInteractive, err := svc.IsWindowsService() if err != nil { log.Error("Unable to ascertain if running as an Interactive Session: %v", err) return @@ -81,7 +81,9 @@ func (g *Manager) start() { if isInteractive { run = debug.Run } - go run(WindowsServiceName, g) + go func() { + _ = run(WindowsServiceName, g) + }() } // Execute makes Manager implement svc.Handler diff --git a/modules/log/console_windows.go b/modules/log/console_windows.go index 61469aacf..ea47e5d62 100644 --- a/modules/log/console_windows.go +++ b/modules/log/console_windows.go @@ -23,7 +23,7 @@ func enableVTMode(console windows.Handle) bool { // 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 // handle to say don't color - mode = mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING + mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING err = windows.SetConsoleMode(console, mode) return err == nil } diff --git a/routers/routes/recovery.go b/routers/routes/recovery.go index 870a219c4..cf4b1a8d8 100644 --- a/routers/routes/recovery.go +++ b/routers/routes/recovery.go @@ -75,7 +75,7 @@ func Recovery() func(next http.Handler) http.Handler { } // Get user from session if logged in. - user, _ := sso.SignedInUser(req, &store, sess) + user, _ := sso.SignedInUser(req, w, &store, sess) if user != nil { store.Data["IsSigned"] = true store.Data["SignedUser"] = user