Fix LDAP sync when Username Attribute is empty (#25278)

Fix #21072

![image](https://github.com/go-gitea/gitea/assets/15528715/96b30beb-7f88-4a60-baae-2e5ad8049555)

Username Attribute is not a required item when creating an
authentication source. If Username Attribute is empty, the username
value of LDAP user cannot be read, so all users from LDAP will be marked
as inactive by mistake when synchronizing external users.

This PR improves the sync logic, if username is empty, the email address
will be used to find user.
forgejo-federated-star
Zettat123 1 year ago committed by GitHub
parent 1a5b7c86a3
commit 33cd74ad70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -6,7 +6,6 @@ package ldap
import ( import (
"context" "context"
"fmt" "fmt"
"sort"
"strings" "strings"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
@ -24,7 +23,6 @@ import (
func (source *Source) Sync(ctx context.Context, updateExisting bool) error { func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
log.Trace("Doing: SyncExternalUsers[%s]", source.authSource.Name) log.Trace("Doing: SyncExternalUsers[%s]", source.authSource.Name)
var existingUsers []int
isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0 isAttributeSSHPublicKeySet := len(strings.TrimSpace(source.AttributeSSHPublicKey)) > 0
var sshKeysNeedUpdate bool var sshKeysNeedUpdate bool
@ -41,9 +39,14 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
default: default:
} }
sort.Slice(users, func(i, j int) bool { usernameUsers := make(map[string]*user_model.User, len(users))
return users[i].LowerName < users[j].LowerName mailUsers := make(map[string]*user_model.User, len(users))
}) keepActiveUsers := make(map[int64]struct{})
for _, u := range users {
usernameUsers[u.LowerName] = u
mailUsers[strings.ToLower(u.Email)] = u
}
sr, err := source.SearchEntries() sr, err := source.SearchEntries()
if err != nil { if err != nil {
@ -59,11 +62,6 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings") log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
} }
sort.Slice(sr, func(i, j int) bool {
return sr[i].LowerName < sr[j].LowerName
})
userPos := 0
orgCache := make(map[string]*organization.Organization) orgCache := make(map[string]*organization.Organization)
teamCache := make(map[string]*organization.Team) teamCache := make(map[string]*organization.Team)
@ -86,21 +84,27 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
return db.ErrCancelledf("During update of %s before completed update of users", source.authSource.Name) return db.ErrCancelledf("During update of %s before completed update of users", source.authSource.Name)
default: default:
} }
if len(su.Username) == 0 { if len(su.Username) == 0 && len(su.Mail) == 0 {
continue continue
} }
if len(su.Mail) == 0 { var usr *user_model.User
su.Mail = fmt.Sprintf("%s@localhost", su.Username) if len(su.Username) > 0 {
usr = usernameUsers[su.LowerName]
}
if usr == nil && len(su.Mail) > 0 {
usr = mailUsers[strings.ToLower(su.Mail)]
} }
var usr *user_model.User if usr != nil {
for userPos < len(users) && users[userPos].LowerName < su.LowerName { keepActiveUsers[usr.ID] = struct{}{}
userPos++ } else if len(su.Username) == 0 {
// we cannot create the user if su.Username is empty
continue
} }
if userPos < len(users) && users[userPos].LowerName == su.LowerName {
usr = users[userPos] if len(su.Mail) == 0 {
existingUsers = append(existingUsers, userPos) su.Mail = fmt.Sprintf("%s@localhost", su.Username)
} }
fullName := composeFullName(su.Name, su.Surname, su.Username) fullName := composeFullName(su.Name, su.Surname, su.Username)
@ -203,12 +207,11 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
// Deactivate users not present in LDAP // Deactivate users not present in LDAP
if updateExisting { if updateExisting {
existPos := 0 for _, usr := range users {
for i, usr := range users { if _, ok := keepActiveUsers[usr.ID]; ok {
for existPos < len(existingUsers) && i > existingUsers[existPos] { continue
existPos++
} }
if usr.IsActive && (existPos >= len(existingUsers) || i < existingUsers[existPos]) {
log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name) log.Trace("SyncExternalUsers[%s]: Deactivating user %s", source.authSource.Name, usr.Name)
usr.IsActive = false usr.IsActive = false
@ -218,6 +221,5 @@ func (source *Source) Sync(ctx context.Context, updateExisting bool) error {
} }
} }
} }
}
return nil return nil
} }

@ -268,6 +268,57 @@ func TestLDAPUserSync(t *testing.T) {
} }
} }
func TestLDAPUserSyncWithEmptyUsernameAttribute(t *testing.T) {
if skipLDAPTests() {
t.Skip()
return
}
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
csrf := GetCSRF(t, session, "/admin/auths/new")
payload := buildAuthSourceLDAPPayload(csrf, "", "", "", "")
payload["attribute_username"] = ""
req := NewRequestWithValues(t, "POST", "/admin/auths/new", payload)
session.MakeRequest(t, req, http.StatusSeeOther)
for _, u := range gitLDAPUsers {
req := NewRequest(t, "GET", "/admin/users?q="+u.UserName)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
tr := htmlDoc.doc.Find("table.table tbody tr")
assert.True(t, tr.Length() == 0)
}
for _, u := range gitLDAPUsers {
req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{
"_csrf": csrf,
"user_name": u.UserName,
"password": u.Password,
})
MakeRequest(t, req, http.StatusSeeOther)
}
auth.SyncExternalUsers(context.Background(), true)
authSource := unittest.AssertExistsAndLoadBean(t, &auth_model.Source{
Name: payload["name"],
})
unittest.AssertCount(t, &user_model.User{
LoginType: auth_model.LDAP,
LoginSource: authSource.ID,
}, len(gitLDAPUsers))
for _, u := range gitLDAPUsers {
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{
Name: u.UserName,
})
assert.True(t, user.IsActive)
}
}
func TestLDAPUserSyncWithGroupFilter(t *testing.T) { func TestLDAPUserSyncWithGroupFilter(t *testing.T) {
if skipLDAPTests() { if skipLDAPTests() {
t.Skip() t.Skip()

Loading…
Cancel
Save