ldap.go 15 KB

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