ldap.go 13 KB

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