* 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 | |||
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 |
@@ -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 |
@@ -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 | |||
} |
@@ -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 | |||
} |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 | |||
} |
@@ -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 |