|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+package service
|
|
|
2
|
+
|
|
|
3
|
+import (
|
|
|
4
|
+ "crypto/tls"
|
|
|
5
|
+ "errors"
|
|
|
6
|
+ "fmt"
|
|
|
7
|
+ "github.com/go-ldap/ldap/v3"
|
|
|
8
|
+ "strconv"
|
|
|
9
|
+ "strings"
|
|
|
10
|
+
|
|
|
11
|
+ "Gwen/config"
|
|
|
12
|
+ "Gwen/global"
|
|
|
13
|
+ "Gwen/model"
|
|
|
14
|
+)
|
|
|
15
|
+
|
|
|
16
|
+// LdapService is responsible for LDAP authentication and user synchronization.
|
|
|
17
|
+type LdapService struct {
|
|
|
18
|
+}
|
|
|
19
|
+
|
|
|
20
|
+// LdapUser represents the user attributes retrieved from LDAP.
|
|
|
21
|
+type LdapUser struct {
|
|
|
22
|
+ Dn string
|
|
|
23
|
+ Username string
|
|
|
24
|
+ Email string
|
|
|
25
|
+ FirstName string
|
|
|
26
|
+ LastName string
|
|
|
27
|
+ MemberOf []string
|
|
|
28
|
+ EnableAttrValue string
|
|
|
29
|
+ Enabled bool
|
|
|
30
|
+}
|
|
|
31
|
+
|
|
|
32
|
+// Name returns the full name of an LDAP user.
|
|
|
33
|
+func (lu *LdapUser) Name() string {
|
|
|
34
|
+ return fmt.Sprintf("%s %s", lu.FirstName, lu.LastName)
|
|
|
35
|
+}
|
|
|
36
|
+
|
|
|
37
|
+// ToUser merges the LdapUser data into a provided *model.User.
|
|
|
38
|
+// If 'u' is nil, it creates and returns a new *model.User.
|
|
|
39
|
+func (lu *LdapUser) ToUser(u *model.User) *model.User {
|
|
|
40
|
+ if u == nil {
|
|
|
41
|
+ u = &model.User{}
|
|
|
42
|
+ }
|
|
|
43
|
+ u.Username = lu.Username
|
|
|
44
|
+ u.Email = lu.Email
|
|
|
45
|
+ u.Nickname = lu.Name()
|
|
|
46
|
+ return u
|
|
|
47
|
+}
|
|
|
48
|
+
|
|
|
49
|
+// connectAndBind creates an LDAP connection, optionally starts TLS, and then binds using the provided credentials.
|
|
|
50
|
+func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) {
|
|
|
51
|
+ conn, err := ldap.DialURL(cfg.Url)
|
|
|
52
|
+ if err != nil {
|
|
|
53
|
+ return nil, fmt.Errorf("failed to dial LDAP: %w", err)
|
|
|
54
|
+ }
|
|
|
55
|
+
|
|
|
56
|
+ if cfg.TLS {
|
|
|
57
|
+ // WARNING: InsecureSkipVerify: true is not recommended for production
|
|
|
58
|
+ if err = conn.StartTLS(&tls.Config{InsecureSkipVerify: !cfg.TlsVerify}); err != nil {
|
|
|
59
|
+ conn.Close()
|
|
|
60
|
+ return nil, fmt.Errorf("failed to start TLS: %w", err)
|
|
|
61
|
+ }
|
|
|
62
|
+ }
|
|
|
63
|
+
|
|
|
64
|
+ // Bind as the "service" user
|
|
|
65
|
+ if err = conn.Bind(username, password); err != nil {
|
|
|
66
|
+ conn.Close()
|
|
|
67
|
+ return nil, fmt.Errorf("failed to bind with service account: %w", err)
|
|
|
68
|
+ }
|
|
|
69
|
+ return conn, nil
|
|
|
70
|
+}
|
|
|
71
|
+
|
|
|
72
|
+// connectAndBindAdmin creates an LDAP connection, optionally starts TLS, and then binds using the admin credentials.
|
|
|
73
|
+func (ls *LdapService) connectAndBindAdmin(cfg *config.Ldap) (*ldap.Conn, error) {
|
|
|
74
|
+ return ls.connectAndBind(cfg, cfg.BindDn, cfg.BindPassword)
|
|
|
75
|
+}
|
|
|
76
|
+
|
|
|
77
|
+// verifyCredentials checks the provided username and password against LDAP.
|
|
|
78
|
+func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password string) error {
|
|
|
79
|
+ ldapConn, err := ls.connectAndBind(cfg, username, password)
|
|
|
80
|
+ if err != nil {
|
|
|
81
|
+ return err
|
|
|
82
|
+ }
|
|
|
83
|
+ defer ldapConn.Close()
|
|
|
84
|
+ return nil
|
|
|
85
|
+}
|
|
|
86
|
+
|
|
|
87
|
+// Authenticate checks the provided username and password against LDAP.
|
|
|
88
|
+// Returns the corresponding *model.User if successful, or an error if not.
|
|
|
89
|
+func (ls *LdapService) Authenticate(username, password string) (*model.User, error) {
|
|
|
90
|
+ cfg := &global.Config.Ldap
|
|
|
91
|
+ // 1. Use a service bind to search for the user DN
|
|
|
92
|
+ sr, err := ls.usernameSearchResult(cfg, username)
|
|
|
93
|
+ if err != nil {
|
|
|
94
|
+ return nil, fmt.Errorf("LDAP search request failed: %w", err)
|
|
|
95
|
+ }
|
|
|
96
|
+ if len(sr.Entries) != 1 {
|
|
|
97
|
+ return nil, errors.New("user does not exist or too many entries returned")
|
|
|
98
|
+ }
|
|
|
99
|
+ entry := sr.Entries[0]
|
|
|
100
|
+ userDN := entry.DN
|
|
|
101
|
+
|
|
|
102
|
+ err = ls.verifyCredentials(cfg, userDN, password)
|
|
|
103
|
+ if err != nil {
|
|
|
104
|
+ return nil, fmt.Errorf("LDAP authentication failed: %w", err)
|
|
|
105
|
+ }
|
|
|
106
|
+ ldapUser := ls.userResultToLdapUser(cfg, entry)
|
|
|
107
|
+ if !ldapUser.Enabled {
|
|
|
108
|
+ return nil, errors.New("UserDisabledAtLdap")
|
|
|
109
|
+ }
|
|
|
110
|
+ user, err := ls.mapToLocalUser(cfg, ldapUser)
|
|
|
111
|
+ if err != nil {
|
|
|
112
|
+ return nil, fmt.Errorf("failed to map LDAP user to local user: %w", err)
|
|
|
113
|
+ }
|
|
|
114
|
+ return user, nil
|
|
|
115
|
+}
|
|
|
116
|
+
|
|
|
117
|
+// mapToLocalUser checks whether the user exists locally; if not, creates one.
|
|
|
118
|
+// If the user exists and Ldap.Sync is enabled, it updates local info.
|
|
|
119
|
+func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.User, error) {
|
|
|
120
|
+ userService := &UserService{}
|
|
|
121
|
+ localUser := userService.InfoByUsername(lu.Username)
|
|
|
122
|
+ isAdmin := ls.isUserAdmin(cfg, lu)
|
|
|
123
|
+ // If the user doesn't exist in local DB, create a new one
|
|
|
124
|
+ if localUser.Id == 0 {
|
|
|
125
|
+ newUser := lu.ToUser(nil)
|
|
|
126
|
+ // Typically, you don’t store LDAP user passwords locally.
|
|
|
127
|
+ // If needed, you can set a random password here.
|
|
|
128
|
+ newUser.IsAdmin = &isAdmin
|
|
|
129
|
+ if err := global.DB.Create(newUser).Error; err != nil {
|
|
|
130
|
+ return nil, fmt.Errorf("failed to create new user: %w", err)
|
|
|
131
|
+ }
|
|
|
132
|
+ return userService.InfoByUsername(lu.Username), nil
|
|
|
133
|
+ }
|
|
|
134
|
+
|
|
|
135
|
+ // If the user already exists and sync is enabled, update local info
|
|
|
136
|
+ if cfg.User.Sync {
|
|
|
137
|
+ originalEmail := localUser.Email
|
|
|
138
|
+ originalNickname := localUser.Nickname
|
|
|
139
|
+ originalIsAdmin := localUser.IsAdmin
|
|
|
140
|
+ lu.ToUser(localUser) // merges LDAP data into the existing user
|
|
|
141
|
+ localUser.IsAdmin = &isAdmin
|
|
|
142
|
+ if err := userService.Update(localUser); err != nil {
|
|
|
143
|
+ // If the update fails, revert to original data
|
|
|
144
|
+ localUser.Email = originalEmail
|
|
|
145
|
+ localUser.Nickname = originalNickname
|
|
|
146
|
+ localUser.IsAdmin = originalIsAdmin
|
|
|
147
|
+ }
|
|
|
148
|
+ }
|
|
|
149
|
+
|
|
|
150
|
+ return localUser, nil
|
|
|
151
|
+}
|
|
|
152
|
+
|
|
|
153
|
+// IsUsernameExists checks if a username exists in LDAP (can be useful for local registration checks).
|
|
|
154
|
+func (ls *LdapService) IsUsernameExists(username string) bool {
|
|
|
155
|
+
|
|
|
156
|
+ cfg := &global.Config.Ldap
|
|
|
157
|
+ if !cfg.Enable {
|
|
|
158
|
+ return false
|
|
|
159
|
+ }
|
|
|
160
|
+ sr, err := ls.usernameSearchResult(cfg, username)
|
|
|
161
|
+ if err != nil {
|
|
|
162
|
+ return false
|
|
|
163
|
+ }
|
|
|
164
|
+ return len(sr.Entries) > 0
|
|
|
165
|
+}
|
|
|
166
|
+
|
|
|
167
|
+// IsEmailExists checks if an email exists in LDAP (can be useful for local registration checks).
|
|
|
168
|
+func (ls *LdapService) IsEmailExists(email string) bool {
|
|
|
169
|
+ cfg := &global.Config.Ldap
|
|
|
170
|
+ if !cfg.Enable {
|
|
|
171
|
+ return false
|
|
|
172
|
+ }
|
|
|
173
|
+ sr, err := ls.emailSearchResult(cfg, email)
|
|
|
174
|
+ if err != nil {
|
|
|
175
|
+ return false
|
|
|
176
|
+ }
|
|
|
177
|
+ return len(sr.Entries) > 0
|
|
|
178
|
+}
|
|
|
179
|
+
|
|
|
180
|
+// usernameSearchResult returns the search result for the given username.
|
|
|
181
|
+func (ls *LdapService) usernameSearchResult(cfg *config.Ldap, username string) (*ldap.SearchResult, error) {
|
|
|
182
|
+ // Build the combined filter for the username
|
|
|
183
|
+ filter := ls.filterField(ls.fieldUsername(cfg), username)
|
|
|
184
|
+ // Create the *ldap.SearchRequest
|
|
|
185
|
+ searchRequest := ls.buildUserSearchRequest(cfg, filter)
|
|
|
186
|
+ return ls.searchResult(cfg, searchRequest)
|
|
|
187
|
+}
|
|
|
188
|
+
|
|
|
189
|
+// emailSearchResult returns the search result for the given email.
|
|
|
190
|
+func (ls *LdapService) emailSearchResult(cfg *config.Ldap, email string) (*ldap.SearchResult, error) {
|
|
|
191
|
+ filter := ls.filterField(ls.fieldEmail(cfg), email)
|
|
|
192
|
+ searchRequest := ls.buildUserSearchRequest(cfg, filter)
|
|
|
193
|
+ return ls.searchResult(cfg, searchRequest)
|
|
|
194
|
+}
|
|
|
195
|
+
|
|
|
196
|
+func (ls *LdapService) searchResult(cfg *config.Ldap, searchRequest *ldap.SearchRequest) (*ldap.SearchResult, error) {
|
|
|
197
|
+ ldapConn, err := ls.connectAndBindAdmin(cfg)
|
|
|
198
|
+ if err != nil {
|
|
|
199
|
+ return nil, err
|
|
|
200
|
+ }
|
|
|
201
|
+ defer ldapConn.Close()
|
|
|
202
|
+ return ldapConn.Search(searchRequest)
|
|
|
203
|
+}
|
|
|
204
|
+
|
|
|
205
|
+// buildUserSearchRequest constructs an LDAP SearchRequest for users given a filter.
|
|
|
206
|
+func (ls *LdapService) buildUserSearchRequest(cfg *config.Ldap, filter string) *ldap.SearchRequest {
|
|
|
207
|
+ baseDn := ls.baseDnUser(cfg) // user-specific base DN, or fallback
|
|
|
208
|
+ filterConfig := cfg.User.Filter
|
|
|
209
|
+ if filterConfig == "" {
|
|
|
210
|
+ filterConfig = "(cn=*)"
|
|
|
211
|
+ }
|
|
|
212
|
+
|
|
|
213
|
+ // Combine the default filter with our field filter, e.g. (&(cn=*)(uid=jdoe))
|
|
|
214
|
+ combinedFilter := fmt.Sprintf("(&%s%s)", filterConfig, filter)
|
|
|
215
|
+
|
|
|
216
|
+ attributes := ls.buildUserAttributes(cfg)
|
|
|
217
|
+
|
|
|
218
|
+ return ldap.NewSearchRequest(
|
|
|
219
|
+ baseDn,
|
|
|
220
|
+ ldap.ScopeWholeSubtree,
|
|
|
221
|
+ ldap.NeverDerefAliases,
|
|
|
222
|
+ 0, // unlimited search results
|
|
|
223
|
+ 0, // no server-side time limit
|
|
|
224
|
+ false, // typesOnly
|
|
|
225
|
+ combinedFilter,
|
|
|
226
|
+ attributes,
|
|
|
227
|
+ nil,
|
|
|
228
|
+ )
|
|
|
229
|
+}
|
|
|
230
|
+
|
|
|
231
|
+// buildUserAttributes returns the list of attributes we want from LDAP user searches.
|
|
|
232
|
+func (ls *LdapService) buildUserAttributes(cfg *config.Ldap) []string {
|
|
|
233
|
+ return []string{
|
|
|
234
|
+ "dn",
|
|
|
235
|
+ ls.fieldUsername(cfg),
|
|
|
236
|
+ ls.fieldEmail(cfg),
|
|
|
237
|
+ ls.fieldFirstName(cfg),
|
|
|
238
|
+ ls.fieldLastName(cfg),
|
|
|
239
|
+ ls.fieldMemberOf(),
|
|
|
240
|
+ ls.fieldUserEnableAttr(cfg),
|
|
|
241
|
+ }
|
|
|
242
|
+}
|
|
|
243
|
+
|
|
|
244
|
+// userResultToLdapUser maps an *ldap.Entry to our LdapUser struct.
|
|
|
245
|
+func (ls *LdapService) userResultToLdapUser(cfg *config.Ldap, entry *ldap.Entry) *LdapUser {
|
|
|
246
|
+ lu := &LdapUser{
|
|
|
247
|
+ Dn: entry.DN,
|
|
|
248
|
+ Username: entry.GetAttributeValue(ls.fieldUsername(cfg)),
|
|
|
249
|
+ Email: entry.GetAttributeValue(ls.fieldEmail(cfg)),
|
|
|
250
|
+ FirstName: entry.GetAttributeValue(ls.fieldFirstName(cfg)),
|
|
|
251
|
+ LastName: entry.GetAttributeValue(ls.fieldLastName(cfg)),
|
|
|
252
|
+ MemberOf: entry.GetAttributeValues(ls.fieldMemberOf()),
|
|
|
253
|
+ EnableAttrValue: entry.GetAttributeValue(ls.fieldUserEnableAttr(cfg)),
|
|
|
254
|
+ }
|
|
|
255
|
+ // Check if the user is enabled based on the LDAP configuration
|
|
|
256
|
+ ls.isUserEnabled(cfg, lu)
|
|
|
257
|
+ return lu
|
|
|
258
|
+}
|
|
|
259
|
+
|
|
|
260
|
+// filterField helps build simple attribute filters, e.g. (uid=username).
|
|
|
261
|
+func (ls *LdapService) filterField(field, value string) string {
|
|
|
262
|
+ return fmt.Sprintf("(%s=%s)", field, value)
|
|
|
263
|
+}
|
|
|
264
|
+
|
|
|
265
|
+// fieldUsername returns the configured username attribute or "uid" if not set.
|
|
|
266
|
+func (ls *LdapService) fieldUsername(cfg *config.Ldap) string {
|
|
|
267
|
+ if cfg.User.Username == "" {
|
|
|
268
|
+ return "uid"
|
|
|
269
|
+ }
|
|
|
270
|
+ return cfg.User.Username
|
|
|
271
|
+}
|
|
|
272
|
+
|
|
|
273
|
+// fieldEmail returns the configured email attribute or "mail" if not set.
|
|
|
274
|
+func (ls *LdapService) fieldEmail(cfg *config.Ldap) string {
|
|
|
275
|
+ if cfg.User.Email == "" {
|
|
|
276
|
+ return "mail"
|
|
|
277
|
+ }
|
|
|
278
|
+ return cfg.User.Email
|
|
|
279
|
+}
|
|
|
280
|
+
|
|
|
281
|
+// fieldFirstName returns the configured first name attribute or "givenName" if not set.
|
|
|
282
|
+func (ls *LdapService) fieldFirstName(cfg *config.Ldap) string {
|
|
|
283
|
+ if cfg.User.FirstName == "" {
|
|
|
284
|
+ return "givenName"
|
|
|
285
|
+ }
|
|
|
286
|
+ return cfg.User.FirstName
|
|
|
287
|
+}
|
|
|
288
|
+
|
|
|
289
|
+// fieldLastName returns the configured last name attribute or "sn" if not set.
|
|
|
290
|
+func (ls *LdapService) fieldLastName(cfg *config.Ldap) string {
|
|
|
291
|
+ if cfg.User.LastName == "" {
|
|
|
292
|
+ return "sn"
|
|
|
293
|
+ }
|
|
|
294
|
+ return cfg.User.LastName
|
|
|
295
|
+}
|
|
|
296
|
+
|
|
|
297
|
+func (ls *LdapService) fieldMemberOf() string {
|
|
|
298
|
+ return "memberOf"
|
|
|
299
|
+}
|
|
|
300
|
+
|
|
|
301
|
+func (ls *LdapService) fieldUserEnableAttr(cfg *config.Ldap) string {
|
|
|
302
|
+ if cfg.User.EnableAttr == "" {
|
|
|
303
|
+ return "userAccountControl"
|
|
|
304
|
+ }
|
|
|
305
|
+ return cfg.User.EnableAttr
|
|
|
306
|
+}
|
|
|
307
|
+
|
|
|
308
|
+// baseDnUser returns the user-specific base DN or the global base DN if none is set.
|
|
|
309
|
+func (ls *LdapService) baseDnUser(cfg *config.Ldap) string {
|
|
|
310
|
+ if cfg.User.BaseDn == "" {
|
|
|
311
|
+ return cfg.BaseDn
|
|
|
312
|
+ }
|
|
|
313
|
+ return cfg.User.BaseDn
|
|
|
314
|
+}
|
|
|
315
|
+
|
|
|
316
|
+// isUserAdmin checks if the user is a member of the admin group.
|
|
|
317
|
+func (ls *LdapService) isUserAdmin(cfg *config.Ldap, ldapUser *LdapUser) bool {
|
|
|
318
|
+ // Check if the admin group is configured
|
|
|
319
|
+ adminGroup := cfg.User.AdminGroup
|
|
|
320
|
+ if adminGroup == "" {
|
|
|
321
|
+ return false
|
|
|
322
|
+ }
|
|
|
323
|
+
|
|
|
324
|
+ // Check "memberOf" directly
|
|
|
325
|
+ if len(ldapUser.MemberOf) > 0 {
|
|
|
326
|
+ for _, group := range ldapUser.MemberOf {
|
|
|
327
|
+ if group == adminGroup {
|
|
|
328
|
+ return true
|
|
|
329
|
+ }
|
|
|
330
|
+ }
|
|
|
331
|
+ return false
|
|
|
332
|
+ }
|
|
|
333
|
+
|
|
|
334
|
+ // For "member" attribute, perform a reverse search on the group
|
|
|
335
|
+ member := "member"
|
|
|
336
|
+ userDN := ldap.EscapeFilter(ldapUser.Dn)
|
|
|
337
|
+ adminGroupDn := ldap.EscapeFilter(adminGroup)
|
|
|
338
|
+ groupFilter := fmt.Sprintf("(%s=%s)", member, userDN)
|
|
|
339
|
+
|
|
|
340
|
+ // Create the LDAP search request
|
|
|
341
|
+ groupSearchRequest := ldap.NewSearchRequest(
|
|
|
342
|
+ adminGroupDn,
|
|
|
343
|
+ ldap.ScopeWholeSubtree,
|
|
|
344
|
+ ldap.NeverDerefAliases,
|
|
|
345
|
+ 0, // Unlimited search results
|
|
|
346
|
+ 0, // No time limit
|
|
|
347
|
+ false, // Return both attributes and DN
|
|
|
348
|
+ groupFilter,
|
|
|
349
|
+ []string{"dn"},
|
|
|
350
|
+ nil,
|
|
|
351
|
+ )
|
|
|
352
|
+
|
|
|
353
|
+ // Perform the group search
|
|
|
354
|
+ groupResult, err := ls.searchResult(cfg, groupSearchRequest)
|
|
|
355
|
+ if err != nil {
|
|
|
356
|
+ return false
|
|
|
357
|
+ }
|
|
|
358
|
+
|
|
|
359
|
+ // If any results are returned, the user is part of the admin group
|
|
|
360
|
+ if len(groupResult.Entries) > 0 {
|
|
|
361
|
+ return true
|
|
|
362
|
+ }
|
|
|
363
|
+ return false
|
|
|
364
|
+
|
|
|
365
|
+}
|
|
|
366
|
+
|
|
|
367
|
+// isUserEnabled checks if the user is enabled based on the LDAP configuration.
|
|
|
368
|
+// If no enable attribute or value is set, all users are considered enabled by default.
|
|
|
369
|
+func (ls *LdapService) isUserEnabled(cfg *config.Ldap, ldapUser *LdapUser) bool {
|
|
|
370
|
+ // Retrieve the enable attribute and expected value from the configuration
|
|
|
371
|
+ enableAttr := cfg.User.EnableAttr
|
|
|
372
|
+ enableAttrValue := cfg.User.EnableAttrValue
|
|
|
373
|
+
|
|
|
374
|
+ // If no enable attribute or value is configured, consider all users as enabled
|
|
|
375
|
+ if enableAttr == "" || enableAttrValue == "" {
|
|
|
376
|
+ ldapUser.Enabled = true
|
|
|
377
|
+ return true
|
|
|
378
|
+ }
|
|
|
379
|
+
|
|
|
380
|
+ // Normalize the enable attribute for comparison
|
|
|
381
|
+ enableAttr = strings.ToLower(enableAttr)
|
|
|
382
|
+
|
|
|
383
|
+ // Handle Active Directory's userAccountControl attribute
|
|
|
384
|
+ if enableAttr == "useraccountcontrol" {
|
|
|
385
|
+ // Parse the userAccountControl value
|
|
|
386
|
+ userAccountControl, err := strconv.Atoi(ldapUser.EnableAttrValue)
|
|
|
387
|
+ if err != nil {
|
|
|
388
|
+ fmt.Printf("[ERROR] Invalid userAccountControl value: %v\n", err)
|
|
|
389
|
+ ldapUser.Enabled = false
|
|
|
390
|
+ return false
|
|
|
391
|
+ }
|
|
|
392
|
+
|
|
|
393
|
+ // Account is disabled if the ACCOUNTDISABLE flag (0x2) is set
|
|
|
394
|
+ const ACCOUNTDISABLE = 0x2
|
|
|
395
|
+ ldapUser.Enabled = (userAccountControl&ACCOUNTDISABLE == 0)
|
|
|
396
|
+ return ldapUser.Enabled
|
|
|
397
|
+ }
|
|
|
398
|
+
|
|
|
399
|
+ // For other attributes, perform a direct comparison with the expected value
|
|
|
400
|
+ ldapUser.Enabled = (ldapUser.EnableAttrValue == enableAttrValue)
|
|
|
401
|
+ return ldapUser.Enabled
|
|
|
402
|
+}
|
|
|
403
|
+
|
|
|
404
|
+// getAttrOfDn retrieves the value of an attribute for a given DN.
|
|
|
405
|
+func (ls *LdapService) getAttrOfDn(cfg *config.Ldap, dn, attr string) string {
|
|
|
406
|
+ searchRequest := ldap.NewSearchRequest(
|
|
|
407
|
+ ldap.EscapeFilter(dn),
|
|
|
408
|
+ ldap.ScopeBaseObject,
|
|
|
409
|
+ ldap.NeverDerefAliases,
|
|
|
410
|
+ 0, // unlimited search results
|
|
|
411
|
+ 0, // no server-side time limit
|
|
|
412
|
+ false, // typesOnly
|
|
|
413
|
+ "(objectClass=*)",
|
|
|
414
|
+ []string{attr},
|
|
|
415
|
+ nil,
|
|
|
416
|
+ )
|
|
|
417
|
+ sr, err := ls.searchResult(cfg, searchRequest)
|
|
|
418
|
+ if err != nil {
|
|
|
419
|
+ return ""
|
|
|
420
|
+ }
|
|
|
421
|
+ if len(sr.Entries) == 0 {
|
|
|
422
|
+ return ""
|
|
|
423
|
+ }
|
|
|
424
|
+ return sr.Entries[0].GetAttributeValue(attr)
|
|
|
425
|
+}
|