Skip to content

Commit a01dd5d

Browse files
committed
add memberOf support for LDAP
1 parent 3f4b67b commit a01dd5d

File tree

8 files changed

+159
-34
lines changed

8 files changed

+159
-34
lines changed

providers/directory/config.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"net/url"
99
"path/filepath"
1010
"strings"
11+
12+
log "github.com/sirupsen/logrus"
1113
)
1214

1315
type Config struct {
@@ -34,7 +36,7 @@ type Info struct {
3436
}
3537

3638
func (c *Config) Parse(config *dynamic.Config, reader dynamic.Reader) error {
37-
c.Entries = &sortedmap.LinkedHashMap[string, Entry]{}
39+
c.Entries = &sortedmap.LinkedHashMap[string, Entry]{KeyNormalizer: normalizeDN}
3840
// copy from old format
3941
for k, v := range c.oldEntries {
4042
c.Entries.Set(k, v)
@@ -168,13 +170,52 @@ func (c *Config) Parse(config *dynamic.Config, reader dynamic.Reader) error {
168170
}
169171
}
170172

173+
c.rebuildMemberOf()
174+
171175
return nil
172176
}
173177

174178
func (c *Config) getSizeLimit() int64 {
175179
return c.SizeLimit
176180
}
177181

182+
func (c *Config) rebuildMemberOf() {
183+
// reset entries
184+
185+
groups := map[string][]string{}
186+
for it := c.Entries.Iter(); it.Next(); {
187+
groupName := it.Key()
188+
for k, v := range it.Value().Attributes {
189+
if k == "member" {
190+
if _, ok := groups[k]; !ok {
191+
groups[k] = []string{}
192+
}
193+
for _, member := range v {
194+
groups[groupName] = append(groups[groupName], member)
195+
}
196+
}
197+
if k == "memberOf" {
198+
delete(it.Value().Attributes, "memberOf")
199+
}
200+
}
201+
}
202+
203+
for group, members := range groups {
204+
for _, member := range members {
205+
e, ok := c.Entries.Get(member)
206+
if !ok {
207+
log.Warnf("LDAP: member %s for group %s not found", member, group)
208+
continue
209+
}
210+
if e.Attributes == nil {
211+
e.Attributes = map[string][]string{}
212+
}
213+
e.Attributes["memberOf"] = append(e.Attributes["memberOf"], group)
214+
c.Entries.Set(member, e)
215+
}
216+
}
217+
}
218+
178219
func (e *Entry) Parse(config *dynamic.Config, reader dynamic.Reader) error {
179220
if aList, ok := e.Attributes["thumbnailphoto"]; ok {
180221
for i, a := range aList {
@@ -208,13 +249,13 @@ func (e *Entry) Parse(config *dynamic.Config, reader dynamic.Reader) error {
208249
return nil
209250
}
210251

211-
func formatDN(dn string) string {
252+
func normalizeDN(dn string) string {
212253
result := ""
213254
for _, rdn := range strings.Split(dn, ",") {
214255
if len(result) > 0 {
215256
result += ","
216257
}
217-
result += strings.TrimSpace(rdn)
258+
result += strings.TrimSpace(strings.ToLower(rdn))
218259
}
219260
return result
220261
}

providers/directory/config_ldif_test.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package directory
22

33
import (
44
"encoding/json"
5-
"github.com/stretchr/testify/require"
65
"mokapi/config/dynamic"
76
"mokapi/config/dynamic/dynamictest"
87
"mokapi/try"
98
"testing"
9+
10+
"github.com/stretchr/testify/require"
1011
)
1112

1213
func TestLdif_Parse(t *testing.T) {
@@ -23,7 +24,7 @@ func TestLdif_Parse(t *testing.T) {
2324
require.NoError(t, err)
2425
require.Len(t, ld.Records, 1)
2526
require.Equal(t, &AddRecord{
26-
Dn: "dc=mokapi,dc=io",
27+
Dn: "dc=mokapi, dc=io",
2728
}, ld.Records[0])
2829
},
2930
},
@@ -34,10 +35,10 @@ func TestLdif_Parse(t *testing.T) {
3435
require.NoError(t, err)
3536
require.Len(t, ld.Records, 2)
3637
require.Equal(t, &AddRecord{
37-
Dn: "dc=mokapi,dc=io",
38+
Dn: "dc=mokapi, dc=io",
3839
}, ld.Records[0])
3940
require.Equal(t, &AddRecord{
40-
Dn: "ou=Sales,dc=mokapi,dc=io",
41+
Dn: "ou=Sales, dc=mokapi, dc=io",
4142
}, ld.Records[1])
4243
},
4344
},
@@ -48,10 +49,10 @@ func TestLdif_Parse(t *testing.T) {
4849
require.NoError(t, err)
4950
require.Len(t, ld.Records, 2)
5051
require.Equal(t, &AddRecord{
51-
Dn: "dc=mokapi,dc=io",
52+
Dn: "dc=mokapi, dc=io",
5253
}, ld.Records[0])
5354
require.Equal(t, &AddRecord{
54-
Dn: "ou=Sales,dc=mokapi,dc=io",
55+
Dn: "ou=Sales, dc=mokapi, dc=io",
5556
}, ld.Records[1])
5657
},
5758
},
@@ -69,7 +70,7 @@ func TestLdif_Parse(t *testing.T) {
6970
require.NoError(t, err)
7071
require.Len(t, ld.Records, 1)
7172
require.Equal(t, &AddRecord{
72-
Dn: "dc=mokapi,dc=io",
73+
Dn: "dc=mokapi, dc=io",
7374
Attributes: map[string][]string{"description": {"foo bar"}},
7475
}, ld.Records[0])
7576
},
@@ -81,7 +82,7 @@ func TestLdif_Parse(t *testing.T) {
8182
require.NoError(t, err)
8283
require.Len(t, ld.Records, 1)
8384
require.Equal(t, &AddRecord{
84-
Dn: "dc=mokapi,dc=io",
85+
Dn: "dc=mokapi, dc=io",
8586
Attributes: map[string][]string{"attribute": {"foo"}},
8687
}, ld.Records[0])
8788
},
@@ -93,7 +94,7 @@ func TestLdif_Parse(t *testing.T) {
9394
require.NoError(t, err)
9495
require.Len(t, ld.Records, 1)
9596
require.Equal(t, &AddRecord{
96-
Dn: "dc=mokapi,dc=io",
97+
Dn: "dc=mokapi, dc=io",
9798
}, ld.Records[0])
9899
},
99100
},
@@ -104,7 +105,7 @@ func TestLdif_Parse(t *testing.T) {
104105
require.NoError(t, err)
105106
require.Len(t, ld.Records, 1)
106107
require.Equal(t, &AddRecord{
107-
Dn: "dc=mokapi,dc=io",
108+
Dn: "dc=mokapi, dc=io",
108109
Attributes: map[string][]string{"foo": {"bar"}},
109110
}, ld.Records[0])
110111
},
@@ -116,7 +117,7 @@ func TestLdif_Parse(t *testing.T) {
116117
require.NoError(t, err)
117118
require.Len(t, ld.Records, 1)
118119
require.Equal(t, &AddRecord{
119-
Dn: "dc=mokapi,dc=io",
120+
Dn: "dc=mokapi, dc=io",
120121
}, ld.Records[0])
121122
},
122123
},
@@ -127,7 +128,7 @@ func TestLdif_Parse(t *testing.T) {
127128
require.NoError(t, err)
128129
require.Len(t, ld.Records, 1)
129130
require.Equal(t, &AddRecord{
130-
Dn: "dc=mokapi,dc=io",
131+
Dn: "dc=mokapi, dc=io",
131132
Attributes: map[string][]string{"foo": {""}},
132133
}, ld.Records[0])
133134
},
@@ -139,7 +140,7 @@ func TestLdif_Parse(t *testing.T) {
139140
require.NoError(t, err)
140141
require.Len(t, ld.Records, 1)
141142
require.Equal(t, &AddRecord{
142-
Dn: "dc=mokapi,dc=io",
143+
Dn: "dc=mokapi, dc=io",
143144
}, ld.Records[0])
144145
},
145146
},
@@ -150,7 +151,7 @@ func TestLdif_Parse(t *testing.T) {
150151
require.NoError(t, err)
151152
require.Len(t, ld.Records, 1)
152153
require.Equal(t, &DeleteRecord{
153-
Dn: "dc=mokapi,dc=io",
154+
Dn: "dc=mokapi, dc=io",
154155
}, ld.Records[0])
155156
},
156157
},
@@ -161,7 +162,7 @@ func TestLdif_Parse(t *testing.T) {
161162
require.NoError(t, err)
162163
require.Len(t, ld.Records, 1)
163164
require.Equal(t, &ModifyRecord{
164-
Dn: "dc=mokapi,dc=io",
165+
Dn: "dc=mokapi, dc=io",
165166
}, ld.Records[0])
166167
},
167168
},
@@ -172,7 +173,7 @@ func TestLdif_Parse(t *testing.T) {
172173
require.NoError(t, err)
173174
require.Len(t, ld.Records, 1)
174175
require.Equal(t, &ModifyRecord{
175-
Dn: "dc=mokapi,dc=io",
176+
Dn: "dc=mokapi, dc=io",
176177
Actions: []*ModifyAction{
177178
{
178179
Type: "add",
@@ -190,7 +191,7 @@ func TestLdif_Parse(t *testing.T) {
190191
require.NoError(t, err)
191192
require.Len(t, ld.Records, 1)
192193
require.Equal(t, &ModifyRecord{
193-
Dn: "dc=mokapi,dc=io",
194+
Dn: "dc=mokapi, dc=io",
194195
Actions: []*ModifyAction{
195196
{
196197
Type: "replace",
@@ -208,7 +209,7 @@ func TestLdif_Parse(t *testing.T) {
208209
require.NoError(t, err)
209210
require.Len(t, ld.Records, 1)
210211
require.Equal(t, &ModifyRecord{
211-
Dn: "dc=mokapi,dc=io",
212+
Dn: "dc=mokapi, dc=io",
212213
Actions: []*ModifyAction{
213214
{
214215
Type: "delete",
@@ -232,7 +233,7 @@ func TestLdif_Parse(t *testing.T) {
232233
require.NoError(t, err)
233234
require.Len(t, ld.Records, 1)
234235
require.Equal(t, &AddRecord{
235-
Dn: "dc=mokapi,dc=io",
236+
Dn: "dc=mokapi, dc=io",
236237
Attributes: map[string][]string{
237238
"photo": {"1234"},
238239
},
@@ -246,7 +247,7 @@ func TestLdif_Parse(t *testing.T) {
246247
require.NoError(t, err)
247248
require.Len(t, ld.Records, 1)
248249
require.Equal(t, &AddRecord{
249-
Dn: "dc=mokapi,dc=io",
250+
Dn: "dc=mokapi, dc=io",
250251
Attributes: map[string][]string{
251252
"description": {"foobar"},
252253
},
@@ -333,7 +334,7 @@ func TestConfig_LDIF(t *testing.T) {
333334
test: func(t *testing.T, c *Config, err error) {
334335
require.NoError(t, err)
335336
require.Equal(t, 3, c.Entries.Len())
336-
require.Len(t, c.Entries.Lookup("dc=mokapi,dc=io").Attributes, 0)
337+
require.Len(t, c.Entries.Lookup("dc=mokapi, dc=io").Attributes, 0)
337338
},
338339
},
339340
{

providers/directory/directory.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ func (d *Directory) serveModify(rw ldap.ResponseWriter, r *ldap.ModifyRequest, c
161161
res = &ldap.ModifyResponse{ResultCode: ee.Code, MatchedDn: r.Dn, Message: err.Error()}
162162
} else {
163163
res = &ldap.ModifyResponse{ResultCode: ldap.Success, MatchedDn: r.Dn}
164+
go d.config.rebuildMemberOf()
164165
}
165166

166167
m, doMonitor := monitor.LdapFromContext(ctx)
@@ -211,6 +212,7 @@ func (d *Directory) serveAdd(rw ldap.ResponseWriter, r *ldap.AddRequest, ctx con
211212
res = &ldap.AddResponse{ResultCode: ee.Code, Message: err.Error()}
212213
} else {
213214
res = &ldap.AddResponse{ResultCode: ldap.Success, MatchedDn: r.Dn}
215+
go d.config.rebuildMemberOf()
214216
}
215217

216218
m, doMonitor := monitor.LdapFromContext(ctx)
@@ -242,6 +244,7 @@ func (d *Directory) serveDelete(rw ldap.ResponseWriter, r *ldap.DeleteRequest, c
242244
res = &ldap.DeleteResponse{ResultCode: ee.Code, MatchedDn: del.Dn, Message: err.Error()}
243245
} else {
244246
res = &ldap.DeleteResponse{ResultCode: ldap.Success, MatchedDn: del.Dn}
247+
go d.config.rebuildMemberOf()
245248
}
246249

247250
m, doMonitor := monitor.LdapFromContext(ctx)
@@ -276,6 +279,7 @@ func (d *Directory) serveModifyDn(rw ldap.ResponseWriter, r *ldap.ModifyDNReques
276279
res = &ldap.ModifyDNResponse{ResultCode: ee.Code, MatchedDn: r.Dn, Message: err.Error()}
277280
} else {
278281
res = &ldap.ModifyDNResponse{ResultCode: ldap.Success, MatchedDn: r.Dn}
282+
go d.config.rebuildMemberOf()
279283
}
280284

281285
m, doMonitor := monitor.LdapFromContext(ctx)

providers/directory/ldif.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (l *Ldif) Parse(config *dynamic.Config, reader dynamic.Reader) error {
8383

8484
switch kv[0] {
8585
case "dn":
86-
dn = formatDN(val)
86+
dn = val
8787
rec = &AddRecord{Dn: dn}
8888
case "changetype":
8989
switch val {

providers/directory/search.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,10 @@ func (p *parser) parseSet(filter string) ([]predicate, int, error) {
250250
}
251251

252252
func (p *parser) predicate(op int, name, value, rule string) (predicate, error) {
253+
if strings.ToLower(name) == "memberof" {
254+
value = normalizeDN(value)
255+
}
256+
253257
switch op {
254258
case ldap.FilterEqualityMatch:
255259
if strings.Contains(value, "*") {
@@ -333,7 +337,7 @@ func (p *parser) substring(name, value string) predicate {
333337

334338
func (p *parser) equal(name, value string) (predicate, error) {
335339
f := func(s string) bool {
336-
return value == s
340+
return strings.ToLower(value) == strings.ToLower(s)
337341
}
338342

339343
if p.s != nil {

providers/directory/search_test.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,63 @@ func TestSearch(t *testing.T) {
549549
}))
550550
res := rr.Message.(*ldap.SearchResponse)
551551

552+
require.Len(t, res.Results, 1)
553+
},
554+
},
555+
{
556+
name: "memberOf",
557+
input: `{ "files": [ "./users.ldif" ] }`,
558+
reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{
559+
"file:/users.ldif": {Raw: []byte("dn: cn=user\n\ndn: cn=group\nmember: cn=user")},
560+
}},
561+
test: func(t *testing.T, h ldap.Handler, err error) {
562+
require.NoError(t, err)
563+
564+
rr := ldaptest.NewRecorder()
565+
h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{
566+
Scope: ldap.ScopeWholeSubtree,
567+
Filter: "(memberOf=cn=group)",
568+
}))
569+
res := rr.Message.(*ldap.SearchResponse)
570+
571+
require.Len(t, res.Results, 1)
572+
},
573+
},
574+
{
575+
name: "memberOf different cases",
576+
input: `{ "files": [ "./users.ldif" ] }`,
577+
reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{
578+
"file:/users.ldif": {Raw: []byte("dn: cn=uSEr\n\ndn: cn=group\nmember: cn=UseR")},
579+
}},
580+
test: func(t *testing.T, h ldap.Handler, err error) {
581+
require.NoError(t, err)
582+
583+
rr := ldaptest.NewRecorder()
584+
h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{
585+
Scope: ldap.ScopeWholeSubtree,
586+
Filter: "(memberOf=cn=GRoup)",
587+
}))
588+
res := rr.Message.(*ldap.SearchResponse)
589+
590+
require.Len(t, res.Results, 1)
591+
},
592+
},
593+
{
594+
name: "memberOf normalize DN",
595+
input: `{ "files": [ "./users.ldif" ] }`,
596+
reader: &dynamictest.Reader{Data: map[string]*dynamic.Config{
597+
"file:/users.ldif": {Raw: []byte("dn: uid=ff, cn=user\n\ndn: uid=cc,cn=group\nmember: uid=ff,cn=user")},
598+
}},
599+
test: func(t *testing.T, h ldap.Handler, err error) {
600+
require.NoError(t, err)
601+
602+
rr := ldaptest.NewRecorder()
603+
h.ServeLDAP(rr, ldaptest.NewRequest(0, &ldap.SearchRequest{
604+
Scope: ldap.ScopeWholeSubtree,
605+
Filter: "(memberOf=uid=cc, cn=group)",
606+
}))
607+
res := rr.Message.(*ldap.SearchResponse)
608+
552609
require.Len(t, res.Results, 1)
553610
},
554611
},

0 commit comments

Comments
 (0)