From a5c21f1cbc875e4e217af88124401510791c9bae Mon Sep 17 00:00:00 2001 From: Pawel Boguslawski Date: Sat, 29 Jan 2022 17:27:16 +0100 Subject: [PATCH 1/3] Account autocreation from LDAP after reverse proxy authentication Gitea allows autocreation of account from external source after successful basic auth but not after successful reverse proxy auth. This mod adds such feature. Unfortunaltely gitea does not sync all user attributes from LDAP for existing users on login like cron.sync_external_users does so changes of first name, surname, e-mail are not updated from LDAP on login for exiting users - only after first login and after sync_external_users task. Related: https://github.com/gogs/gogs/issues/2498 Author-Change-Id: IB#1104925 --- services/auth/reverseproxy.go | 60 +++++++++++++++++++--- services/auth/source/ldap/source_search.go | 19 +++++-- 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 3e44d8b8639ca..9275432793279 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,74 @@ 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 + } + + // Just return user if session is estabilshed already. + user := SessionUser(sess) + if user != nil { + return user + } + 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_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 From ca00ec69845f5d3905a22954e9d9cef160c4180f Mon Sep 17 00:00:00 2001 From: Pawel Boguslawski Date: Fri, 4 Feb 2022 10:28:08 +0100 Subject: [PATCH 2/3] Added missing data synchronization after LDAP auth Gitea should synchronize same data from LDAP on user authentication that is synchronized in cron task. Fixes: a5c21f1cbc875e4e217af88124401510791c9bae Related: https://github.com/go-gitea/gitea/pull/18452 Author-Change-Id: IB#1104925 --- services/auth/source/ldap/source_authenticate.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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 { From dfc544c401c2674886ec40c6587997d5a9337b56 Mon Sep 17 00:00:00 2001 From: Pawel Boguslawski Date: Thu, 28 Mar 2024 12:08:14 +0100 Subject: [PATCH 3/3] Remove unnecessary session verification Fixes: a5c21f1cbc875e4e217af88124401510791c9bae Related: https://github.com/go-gitea/gitea/pull/18452#discussion_r1438716251 Author-Change-Id: IB#1104925 Signed-off-by: Pawel Boguslawski --- services/auth/reverseproxy.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/services/auth/reverseproxy.go b/services/auth/reverseproxy.go index 9275432793279..60fcb642b1262 100644 --- a/services/auth/reverseproxy.go +++ b/services/auth/reverseproxy.go @@ -65,11 +65,7 @@ func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store Da return nil } - // Just return user if session is estabilshed already. - user := SessionUser(sess) - if user != nil { - return user - } + var user *user_model.User = nil username := r.getUserName(req) if len(username) == 0 {