ldap.go 15 KB

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