diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 3e44d8b8639ca..60fcb642b1262 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -6,9 +6,11 @@ package auth import ( + "fmt" "net/http" "strings" + "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -55,32 +57,70 @@ func (r *ReverseProxy) Name() string { // 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) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *user_model.User { + + // Internal API should not use this auth method. + if middleware.IsInternalPath(req) { + return nil + } + + var user *user_model.User = nil + username := r.getUserName(req) if len(username) == 0 { return nil } log.Trace("ReverseProxy Authorization: Found username: %s", username) - user, err := user_model.GetUserByName(username) - if err != nil { - if !user_model.IsErrUserNotExist(err) || !r.isAutoRegisterAllowed() { - log.Error("GetUserByName: %v", err) + var err error + + if r.isAutoRegisterAllowed() { + // Use auto registration from reverse proxy if ENABLE_REVERSE_PROXY_AUTO_REGISTRATION enabled. + if user, err = user_model.GetUserByName(username); err != nil { + if user_model.IsErrUserNotExist(err) && r.isAutoRegisterAllowed() { + if user = r.newUser(req); user == nil { + return nil + } + } else { + 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 = UserSignIn(username, ""); err != nil { + if !user_model.IsErrUserNotExist(err) { + log.Error("UserSignIn: %v", err) + } return nil } - user = r.newUser(req) } // Make sure requests to API paths, attachment downloads, git and LFS do not create a new session if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawReleaseOrLFSPath(req) { if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) { + + // Register last login. + user.SetLastLogin() + + if err = user_model.UpdateUserCols(db.DefaultContext, user, "last_login_unix"); err != nil { + log.Error(fmt.Sprintf("ReverseProxy Authorization: error updating user last login time [user: %d]", user.ID)) + } + + // Initialize new session. Will set lang and CSRF cookies. handleSignIn(w, req, sess, user) + + log.Trace("ReverseProxy Authorization: Logged in user %-v", 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. } - store.GetData()["IsReverseProxy"] = true - log.Trace("ReverseProxy Authorization: Logged in user %-v", user) + store.GetData()["IsReverseProxy"] = true return user } diff --git a/services/auth/source/ldap/source_authenticate.go b/services/auth/source/ldap/source_authenticate.go index 52971bb87e58c..665f1a73d21fd 100644 --- a/services/auth/source/ldap/source_authenticate.go +++ b/services/auth/source/ldap/source_authenticate.go @@ -39,6 +39,17 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str } if user != nil && !user.ProhibitLogin { cols := make([]string, 0) + fullName := composeFullName(sr.Name, sr.Surname, sr.Username) + if user.FullName != fullName { + // Update user fullname if changed. + user.FullName = fullName + cols = append(cols, "full_name") + } + if !strings.EqualFold(user.Email, sr.Mail) { + // Update user e-mail if changed. + user.Email = sr.Mail + cols = append(cols, "email") + } if len(source.AdminFilter) > 0 && user.IsAdmin != sr.IsAdmin { // Change existing admin flag only if AdminFilter option is set user.IsAdmin = sr.IsAdmin @@ -49,6 +60,11 @@ func (source *Source) Authenticate(user *user_model.User, userName, password str user.IsRestricted = sr.IsRestricted cols = append(cols, "is_restricted") } + if !user.IsActive { + // User existing in LDAP should be active in application. + user.IsActive = true + cols = append(cols, "is_active") + } if len(cols) > 0 { err = user_model.UpdateUserCols(db.DefaultContext, user, cols...) if err != nil { diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index 1f1cca270d40a..d7a5a17f50dac 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "github.com/go-ldap/ldap/v3" ) @@ -194,11 +195,23 @@ 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 { + // See https://tools.ietf.org/search/rfc4513#section-5.1.2 - if len(passwd) == 0 { + // Don't authenticate against LDAP if already authenticated by reverse proxy. + if setting.Service.EnableReverseProxyAuth { + if directBind { + log.Debug("Cannot bind pre-authenticated user %s. BindDN must be used.", name) + return nil + } + if !ls.AttributesInBind { + log.Debug("Cannot get attributes for pre-authenticated user %s without --attributes-in-bind.", name) + return nil + } + } else if len(passwd) == 0 { log.Debug("Auth. failed for %s, password cannot be empty", name) return nil } + l, err := dial(ls) if err != nil { log.Error("LDAP Connect error, %s:%v", ls.Host, err) @@ -361,8 +374,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 && !setting.Service.EnableReverseProxyAuth { + // 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