package ldap import ( "crypto/md5" "encoding/hex" "encoding/json" "errors" "fmt" "github.com/astaxie/beego/logs" "github.com/go-ldap/ldap/v3" "nginx-ui/server/models" "strings" ) type Client struct { *ldap.Conn Url string Connected bool BaseDN string Admin string Password string ServerKey string } var ActiveClients = make(map[string]*Client) func Create(url string, baseDN string) *Client { var client = &Client{ Url: url, BaseDN: baseDN, } conn, err := ldap.DialURL(client.Url) if err != nil { logs.Error("dialUrl fail: %v", err) client.Connected = false } else { client.Conn = conn client.Connected = true } return client } func GetActiveClient(server *models.LdapServer) (*Client, error) { client := ActiveClients[server.Key] if client == nil { client = Create(server.Url, server.BaseDN) err := client.Bind(server.UserName, server.Password, true) if err != nil { logs.Error("Bind fail: %v", err) return nil, err } ActiveClients[server.Key] = client client.ServerKey = server.Key } return client, nil } func (c *Client) Close() { if c.Connected && c.Conn != nil { c.Conn.Close() } } // Bind 验证账号密码? func (c *Client) Bind(username string, password string, isAdmin bool) error { err := c.Conn.Bind(username, password) if err != nil { logs.Error("GSSAPIBind failed, err:%v", err) return err } if isAdmin { c.Admin = username c.Password = password } return nil } func createUser(entry *ldap.Entry) models.LdapUser { user := models.LdapUser{ Account: entry.GetAttributeValue("uid"), DN: entry.DN, Password: entry.GetAttributeValue("userPassword"), UserName: entry.GetAttributeValue("cn"), Mail: entry.GetAttributeValue("mail"), } jsonBytes, err := json.Marshal(entry.Attributes) if err != nil { logs.Error("marshal fail : %v", err) user.Remark = "attributes marshal fail: " + err.Error() } else { user.Attributes = string(jsonBytes) } return user } // Search 搜索用户 eg. (&(objectClass=organizationalPerson)) func (c *Client) Search(filter string) ([]models.LdapUser, error) { if filter == "" { filter = "(objectClass=*)" } searchRequest := ldap.NewSearchRequest( c.BaseDN, // The base dn to search ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, filter, // The filter to apply []string{"*"}, // A list attributes to retrieve,"dn", "cn", "objectClass", nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { logs.Error("search fail : %v", err) return nil, err } var users []models.LdapUser for _, entry := range sr.Entries { user := createUser(entry) user.ServerKey = c.ServerKey users = append(users, user) } return users, nil } func (c *Client) ModifyAdminPassword() error { return nil } func (c *Client) ModifyPassword() error { return nil } func (c *Client) Modify() error { return nil } func (c *Client) Authentication(account string, password string) (*models.LdapUser, error) { // The username and password we want to check users, err := c.Search(fmt.Sprintf("(&(objectClass=*)(uid=%s))", ldap.EscapeFilter(account))) if err != nil { logs.Error("search fail: %v", err) return nil, err } if len(users) != 1 { logs.Error("User does not exist or too many entries returned") return nil, errors.New("未找到用户或者账号重复!") } userDN := users[0].Account userPassword := users[0].Password if strings.HasPrefix(userPassword, "{MD5}") { pass := md5.Sum([]byte(password)) password = hex.EncodeToString(pass[:]) if strings.TrimPrefix(userPassword, "{MD5}") != password { return nil, errors.New("登录失败,账号或者密码不正确!") } } else { client, err := ldap.DialURL(c.Url) if err != nil { logs.Error("DialURL failed, err:%v", err) return nil, errors.New("服务连接异常!") } defer client.Close() err = client.Bind(userDN, password) if err != nil { logs.Error("GSSAPIBind failed, err:%v", err) return nil, errors.New("登录失败,账号或者密码不正确!") } } return &users[0], nil }