Skip to content

Commit

Permalink
Account autocreation from LDAP after reverse proxy authentication
Browse files Browse the repository at this point in the history
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: gogs/gogs#2498
Author-Change-Id: IB#1104925
  • Loading branch information
pboguslawski committed Jan 29, 2022
1 parent b7c6457 commit a5c21f1
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 11 deletions.
60 changes: 52 additions & 8 deletions services/auth/reverseproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down
19 changes: 16 additions & 3 deletions services/auth/source/ldap/source_search.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"strings"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"

"github.com/go-ldap/ldap/v3"
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit a5c21f1

Please sign in to comment.