package ldapauth import ( "crypto/tls" "fmt" "strconv" "time" "net/http" "github.com/mattermost/mattermost-server/app" "github.com/mattermost/mattermost-server/einterfaces" "github.com/mattermost/mattermost-server/model" "github.com/mattermost/mattermost-server/store" "gopkg.in/ldap.v2" ) func init() { app.RegisterLdapInterface(NewLdapInterface) } type LdapConnection struct { Settings *model.LdapSettings Ldap *ldap.Conn } func NewLdapConnection(settings *model.LdapSettings) (*LdapConnection, *model.AppError) { c := new(LdapConnection) c.Settings = settings addr := fmt.Sprintf("%s:%d", *c.Settings.LdapServer, *c.Settings.LdapPort) tlsConfig := &tls.Config{InsecureSkipVerify: *c.Settings.SkipCertificateVerification} if *c.Settings.ConnectionSecurity == "TLS" { ldapConn, err := ldap.DialTLS("tcp", addr, tlsConfig) if err != nil { return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.unable_to_connect.app_error", nil, err.Error(), http.StatusBadRequest) } c.Ldap = ldapConn } else { ldapConn, err := ldap.Dial("tcp", addr) if err != nil { return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.unable_to_connect.app_error", nil, err.Error(), http.StatusBadRequest) } if *c.Settings.ConnectionSecurity == "STARTTLS" { if err := ldapConn.StartTLS(tlsConfig); err != nil { return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.unable_to_connect.app_error", nil, err.Error(), http.StatusBadRequest) } } c.Ldap = ldapConn } c.Ldap.SetTimeout(time.Duration(*c.Settings.QueryTimeout) * time.Second) if len(*c.Settings.BindUsername) > 0 { if err := c.Ldap.Bind(*c.Settings.BindUsername, *c.Settings.BindPassword); err != nil { return nil, model.NewAppError("NewLdapConnection", "ent.ldap.do_login.bind_admin_user.app_error", nil, err.Error(), http.StatusBadRequest) } } return c, nil } func (c LdapConnection) Close() { c.Ldap.Close() } func (c LdapConnection) Search(filter string, attributes []string) (map[string]string, *model.AppError) { searchRequest := ldap.NewSearchRequest( *c.Settings.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, filter, attributes, nil, ) result, err := c.Ldap.Search(searchRequest) if err != nil { return nil, model.NewAppError("Search", "ent.ldap.do_login.search_ldap_server.app_error", nil, err.Error(), http.StatusBadRequest) } if len(result.Entries) == 0 { return nil, model.NewAppError("Search", "ent.ldap.do_login.user_not_registered.app_error", nil, "filter="+filter, http.StatusUnauthorized) } else if len(result.Entries) > 1 { return nil, model.NewAppError("Search", "ent.ldap.do_login.matched_to_many_users.app_error", nil, "filter="+filter, http.StatusBadRequest) } values := map[string]string{} for _, attribute := range attributes { values[attribute] = result.Entries[0].GetAttributeValue(attribute) } return values, nil } func (c LdapConnection) FindAuthData(id string) (string, *model.AppError) { values, err := c.GetUserAttributes(id, []string{*c.Settings.IdAttribute}) if err != nil { return "", err } authData := values[*c.Settings.IdAttribute] if len(authData) == 0 { return "", model.NewAppError("FindUser", "ent.ldap.do_login.search_ldap_server.app_error", nil, "userId="+id, http.StatusBadRequest) } return authData, nil } func addAttribute(attributes []string, attr string) []string { if len(attr) == 0 { return attributes } for _, attribute := range attributes { if attribute == attr { return attributes } } return append(attributes, attr) } func (c LdapConnection) FindUser(id string) (*model.User, *model.AppError) { attributes := []string{*c.Settings.IdAttribute, *c.Settings.UsernameAttribute, *c.Settings.EmailAttribute} attributes = addAttribute(attributes, *c.Settings.FirstNameAttribute) attributes = addAttribute(attributes, *c.Settings.LastNameAttribute) attributes = addAttribute(attributes, *c.Settings.NicknameAttribute) attributes = addAttribute(attributes, *c.Settings.PositionAttribute) attrs, err := c.GetUserAttributes(id, attributes) if err != nil { return nil, err } user := &model.User{} user.Username = attrs[*c.Settings.UsernameAttribute] user.Email = attrs[*c.Settings.EmailAttribute] authData := attrs[*c.Settings.IdAttribute] user.AuthData = &authData /* optional user attributes */ if len(*c.Settings.FirstNameAttribute) > 0 { user.FirstName = attrs[*c.Settings.FirstNameAttribute] } if len(*c.Settings.LastNameAttribute) > 0 { user.LastName = attrs[*c.Settings.LastNameAttribute] } if len(*c.Settings.NicknameAttribute) > 0 { user.Nickname = attrs[*c.Settings.NicknameAttribute] } if len(*c.Settings.PositionAttribute) > 0 { user.Position = attrs[*c.Settings.PositionAttribute] } return user, nil } func (c LdapConnection) GetUserAttributes(id string, attributes []string) (map[string]string, *model.AppError) { loginIdAttribute := *c.Settings.LoginIdAttribute if len(loginIdAttribute) == 0 { loginIdAttribute = *c.Settings.UsernameAttribute } filter := fmt.Sprintf("(%s=%s)", loginIdAttribute, ldap.EscapeFilter(id)) if len(*c.Settings.UserFilter) > 0 { filter = fmt.Sprintf("(&%s%s)", filter, *c.Settings.UserFilter) } values, err := c.Search(filter, attributes) if err != nil { return nil, err } return values, nil } func (c LdapConnection) CheckPassword(authData string, password string) *model.AppError { if err := c.Ldap.Bind(authData, password); err != nil { return model.NewAppError("CheckPasswordAuthData", "ent.ldap.do_login.invalid_password.app_error", nil, "auth_data="+authData, http.StatusUnauthorized) } return nil } type LdapInterface struct { App *app.App; } func NewLdapInterface(app *app.App) einterfaces.LdapInterface { return LdapInterface{App: app} } func (i LdapInterface) NewLdapConnection() (*LdapConnection, *model.AppError) { settings := &i.App.GetConfig().LdapSettings return NewLdapConnection(settings) } func (i LdapInterface) DoLogin(id string, password string) (*model.User, *model.AppError) { c, err := i.NewLdapConnection() if err != nil { return nil, err } defer c.Close() ldapUser, err := c.FindUser(id) if err != nil { return nil, err } if err := c.CheckPassword(*ldapUser.AuthData, password); err != nil { return nil, err } return i.GetLdapUser(ldapUser) } func (i LdapInterface) GetUser(id string) (*model.User, *model.AppError) { c, err := i.NewLdapConnection() if err != nil { return nil, err } defer c.Close() ldapUser, err := c.FindUser(id) if err != nil { return nil, err } return i.GetLdapUser(ldapUser) } func (i LdapInterface) GetUserAttributes(id string, attributes []string) (map[string]string, *model.AppError) { c, err := i.NewLdapConnection() if err != nil { return nil, err } defer c.Close() return c.GetUserAttributes(id, attributes) } func (i LdapInterface) CheckPassword(id string, password string) *model.AppError { c, err := i.NewLdapConnection() if err != nil { return err } defer c.Close() authData, err := c.FindAuthData(id) if err != nil { return err } return c.CheckPassword(authData, password) } func (i LdapInterface) CheckPasswordAuthData(authData string, password string) *model.AppError { c, err := i.NewLdapConnection() if err != nil { return err } defer c.Close() return c.CheckPassword(authData, password) } func (i LdapInterface) SwitchToLdap(userId, ldapId, ldapPassword string) *model.AppError { c, err := i.NewLdapConnection() if err != nil { return err } defer c.Close() authData, err := c.FindAuthData(ldapId) if err != nil { return err } if err := c.CheckPassword(authData, ldapPassword); err != nil { return err } if res := <-i.App.Srv.Store.User().UpdateAuthData(userId, model.USER_AUTH_SERVICE_LDAP, &authData, "", false); res.Err != nil { return res.Err } return nil } func (i LdapInterface) ValidateFilter(filter string) *model.AppError { if _, err := ldap.CompileFilter(filter); err != nil { return model.NewAppError("ValidateFilter", "ent.ldap.validate_filter.app_error", nil, err.Error(), http.StatusBadRequest) } return nil } func (i LdapInterface) RunTest() *model.AppError { c, err := i.NewLdapConnection() if err != nil { return err } defer c.Close() return nil } func (i LdapInterface) MigrateIDAttribute(toAttribute string) error { // not required for login // TODO return nil } func (i LdapInterface) StartSynchronizeJob(waitForJobToFinish bool) (*model.Job, *model.AppError) { // not required for login // TODO return nil, nil } func (i LdapInterface) GetAllLdapUsers() ([]*model.User, *model.AppError) { // not used (maybe used by synchronization job) // TODO return nil, nil } func (i LdapInterface) CreateLdapUser(user *model.User) (*model.User, *model.AppError) { found := true count := 0 for found { if found = i.App.IsUsernameTaken(user.Username); found { user.Username = user.Username + strconv.Itoa(count) count++ } } user.AuthService = model.USER_AUTH_SERVICE_LDAP user.EmailVerified = true ruser, err := i.App.CreateUser(user) if err != nil { return nil, model.NewAppError("CreateUser", "ent.ldap.create_fail", nil, err.Error(), http.StatusBadRequest) } return ruser, nil } func (i LdapInterface) UpdateLdapUserAttrs(user *model.User, ldapUser *model.User) *model.AppError { userAttrsChanged := false if ldapUser.Username != user.Username { if existingUser, _ := i.App.GetUserByUsername(ldapUser.Username); existingUser == nil { user.Username = ldapUser.Username userAttrsChanged = true } } if ldapUser.GetFullName() != user.GetFullName() { user.FirstName = ldapUser.FirstName user.LastName = ldapUser.LastName userAttrsChanged = true } if ldapUser.Nickname != user.Nickname { user.Nickname = ldapUser.Nickname userAttrsChanged = true } if ldapUser.Position != user.Position { user.Position = ldapUser.Position userAttrsChanged = true } if ldapUser.Email != user.Email { if existingUser, _ := i.App.GetUserByEmail(ldapUser.Email); existingUser == nil { user.Email = ldapUser.Email userAttrsChanged = true } } if userAttrsChanged { result := <-i.App.Srv.Store.User().Update(user, true) if result.Err != nil { return result.Err } user = result.Data.([2]*model.User)[0] i.App.InvalidateCacheForUser(user.Id) } return nil } func (i LdapInterface) GetLdapUser(ldapUser *model.User) (*model.User, *model.AppError) { user, err := i.App.GetUserByAuth(ldapUser.AuthData, model.USER_AUTH_SERVICE_LDAP) if err != nil { if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR { return i.CreateLdapUser(ldapUser) } return nil, err } if err := i.UpdateLdapUserAttrs(user, ldapUser); err != nil { return nil, err } return user, nil }