diff --git a/models/login_source.go b/models/login_source.go index 1de24a9cf70b5..5215f4275f3af 100644 --- a/models/login_source.go +++ b/models/login_source.go @@ -456,8 +456,8 @@ func composeFullName(firstname, surname, username string) string { // LoginViaLDAP queries if login/password is valid against the LDAP directory pool, // and create a local user if success when enabled. -func LoginViaLDAP(user *User, login, password string, source *LoginSource) (*User, error) { - sr := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP) +func LoginViaLDAP(user *User, login, password string, alreadyAuthenticated bool, source *LoginSource) (*User, error) { + sr := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LoginDLDAP, alreadyAuthenticated) if sr == nil { // User not in LDAP, do nothing return nil, ErrUserNotExist{0, login, 0} @@ -701,7 +701,7 @@ func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMCon } // ExternalUserLogin attempts a login using external source types. -func ExternalUserLogin(user *User, login, password string, source *LoginSource) (*User, error) { +func ExternalUserLogin(user *User, login, password string, alreadyAuthenticated bool, source *LoginSource) (*User, error) { if !source.IsActived { return nil, ErrLoginSourceNotActived } @@ -709,7 +709,7 @@ func ExternalUserLogin(user *User, login, password string, source *LoginSource) var err error switch source.Type { case LoginLDAP, LoginDLDAP: - user, err = LoginViaLDAP(user, login, password, source) + user, err = LoginViaLDAP(user, login, password, alreadyAuthenticated, source) case LoginSMTP: user, err = LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig)) case LoginPAM: @@ -731,8 +731,8 @@ func ExternalUserLogin(user *User, login, password string, source *LoginSource) return user, nil } -// UserSignIn validates user name and password. -func UserSignIn(username, password string) (*User, error) { +// UserSignIn validates user name and password. Password verification in LDAP skipped if already authenticated. +func UserSignIn(username, password string, alreadyAuthenticated bool) (*User, error) { var user *User if strings.Contains(username, "@") { user = &User{Email: strings.ToLower(strings.TrimSpace(username))} @@ -793,7 +793,7 @@ func UserSignIn(username, password string) (*User, error) { return nil, ErrLoginSourceNotExist{user.LoginSource} } - return ExternalUserLogin(user, user.LoginName, password, &source) + return ExternalUserLogin(user, user.LoginName, password, alreadyAuthenticated, &source) } } @@ -807,7 +807,7 @@ func UserSignIn(username, password string) (*User, error) { // don't try to authenticate against OAuth2 and SSPI sources here continue } - authUser, err := ExternalUserLogin(nil, username, password, source) + authUser, err := ExternalUserLogin(nil, username, password, alreadyAuthenticated, source) if err == nil { return authUser, nil } diff --git a/modules/auth/ldap/ldap.go b/modules/auth/ldap/ldap.go index 7649639d36195..288ae724561e8 100644 --- a/modules/auth/ldap/ldap.go +++ b/modules/auth/ldap/ldap.go @@ -230,12 +230,22 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { } // SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter -func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResult { +func (ls *Source) SearchEntry(name, passwd string, directBind bool, alreadyAuthenticated bool) *SearchResult { // See https://tools.ietf.org/search/rfc4513#section-5.1.2 - if len(passwd) == 0 { + if len(passwd) == 0 && !alreadyAuthenticated { log.Debug("Auth. failed for %s, password cannot be empty", name) return nil } + if directBind && alreadyAuthenticated { + log.Debug("Cannot bind using user %s credentials - user already authenticated. BindDN must be used.", name) + return nil + } + + if !ls.AttributesInBind && alreadyAuthenticated { + log.Debug("Cannot get attributes using user %s credentials - user already authenticated; --attributes-in-bind must be used.", name) + return nil + } + l, err := dial(ls) if err != nil { log.Error("LDAP Connect error, %s:%v", ls.Host, err) @@ -393,8 +403,8 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul isRestricted = checkRestricted(l, ls, userDN) } - if !directBind && ls.AttributesInBind { - // binds user (checking password) after looking-up attributes in BindDN context + if !directBind && ls.AttributesInBind && !alreadyAuthenticated { + // binds user (checking password) after looking-up attributes in BindDN context if not already authenticated err = bindUser(l, userDN, passwd) if err != nil { return nil diff --git a/modules/auth/sso/basic.go b/modules/auth/sso/basic.go index b5885d38db1e0..52da13d293042 100644 --- a/modules/auth/sso/basic.go +++ b/modules/auth/sso/basic.go @@ -100,7 +100,7 @@ func (b *Basic) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models } if u == nil { - u, err = models.UserSignIn(uname, passwd) + u, err = models.UserSignIn(uname, passwd, false) if err != nil { if !models.IsErrUserNotExist(err) { log.Error("UserSignIn: %v", err) diff --git a/modules/auth/sso/reverseproxy.go b/modules/auth/sso/reverseproxy.go index 1b543ce1046b9..ab44bc94bc856 100644 --- a/modules/auth/sso/reverseproxy.go +++ b/modules/auth/sso/reverseproxy.go @@ -6,6 +6,7 @@ package sso import ( + "fmt" "strings" "code.gitea.io/gitea/models" @@ -60,20 +61,66 @@ func (r *ReverseProxy) IsEnabled() bool { // the revese proxy. // 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. +// Returns nil if header is empty or internal API is being called. func (r *ReverseProxy) VerifyAuthData(ctx *macaron.Context, sess session.Store) *models.User { + + // Internal API should not use this auth method. + if isInternalPath(ctx) { + return nil + } + + // Just return user if session is estabilshed already. + user := SessionUser(sess) + if user != nil { + return user + } + + // If no session established, get username from header. username := r.getUserName(ctx) if len(username) == 0 { return nil } - user, err := models.GetUserByName(username) - if err != nil { - if models.IsErrUserNotExist(err) && r.isAutoRegisterAllowed() { - return r.newUser(ctx) + var err error + + if r.isAutoRegisterAllowed() { + // Use auto registration from reverse proxy if ENABLE_REVERSE_PROXY_AUTO_REGISTRATION enabled. + if user, err = models.GetUserByName(username); err != nil { + if models.IsErrUserNotExist(err) && r.isAutoRegisterAllowed() { + if user = r.newUser(ctx); user == nil { + return nil + } + } else { + log.Error("GetUserByName: %v", err) + return nil + } } - log.Error("GetUserByName: %v", err) - return nil + } else { + // Use auto registration from other backends if ENABLE_REVERSE_PROXY_AUTO_REGISTRATION not enabled. + if user, err = models.UserSignIn(username, "", true); err != nil { + if !models.IsErrUserNotExist(err) { + log.Error("UserSignIn: %v", err) + } + return nil + } + } + + // Make sure requests to API paths and PWA resources do not create a new session. + if !isAPIPath(ctx) && !isAttachmentDownload(ctx) { + + // Update last user login timestamp. + user.SetLastLogin() + if err = models.UpdateUserCols(user, false, "last_login_unix"); err != nil { + log.Error(fmt.Sprintf("VerifyAuthData: error updating user last login time [user: %d]", user.ID)) + } + + // Initialize new session. Will set lang and CSRF cookies. + handleSignIn(ctx, sess, user) + + // Unfortunatelly we cannot do redirect here (would break git HTTP requests) to + // reload page with user locale so first page after login may be displayed in + // wrong language. Language handling in SSO mode should be reconsidered + // in future gitea versions. } return user diff --git a/routers/org/setting.go b/routers/org/setting.go index 454714c7eb8eb..5c281fc53a532 100644 --- a/routers/org/setting.go +++ b/routers/org/setting.go @@ -142,7 +142,7 @@ func SettingsDelete(ctx *context.Context) { org := ctx.Org.Organization if ctx.Req.Method == "POST" { - if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil { + if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password"), false); err != nil { if models.IsErrUserNotExist(err) { ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_password"), tplSettingsDelete, nil) } else { diff --git a/routers/repo/http.go b/routers/repo/http.go index 0d5aa2e3e262e..c8c045065eb1b 100644 --- a/routers/repo/http.go +++ b/routers/repo/http.go @@ -216,7 +216,7 @@ func HTTP(ctx *context.Context) { if authUser == nil { // Check username and password - authUser, err = models.UserSignIn(authUsername, authPasswd) + authUser, err = models.UserSignIn(authUsername, authPasswd, false) if err != nil { if models.IsErrUserProhibitLogin(err) { ctx.HandleText(http.StatusForbidden, "User is not permitted to login") diff --git a/routers/user/auth.go b/routers/user/auth.go index 96a73c9dd4632..dab5355e4366c 100644 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -169,7 +169,7 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { return } - u, err := models.UserSignIn(form.UserName, form.Password) + u, err := models.UserSignIn(form.UserName, form.Password, false) if err != nil { if models.IsErrUserNotExist(err) { ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) @@ -805,7 +805,7 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) { return } - u, err := models.UserSignIn(signInForm.UserName, signInForm.Password) + u, err := models.UserSignIn(signInForm.UserName, signInForm.Password, false) if err != nil { if models.IsErrUserNotExist(err) { ctx.Data["user_exists"] = true diff --git a/routers/user/auth_openid.go b/routers/user/auth_openid.go index ba2c8be8c24cb..df372967daacf 100644 --- a/routers/user/auth_openid.go +++ b/routers/user/auth_openid.go @@ -288,7 +288,7 @@ func ConnectOpenIDPost(ctx *context.Context, form auth.ConnectOpenIDForm) { ctx.Data["EnableOpenIDSignUp"] = setting.Service.EnableOpenIDSignUp ctx.Data["OpenID"] = oid - u, err := models.UserSignIn(form.UserName, form.Password) + u, err := models.UserSignIn(form.UserName, form.Password, false) if err != nil { if models.IsErrUserNotExist(err) { ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplConnectOID, &form) diff --git a/routers/user/setting/account.go b/routers/user/setting/account.go index 99e20177bc986..6282e191ee890 100644 --- a/routers/user/setting/account.go +++ b/routers/user/setting/account.go @@ -218,7 +218,7 @@ func DeleteAccount(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["PageIsSettingsAccount"] = true - if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password")); err != nil { + if _, err := models.UserSignIn(ctx.User.Name, ctx.Query("password"), false); err != nil { if models.IsErrUserNotExist(err) { loadAccountData(ctx)