package ldap import ( "errors" "fmt" "sort" "strconv" "gogs.carduccidante.edu.it/karmen/core/config" "gogs.carduccidante.edu.it/karmen/core/orm" ldap "gopkg.in/ldap.v2" ) type DNer interface { DN() string } type CompleteNameI interface { CompleteName() string } type Client struct { Conn *ldap.Conn Config *config.ConfigT } func (c *Client) Close() { c.Conn.Close() } func NewClient(host string, config *config.ConfigT) (*Client, error) { var err error client := new(Client) client.Config = config client.Conn, err = ldap.Dial("tcp", host) if err != nil { return nil, err } err = client.Conn.Bind(config.AdminCN(), config.Ldap.AdminPassword) if err != nil { return nil, err } return client, nil } func (c *Client) AddUser(user orm.User) error { mailDir := fmt.Sprintf("%s/%s/", c.Config.Ldap.MailDirBasePath, user.Username()) uidNumber, err := c.NextMailUIDNumber() if err != nil { return err } addRequest := ldap.NewAddRequest(user.DN()) addRequest.Attribute("objectClass", []string{ "inetOrgPerson", "posixAccount", "PostfixBookMailAccount", "organizationalPerson", "extensibleObject", }) addRequest.Attribute("sn", []string{user.GetSurname()}) addRequest.Attribute("uid", []string{user.Username()}) addRequest.Attribute("homeDirectory", []string{fmt.Sprintf("/home/users/%s", user.Username())}) addRequest.Attribute("givenName", []string{user.GetSurname()}) addRequest.Attribute("mail", []string{fmt.Sprintf("%s@%s", user.Username(), c.Config.Domain)}) addRequest.Attribute("mailEnabled", []string{"TRUE"}) addRequest.Attribute("mailGidNumber", []string{c.Config.Ldap.MailGIDNumber}) addRequest.Attribute("mailUidNumber", []string{uidNumber}) addRequest.Attribute("uidNumber", []string{uidNumber}) addRequest.Attribute("gidNumber", []string{uidNumber}) addRequest.Attribute("uniqueIdentifier", []string{user.Username()}) addRequest.Attribute("mailHomeDirectory", []string{mailDir}) addRequest.Attribute("mailStorageDirectory", []string{"maildir:" + mailDir}) addRequest.Attribute("mailQuota", []string{c.Config.Ldap.MailQuota}) addRequest.Attribute("userPassword", []string{fmt.Sprintf("{SSHA}%s", orm.SaltPassword(user.GetPlainPassword()))}) err = c.Conn.Add(addRequest) if err != nil { return err } return nil } func (c *Client) UpdateUser(user orm.User) error { mailDir := fmt.Sprintf("%s/%s/", c.Config.Ldap.MailDirBasePath, user.Username()) modRequest := ldap.NewModifyRequest(user.DN()) modRequest.Replace("mail", []string{fmt.Sprintf("%s@%s", user.Username(), c.Config.Domain)}) modRequest.Replace("mailHomeDirectory", []string{mailDir}) modRequest.Replace("mailStorageDirectory", []string{"maildir:" + mailDir}) modRequest.Replace("mailEnabled", []string{"TRUE"}) modRequest.Replace("mailGidNumber", []string{c.Config.Ldap.MailGIDNumber}) modRequest.Replace("mailQuota", []string{c.Config.Ldap.MailQuota}) err := c.Conn.Modify(modRequest) if err != nil { return err } return nil } func (c *Client) UpdateUserPassword(user orm.User) error { modRequest := ldap.NewModifyRequest(user.DN()) modRequest.Replace("userPassword", []string{fmt.Sprintf("{SSHA}%s", orm.SaltPassword(user.GetPlainPassword()))}) err := c.Conn.Modify(modRequest) if err != nil { return err } return nil } func (c *Client) VerifyUserLogin(username, password string) error { // Search for the given username searchRequest := ldap.NewSearchRequest( c.DomainDN(), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=organizationalPerson)(uid=%s))", username), []string{"dn"}, nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { return err } if len(sr.Entries) != 1 { return errors.New("User does not exist or too many entries returned") } userdn := sr.Entries[0].DN // Bind as the user to verify their password err = c.Conn.Bind(userdn, password) if err != nil { return err } return nil } func (c *Client) DeleteUser(user orm.User) error { delRequest := ldap.NewDelRequest(user.DN(), nil) err := c.Conn.Del(delRequest) if err != nil { return err } return nil } func (c *Client) DeleteByDN(dn string) error { delRequest := ldap.NewDelRequest(dn, nil) err := c.Conn.Del(delRequest) if err != nil { return err } return nil } func (c *Client) AddUserToGroup(user orm.User, groupDN string) error { memberAttr, err := c.memberAttribute(groupDN) if err != nil { return err } modRequest := ldap.NewModifyRequest(fmt.Sprintf("%s,%s", groupDN, c.GroupsDN())) switch memberAttr { case "member": modRequest.Add(memberAttr, []string{user.DN()}) case "memberuid": modRequest.Add(memberAttr, []string{user.Username()}) default: return errors.New(fmt.Sprintf("Attribute %s is not supported!", memberAttr)) } err = c.Conn.Modify(modRequest) if err != nil { return err } return nil } func (c *Client) RemoveUserFromGroup(user *orm.Credential, groupDN string) error { memberAttr, err := c.memberAttribute(groupDN) if err != nil { return err } modRequest := ldap.NewModifyRequest(fmt.Sprintf("%s,%s", groupDN, c.GroupsDN())) switch memberAttr { case "member": modRequest.Delete(memberAttr, []string{user.DN()}) case "memberuid": modRequest.Delete(memberAttr, []string{user.Username()}) default: return errors.New(fmt.Sprintf("Attribute %s is not supported!", memberAttr)) } err = c.Conn.Modify(modRequest) if err != nil { return err } return nil } func (c *Client) RemoveUserFromGroupByMemberValue(memberValue string, groupDN string) error { memberAttr, err := c.memberAttribute(groupDN) if err != nil { return err } modRequest := ldap.NewModifyRequest(fmt.Sprintf("%s,%s", groupDN, c.GroupsDN())) switch memberAttr { case "member": modRequest.Delete(memberAttr, []string{memberValue}) case "memberuid": modRequest.Delete(memberAttr, []string{memberValue}) default: return errors.New(fmt.Sprintf("Attribute %s is not supported!", memberAttr)) } err = c.Conn.Modify(modRequest) if err != nil { return err } return nil } func (c *Client) IsUserInGroup(user *orm.Credential, groupDN string) (bool, error) { memberAttr, err := c.memberAttribute(groupDN) if err != nil { return false, err } var memberValue string switch memberAttr { case "member": memberValue = user.DN() case "memberuid": memberValue = user.Username() default: return false, errors.New(fmt.Sprintf("Attribute %s is not supported!", memberAttr)) } searchRequest := ldap.NewSearchRequest( fmt.Sprintf("%s,%s", groupDN, c.GroupsDN()), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(%s=%s))", memberAttr, memberValue), []string{memberAttr}, nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { return false, err } return len(sr.Entries) > 0, nil } func (c *Client) GroupMembers(groupDN string) ([]*ldap.Entry, error) { searchRequest := ldap.NewSearchRequest( fmt.Sprintf("%s,%s", groupDN, c.GroupsDN()), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(|(member=*)(memberuid=*))", []string{"member", "memberuid"}, nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { return nil, err } return sr.Entries, nil } func (c *Client) Users(searchDN string) ([]*ldap.Entry, error) { result, err := c.Search( searchDN, "(&(objectClass=organizationalPerson))", []string{"dn", "cn"}, ) if err != nil { return nil, err } return result.Entries, nil } func (c *Client) Search(base string, filter string, attributes []string) (*ldap.SearchResult, error) { searchRequest := ldap.NewSearchRequest( base, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, filter, attributes, nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { return nil, err } return sr, nil } func (c *Client) UserExists(user *orm.Credential, searchDN string) (bool, error) { searchRequest := ldap.NewSearchRequest( searchDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, fmt.Sprintf("(&(objectClass=organizationalPerson)(cn=%s))", user.CompleteName()), []string{"dn", "cn"}, nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { return false, err } return len(sr.Entries) > 0, nil } func (c *Client) DomainDN() string { return c.Config.DomainDN() } func (c *Client) GroupsDN() string { return fmt.Sprintf("%s,%s", c.Config.Ldap.GroupsDN, c.DomainDN()) } func (c *Client) PeopleDN() string { return fmt.Sprintf("%s,%s", c.Config.Ldap.PeopleDN, c.DomainDN()) } func (c *Client) NextMailUIDNumber() (string, error) { searchRequest := ldap.NewSearchRequest( c.PeopleDN(), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(mailUidNumber=*)", []string{"mailUidNumber"}, nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { return "0", err } if len(sr.Entries) == 0 { return c.Config.Ldap.FirstUIDNumber, nil } var uids []int for _, e := range sr.Entries { n, err := strconv.Atoi(e.Attributes[0].Values[0]) uids = append(uids, n) if err != nil { return "0", err } } sort.Ints(uids) nextUid := uids[len(uids)-1] + 1 return strconv.Itoa(nextUid), nil } func (c *Client) memberAttribute(groupDN string) (string, error) { searchRequest := ldap.NewSearchRequest( fmt.Sprintf("%s,%s", groupDN, c.GroupsDN()), ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, "(member=*)", []string{"member"}, nil, ) sr, err := c.Conn.Search(searchRequest) if err != nil { return "", err } if len(sr.Entries) > 0 { return "member", nil } return "memberuid", nil } func (c *Client) personDN(person CompleteNameI, baseDN string) string { dn := fmt.Sprintf("cn=%s,%s", person.CompleteName(), baseDN) return dn }