ldap.go 15 KB

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