diff --git a/internal/outpost/ldap/search/request.go b/internal/outpost/ldap/search/request.go index e44f36d4a3..5d473f9f2c 100644 --- a/internal/outpost/ldap/search/request.go +++ b/internal/outpost/ldap/search/request.go @@ -53,6 +53,14 @@ func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Re if err != nil && len(searchReq.Filter) > 0 { l.WithError(err).WithField("objectClass", filterOC).Warning("invalid filter object class") } + + // Handle comma-separated attributes + normalizedAttributes := normalizeAttributes(searchReq.Attributes) + if len(normalizedAttributes) != len(searchReq.Attributes) { + // Create a copy of the search request with normalized attributes + searchReq.Attributes = normalizedAttributes + } + return &Request{ SearchRequest: searchReq, BindDN: bindDN, @@ -64,6 +72,31 @@ func NewRequest(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (*Re }, span } +// normalizeAttributes handles the case where attributes might be passed as comma-separated strings +// rather than as individual array elements +func normalizeAttributes(attributes []string) []string { + if len(attributes) == 0 { + return attributes + } + + result := make([]string, 0, len(attributes)) + for _, attr := range attributes { + if strings.Contains(attr, ",") { + // Split comma-separated attributes and add them individually + parts := strings.Split(attr, ",") + for _, part := range parts { + part = strings.TrimSpace(part) + if part != "" { + result = append(result, part) + } + } + } else { + result = append(result, attr) + } + } + return result +} + func (r *Request) Context() context.Context { return r.ctx } diff --git a/internal/outpost/ldap/search/request_test.go b/internal/outpost/ldap/search/request_test.go new file mode 100644 index 0000000000..fb8032e073 --- /dev/null +++ b/internal/outpost/ldap/search/request_test.go @@ -0,0 +1,98 @@ +package search + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNormalizeAttributes(t *testing.T) { + tests := []struct { + name string + input []string + expectedOutput []string + }{ + { + name: "Empty input", + input: []string{}, + expectedOutput: []string{}, + }, + { + name: "No commas", + input: []string{"uid", "cn", "sn"}, + expectedOutput: []string{"uid", "cn", "sn"}, + }, + { + name: "Single comma-separated string", + input: []string{"uid,cn,sn"}, + expectedOutput: []string{"uid", "cn", "sn"}, + }, + { + name: "Mixed input", + input: []string{"uid,cn", "sn"}, + expectedOutput: []string{"uid", "cn", "sn"}, + }, + { + name: "With spaces", + input: []string{"uid, cn, sn"}, + expectedOutput: []string{"uid", "cn", "sn"}, + }, + { + name: "Empty parts", + input: []string{"uid,, cn"}, + expectedOutput: []string{"uid", "cn"}, + }, + { + name: "Single element", + input: []string{"uid"}, + expectedOutput: []string{"uid"}, + }, + { + name: "Only commas", + input: []string{",,,"}, + expectedOutput: []string{}, + }, + { + name: "Multiple comma-separated attributes", + input: []string{"uid,cn", "sn,mail", "givenName"}, + expectedOutput: []string{"uid", "cn", "sn", "mail", "givenName"}, + }, + { + name: "Case preservation", + input: []string{"uid,CN,sAMAccountName"}, + expectedOutput: []string{"uid", "CN", "sAMAccountName"}, + }, + { + name: "Leading and trailing spaces", + input: []string{" uid , cn , sn "}, + expectedOutput: []string{"uid", "cn", "sn"}, + }, + { + name: "Real-world LDAP attribute examples", + input: []string{"objectClass,memberOf,mail", "sAMAccountName,userPrincipalName"}, + expectedOutput: []string{"objectClass", "memberOf", "mail", "sAMAccountName", "userPrincipalName"}, + }, + { + name: "Jira-style attribute format", + input: []string{"uid,cn,sn"}, + expectedOutput: []string{"uid", "cn", "sn"}, + }, + { + name: "Single string with single attribute", + input: []string{"cn"}, + expectedOutput: []string{"cn"}, + }, + { + name: "Mix of standard and operational attributes", + input: []string{"uid,+", "createTimestamp"}, + expectedOutput: []string{"uid", "+", "createTimestamp"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := normalizeAttributes(tt.input) + assert.Equal(t, tt.expectedOutput, result) + }) + } +}