Skip to content

Commit a5c21f1

Browse files
committed
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: gogs/gogs#2498 Author-Change-Id: IB#1104925
1 parent b7c6457 commit a5c21f1

File tree

2 files changed

+68
-11
lines changed

2 files changed

+68
-11
lines changed

services/auth/reverseproxy.go

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
package auth
77

88
import (
9+
"fmt"
910
"net/http"
1011
"strings"
1112

13+
"code.gitea.io/gitea/models/db"
1214
user_model "code.gitea.io/gitea/models/user"
1315
"code.gitea.io/gitea/modules/log"
1416
"code.gitea.io/gitea/modules/setting"
@@ -55,32 +57,74 @@ func (r *ReverseProxy) Name() string {
5557
// the revese proxy.
5658
// If a username is available in the "setting.ReverseProxyAuthUser" header an existing
5759
// user object is returned (populated with username or email found in header).
58-
// Returns nil if header is empty.
60+
// Returns nil if header is empty or internal API is being called.
5961
func (r *ReverseProxy) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) *user_model.User {
62+
63+
// Internal API should not use this auth method.
64+
if middleware.IsInternalPath(req) {
65+
return nil
66+
}
67+
68+
// Just return user if session is estabilshed already.
69+
user := SessionUser(sess)
70+
if user != nil {
71+
return user
72+
}
73+
6074
username := r.getUserName(req)
6175
if len(username) == 0 {
6276
return nil
6377
}
6478
log.Trace("ReverseProxy Authorization: Found username: %s", username)
6579

66-
user, err := user_model.GetUserByName(username)
67-
if err != nil {
68-
if !user_model.IsErrUserNotExist(err) || !r.isAutoRegisterAllowed() {
69-
log.Error("GetUserByName: %v", err)
80+
var err error
81+
82+
if r.isAutoRegisterAllowed() {
83+
// Use auto registration from reverse proxy if ENABLE_REVERSE_PROXY_AUTO_REGISTRATION enabled.
84+
if user, err = user_model.GetUserByName(username); err != nil {
85+
if user_model.IsErrUserNotExist(err) && r.isAutoRegisterAllowed() {
86+
if user = r.newUser(req); user == nil {
87+
return nil
88+
}
89+
} else {
90+
log.Error("GetUserByName: %v", err)
91+
return nil
92+
}
93+
}
94+
} else {
95+
// Use auto registration from other backends if ENABLE_REVERSE_PROXY_AUTO_REGISTRATION not enabled.
96+
if user, _, err = UserSignIn(username, ""); err != nil {
97+
if !user_model.IsErrUserNotExist(err) {
98+
log.Error("UserSignIn: %v", err)
99+
}
70100
return nil
71101
}
72-
user = r.newUser(req)
73102
}
74103

75104
// Make sure requests to API paths, attachment downloads, git and LFS do not create a new session
76105
if !middleware.IsAPIPath(req) && !isAttachmentDownload(req) && !isGitRawReleaseOrLFSPath(req) {
77106
if sess != nil && (sess.Get("uid") == nil || sess.Get("uid").(int64) != user.ID) {
107+
108+
// Register last login.
109+
user.SetLastLogin()
110+
111+
if err = user_model.UpdateUserCols(db.DefaultContext, user, "last_login_unix"); err != nil {
112+
log.Error(fmt.Sprintf("ReverseProxy Authorization: error updating user last login time [user: %d]", user.ID))
113+
}
114+
115+
// Initialize new session. Will set lang and CSRF cookies.
78116
handleSignIn(w, req, sess, user)
117+
118+
log.Trace("ReverseProxy Authorization: Logged in user %-v", user)
79119
}
120+
121+
// Unfortunatelly we cannot do redirect here (would break git HTTP requests) to
122+
// reload page with user locale so first page after login may be displayed in
123+
// wrong language. Language handling in SSO mode should be reconsidered
124+
// in future gitea versions.
80125
}
81-
store.GetData()["IsReverseProxy"] = true
82126

83-
log.Trace("ReverseProxy Authorization: Logged in user %-v", user)
127+
store.GetData()["IsReverseProxy"] = true
84128
return user
85129
}
86130

services/auth/source/ldap/source_search.go

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strings"
1414

1515
"code.gitea.io/gitea/modules/log"
16+
"code.gitea.io/gitea/modules/setting"
1617

1718
"github.com/go-ldap/ldap/v3"
1819
)
@@ -194,11 +195,23 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool {
194195

195196
// SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
196197
func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResult {
198+
197199
// See https://tools.ietf.org/search/rfc4513#section-5.1.2
198-
if len(passwd) == 0 {
200+
// Don't authenticate against LDAP if already authenticated by reverse proxy.
201+
if setting.Service.EnableReverseProxyAuth {
202+
if directBind {
203+
log.Debug("Cannot bind pre-authenticated user %s. BindDN must be used.", name)
204+
return nil
205+
}
206+
if !ls.AttributesInBind {
207+
log.Debug("Cannot get attributes for pre-authenticated user %s without --attributes-in-bind.", name)
208+
return nil
209+
}
210+
} else if len(passwd) == 0 {
199211
log.Debug("Auth. failed for %s, password cannot be empty", name)
200212
return nil
201213
}
214+
202215
l, err := dial(ls)
203216
if err != nil {
204217
log.Error("LDAP Connect error, %s:%v", ls.Host, err)
@@ -361,8 +374,8 @@ func (ls *Source) SearchEntry(name, passwd string, directBind bool) *SearchResul
361374
isRestricted = checkRestricted(l, ls, userDN)
362375
}
363376

364-
if !directBind && ls.AttributesInBind {
365-
// binds user (checking password) after looking-up attributes in BindDN context
377+
if !directBind && ls.AttributesInBind && !setting.Service.EnableReverseProxyAuth {
378+
// Binds user (checking password) after looking-up attributes in BindDN context if not already authenticated.
366379
err = bindUser(l, userDN, passwd)
367380
if err != nil {
368381
return nil

0 commit comments

Comments
 (0)