diff --git a/etc/monapi.yml b/etc/monapi.yml index 5ee97668..030c3a46 100644 --- a/etc/monapi.yml +++ b/etc/monapi.yml @@ -14,11 +14,19 @@ ldap: host: "ldap.example.org" port: 389 baseDn: "dc=example,dc=org" + # AD: manange@example.org bindUser: "cn=manager,dc=example,dc=org" bindPass: "*******" # openldap: (&(uid=%s)) # AD: (&(sAMAccountName=%s)) authFilter: "(&(uid=%s))" + attributes: + dispname: "cn" + email: "mail" + phone: "mobile" + im: "" + coverAttributes: false + autoRegist: false tls: false startTLS: false diff --git a/src/model/ldap.go b/src/model/ldap.go new file mode 100644 index 00000000..c1a22bb1 --- /dev/null +++ b/src/model/ldap.go @@ -0,0 +1,84 @@ +package model + +import ( + "crypto/tls" + "fmt" + + "github.com/didi/nightingale/src/modules/monapi/config" + "gopkg.in/ldap.v3" +) + +func genLdapAttributeSearchList() []string { + ldapAttributes := []string{} + attrs := config.Get().LDAP.Attributes + if attrs.Dispname != "" { + ldapAttributes = append(ldapAttributes, attrs.Dispname) + } + if attrs.Email != "" { + ldapAttributes = append(ldapAttributes, attrs.Email) + } + if attrs.Phone != "" { + ldapAttributes = append(ldapAttributes, attrs.Phone) + } + if attrs.Im != "" { + ldapAttributes = append(ldapAttributes, attrs.Im) + } + return ldapAttributes +} + +func ldapReq(user, pass string) (*ldap.SearchResult, error) { + var conn *ldap.Conn + var err error + lc := config.Get().LDAP + addr := fmt.Sprintf("%s:%d", lc.Host, lc.Port) + + if lc.TLS { + conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: true}) + } else { + conn, err = ldap.Dial("tcp", addr) + } + + if err != nil { + return nil, fmt.Errorf("cannot dial ldap: %v", err) + } + + defer conn.Close() + + if !lc.TLS && lc.StartTLS { + err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true}) + if err != nil { + return nil, fmt.Errorf("ldap.conn startTLS fail: %v", err) + } + } + + err = conn.Bind(lc.BindUser, lc.BindPass) + if err != nil { + return nil, fmt.Errorf("bind ldap fail: %v, use %s", err, lc.BindUser) + } + + searchRequest := ldap.NewSearchRequest( + lc.BaseDn, // The base dn to search + ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, + fmt.Sprintf(lc.AuthFilter, user), // The filter to apply + genLdapAttributeSearchList(), // A list attributes to retrieve + nil, + ) + + sr, err := conn.Search(searchRequest) + if err != nil { + return nil, fmt.Errorf("ldap search fail: %v", err) + } + + if len(sr.Entries) == 0 { + return nil, fmt.Errorf("cannot find such user: %v", user) + } + + if len(sr.Entries) > 1 { + return nil, fmt.Errorf("multi users is search, query user: %v", user) + } + + if err := conn.Bind(sr.Entries[0].DN, pass); err != nil { + return nil, fmt.Errorf("password error") + } + return sr, nil +} diff --git a/src/model/user.go b/src/model/user.go index cc95e2db..666398c2 100644 --- a/src/model/user.go +++ b/src/model/user.go @@ -1,7 +1,6 @@ package model import ( - "crypto/tls" "fmt" "log" "strings" @@ -118,6 +117,23 @@ func (u *User) CanModifyTeam(t *Team) (bool, error) { return cnt > 0, err } +func (u *User) CopyLdapAttr(sr *ldap.SearchResult) { + attrs := config.Get().LDAP.Attributes + if attrs.Dispname != "" { + u.Dispname = sr.Entries[0].GetAttributeValue(attrs.Dispname) + } + if attrs.Email != "" { + u.Email = sr.Entries[0].GetAttributeValue(attrs.Email) + } + if attrs.Phone != "" { + u.Phone = sr.Entries[0].GetAttributeValue(attrs.Phone) + } + if attrs.Im != "" { + u.Im = sr.Entries[0].GetAttributeValue(attrs.Im) + } + return +} + func InitRoot() { var u User has, err := DB["uic"].Where("username=?", "root").Get(&u) @@ -147,78 +163,31 @@ func InitRoot() { } func LdapLogin(user, pass string) error { - var conn *ldap.Conn - var err error - - lc := config.Get().LDAP - addr := fmt.Sprintf("%s:%d", lc.Host, lc.Port) - - if lc.TLS { - conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: true}) - } else { - conn, err = ldap.Dial("tcp", addr) - } - - if err != nil { - return fmt.Errorf("cannot dial ldap: %v", err) - } - - defer conn.Close() - - if !lc.TLS && lc.StartTLS { - err = conn.StartTLS(&tls.Config{InsecureSkipVerify: true}) - if err != nil { - return fmt.Errorf("ldap.conn startTLS fail: %v", err) - } - } - - err = conn.Bind(lc.BindUser, lc.BindPass) - if err != nil { - return fmt.Errorf("bind ldap fail: %v, use %s", err, lc.BindUser) - } - - searchRequest := ldap.NewSearchRequest( - lc.BaseDn, // The base dn to search - ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - fmt.Sprintf(lc.AuthFilter, user), // The filter to apply - []string{}, // A list attributes to retrieve - nil, - ) - - sr, err := conn.Search(searchRequest) - if err != nil { - return fmt.Errorf("ldap search fail: %v", err) - } - - if len(sr.Entries) == 0 { - return fmt.Errorf("cannot find such user: %v", user) - } - - if len(sr.Entries) > 1 { - return fmt.Errorf("multi users is search, query user: %v", user) - } - - err = conn.Bind(sr.Entries[0].DN, pass) - if err != nil { - return fmt.Errorf("password error") - } - - cnt, err := DB["uic"].Where("username=?", user).Count(new(User)) + sr, err := ldapReq(user, pass) if err != nil { return err } - if cnt > 0 { - return nil - } - - u := &User{ - Username: user, - Password: "******", - Dispname: "", - Email: "", + var u User + has, err := DB["uic"].Where("username=?", user).Get(&u) + if err != nil { + return err + } + u.CopyLdapAttr(sr) + if has { + if config.Get().LDAP.CoverAttributes { + _, err := DB["uic"].Where("id=?", u.Id).Update(u) + return err + } else { + return nil + } + } + if !config.Get().LDAP.AutoRegist { + return fmt.Errorf("user has not be created, may be you should enable auto regist: %v", user) } + u.Username = user + u.Password = "******" _, err = DB["uic"].Insert(u) return err } diff --git a/src/modules/monapi/config/yaml.go b/src/modules/monapi/config/yaml.go index dd618dca..ad0e870a 100644 --- a/src/modules/monapi/config/yaml.go +++ b/src/modules/monapi/config/yaml.go @@ -64,14 +64,24 @@ type httpSection struct { } type ldapSection struct { - Host string `yaml:"host"` - Port int `yaml:"port"` - BaseDn string `yaml:"baseDn"` - BindUser string `yaml:"bindUser"` - BindPass string `yaml:"bindPass"` - AuthFilter string `yaml:"authFilter"` - TLS bool `yaml:"tls"` - StartTLS bool `yaml:"startTLS"` + Host string `yaml:"host"` + Port int `yaml:"port"` + BaseDn string `yaml:"baseDn"` + BindUser string `yaml:"bindUser"` + BindPass string `yaml:"bindPass"` + AuthFilter string `yaml:"authFilter"` + Attributes ldapAttributes `yaml:"attributes"` + CoverAttributes bool `yaml:"coverAttributes"` + AutoRegist bool `yaml:"autoRegist"` + TLS bool `yaml:"tls"` + StartTLS bool `yaml:"startTLS"` +} + +type ldapAttributes struct { + Dispname string `yaml:"dispname"` + Phone string `yaml:"phone"` + Email string `yaml:"email"` + Im string `yaml:"im"` } var (