userauth_ldap.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. package dbdata
  2. import (
  3. "crypto/tls"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "reflect"
  9. "regexp"
  10. "strconv"
  11. "time"
  12. "github.com/go-ldap/ldap"
  13. )
  14. type AuthLdap struct {
  15. Addr string `json:"addr"`
  16. Tls bool `json:"tls"`
  17. BindName string `json:"bind_name"`
  18. BindPwd string `json:"bind_pwd"`
  19. BaseDn string `json:"base_dn"`
  20. ObjectClass string `json:"object_class"`
  21. SearchAttr string `json:"search_attr"`
  22. MemberOf string `json:"member_of"`
  23. }
  24. func init() {
  25. authRegistry["ldap"] = reflect.TypeOf(AuthLdap{})
  26. }
  27. func (auth AuthLdap) checkData(authData map[string]interface{}) error {
  28. authType := authData["type"].(string)
  29. bodyBytes, err := json.Marshal(authData[authType])
  30. if err != nil {
  31. return errors.New("LDAP配置填写有误")
  32. }
  33. json.Unmarshal(bodyBytes, &auth)
  34. // 支持域名和IP, 必须填写端口
  35. if !ValidateIpPort(auth.Addr) && !ValidateDomainPort(auth.Addr) {
  36. return errors.New("LDAP的服务器地址(含端口)填写有误")
  37. }
  38. if auth.BindName == "" {
  39. return errors.New("LDAP的管理员 DN不能为空")
  40. }
  41. if auth.BindPwd == "" {
  42. return errors.New("LDAP的管理员密码不能为空")
  43. }
  44. if auth.BaseDn == "" || !ValidateDN(auth.BaseDn) {
  45. return errors.New("LDAP的Base DN填写有误")
  46. }
  47. if auth.ObjectClass == "" {
  48. return errors.New("LDAP的用户对象类填写有误")
  49. }
  50. if auth.SearchAttr == "" {
  51. return errors.New("LDAP的用户唯一ID不能为空")
  52. }
  53. if auth.MemberOf != "" && !ValidateDN(auth.MemberOf) {
  54. return errors.New("LDAP的受限用户组填写有误")
  55. }
  56. return nil
  57. }
  58. func (auth AuthLdap) checkUser(name, pwd string, g *Group) error {
  59. pl := len(pwd)
  60. if name == "" || pl < 1 {
  61. return fmt.Errorf("%s %s", name, "密码错误")
  62. }
  63. authType := g.Auth["type"].(string)
  64. if _, ok := g.Auth[authType]; !ok {
  65. return fmt.Errorf("%s %s", name, "LDAP的ldap值不存在")
  66. }
  67. bodyBytes, err := json.Marshal(g.Auth[authType])
  68. if err != nil {
  69. return fmt.Errorf("%s %s", name, "LDAP Marshal出现错误")
  70. }
  71. err = json.Unmarshal(bodyBytes, &auth)
  72. if err != nil {
  73. return fmt.Errorf("%s %s", name, "LDAP Unmarshal出现错误")
  74. }
  75. // 检测服务器和端口的可用性
  76. con, err := net.DialTimeout("tcp", auth.Addr, 3*time.Second)
  77. if err != nil {
  78. return fmt.Errorf("%s %s", name, "LDAP服务器连接异常, 请检测服务器和端口")
  79. }
  80. defer con.Close()
  81. // 连接LDAP
  82. l, err := ldap.Dial("tcp", auth.Addr)
  83. if err != nil {
  84. return fmt.Errorf("LDAP连接失败 %s %s", auth.Addr, err.Error())
  85. }
  86. defer l.Close()
  87. if auth.Tls {
  88. err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
  89. if err != nil {
  90. return fmt.Errorf("%s LDAP TLS连接失败 %s", name, err.Error())
  91. }
  92. }
  93. err = l.Bind(auth.BindName, auth.BindPwd)
  94. if err != nil {
  95. return fmt.Errorf("%s LDAP 管理员 DN或密码填写有误 %s", name, err.Error())
  96. }
  97. if auth.ObjectClass == "" {
  98. auth.ObjectClass = "person"
  99. }
  100. filterAttr := "(objectClass=" + auth.ObjectClass + ")"
  101. filterAttr += "(" + auth.SearchAttr + "=" + name + ")"
  102. if auth.MemberOf != "" {
  103. filterAttr += "(memberOf:=" + auth.MemberOf + ")"
  104. }
  105. searchRequest := ldap.NewSearchRequest(
  106. auth.BaseDn,
  107. ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 3, false,
  108. fmt.Sprintf("(&%s)", filterAttr),
  109. []string{},
  110. nil,
  111. )
  112. sr, err := l.Search(searchRequest)
  113. if err != nil {
  114. return fmt.Errorf("%s LDAP 查询失败 %s %s %s", name, auth.BaseDn, filterAttr, err.Error())
  115. }
  116. if len(sr.Entries) != 1 {
  117. if len(sr.Entries) == 0 {
  118. return fmt.Errorf("LDAP 找不到 %s 用户, 请检查用户或LDAP配置参数", name)
  119. }
  120. return fmt.Errorf("LDAP发现 %s 用户,存在多个账号", name)
  121. }
  122. err = parseEntries(sr)
  123. if err != nil {
  124. return fmt.Errorf("LDAP %s 用户 %s", name, err.Error())
  125. }
  126. userDN := sr.Entries[0].DN
  127. err = l.Bind(userDN, pwd)
  128. if err != nil {
  129. return fmt.Errorf("%s LDAP 登入失败,请检查登入的账号或密码 %s", name, err.Error())
  130. }
  131. return nil
  132. }
  133. func parseEntries(sr *ldap.SearchResult) error {
  134. for _, attr := range sr.Entries[0].Attributes {
  135. switch attr.Name {
  136. case "shadowExpire":
  137. // -1 启用, 1 停用, >1 从1970-01-01至到期日的天数
  138. val, _ := strconv.ParseInt(attr.Values[0], 10, 64)
  139. if val == -1 {
  140. return nil
  141. }
  142. if val == 1 {
  143. return fmt.Errorf("账号已停用")
  144. }
  145. if val > 1 {
  146. expireTime := time.Unix(val*86400, 0)
  147. t := time.Date(expireTime.Year(), expireTime.Month(), expireTime.Day(), 23, 59, 59, 0, time.Local)
  148. if t.Before(time.Now()) {
  149. return fmt.Errorf("账号已过期(过期日期: %s)", t.Format("2006-01-02"))
  150. }
  151. return nil
  152. }
  153. return fmt.Errorf("账号shadowExpire值异常: %d", val)
  154. }
  155. }
  156. return nil
  157. }
  158. func ValidateDomainPort(addr string) bool {
  159. re := regexp.MustCompile(`^([a-zA-Z0-9][-a-zA-Z0-9]{0,62}\.)+[A-Za-z]{2,18}\:([0-9]|[1-9]\d{1,3}|[1-5]\d{4}|6[0-5]{2}[0-3][0-5])$`)
  160. return re.MatchString(addr)
  161. }
  162. func ValidateDN(dn string) bool {
  163. re := regexp.MustCompile(`^(?:(?:CN|cn|OU|ou|DC|dc)\=[^,'"]+,)*(?:CN|cn|OU|ou|DC|dc)\=[^,'"]+$`)
  164. return re.MatchString(dn)
  165. }