package ldap import ( "encoding/json" "errors" "fmt" "github.com/astaxie/beego/logs" "github.com/go-ldap/ldap/v3" "nginx-ui/server/models" ) type Client struct { *ldap.Conn Url string Connected bool BaseDN string Admin string Password string ServerKey string pool *ConnectionPool } var ActiveClients = make(map[string]*Client) func createClient(url string, baseDN string, createPool bool) *Client { var client = &Client{ Url: url, BaseDN: baseDN, } if createPool { client.pool = NewConnectionPool(5, func() (interface{}, error) { c := createClient(url, baseDN, false) if c.Connected { return c, nil } return nil, errors.New("连接服务失败!") }) } 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 = createClient(server.Url, server.BaseDN, true) err := client.Bind(server.UserName, server.Password, true) if err != nil { logs.Error("Bind fail: %v", err) return nil, err } client.ServerKey = server.Key ActiveClients[server.Key] = client } return client, nil } func CloseActiveClient(server *models.LdapServer) { delete(ActiveClients, server.Key) client := ActiveClients[server.Key] if client == nil { return } client.Close() } func (c *Client) Close() { if c.Connected && c.Conn != nil { c.Conn.Close() } } func (c *Client) Acquire() (*Client, error) { inter, err := c.pool.Acquire() if err != nil { return nil, err } return inter.(*Client), err } // 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 } // Search 搜索用户 eg. (&(objectClass=organizationalPerson)) func (c *Client) Search(filter string) ([]*ldap.Entry, 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 } return sr.Entries, nil } // SearchByAccount 指定账号搜索用户 func (c *Client) SearchByAccount(account string) (*ldap.Entry, error) { filter := fmt.Sprintf("(&(objectClass=*)(uid=%s))", account) 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 } if len(sr.Entries) < 1 { logs.Error("no account found for: %v", account) } return sr.Entries[0], nil } // 通过管理员修改密码,而非自行修改密码 func (c *Client) ModifyPasswordByAdmin(dn string, newPassword string) error { passwordModifyRequest := ldap.NewPasswordModifyRequest(dn, "", newPassword) _, err := c.PasswordModify(passwordModifyRequest) if err != nil { logs.Error("Password could not be changed: %s", err.Error()) return err } return nil } // ModifyPassword 自行修改密码 func (c *Client) ModifyPassword(userDN string, password string, newPassword string) error { l, err := c.Acquire() if err != nil { logs.Error(err) return err } err = l.Bind(userDN, password, false) if err != nil { logs.Error(err) return errors.New("密码验证失败:" + err.Error()) } passwordModifyRequest := ldap.NewPasswordModifyRequest("", password, newPassword) _, err = l.PasswordModify(passwordModifyRequest) if err != nil { logs.Error("Password could not be changed: %s", err.Error()) } return nil } func (c *Client) Modify() error { return nil } // Add 新增用户 // 搜索指定账号:(&(objectClass=*)(uid=%s)) func (c *Client) Add(user *models.LdapUser) error { entries, err := c.Search(fmt.Sprintf("(&(objectClass=*)(uid=%s))", user.Account)) if err != nil { return err } if len(entries) > 0 { entry := entries[0] if entry.DN != user.DN { logs.Warn("DN not match: {}, {}", entry.DN, user.DN) return errors.New("已存在该账号,但DN不相同!") } } var attrs []ldap.EntryAttribute err = json.Unmarshal([]byte(user.Attributes), &attrs) if err != nil { return err } isUpdate := len(entries) == 1 if isUpdate { request := ldap.NewModifyRequest(user.DN, nil) var attrMap = make(map[string][]string) for _, attr := range entries[0].Attributes { attrMap[attr.Name] = attr.Values } for _, attr := range attrs { if attrMap[attr.Name] == nil { request.Add(attr.Name, attr.Values) } else { request.Replace(attr.Name, attr.Values) } } err = c.Conn.Modify(request) } else { request := ldap.NewAddRequest(user.DN, nil) for _, attr := range attrs { request.Attribute(attr.Name, attr.Values) } err = c.Conn.Add(request) } if err != nil { logs.Error("Add fail: %v", err) } return err } func (c *Client) Authentication(userDN string, password string) error { client, err := c.Acquire() if err != nil { return err } err = client.Bind(userDN, password, false) if err != nil { logs.Error("GSSAPIBind failed, err:%v", err) return errors.New("登录失败,账号或者密码不正确!") } c.pool.Release(client) return nil }