From 86152d996ca0f295e8623a2aa971c0f8a89b2aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=A5=BA?= Date: Sun, 12 Apr 2026 21:55:48 +0800 Subject: [PATCH] fix(ldap): resolve posixGroup memberUid as bare usernames (#1408) (#1422) --- backend/internal/service/ldap_service.go | 6 +++ backend/internal/service/ldap_service_test.go | 44 ++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/backend/internal/service/ldap_service.go b/backend/internal/service/ldap_service.go index 3d59b3de..0e055f55 100644 --- a/backend/internal/service/ldap_service.go +++ b/backend/internal/service/ldap_service.go @@ -391,6 +391,12 @@ func (s *LdapService) resolveGroupMemberUsername(ctx context.Context, client lda return norm.NFC.String(username) } + // posixGroup (and similar) stores bare usernames in memberUid, not DNs. Treat any value + // that is not a valid DN as the username directly — see https://github.com/pocket-id/pocket-id/issues/1408 + if _, err := ldap.ParseDN(member); err != nil { + return norm.NFC.String(member) + } + // As a fallback, query LDAP for the referenced entry userSearchReq := ldap.NewSearchRequest( member, diff --git a/backend/internal/service/ldap_service_test.go b/backend/internal/service/ldap_service_test.go index 9722946a..d340cc74 100644 --- a/backend/internal/service/ldap_service_test.go +++ b/backend/internal/service/ldap_service_test.go @@ -141,6 +141,48 @@ func TestLdapServiceSyncAllReconcilesUsersAndGroups(t *testing.T) { assert.ElementsMatch(t, []string{"alice", "bob"}, usernames(team.Users)) } +// Regression: posixGroup uses memberUid (bare uid values), not member DNs — issue #1408. +func TestLdapServiceSyncAllMapsPosixGroupMemberUid(t *testing.T) { + appCfg := defaultTestLDAPAppConfig() + appCfg.LdapUserGroupSearchFilter = model.AppConfigVariable{Value: "(objectClass=posixGroup)"} + appCfg.LdapAttributeGroupMember = model.AppConfigVariable{Value: "memberUid"} + + service, db := newTestLdapServiceWithAppConfig(t, appCfg, newFakeLDAPClient( + ldapSearchResult( + ldapEntry("uid=alice,ou=users,dc=example,dc=com", map[string][]string{ + "entryUUID": {"u-alice"}, + "uid": {"alice"}, + "mail": {"alice@example.com"}, + "givenName": {"Alice"}, + "sn": {"Jones"}, + "displayName": {""}, + }), + ldapEntry("uid=bob,ou=users,dc=example,dc=com", map[string][]string{ + "entryUUID": {"u-bob"}, + "uid": {"bob"}, + "mail": {"bob@example.com"}, + "givenName": {"Bob"}, + "sn": {"Brown"}, + "displayName": {""}, + }), + ), + ldapSearchResult( + ldapEntry("cn=users,ou=groups,dc=example,dc=com", map[string][]string{ + "entryUUID": {"g-users"}, + "cn": {"users"}, + "memberUid": {"alice", "bob", "unknown"}, + }), + ), + )) + + require.NoError(t, service.SyncAll(t.Context())) + + var group model.UserGroup + require.NoError(t, db.Preload("Users").First(&group, "ldap_id = ?", "g-users").Error) + assert.Equal(t, "users", group.Name) + assert.ElementsMatch(t, []string{"alice", "bob"}, usernames(group.Users)) +} + func TestLdapServiceSyncAllHandlesDuplicateLDAPIDsInSingleRun(t *testing.T) { service, db := newTestLdapService(t, newFakeLDAPClient( ldapSearchResult( @@ -325,7 +367,7 @@ func newFakeLDAPClient(userResult, groupResult *ldap.SearchResult) ldapClient { switch searchRequest.Filter { case "(objectClass=person)": return userResult, nil - case "(objectClass=groupOfNames)": + case "(objectClass=groupOfNames)", "(objectClass=posixGroup)": return groupResult, nil default: return &ldap.SearchResult{}, nil