|
|
@@ -4,15 +4,30 @@ import (
|
|
4
|
4
|
"crypto/tls"
|
|
5
|
5
|
"errors"
|
|
6
|
6
|
"fmt"
|
|
7
|
|
- "github.com/go-ldap/ldap/v3"
|
|
8
|
7
|
"strconv"
|
|
9
|
8
|
"strings"
|
|
10
|
9
|
|
|
|
10
|
+ "github.com/go-ldap/ldap/v3"
|
|
|
11
|
+
|
|
11
|
12
|
"Gwen/config"
|
|
12
|
13
|
"Gwen/global"
|
|
13
|
14
|
"Gwen/model"
|
|
14
|
15
|
)
|
|
15
|
16
|
|
|
|
17
|
+var (
|
|
|
18
|
+ ErrLdapNotEnabled = errors.New("LdapNotEnabled")
|
|
|
19
|
+ ErrLdapUserDisabled = errors.New("UserDisabledAtLdap")
|
|
|
20
|
+ ErrLdapUserNotFound = errors.New("UserNotFound")
|
|
|
21
|
+ ErrLdapMailNotMatch = errors.New("MailNotMatch")
|
|
|
22
|
+ ErrLdapConnectFailed = errors.New("LdapConnectFailed")
|
|
|
23
|
+ ErrLdapSearchFailed = errors.New("LdapSearchRequestFailed")
|
|
|
24
|
+ ErrLdapTlsFailed = errors.New("LdapStartTLSFailed")
|
|
|
25
|
+ ErrLdapBindService = errors.New("LdapBindServiceFailed")
|
|
|
26
|
+ ErrLdapBindFailed = errors.New("LdapBindFailed")
|
|
|
27
|
+ ErrLdapToLocalUserFailed = errors.New("LdapToLocalUserFailed")
|
|
|
28
|
+ ErrLdapCreateUserFailed = errors.New("LdapCreateUserFailed")
|
|
|
29
|
+)
|
|
|
30
|
+
|
|
16
|
31
|
// LdapService is responsible for LDAP authentication and user synchronization.
|
|
17
|
32
|
type LdapService struct {
|
|
18
|
33
|
}
|
|
|
@@ -43,6 +58,11 @@ func (lu *LdapUser) ToUser(u *model.User) *model.User {
|
|
43
|
58
|
u.Username = lu.Username
|
|
44
|
59
|
u.Email = lu.Email
|
|
45
|
60
|
u.Nickname = lu.Name()
|
|
|
61
|
+ if lu.Enabled {
|
|
|
62
|
+ u.Status = model.COMMON_STATUS_ENABLE
|
|
|
63
|
+ } else {
|
|
|
64
|
+ u.Status = model.COMMON_STATUS_DISABLED
|
|
|
65
|
+ }
|
|
46
|
66
|
return u
|
|
47
|
67
|
}
|
|
48
|
68
|
|
|
|
@@ -50,21 +70,21 @@ func (lu *LdapUser) ToUser(u *model.User) *model.User {
|
|
50
|
70
|
func (ls *LdapService) connectAndBind(cfg *config.Ldap, username, password string) (*ldap.Conn, error) {
|
|
51
|
71
|
conn, err := ldap.DialURL(cfg.Url)
|
|
52
|
72
|
if err != nil {
|
|
53
|
|
- return nil, fmt.Errorf("failed to dial LDAP: %w", err)
|
|
|
73
|
+ return nil, errors.Join(ErrLdapConnectFailed, err)
|
|
54
|
74
|
}
|
|
55
|
75
|
|
|
56
|
76
|
if cfg.TLS {
|
|
57
|
77
|
// WARNING: InsecureSkipVerify: true is not recommended for production
|
|
58
|
78
|
if err = conn.StartTLS(&tls.Config{InsecureSkipVerify: !cfg.TlsVerify}); err != nil {
|
|
59
|
79
|
conn.Close()
|
|
60
|
|
- return nil, fmt.Errorf("failed to start TLS: %w", err)
|
|
|
80
|
+ return nil, errors.Join(ErrLdapTlsFailed, err)
|
|
61
|
81
|
}
|
|
62
|
82
|
}
|
|
63
|
83
|
|
|
64
|
84
|
// Bind as the "service" user
|
|
65
|
85
|
if err = conn.Bind(username, password); err != nil {
|
|
66
|
86
|
conn.Close()
|
|
67
|
|
- return nil, fmt.Errorf("failed to bind with service account: %w", err)
|
|
|
87
|
+ return nil, errors.Join(ErrLdapBindService, err)
|
|
68
|
88
|
}
|
|
69
|
89
|
return conn, nil
|
|
70
|
90
|
}
|
|
|
@@ -87,29 +107,17 @@ func (ls *LdapService) verifyCredentials(cfg *config.Ldap, username, password st
|
|
87
|
107
|
// Authenticate checks the provided username and password against LDAP.
|
|
88
|
108
|
// Returns the corresponding *model.User if successful, or an error if not.
|
|
89
|
109
|
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)
|
|
|
110
|
+ ldapUser, err := ls.GetUserInfoByUsernameLdap(username)
|
|
103
|
111
|
if err != nil {
|
|
104
|
|
- return nil, fmt.Errorf("LDAP authentication failed: %w", err)
|
|
|
112
|
+ return nil, err
|
|
105
|
113
|
}
|
|
106
|
|
- ldapUser := ls.userResultToLdapUser(cfg, entry)
|
|
107
|
114
|
if !ldapUser.Enabled {
|
|
108
|
|
- return nil, errors.New("UserDisabledAtLdap")
|
|
|
115
|
+ return nil, ErrLdapUserDisabled
|
|
109
|
116
|
}
|
|
|
117
|
+ cfg := &global.Config.Ldap
|
|
110
|
118
|
user, err := ls.mapToLocalUser(cfg, ldapUser)
|
|
111
|
119
|
if err != nil {
|
|
112
|
|
- return nil, fmt.Errorf("failed to map LDAP user to local user: %w", err)
|
|
|
120
|
+ return nil, errors.Join(ErrLdapToLocalUserFailed, err)
|
|
113
|
121
|
}
|
|
114
|
122
|
return user, nil
|
|
115
|
123
|
}
|
|
|
@@ -126,8 +134,9 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
|
|
126
|
134
|
// Typically, you don’t store LDAP user passwords locally.
|
|
127
|
135
|
// If needed, you can set a random password here.
|
|
128
|
136
|
newUser.IsAdmin = &isAdmin
|
|
|
137
|
+ newUser.GroupId = 1
|
|
129
|
138
|
if err := global.DB.Create(newUser).Error; err != nil {
|
|
130
|
|
- return nil, fmt.Errorf("failed to create new user: %w", err)
|
|
|
139
|
+ return nil, errors.Join(ErrLdapCreateUserFailed, err)
|
|
131
|
140
|
}
|
|
132
|
141
|
return userService.InfoByUsername(lu.Username), nil
|
|
133
|
142
|
}
|
|
|
@@ -137,6 +146,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
|
|
137
|
146
|
originalEmail := localUser.Email
|
|
138
|
147
|
originalNickname := localUser.Nickname
|
|
139
|
148
|
originalIsAdmin := localUser.IsAdmin
|
|
|
149
|
+ originalStatus := localUser.Status
|
|
140
|
150
|
lu.ToUser(localUser) // merges LDAP data into the existing user
|
|
141
|
151
|
localUser.IsAdmin = &isAdmin
|
|
142
|
152
|
if err := userService.Update(localUser); err != nil {
|
|
|
@@ -144,6 +154,7 @@ func (ls *LdapService) mapToLocalUser(cfg *config.Ldap, lu *LdapUser) (*model.Us
|
|
144
|
154
|
localUser.Email = originalEmail
|
|
145
|
155
|
localUser.Nickname = originalNickname
|
|
146
|
156
|
localUser.IsAdmin = originalIsAdmin
|
|
|
157
|
+ localUser.Status = originalStatus
|
|
147
|
158
|
}
|
|
148
|
159
|
}
|
|
149
|
160
|
|
|
|
@@ -177,6 +188,56 @@ func (ls *LdapService) IsEmailExists(email string) bool {
|
|
177
|
188
|
return len(sr.Entries) > 0
|
|
178
|
189
|
}
|
|
179
|
190
|
|
|
|
191
|
+// GetUserInfoByUsernameLdap returns the user info from LDAP for the given username.
|
|
|
192
|
+func (ls *LdapService) GetUserInfoByUsernameLdap(username string) (*LdapUser, error) {
|
|
|
193
|
+ cfg := &global.Config.Ldap
|
|
|
194
|
+ if !cfg.Enable {
|
|
|
195
|
+ return nil, ErrLdapNotEnabled
|
|
|
196
|
+ }
|
|
|
197
|
+ sr, err := ls.usernameSearchResult(cfg, username)
|
|
|
198
|
+ if err != nil {
|
|
|
199
|
+ return nil, errors.Join(ErrLdapSearchFailed, err)
|
|
|
200
|
+ }
|
|
|
201
|
+ if len(sr.Entries) != 1 {
|
|
|
202
|
+ return nil, ErrLdapUserNotFound
|
|
|
203
|
+ }
|
|
|
204
|
+ return ls.userResultToLdapUser(cfg, sr.Entries[0]), nil
|
|
|
205
|
+}
|
|
|
206
|
+
|
|
|
207
|
+// GetUserInfoByUsernameLocal returns the user info from LDAP for the given username. If the user exists, it will sync the user info to the local database.
|
|
|
208
|
+func (ls *LdapService) GetUserInfoByUsernameLocal(username string) (*model.User, error) {
|
|
|
209
|
+ ldapUser, err := ls.GetUserInfoByUsernameLdap(username)
|
|
|
210
|
+ if err != nil {
|
|
|
211
|
+ return &model.User{}, err
|
|
|
212
|
+ }
|
|
|
213
|
+ return ls.mapToLocalUser(&global.Config.Ldap, ldapUser)
|
|
|
214
|
+}
|
|
|
215
|
+
|
|
|
216
|
+// GetUserInfoByEmailLdap returns the user info from LDAP for the given email.
|
|
|
217
|
+func (ls *LdapService) GetUserInfoByEmailLdap(email string) (*LdapUser, error) {
|
|
|
218
|
+ cfg := &global.Config.Ldap
|
|
|
219
|
+ if !cfg.Enable {
|
|
|
220
|
+ return nil, ErrLdapNotEnabled
|
|
|
221
|
+ }
|
|
|
222
|
+ sr, err := ls.emailSearchResult(cfg, email)
|
|
|
223
|
+ if err != nil {
|
|
|
224
|
+ return nil, errors.Join(ErrLdapSearchFailed, err)
|
|
|
225
|
+ }
|
|
|
226
|
+ if len(sr.Entries) != 1 {
|
|
|
227
|
+ return nil, ErrLdapUserNotFound
|
|
|
228
|
+ }
|
|
|
229
|
+ return ls.userResultToLdapUser(cfg, sr.Entries[0]), nil
|
|
|
230
|
+}
|
|
|
231
|
+
|
|
|
232
|
+// GetUserInfoByEmailLocal returns the user info from LDAP for the given email. if the user exists, it will synchronize the user information to local database.
|
|
|
233
|
+func (ls *LdapService) GetUserInfoByEmailLocal(email string) (*model.User, error) {
|
|
|
234
|
+ ldapUser, err := ls.GetUserInfoByEmailLdap(email)
|
|
|
235
|
+ if err != nil {
|
|
|
236
|
+ return &model.User{}, err
|
|
|
237
|
+ }
|
|
|
238
|
+ return ls.mapToLocalUser(&global.Config.Ldap, ldapUser)
|
|
|
239
|
+}
|
|
|
240
|
+
|
|
180
|
241
|
// usernameSearchResult returns the search result for the given username.
|
|
181
|
242
|
func (ls *LdapService) usernameSearchResult(cfg *config.Ldap, username string) (*ldap.SearchResult, error) {
|
|
182
|
243
|
// Build the combined filter for the username
|