@ -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"
"code.gitea.io/gitea/modules/templates"
"gitea.com/macaron/macaron"
"gitea.com/macaron/session"
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 )
if err != nil {
return err
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 {
func ( s * SSPI ) VerifyAuthData ( req * http . Request , w http . ResponseWriter , store DataStore , sess SessionStore ) * models . User {
if ! s . shouldAuthenticate ( ctx ) {
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" ) )
store . GetData ( ) [ "Flash" ] = map [ string ] string {
ctx . Data [ "EnableOpenIDSignIn" ] = setting . Service . EnableOpenIDSignIn
"ErrMsg" : err . Error ( ) ,
ctx . Data [ "EnableSSPI" ] = true
}
ctx . HTML ( 401 , string ( tplSignIn ) )
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 ) {
if ! isAPIPath ( req ) && ! isAttachmentDownload ( req ) {
handleSignIn ( ctx , sess , user )
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 . R eq. FormValue ( "auth_with_sspi" ) == "1" {
} else if r eq. 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