From 87aa5bce9b5eff29d9282f2469b8f08e232a1951 Mon Sep 17 00:00:00 2001 From: puni9869 Date: Mon, 8 Jun 2026 11:41:30 +0530 Subject: [PATCH 3/6] fix(ldap): use base scope for user attribute lookups --- services/auth/source/ldap/source_search.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index 52aec7563f..657eb8891e 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -348,8 +348,10 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear } log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", source.AttributeUsername, source.AttributeName, source.AttributeSurname, source.AttributeMail, source.AttributeSSHPublicKey, source.AttributeAvatar, source.UserUID, userFilter, userDN) + + // FIX: ScopeBaseObject targets the exact single record for attribute extraction search := ldap.NewSearchRequest( - userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, + userDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) sr, err := l.Search(search) @@ -462,6 +464,8 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) { } log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v' with filter %s and base %s", source.AttributeUsername, source.AttributeName, source.AttributeSurname, source.AttributeMail, source.AttributeSSHPublicKey, source.AttributeAvatar, userFilter, source.UserBase) + + // FIX: Restored ScopeWholeSubtree here since source.UserBase is a container, not a leaf. search := ldap.NewSearchRequest( source.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter, attribs, nil) From 1c7b0c84986415381abfd77d04ac5e2457d13c62 Mon Sep 17 00:00:00 2001 From: puni9869 Date: Tue, 9 Jun 2026 09:53:15 +0530 Subject: [PATCH 4/6] adding userObjectClass filter --- services/auth/source/ldap/source_search.go | 17 ++++++++++++++--- services/auth/source/ldap/source_search_test.go | 17 +++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 services/auth/source/ldap/source_search_test.go diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index 657eb8891e..cef0c0a0e2 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -249,6 +249,13 @@ func (source *Source) getUserAttributeListedInGroup(entry *ldap.Entry) string { return entry.GetAttributeValue(source.UserUID) } +func userAttributeFilter(userFilter string, userDNFoundBySearch bool) string { + if userDNFoundBySearch { + return "(objectClass=*)" + } + return userFilter +} + // SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchResult { if MockedSearchEntry != nil { @@ -274,6 +281,7 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear defer l.Close() var userDN string + userDNFoundBySearch := false if directBind { log.Trace("LDAP will bind directly via UserDN template: %s", source.UserDN) @@ -297,6 +305,7 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear if !ok { return nil } + userDNFoundBySearch = true } } else { log.Trace("LDAP will use BindDN.") @@ -318,6 +327,7 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear if !found { return nil } + userDNFoundBySearch = true } if !source.AttributesInBind { @@ -332,6 +342,7 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear if !ok { return nil } + attributeFilter := userAttributeFilter(userFilter, userDNFoundBySearch) isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" isAttributeAvatarSet := strings.TrimSpace(source.AttributeAvatar) != "" @@ -347,11 +358,11 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear attribs = append(attribs, source.AttributeAvatar) } - log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", source.AttributeUsername, source.AttributeName, source.AttributeSurname, source.AttributeMail, source.AttributeSSHPublicKey, source.AttributeAvatar, source.UserUID, userFilter, userDN) + log.Trace("Fetching attributes '%v', '%v', '%v', '%v', '%v', '%v', '%v' with filter '%s' and base '%s'", source.AttributeUsername, source.AttributeName, source.AttributeSurname, source.AttributeMail, source.AttributeSSHPublicKey, source.AttributeAvatar, source.UserUID, attributeFilter, userDN) - // FIX: ScopeBaseObject targets the exact single record for attribute extraction + // userDN is already the resolved leaf entry; do not search child entries. search := ldap.NewSearchRequest( - userDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, userFilter, + userDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, attributeFilter, attribs, nil) sr, err := l.Search(search) diff --git a/services/auth/source/ldap/source_search_test.go b/services/auth/source/ldap/source_search_test.go new file mode 100644 index 0000000000..4beaeeec4b --- /dev/null +++ b/services/auth/source/ldap/source_search_test.go @@ -0,0 +1,17 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package ldap + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUserAttributeFilter(t *testing.T) { + const userFilter = "(&(objectClass=posixAccount)(uid=user1))" + + assert.Equal(t, "(objectClass=*)", userAttributeFilter(userFilter, true)) + assert.Equal(t, userFilter, userAttributeFilter(userFilter, false)) +} From ed87ef8b693defd3953ef412249f0bf905d59b4e Mon Sep 17 00:00:00 2001 From: puni9869 Date: Tue, 9 Jun 2026 10:05:18 +0530 Subject: [PATCH 5/6] fix: addressed the review comment --- services/auth/source/ldap/source_search.go | 9 +++------ services/auth/source/ldap/source_search_test.go | 17 ----------------- 2 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 services/auth/source/ldap/source_search_test.go diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index cef0c0a0e2..d1c577c8f6 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -249,8 +249,8 @@ func (source *Source) getUserAttributeListedInGroup(entry *ldap.Entry) string { return entry.GetAttributeValue(source.UserUID) } -func userAttributeFilter(userFilter string, userDNFoundBySearch bool) string { - if userDNFoundBySearch { +func userAttributeFilter(userFilter string, directBind bool, userBase string) string { + if !directBind || userBase != "" { return "(objectClass=*)" } return userFilter @@ -281,7 +281,6 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear defer l.Close() var userDN string - userDNFoundBySearch := false if directBind { log.Trace("LDAP will bind directly via UserDN template: %s", source.UserDN) @@ -305,7 +304,6 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear if !ok { return nil } - userDNFoundBySearch = true } } else { log.Trace("LDAP will use BindDN.") @@ -327,7 +325,6 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear if !found { return nil } - userDNFoundBySearch = true } if !source.AttributesInBind { @@ -342,7 +339,7 @@ func realSearchEntry(source *Source, name, passwd string, directBind bool) *Sear if !ok { return nil } - attributeFilter := userAttributeFilter(userFilter, userDNFoundBySearch) + attributeFilter := userAttributeFilter(userFilter, directBind, source.UserBase) isAttributeSSHPublicKeySet := strings.TrimSpace(source.AttributeSSHPublicKey) != "" isAttributeAvatarSet := strings.TrimSpace(source.AttributeAvatar) != "" diff --git a/services/auth/source/ldap/source_search_test.go b/services/auth/source/ldap/source_search_test.go deleted file mode 100644 index 4beaeeec4b..0000000000 --- a/services/auth/source/ldap/source_search_test.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package ldap - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestUserAttributeFilter(t *testing.T) { - const userFilter = "(&(objectClass=posixAccount)(uid=user1))" - - assert.Equal(t, "(objectClass=*)", userAttributeFilter(userFilter, true)) - assert.Equal(t, userFilter, userAttributeFilter(userFilter, false)) -} From 8d0f164a326db6cdee5d0ed471512a66c2e96ce7 Mon Sep 17 00:00:00 2001 From: puni9869 Date: Tue, 9 Jun 2026 10:14:32 +0530 Subject: [PATCH 6/6] fix: apply for admin --- services/auth/source/ldap/source_search.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/auth/source/ldap/source_search.go b/services/auth/source/ldap/source_search.go index d1c577c8f6..78ca1d4b16 100644 --- a/services/auth/source/ldap/source_search.go +++ b/services/auth/source/ldap/source_search.go @@ -153,7 +153,7 @@ func checkAdmin(l *ldap.Conn, ls *Source, userDN string) bool { } log.Trace("Checking admin with filter %s and base %s", ls.AdminFilter, userDN) search := ldap.NewSearchRequest( - userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ls.AdminFilter, + userDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, ls.AdminFilter, []string{ls.AttributeName}, nil) @@ -178,7 +178,7 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool { } log.Trace("Checking restricted with filter %s and base %s", ls.RestrictedFilter, userDN) search := ldap.NewSearchRequest( - userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ls.RestrictedFilter, + userDN, ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false, ls.RestrictedFilter, []string{ls.AttributeName}, nil)