1 package ldap
2
3 import (
4 "context"
5 "crypto/tls"
6 "log"
7 "testing"
8
9 ber "github.com/go-asn1-ber/asn1-ber"
10 )
11
12 const (
13 ldapServer = "ldap://ldap.itd.umich.edu:389"
14 ldapsServer = "ldaps://ldap.itd.umich.edu:636"
15 baseDN = "dc=umich,dc=edu"
16 )
17
18 var filter = []string{
19 "(cn=cis-fac)",
20 "(&(owner=*)(cn=cis-fac))",
21 "(&(objectclass=rfc822mailgroup)(cn=*Computer*))",
22 "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))",
23 }
24
25 var attributes = []string{
26 "cn",
27 "description",
28 }
29
30 func TestUnsecureDialURL(t *testing.T) {
31 l, err := DialURL(ldapServer)
32 if err != nil {
33 t.Fatal(err)
34 }
35 defer l.Close()
36 }
37
38 func TestSecureDialURL(t *testing.T) {
39 l, err := DialURL(ldapsServer, DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
40 if err != nil {
41 t.Fatal(err)
42 }
43 defer l.Close()
44 }
45
46 func TestStartTLS(t *testing.T) {
47 l, err := DialURL(ldapServer)
48 if err != nil {
49 t.Fatal(err)
50 }
51 defer l.Close()
52 err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
53 if err != nil {
54 t.Fatal(err)
55 }
56 }
57
58 func TestTLSConnectionState(t *testing.T) {
59 l, err := DialURL(ldapServer)
60 if err != nil {
61 t.Fatal(err)
62 }
63 defer l.Close()
64 err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
65 if err != nil {
66 t.Fatal(err)
67 }
68
69 cs, ok := l.TLSConnectionState()
70 if !ok {
71 t.Errorf("TLSConnectionState returned ok == false; want true")
72 }
73 if cs.Version == 0 || !cs.HandshakeComplete {
74 t.Errorf("ConnectionState = %#v; expected Version != 0 and HandshakeComplete = true", cs)
75 }
76 }
77
78 func TestSearch(t *testing.T) {
79 l, err := DialURL(ldapServer)
80 if err != nil {
81 t.Fatal(err)
82 }
83 defer l.Close()
84
85 searchRequest := NewSearchRequest(
86 baseDN,
87 ScopeWholeSubtree, DerefAlways, 0, 0, false,
88 filter[0],
89 attributes,
90 nil)
91
92 sr, err := l.Search(searchRequest)
93 if err != nil {
94 t.Fatal(err)
95 }
96 t.Logf("TestSearch: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries))
97 }
98
99 func TestSearchStartTLS(t *testing.T) {
100 l, err := DialURL(ldapServer)
101 if err != nil {
102 t.Fatal(err)
103 }
104 defer l.Close()
105
106 searchRequest := NewSearchRequest(
107 baseDN,
108 ScopeWholeSubtree, DerefAlways, 0, 0, false,
109 filter[0],
110 attributes,
111 nil)
112
113 sr, err := l.Search(searchRequest)
114 if err != nil {
115 t.Fatal(err)
116 }
117
118 t.Logf("TestSearchStartTLS: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries))
119
120 t.Log("TestSearchStartTLS: upgrading with startTLS")
121 err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
122 if err != nil {
123 t.Fatal(err)
124 }
125
126 sr, err = l.Search(searchRequest)
127 if err != nil {
128 t.Fatal(err)
129 }
130
131 t.Logf("TestSearchStartTLS: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries))
132 }
133
134 func TestSearchWithPaging(t *testing.T) {
135 l, err := DialURL(ldapServer)
136 if err != nil {
137 t.Fatal(err)
138 }
139 defer l.Close()
140
141 err = l.UnauthenticatedBind("")
142 if err != nil {
143 t.Fatal(err)
144 }
145
146 searchRequest := NewSearchRequest(
147 baseDN,
148 ScopeWholeSubtree, DerefAlways, 0, 0, false,
149 filter[2],
150 attributes,
151 nil)
152 sr, err := l.SearchWithPaging(searchRequest, 5)
153 if err != nil {
154 t.Fatal(err)
155 }
156
157 t.Logf("TestSearchWithPaging: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries))
158
159 searchRequest = NewSearchRequest(
160 baseDN,
161 ScopeWholeSubtree, DerefAlways, 0, 0, false,
162 filter[2],
163 attributes,
164 []Control{NewControlPaging(5)})
165 sr, err = l.SearchWithPaging(searchRequest, 5)
166 if err != nil {
167 t.Fatal(err)
168 }
169
170 t.Logf("TestSearchWithPaging: %s -> num of entries = %d", searchRequest.Filter, len(sr.Entries))
171
172 searchRequest = NewSearchRequest(
173 baseDN,
174 ScopeWholeSubtree, DerefAlways, 0, 0, false,
175 filter[2],
176 attributes,
177 []Control{NewControlPaging(500)})
178 _, err = l.SearchWithPaging(searchRequest, 5)
179 if err == nil {
180 t.Fatal("expected an error when paging size in control in search request doesn't match size given in call, got none")
181 }
182 }
183
184 func searchGoroutine(t *testing.T, l *Conn, results chan *SearchResult, i int) {
185 searchRequest := NewSearchRequest(
186 baseDN,
187 ScopeWholeSubtree, DerefAlways, 0, 0, false,
188 filter[i],
189 attributes,
190 nil)
191 sr, err := l.Search(searchRequest)
192 if err != nil {
193 t.Error(err)
194 results <- nil
195 return
196 }
197 results <- sr
198 }
199
200 func testMultiGoroutineSearch(t *testing.T, TLS bool, startTLS bool) {
201 var l *Conn
202 var err error
203 if TLS {
204 l, err = DialURL(ldapsServer, DialWithTLSConfig(&tls.Config{InsecureSkipVerify: true}))
205 if err != nil {
206 t.Fatal(err)
207 }
208 defer l.Close()
209 } else {
210 l, err = DialURL(ldapServer)
211 if err != nil {
212 t.Fatal(err)
213 }
214 defer l.Close()
215 if startTLS {
216 t.Log("TestMultiGoroutineSearch: using StartTLS...")
217 err := l.StartTLS(&tls.Config{InsecureSkipVerify: true})
218 if err != nil {
219 t.Fatal(err)
220 }
221 }
222 }
223
224 results := make([]chan *SearchResult, len(filter))
225 for i := range filter {
226 results[i] = make(chan *SearchResult)
227 go searchGoroutine(t, l, results[i], i)
228 }
229 for i := range filter {
230 sr := <-results[i]
231 if sr == nil {
232 t.Errorf("Did not receive results from goroutine for %q", filter[i])
233 } else {
234 t.Logf("TestMultiGoroutineSearch(%d): %s -> num of entries = %d", i, filter[i], len(sr.Entries))
235 }
236 }
237 }
238
239 func TestMultiGoroutineSearch(t *testing.T) {
240 testMultiGoroutineSearch(t, false, false)
241 testMultiGoroutineSearch(t, true, true)
242 testMultiGoroutineSearch(t, false, true)
243 }
244
245 func TestEscapeFilter(t *testing.T) {
246 if got, want := EscapeFilter("a\x00b(c)d*e\\f"), `a\00b\28c\29d\2ae\5cf`; got != want {
247 t.Errorf("Got %s, expected %s", want, got)
248 }
249 if got, want := EscapeFilter("Lučić"), `Lu\c4\8di\c4\87`; got != want {
250 t.Errorf("Got %s, expected %s", want, got)
251 }
252 }
253
254 func TestCompare(t *testing.T) {
255 l, err := DialURL(ldapServer)
256 if err != nil {
257 t.Fatal(err)
258 }
259 defer l.Close()
260
261 const dn = "cn=math mich,ou=User Groups,ou=Groups,dc=umich,dc=edu"
262 const attribute = "cn"
263 const value = "math mich"
264
265 sr, err := l.Compare(dn, attribute, value)
266 if err != nil {
267 t.Fatal(err)
268 }
269
270 t.Log("Compare result:", sr)
271 }
272
273 func TestMatchDNError(t *testing.T) {
274 l, err := DialURL(ldapServer)
275 if err != nil {
276 t.Fatal(err)
277 }
278 defer l.Close()
279
280 const wrongBase = "ou=roups,dc=umich,dc=edu"
281
282 searchRequest := NewSearchRequest(
283 wrongBase,
284 ScopeWholeSubtree, DerefAlways, 0, 0, false,
285 filter[0],
286 attributes,
287 nil)
288
289 _, err = l.Search(searchRequest)
290 if err == nil {
291 t.Fatal("Expected Error, got nil")
292 }
293
294 t.Log("TestMatchDNError:", err)
295 }
296
297 func Test_addControlDescriptions(t *testing.T) {
298 type args struct {
299 packet *ber.Packet
300 }
301 tests := []struct {
302 name string
303 args args
304 wantErr bool
305 }{
306 {name: "timeBeforeExpiration", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x29, 0x30, 0x27, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0xa, 0x30, 0x8, 0xa0, 0x6, 0x80, 0x4, 0x7f, 0xff, 0xf6, 0x5c})}, wantErr: false},
307 {name: "graceAuthNsRemaining", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x26, 0x30, 0x24, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x7, 0x30, 0x5, 0xa0, 0x3, 0x81, 0x1, 0x11})}, wantErr: false},
308 {name: "passwordExpired", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x0})}, wantErr: false},
309 {name: "accountLocked", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x1})}, wantErr: false},
310 {name: "passwordModNotAllowed", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x3})}, wantErr: false},
311 {name: "mustSupplyOldPassword", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x4})}, wantErr: false},
312 {name: "insufficientPasswordQuality", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x5})}, wantErr: false},
313 {name: "passwordTooShort", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x6})}, wantErr: false},
314 {name: "passwordTooYoung", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x7})}, wantErr: false},
315 {name: "passwordInHistory", args: args{packet: ber.DecodePacket([]byte{0xa0, 0x24, 0x30, 0x22, 0x4, 0x19, 0x31, 0x2e, 0x33, 0x2e, 0x36, 0x2e, 0x31, 0x2e, 0x34, 0x2e, 0x31, 0x2e, 0x34, 0x32, 0x2e, 0x32, 0x2e, 0x32, 0x37, 0x2e, 0x38, 0x2e, 0x35, 0x2e, 0x31, 0x4, 0x5, 0x30, 0x3, 0x81, 0x1, 0x8})}, wantErr: false},
316 }
317 for _, tt := range tests {
318 t.Run(tt.name, func(t *testing.T) {
319 if err := addControlDescriptions(tt.args.packet); (err != nil) != tt.wantErr {
320 t.Errorf("addControlDescriptions() error = %v, wantErr %v", err, tt.wantErr)
321 }
322 })
323 }
324 }
325
326 func TestEscapeDN(t *testing.T) {
327 tests := []struct {
328 name string
329 dn string
330 want string
331 }{
332 {name: "emptyString", dn: "", want: ""},
333 {name: "comma", dn: "test,user", want: "test\\,user"},
334 {name: "numberSign", dn: "#test#user#", want: "\\#test#user#"},
335 {name: "backslash", dn: "\\test\\user\\", want: "\\\\test\\\\user\\\\"},
336 {name: "whitespaces", dn: " test user ", want: "\\ test user \\ "},
337 {name: "nullByte", dn: "\u0000te\x00st\x00user" + string(rune(0)), want: "\\00te\\00st\\00user\\00"},
338 {name: "variousCharacters", dn: "test\"+,;<>\\-_user", want: "test\\\"\\+\\,\\;\\<\\>\\\\-_user"},
339 {name: "multiByteRunes", dn: "test\u0391user ", want: "test\u0391user\\ "},
340 }
341 for _, tt := range tests {
342 t.Run(tt.name, func(t *testing.T) {
343 if got := EscapeDN(tt.dn); got != tt.want {
344 t.Errorf("EscapeDN(%s) = %s, expected %s", tt.dn, got, tt.want)
345 }
346 })
347 }
348 }
349
350 func TestSearchAsync(t *testing.T) {
351 l, err := DialURL(ldapServer)
352 if err != nil {
353 t.Fatal(err)
354 }
355 defer l.Close()
356
357 searchRequest := NewSearchRequest(
358 baseDN,
359 ScopeWholeSubtree, DerefAlways, 0, 0, false,
360 filter[2],
361 attributes,
362 nil)
363
364 srs := make([]*Entry, 0)
365 ctx := context.Background()
366 r := l.SearchAsync(ctx, searchRequest, 64)
367 for r.Next() {
368 srs = append(srs, r.Entry())
369 }
370 if err := r.Err(); err != nil {
371 log.Fatal(err)
372 }
373
374 t.Logf("TestSearcAsync: %s -> num of entries = %d", searchRequest.Filter, len(srs))
375 }
376
377 func TestSearchAsyncAndCancel(t *testing.T) {
378 l, err := DialURL(ldapServer)
379 if err != nil {
380 t.Fatal(err)
381 }
382 defer l.Close()
383
384 searchRequest := NewSearchRequest(
385 baseDN,
386 ScopeWholeSubtree, DerefAlways, 0, 0, false,
387 filter[2],
388 attributes,
389 nil)
390
391 cancelNum := 10
392 srs := make([]*Entry, 0)
393 ctx, cancel := context.WithCancel(context.Background())
394 defer cancel()
395 r := l.SearchAsync(ctx, searchRequest, 0)
396 for r.Next() {
397 srs = append(srs, r.Entry())
398 if len(srs) == cancelNum {
399 cancel()
400 }
401 }
402 if err := r.Err(); err != nil {
403 log.Fatal(err)
404 }
405
406 if len(srs) > cancelNum+3 {
407
408
409 t.Errorf("Got entries %d, expected < %d", len(srs), cancelNum+3)
410 }
411 t.Logf("TestSearchAsyncAndCancel: %s -> num of entries = %d", searchRequest.Filter, len(srs))
412 }
413
View as plain text